Solodit 检查清单解析:拒绝服务攻击 第2部分

  • cyfrin
  • 发布于 3天前
  • 阅读 212

本文继续讨论了Solodit智能合约检查清单,重点介绍了如何防止拒绝服务(DoS)攻击,分析了队列处理漏洞、低精度代币的挑战以及如何安全处理外部调用的重要性。通过实例说明了每种漏洞的潜在风险及对应的解决方案,以提高智能合约的安全性和抵御能力。

Solodit 检查表解析 (2):拒绝服务攻击 第2部分

了解如何通过确保队列安全、处理低小数点代币以及安全管理外部调用来防止智能合约中的拒绝服务 (DoS) 攻击。

欢迎回到“ Solodit 检查表解析”系列。我们将继续探索 Solodit 智能合约审计 检查表,专注于编写安全且具有弹性的 智能合约 的实用策略。

该检查表帮助你识别潜在漏洞并实施主动安全措施。在之前的部分“ Solodit 检查表解析 (1)”中,我们研究了与拒绝服务 (DoS) 攻击相关的三个检查项 (提取模式、最低 交易 金额和黑名单),并为开发者提供了解决方案。如果你还没有阅读,我建议在继续之前先查看该文章。

在这篇文章中,我们将讨论检查表中的三个其他项目,再次聚焦于 DoS 攻击。我们将深入探讨 队列处理漏洞低小数点代币带来的挑战以及 安全处理外部合约调用的重要性

为了获得最佳体验,请打开一个 Solodit 检查表 的标签以供参考。

简要概述

以下是今天我们将讨论的内容:

  • SOL-AM-DOSA-4:阻塞队列处理:当攻击者操纵队列以暂停或中断其操作时,就会出现此问题,从而导致 DoS。我们将研究攻击者如何破坏处理队列。

  • SOL-AM-DOSA-5:低小数点代币:处理小数位数低的代币时会出现的问题。计算,特别是除法,可能会被截断为零,导致意外且有害的行为。这类低精度代币可能导致整数除法的问题,从而干扰关键功能。

  • SOL-AM-DOSA-6:不安全的外部调用:我们将探讨如何依赖外部合约而没有适当的错误处理创建漏洞。当外部调用的失败没有正确处理时,可能导致整个合约发生 回滚

现实世界的攻击证明了这些漏洞可能造成的 潜在损害。由于看似微小的疏忽,项目遭受了巨大的损失。

Solodit 检查表基于审计发现、漏洞赏金报告和现实世界事件。通过研究这些检查项,你可以 从过去的错误中学习,改善代码的安全性

现在,让我们通过示例代码逐一查看每个检查项。这些示例已简化,以突出核心漏洞。

SOL-AM-DOSA-4:攻击者能否阻止或防止队列处理以导致服务中断?

问题:如果你的智能合约依赖队列进行任务处理,攻击者可能会 操纵队列中的特定状态 来阻止正确处理。

检查表问题:“攻击者能否阻止或防止队列处理以导致服务中断?”

解决方案:你的队列处理机制需要 强大的错误处理和回退机制,以确保即使出现问题也能继续处理。

示例

考虑一个提取队列的示例:

  1. 用户请求提取,某些标志表示请求处于活动状态。

  2. 攻击者可以在提取被入队后利用 resetUserStatus 函数,阻止其他用户的同样操作。

   // 易受攻击的函数:可以被利用
    function resetUserStatus() external {
        // 任何人在仍然处于队列中的情况下都可以重置他们的状态
        withdrawalRequested[msg.sender] = false;
        // 注意:用户没有被移出队列!
    }

    // 处理队列中的下一次提取
    function processNextWithdrawal() external {
        require(withdrawalQueue.length > currentIndex, "没有可处理的提取");

        // 获取下一次提取
        Withdrawal memory withdrawal = withdrawalQueue[currentIndex];

        // 可被攻击者利用:此检查可能会被重置状态所攻击
        require(withdrawalRequested[withdrawal.user], "提取请求不再有效");

        // 处理提取
        uint256 amount = withdrawal.amount;
        require(balances[withdrawal.user] >= amount, "余额不足");

        // 更新余额
        balances[withdrawal.user] -= amount;

        // 重置提取请求
        withdrawalRequested[withdrawal.user] = false;

        // 发送资金
        (bool success, ) = payable(withdrawal.user).call{value:amount}("");
        require(success, "资金发送失败");

        // 移动到队列中的下一个
        currentIndex++;
    }

要利用这一点,攻击者可以执行以下操作:

  1. 发起提取请求。
  2. 调用 resetUserStatus 函数。
  3. processNextWithdrawal 函数将回滚,造成持续的 DoS 攻击。

