Ethernaut Lvl 11 Elevator Walkthrough: How to abuse Solidity interfaces and function state modifiers

This is a in-depth series around Zeppelin team’s smart contract security puzzles. We learn key Solidity concepts to solve the puzzles 100% on your own.

Nicole Zhu
Coinmonks
Published in
4 min readAug 27, 2018

--

This levels requires you to take the elevator to the top floor.

What are Pure & View functions

Solidity functions have function modifiers which execute at the start of each function call. You are already familiar with visibility modifiers such as public and private, which determine who gets to invoke these functions.

Similarly, pure and view are built-in state modifiers. They “promise” how functions will interact with data on Ethereum blockchain, i.e. the state.

In order of increasing state permissions:

  • pure: promises functions that will neither read from nor modify the state. Note: Pure replaces constant in more recent compilers.
  • view: promises functions that will only read, but not modify the state
  • default: [no modifier] promises functions that will read and modify the the state

In theory, you should follow modifier best practices (see below):

Security warning: in earlier compiler versions, it allows and doesn’t give warnings when the function betrays their modifier promise. So a pure function can break its promise and modify function state, without any warning.

It’s important to treat these data modifiers as promises on data mutability, rather than guarantees.

What are Interfaces

Interfaces allow different contract classes to talk to each other.

Think of interfaces as an ABI (or API) declaration that forces contracts to all communicate in the same language/data structure. But interfaces do not prescribe the logic inside the functions, leaving the developer to implement his own business layer.

Contract Interfaces specifies the WHAT but not the HOW

Developers typically use interfaces:

  • To design contracts: by generating a working ABI first, before implementing the actual contract.
  • For token contracts: by declaring a shared language, so different contracts can use these tokens to handle their business logic.
  • Not used: some developer want to scrap interfaces altogether, in favor of abstract classes*.

*Note: Abstract classes share similar security vulnerabilities with interfaces. In abstract contracts some functions are already programmed, but can be easily overridden.

Detailed Walkthrough

To pass this level, we have to make this check first return true and then return false within a single goTo() function call:

// 1st check has to return false
if (! building.isLastFloor(_floor)) {
floor = _floor;
// 2nd check has to return true
top = building.isLastFloor(floor); //winning statement
}

Notice Elevator.sol never implemented the isLastFloor() function from the Building interface.

This means we can create a malicious contract called Building that implements this function. Then, if we invoke goto() from the malicious contract, our version of the isLastFloor function will be used in the context of our level’s Elevator.sol instance!

  1. Below Elevator.sol, create a Building.sol contract that eventually invokes Elevator.goto() on some arbitrary floor:
contract Building {
Elevator public el = Elevator(YOUR_LVL_INSTANCE);
function hack() public {
el.goTo(1);
}
}

2. Implement isLastFloor with a switch that guarantees a true, then false response. Make sure your function declaration and modifiers are consistent with the interface:

3. Using Remix, invoke hack() to topple all the dominos. Notice that although isLastFloor promised to be a pure function, it did change blockchain state.

A quick await contract.top() now reveals we are at the top floor.

Key Security Takeaways

  • Interfaces do not guarantee contract security. Remember that just because another contract uses the same interface, doesn’t mean it will behave as intended!
  • Be careful when inheriting contracts that extend from interfaces. Each layer of abstraction introduces security issues through information obscurity. This makes each generation of the contract less and less secure than the previous.
  • What out for the compiler version you are using or inheriting from. View and pure promises might be violated without you knowing!

“Solidity compiler does nothing to enforce that a view or constant function is not modifying state. The same applies to pure functions, which should not read state but they can. Make sure you read Solidity's documentation and learn its caveats.” — Ethernaut

More Levels

Get Best Software Deals Directly In Your Inbox

--

--