function withdrawBalance() public { uint amountToWithdraw = userBalances[msg.sender]; require(msg.sender.call.value(amountToWithdraw)()); // At this point, the call's code is executed, and can call withdrawBalance again userBalances[msg.sender] = 0; }
因为用户余额直到最后被提取完才会被设置成 0,所以第二次(和更之后)的调用仍然会成功,从而一次又一次地提取余额。一个非常类似的 bug 是 DAO 攻击中的一个漏洞。
function transfer(address to, uint amount) { if (userBalances[msg.sender] >= amount) { userBalance[to] += amount; userBalance[msg.sender] -= amount; } }
function withdrawBalance() public { uint amountToWithdraw = userBalances[msg.sender]; require(msg.sender.call.value(amountToWithdraw)()); // At this point, the caller's code is executed, and can call transfer() userBalances[msg.sender] = 0; }
在这个例子中,当攻击者的代码在 withdrawBalance() 中的外部调用中被执行,攻击者会调用 transfer()。只要他们的余额不是 0,那么他们就可以在已经收到过一次提币的情况下,再次转移 token。这个漏洞也在 DAO 攻击中被使用。
function withdraw(address recipient) public { uint amountToWithdraw = userBalances[recipient]; rewardsForA[recipient] = 0; require(recipient.call.value(amountToWithdraw)()); }
function getFirstWithdrawalBonus(address recipient) public { require(!claimedBonus[recipient]); // Each recipient should only be able to claim the bonus once
rewardsForA[recipient] += 100; withdraw(recipient); // At this point, the caller will be able to execute getFirstWithdrawalBonus again. claimedBonus[recipient] = true; }
function untrustedWithdraw(address recipient) public { uint amountToWithdraw = userBalances[recipient]; rewardsForA[recipient] = 0; require(recipient.call.value(amountToWithdraw)()); }
function untrustedGetFirstWithdrawalBonus(address recipient) public { require(!claimedBonus[recipient]); // Each recipient should only be able to claim the bonus once
claimedBonus[recipient] = true; rewardsForA[recipient] += 100; untrustedWithdraw(recipient); // claimedBonus has been set to true, so reentry is impossible }
// Note: This is a rudimentary example, and mutexes are particularly useful where there is substantial logic and/or shared state mapping(address => uint) private balance; bool private lockBalances;
function deposit() payable public returns (bool) { require(!lockBalances); lockBalances = true; balances[msg.sender] += msg.value; lockBalances = false; return true; }
function withdraw(uint amount) payable public returns (bool) { require(!lockBalances && amount > 0 && balances[msg.sender] >= amount); lockBalances = true;
if (msg.sender.call(amount)()) { balances[msg.sender] -= amount; }
address[] private refundAddresses; mapping(address => uint) public refunds;
// bad function refundAll() public { for (uint x; x < refundAddresses.length; x++) { // // arbitrary length iteration based on how many addresses participated require(refundAddresses[x].send(refunds[refundAddresses[x]])) // doubly bad, now a single failure on send will hold up all funds } }
近期评论