打破 Gas 陷阱:保护智能合约免受拒绝服务攻击

本文是智能合约安全系列文章的第一部分,主要讨论了智能合约中拒绝服务(DoS)攻击的威胁与防范。文章通过实际案例Fomo3D,讲解了DoS攻击如何利用以太坊的gas限制使合约不可用,并重点介绍了利用无限制循环和外部调用失败两种主要攻击手段。文章还提供了使用“拉取而非推送”模式、限制状态增长、熔断器等多种缓解措施,以及Slither、MythX、Foundry等高级工具,以构建更具弹性的智能合约。

智能合约安全:Solodit 清单系列第一部分

简介:智能合约中 DoS 的隐患

想象一下:你已经在以太坊上启动了一个去中心化拍卖平台。它正逐渐受到关注。用户在竞标,资金在流动,一切进展顺利——直到一个恶意行为者毁了这一切。

他们用成千上万的小额出价来 spam 你的合约。突然,你的退款功能停止工作。资金被锁定,用户感到沮丧,你项目的声誉岌岌可危。

这就是拒绝服务(DoS)攻击的影响——一种不会窃取资金,但会使你的智能合约完全无法使用的威胁。

在以太坊生态系统中——每个操作都需要花费 gas,并且每个区块都有严格的 gas 上限(截至 2025 年 6 月,目前约为 3000 万 gas)——攻击者可以利用 gas 限制来导致失败。

在这篇 智能合约安全:Solodit 清单系列 的第一篇文章中,我们将重点关注来自 Solodit 安全清单 的 SOL-AM-DoS-1 漏洞,特别是无界循环外部调用失败

我们将探讨:

  • 真实的 DoS 攻击示例
  • 存在漏洞的代码与安全代码
  • 经过验证的防御模式
  • Gas 高效的架构策略

无论你是 Solidity 开发者还是安全审计员,本指南都将帮助你构建更具弹性的智能合约。

为什么 DoS 攻击如此重要

智能合约运行在 以太坊虚拟机(EVM) 上,每条指令都会消耗 gas。当交易的 gas 使用量超过 区块 gas 上限 时,交易就会失败。

DoS 攻击者会利用这一点:

  • 锁定资金:提款或退款等关键功能失败。
  • 浪费 Gas:失败的交易仍然会消耗 gas。
  • 损害声誉:你的 dApp 变得不可靠或无法使用。

案例分析: Fomo3D (2018)

Fomo3D 游戏是一款病毒式传播的以太坊 dApp,当攻击者用小额交易 spam 合约以膨胀其状态时,它面临着 DoS 攻击。诸如奖金分配之类的关键功能变得 gas 消耗过大,延迟了支付并使玩家感到沮丧。该事件强调了面向公众的合约中 gas 高效设计的重要性。

DoS 攻击如何在智能合约中起作用

主要向量(根据 Solodit)

  1. 无界循环:在没有 gas 约束的情况下迭代动态数组或映射。随着数据增长(例如,更多用户或交易),gas 成本急剧上升,超过区块限制。
  2. 外部调用失败:对外部合约或地址的调用发生 revert、消耗过多 gas 或阻止执行,从而停止调用合约。

有漏洞的代码示例:DoS 陷阱

让我们检查一个有漏洞的拍卖合约,看看 DoS 攻击如何利用无界循环和外部调用。该合约允许用户使用 ETH 出价,并在拍卖结束时退还给他们。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract VulnerableAuction {
    address[] public bidders;
    uint256[] public bids;
    // Users bid by sending ETH
    function bid() external payable {
        bidders.push(msg.sender);
        bids.push(msg.value);
    }
    // Refund all bidders at once
    function refundAll() external {
        for (uint256 i = 0; i < bidders.length; i++) {
            (bool success, ) = bidders[i].call{value: bids[i]}("");
            require(success, "Refund failed");
        }
    }
}

攻击场景

