本文是智能合约安全系列文章的第一部分,主要讨论了智能合约中拒绝服务(DoS)攻击的威胁与防范。文章通过实际案例Fomo3D,讲解了DoS攻击如何利用以太坊的gas限制使合约不可用,并重点介绍了利用无限制循环和外部调用失败两种主要攻击手段。文章还提供了使用“拉取而非推送”模式、限制状态增长、熔断器等多种缓解措施,以及Slither、MythX、Foundry等高级工具,以构建更具弹性的智能合约。
智能合约安全:Solodit 清单系列的第一部分
想象一下:你已经在以太坊上启动了一个去中心化拍卖平台。它正逐渐受到关注。用户在竞标,资金在流动,一切进展顺利——直到一个恶意行为者毁了这一切。
他们用成千上万的小额出价来 spam 你的合约。突然,你的退款功能停止工作。资金被锁定,用户感到沮丧,你项目的声誉岌岌可危。
这就是拒绝服务(DoS)攻击的影响——一种不会窃取资金,但会使你的智能合约完全无法使用的威胁。
在以太坊生态系统中——每个操作都需要花费 gas,并且每个区块都有严格的 gas 上限(截至 2025 年 6 月,目前约为 3000 万 gas)——攻击者可以利用 gas 限制来导致失败。
在这篇 智能合约安全:Solodit 清单系列 的第一篇文章中,我们将重点关注来自 Solodit 安全清单 的 SOL-AM-DoS-1 漏洞,特别是无界循环和外部调用失败。
我们将探讨:
无论你是 Solidity 开发者还是安全审计员,本指南都将帮助你构建更具弹性的智能合约。
智能合约运行在 以太坊虚拟机(EVM) 上,每条指令都会消耗 gas。当交易的 gas 使用量超过 区块 gas 上限 时,交易就会失败。
DoS 攻击者会利用这一点:
Fomo3D 游戏是一款病毒式传播的以太坊 dApp,当攻击者用小额交易 spam 合约以膨胀其状态时,它面临着 DoS 攻击。诸如奖金分配之类的关键功能变得 gas 消耗过大,延迟了支付并使玩家感到沮丧。该事件强调了面向公众的合约中 gas 高效设计的重要性。
让我们检查一个有漏洞的拍卖合约,看看 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 灾难。以下是攻击者如何利用它:
bidders.length
。refundAll()
时,循环的 gas 成本超过 3000 万 gas 的限制,导致交易 revert。2. 恶意接受者利用:
receive()
函数):contract MaliciousBidder {
receive() external payable { revert("No refunds"); }
}
refundAll()
尝试将 ETH 发送到此合约时,它会 revert,从而停止循环并阻止所有后续退款。refundAll()
函数假定所有退款都将成功,并且竞标者列表保持可管理。实际上,单个恶意行为者或庞大的用户群可能会瘫痪该合约,从而无限期地锁定资金。
核心思想:让用户提取自己的资金,而不是将资金推送给循环中的每个人。
这是拍卖合约的安全版本:
// 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");
}
}
安全的工作流程避免了级联故障,从而确保每个用户的提款都是独立的并且具有 DoS 抵抗性。
除了无界循环之外,其他 DoS 向量也需要注意:
对不受信任的合约的外部调用(例如,call
或 transfer
)可能会 revert 或消耗过多的 gas。
有漏洞的代码:
function transferTo(address recipient, uint256 amount) external {
recipient.transfer(amount); // 如果接收者 revert,则失败
}
缓解措施:
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;
}
攻击者可能会膨胀合约状态(例如,通过 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];
}
}
攻击者部署在被调用时会消耗过多 gas 的合约,从而减慢或阻止调用者。
缓解措施:
call{value: amount, gas: 50000}
)。为了与 Solodit 清单和行业标准保持一致,请采用以下实践:
forge test --gas-report
)之类的工具来识别效率低下的地方。截至 2025 年 6 月,智能合约安全生态系统提供了强大的工具来检测和预防 DoS 漏洞:
slither --detect high-gas
)。为了确保你的合约具有 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 分析:使用 Foundry 的 forge test --gas-report
来识别高 gas 函数。
安全的实现确保了流畅的用户体验,具有明确的错误处理并且没有级联故障。
拒绝服务攻击利用以太坊的 gas 约束来破坏智能合约的功能,从而对 DeFi、NFT 市场和其他 dApp 构成重大威胁。通过解决诸如无界循环和外部调用失败之类的漏洞(如 Solodit 清单(SOL-AM-DoS-1)中所述),开发人员可以创建弹性合约。pull-over-push 模式,结合 gas 高效的设计、强大的测试以及诸如 Slither 和 Foundry 之类的高级工具,可确保合约在压力下保持功能。
本文开启了智能合约安全:Solodit 清单系列。接下来,我们将解决重入攻击,探索臭名昭著的 DAO hack,模拟漏洞利用,并实施诸如 OpenZeppelin 的 ReentrancyGuard 之类的安全模式。无论你是构建下一个大型 DeFi 协议还是审计现有合约,掌握 DoS 缓解是迈向安全、无需信任的未来的关键一步。
请继续关注第 2 部分:征服重入攻击!
- 原文链接: medium.com/@ankitacode11...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!