补救措施

  • 限制修改 withdrawalRequested 状态的权限给管理员。
  • 确保验证检查以避免零值交易。
  • 实施回退函数以处理意外错误。

SOL-AM-DOSA-5:低小数点代币会导致 DoS 吗?

问题:低小数位数的代币可能导致 整数除法问题,从而四舍五入至零

想象一个代币流媒体合约在一段时间内分配代币。如果 tokensPerSecond 由于与低小数点代币的整数除法而四舍五入为零,分配函数将被阻止。检查表问题:“低小数点代币会导致 DoS 吗?” 解决方案:实施 处理低小数点的逻辑,以防止由于四舍五入错误而中断交易过程。 示例

考虑一个 TokenStream 合约,该合约定期向用户流送一定数量的代币,其中:

  1. total_tokens 需要转移到合约
  2. token_per_second 可能被四舍五入为零,因为我们使用的是小数点为1的代币
  3. distributeTokens 函数将回滚
contract TokenStream {
    IERC20 public token;
    uint256 public streamDuration;
    uint256 public tokensPerSecond;

    constructor(IERC20 _token, uint256 _streamDuration, uint256 _tokensPerSecond) {
        token = _token;
        streamDuration = _streamDuration;
        tokensPerSecond = _tokensPerSecond;
    }

    function distributeTokens(address recipient) external {
        uint256 balance = token.balanceOf(address(this));
        uint256 amount = tokensPerSecond * streamDuration;

        uint256 tokensToSend = amount > balance ? balance : amount;

        require(tokensToSend > 0, "没有足够的代币进行流送");
        token.transfer(recipient, tokensToSend);
    }
}

contract LowDecimalToken is ERC20 {
    constructor() ERC20("LowDecimalToken", "LDT") {
        _mint(msg.sender, 100000 * (10 ** decimals()));
    }

    function decimals() public view virtual override returns (uint8) {
        return 1; // 模拟小数点为低的代币
    }
}

在这种情况下,TokenStreamTest 中的 testDOSWithLowDecimalTokens 测试将会回滚。

补救措施:确保合约正确处理低小数点代币,通过 缩放数学公式来减轻整数四舍五入 在计算过程中导致的问题。

SOL-AM-DOSA-6:协议是否安全地处理外部合约交互?

问题:许多智能合约依赖外部合约进行交互。外部合约的意外行为可能导致整个系统回滚。未能处理这些外部错误会导致 DoS 漏洞。

检查表问题:“协议是否安全地处理外部合约交互?”

解决方案:确保 外部合约交互的强大错误处理,以保护协议完整性,无论外部合约的性能如何。

示例:考虑一个与外部 Chainlink 价格数据源交互的合约。如果没有使用 try/catch 进行适当的错误处理,任何来自外部数据源的回滚将向上级传播,并导致合约回滚。

  1. getPrice 函数检索价格数据。
  2. 当外部 Chainlink 预言机失败时,整个代码将回滚。
  3. calculateSomethingImportant 函数依赖于 getPrice 并将也会回滚。
contract PriceDependentContract {
    AggregatorV3Interface public priceFeed;

    constructor(address _priceFeed) {
        priceFeed = AggregatorV3Interface(_priceFeed);
    }

    // 易受攻击的函数,在没有处理可能的 Chainlink 回滚的情况下获取价格
    function getPrice() public view returns (uint256) {
        (, int256 price, , , ) = priceFeed.latestRoundData(); // 易受攻击的行:没有错误处理
        require(price > 0, "价格必须为正");
        return uint256(price);
    }

    function calculateSomethingImportant() public view returns (uint256) {
        uint256 price = getPrice();
        // ... 使用价格的一些重要计算
        return price * 2;
    }

补救措施将外部合约调用包装在 try/catch 块中 以处理回滚错误,并实施回退或缓存值。

注意:有一个边缘情况是外部合约故意消耗 gas,这也可能使 catch 块失败!我们稍后会讨论这一点。

结论

我们探讨了三个关键检查项,旨在增强你的智能合约抵御 DoS 攻击的能力:队列处理漏洞与低小数点代币相关的挑战以及 安全处理外部合约交互的重要性

请记住,提供的示例旨在说明核心漏洞。理解基本原则 并将这些概念调整为你的具体用例是非常重要的。

通过实施这些建议,你可以显著提高智能合约的安全性和韧性。

请继续关注“Solodit 检查表解析”系列的下一部分。

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

0 条评论

请先 登录 后评论
cyfrin
cyfrin
Securing the blockchain and its users. Industry-leading smart contract audits, tools, and education.