该合约是一个等待发生的 DoS 灾难。以下是攻击者如何利用它:

  1. Gas 限制利用
  • 攻击者提交数千个小额出价(例如,每个 1 wei),从而膨胀 bidders.length
  • 当调用 refundAll() 时,循环的 gas 成本超过 3000 万 gas 的限制,导致交易 revert。
  • 用户无法收到退款,并且合约变得无法使用。

2. 恶意接受者利用

  • 攻击者部署一个合约,该合约在收到 ETH 时 revert(例如,通过恶意的 receive() 函数):
contract MaliciousBidder {
    receive() external payable { revert("No refunds"); }
}
  • refundAll() 尝试将 ETH 发送到此合约时,它会 revert,从而停止循环并阻止所有后续退款。

为什么它很危险

refundAll() 函数假定所有退款都将成功,并且竞标者列表保持可管理。实际上,单个恶意行为者或庞大的用户群可能会瘫痪该合约,从而无限期地锁定资金。

解决方案:使用 Pull-Over-Push 模式

核心思想:让用户提取自己的资金,而不是将资金推送给循环中的每个人。

  • 消除支付逻辑中无界循环。
  • 在用户之间分配 gas 成本。
  • 隔离故障,确保一个恶意用户不会阻止其他人。

这是拍卖合约的安全版本:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

