Ethernaut#11 - Elevator
Ethernaut #11 - Elevator
This elevator won’t let you reach the top of your building. Right?
Things that might help:
- Sometimes solidity is not good at keeping promises.
- This Elevator expects to be used from a Building.
1 | // SPDX-License-Identifier: MIT |
Contract Breakdown
Let us breakdown the contract to understand what each function and piece of code does. The code consists of an interface Building and a contract Elevator compiled with solidity version ^0.8.0.
Interface
1 | interface Building { |
The Building interface defines a single function isLastFloor that takes a uint and returns a bool. Notice something important here: the function is NOT marked as view or pure. This means the implementation is allowed to modify state.
Variables
1 | bool public top; |
top is a boolean that indicates whether we have reached the top floor. floor stores the current floor number.
Functions
1 | function goTo(uint _floor) public { |
The goTo function casts msg.sender as a Building and calls isLastFloor on it twice. First it checks if the floor is NOT the last floor. If that check passes (returns false), it sets the floor and then calls isLastFloor again, assigning the result to top.
Solution
The vulnerability here is that the Elevator contract calls isLastFloor twice and trusts that both calls will return the same value. But since isLastFloor is not restricted to being a view function, our implementation can return different values on each call.
The trick is to implement a
Buildingcontract whereisLastFloorreturnsfalseon the first call (to pass theifcheck) andtrueon the second call (to settopto true).
1 | // SPDX-License-Identifier: MIT |
Steps to pass:
- Copy the
HackElevatorcontract to Remix and compile it. - Deploy it with the Elevator instance address from Ethernaut.
- Call
attack(). - When
goTois called, it calls ourisLastFloor. The first call returnsfalse(passing the if check), and the second call returnstrue(settingtop = true). - We have reached the top of the building.
Never trust external contract implementations to behave consistently, especially when the interface doesn’t enforce
vieworpure. If a function should not modify state, always mark it asviewin the interface. Even then, be cautious about calling external contracts multiple times and assuming consistent results.