contract FixedAuction {
    mapping(address => uint256) public refunds;
    address[] public bidders;
    uint256[] public bids;
    // Users bid by sending ETH
    function bid() external payable {
        bidders.push(msg.sender);
        bids.push(msg.value);
    }
    // Queue refunds for all bidders
    function queueRefunds() external {
        for (uint256 i = 0; i < bidders.length; i++) {
            refunds[bidders[i]] += bids[i];
        }
        delete bidders;
        delete bids;
    }
    // Users pull their refunds
    function withdrawRefund() external {
        uint256 amount = refunds[msg.sender];
        require(amount > 0, "No refund available");
        refunds[msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Refund failed");
    }
}

工作流程对比

安全优势

  • 提款路径中没有无界循环。
  • 恶意接受者只会影响他们自己的提款。
  • Gas 成本是可预测的,并在用户之间分配。

工作流程可视化

安全的工作流程避免了级联故障,从而确保每个用户的提款都是独立的并且具有 DoS 抵抗性。

其他 DoS 漏洞和缓解措施

除了无界循环之外,其他 DoS 向量也需要注意:

1. 外部调用失败

对不受信任的合约的外部调用(例如,calltransfer)可能会 revert 或消耗过多的 gas。

有漏洞的代码

function transferTo(address recipient, uint256 amount) external {
    recipient.transfer(amount); // 如果接收者 revert,则失败
}

缓解措施

  • 使用具有有限 gas 和错误处理的底层 call
  • 实施熔断器以在攻击期间暂停操作。
bool public paused;
modifier whenNotPaused() {
    require(!paused, "Contract paused");
    _;
}

function transferTo(address recipient, uint256 amount) external whenNotPaused {
    (bool success, ) = recipient.call{value: amount, gas: 50000}("");
    require(success, "Transfer failed");
}
function pause() external {
    paused = true;
}

2. 状态膨胀

攻击者可能会膨胀合约状态(例如,通过 spam 出价),从而增加合法操作的 gas 成本。

缓解措施

  • 限制动态数据结构的大小:
uint256 public constant MAX_BIDDERS = 1000;

function bid() external payable {
    require(bidders.length < MAX_BIDDERS, "Too many bidders");
    bidders.push(msg.sender);
    bids.push(msg.value);
}
  • 对大型操作使用批量处理:
function queueRefunds(uint256 start, uint256 limit) external {
    uint256 end = start + limit < bidders.length ? start + limit : bidders.length;
    for (uint256 i = start; i < end; i++) {
        refunds[bidders[i]] += bids[i];
    }
}

3. Gas Griefing

攻击者部署在被调用时会消耗过多 gas 的合约,从而减慢或阻止调用者。

缓解措施

  • 限制转发到外部调用的 gas(例如,call{value: amount, gas: 50000})。
  • 使用诸如 Forta 之类的工具来监视 gas 使用情况以检测异常。

编写具有 DoS 抵抗性的智能合约的最佳实践

为了与 Solodit 清单和行业标准保持一致,请采用以下实践:

  1. 使用映射而不是数组:映射提供恒定时间访问,这与数组不同,数组的 gas 成本呈线性增长。
  2. 限制循环迭代:批量处理数据(例如,每次交易 50 个项目)或使用分页。
  3. 实施基于 Pull 的模式:通过让用户单独声明资金来分配 gas 成本。
  4. 限制状态增长:限制动态数据结构以防止状态膨胀。
  5. 添加熔断器:包括暂停功能,以便在攻击期间紧急停止。
  6. 优化 Gas 使用:使用诸如 Foundry 的 gas 报告(forge test --gas-report)之类的工具来识别效率低下的地方。
  7. 广泛测试
  • 使用 HardhatFoundry 模拟大型数据集(例如,10,000 个竞标者)。
  • 使用诸如 Echidna 之类的模糊测试工具来测试边缘情况。
  • Fork 主网以在实际情况下测试 gas 限制。

DoS 预防的高级工具(2025 年更新)

截至 2025 年 6 月,智能合约安全生态系统提供了强大的工具来检测和预防 DoS 漏洞:

  • Slither (0.10.x):标记无界循环和高 gas 操作(slither --detect high-gas)。
  • MythX:结合静态和动态分析来识别 gas 密集型代码路径。
  • Foundry:增强的固定性测试,通过针对主网 Fork 进行差分模糊测试。
  • Forta:用于实时监控 gas griefing 和状态膨胀攻击。
  • Gas Station Network (GSN):将 gas 成本分担给用于元交易的中继器,从而降低了用户面临的 gas 风险。

DoS 弹性测试

为了确保你的合约具有 DoS 抵抗性,请实施以下测试策略:

it("handles large bidder lists", async () => {
    for (let i = 0; i < 1000; i++) {
        await auction.bid({ value: ethers.utils.parseEther("1") });
    }
    await expect(auction.queueRefunds()).to.not.be.reverted;
});

2. 模糊测试:使用 Echidna 模拟随机输入和高迭代计数。

3. 主网 Fork:使用 Hardhat Fork 以太坊主网,以在实际情况下测试 gas 限制。

4. Gas 分析:使用 Foundryforge test --gas-report 来识别高 gas 函数。

用户流程可视化

安全的实现确保了流畅的用户体验,具有明确的错误处理并且没有级联故障。

结论:构建具有 DoS 抵抗性的合约

拒绝服务攻击利用以太坊的 gas 约束来破坏智能合约的功能,从而对 DeFi、NFT 市场和其他 dApp 构成重大威胁。通过解决诸如无界循环和外部调用失败之类的漏洞(如 Solodit 清单(SOL-AM-DoS-1)中所述),开发人员可以创建弹性合约。pull-over-push 模式,结合 gas 高效的设计、强大的测试以及诸如 SlitherFoundry 之类的高级工具,可确保合约在压力下保持功能。

本文开启了智能合约安全:Solodit 清单系列。接下来,我们将解决重入攻击,探索臭名昭著的 DAO hack,模拟漏洞利用,并实施诸如 OpenZeppelin 的 ReentrancyGuard 之类的安全模式。无论你是构建下一个大型 DeFi 协议还是审计现有合约,掌握 DoS 缓解是迈向安全、无需信任的未来的关键一步。

请继续关注第 2 部分:征服重入攻击!

  • 原文链接: medium.com/@ankitacode11...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
ankitacode11
ankitacode11
江湖只有他的大名,没有他的介绍。