智能合约安全审计入门:拒绝服务攻击

  • slowmist
  • 发布于 2023-01-21 11:26
  • 阅读 40

本文探讨了智能合约中的拒绝服务(DoS)攻击,并提供了一个 KingOfEther 合约的漏洞示例,解释了攻击原理,并给出了修复建议。文章还从开发者和审计员的角度,分析了如何避免和检测此类漏洞,包括代码逻辑错误、外部调用处理不当以及权限管理不当等问题。

背景

拒绝服务(DoS)是智能合约安全中的一个问题,类似于传统的网络安全。

先决知识

传统网络安全拒绝服务(DoS):DoS 是拒绝服务的缩写。当对服务的干扰降低或消除其可用性时,就会发生拒绝服务。以下是针对网络协议的常见拒绝服务攻击的示例:SYN Flood、IP Spoofing、UDP Flood、Ping Flood、Teardrop Attack、LAND attack、Smurf attack、Fraggle attack 等。

智能合约拒绝服务攻击:一种安全问题,可能导致代码逻辑错误、兼容性问题或过多的调用深度(区块链虚拟机的特性),从而导致智能合约无法正常运行。智能合约拒绝服务攻击方法相对简单,包括但不限于以下三种:

  • 基于代码逻辑的拒绝服务攻击:这种类型的拒绝服务攻击通常是由合约代码逻辑的不准确引起的。最常见的例子是合约中存在循环遍历传入的 mapping 或数组的逻辑。当传入的 map 或数组没有长度限制时,攻击者可以通过传入一个超长的 map 或数组进行循环遍历来消耗大量的 Gas,导致交易的 Gas 溢出,最终导致智能合约无法运行。
  • 基于外部调用的拒绝服务攻击:这种拒绝服务攻击是由合约对外部调用的处理不当引起的。例如,在智能合约中,存在一个根据外部函数的执行情况来改变合约状态的节点,但没有处理交易失败的情况。有了这种能力,攻击者可以故意导致交易失败,而智能合约会一直尝试处理相同的无效数据。由于在这种环境下智能合约逻辑卡无法进一步使用,因此智能合约会暂时或永久失效。
  • 基于运营管理的拒绝服务攻击:在智能合约中,例如,Owner 账户经常作为管理员,拥有广泛的权限。当 Owner 角色失败或私钥丢失时,转移功能等容易受到非主观拒绝服务攻击,这可能导致(例如)启用或暂停转移功能。

漏洞示例

在我看来,由于常见的背景知识,每个人都熟悉拒绝服务攻击的概念。外部调用拒绝服务攻击是三种 DOS 攻击中最常见的一种。为了提供详尽的介绍,我们将在下面引导你完成一个典型的代码示例:

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

contract KingOfEther {
    address public king;
    uint public balance;

    function claimThrone() external payable {
        require(msg.value > balance, "Need to pay more to become the king");

        (bool sent, ) = king.call{value: balance}("");
        require(sent, "Failed to send Ether");

        balance = msg.value;
        king = msg.sender;
    }
}

漏洞分析

我们可以从上面的合约中看到,目标是选出“以太之王”。claimThrone() 合约允许用户通过输入任何大于前一个用户的以太币数量来竞争“以太之王”的称号。如果币值高于前一个玩家的 ETH,则 ETH 将保留在合约中,新玩家将被加冕为“以太之王”,而旧玩家的 ETH 将以相同的方式返还给他们。

我们可以看到,生成新国王和返回旧国王的逻辑在同一个函数中完成,并且在 claimThrone() 中也检查了退款返回值。让我们结合这些特性来完成攻击。

攻击合约

注意:以下攻击情景和合约代码逻辑仅为示例,仅用于演示目的。

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

contract Attack {
    KingOfEther kingOfEther;

    constructor(KingOfEther _kingOfEther) {
        kingOfEther = KingOfEther(_kingOfEther);
    }

    function attack() public payable {
        kingOfEther.claimThrone{value: msg.value}();
    }
}

首先让我们分析一下攻击过程:

  1. Alice 部署 KingOfEther 合约。
  2. Alice 调用 KingOfEther.claimThrone() 向 KingOfEther 合约发送 1 个以太币,成为“以太之王”。
  3. Bob 调用 KingOfEther.claimThrone() 向 KingOfEther 合约发送 2 个以太币,成为新的国王。
  4. Alice 收到 1 ETH 的退款。
  5. Eve 使用 KingOfEther 的地址部署攻击合约。
  6. Eve 调用 Attack.attack() 向 KingOfEther 合约发送 3 个以太币。
  7. 攻击合约成为新的国王。
  8. Bob 对结果不满意,因此他再次调用 KingOfEther.claimThrone(),这次向 KingOfEther 合约发送 20 个以太币,以证明他的“资金能力”。
  9. Bob 发现他的交易被撤销了,他不再是新的国王。到目前为止,Eve 的攻击已使 KingOfEther 合约永久失效,而 Attack 合约已成为永恒的“以太之王”。

富有而有魅力的 Bob 感到沮丧。如果他如此富有,为什么不能成为国王呢?

在你的收件箱中获取 SlowMist 的故事

免费加入 Medium,以获取作者的更新。

让我们看看为什么。

当 Bob 调用 KingOfEther.claimThrone() 向 KingOfEther 合约发送 20 个以太币时,将触发 KingOfEther.claimThrone() 的退款逻辑,Eve 的 3 个以太币将返回到 Attack 合约。让我们再次检查 Attack 合约。该合约未实现 payable 的 fallback() 方法,因此无法接收以太币。因此,KingOfEther.claimThrone() 的退款逻辑将始终失败,并且其返回值将始终为 false。(sent, “Failed to send Ether”) 检查会始终反转。只要触发退款,在 KingOfEther 合约中继 Attack 合约之后,任何人都无法成为新的国王。因此,Eve 成功地执行了拒绝服务攻击。

修复建议

作为开发者:

  1. 在开发智能合约时,应注意处理持续失败问题,例如异步处理可能失败的外部调用逻辑。
  2. 使用 Call 进行外部调用、循环和遍历时,请注意 gas 消耗。
  3. 避免过度授权给单个角色。在处理合约权限时,应实现合理的权限划分,并且应使用多重签名钱包管理具有权限的角色,以防止因私钥泄漏而导致权限丢失。

以下是先前提到的易受攻击合约的修复示例:

// SPDX-License-Identifier: MITpragma solidity ^0.8.13;
contract KingOfEther {    address public king;    uint public KingValue;    mapping(address => uint) public balances;
    function claimThrone() external payable {        balances[msg.sender] += msg.value;
        require(balances[msg.sender] > balance, "Need to pay more to become the king");                KingValue = balances[msg.sender];        king = msg.sender;    }
    function withdraw() public {        require(msg.sender != king, "Current king cannot withdraw");
        uint amount = balances[msg.sender];        balances[msg.sender] = 0;
        (bool sent, ) = msg.sender.call{value: amount}("");        require(sent, "Failed to send Ether");    }}

balances map 已添加到修复合约中,该合约记录了每个人放入合约中的以太币总数。与之前的合约相比,玩家现在可以增加以太币来夺回王位。修复版本的关键点在于,存在一种异步处理退款逻辑的方法。要获得退款,玩家必须手动调用 withdraw()。即使恶意玩家拒绝接受以太币,这也没有任何影响,也不会导致先前提到的拒绝服务。

作为审计员:

  • 内部合约分析:
  1. 确定合约是否包含任何影响其可用性的逻辑错误。
  2. 密切注意由于虚拟机调用深度过大(最大深度为 1024)而导致 DoS 的可能性。
  3. 专注于代码逻辑是否消耗大量 Gas。
  • 外部合约分析:
  1. 密切注意与外部合约交互时的兼容性问题,例如:未处理 TRC20-USDT 的返回值兼容性,这会导致 token 被锁定。
  2. 检查外部合约调用的返回值是否与预期结果相符。
  • 权限管理分析:

在审计期间,必须检查和确认所有函数方法的可见性和访问权限。有必要结合项目方提供的设计文档,以确认审计期间的权限是否符合设计文档的描述。如果确定存在过多的授权或权限划分不明确,则必须与项目团队沟通,以改进其流程和方法,以确保合约生效时防止行政和运营错误。

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

0 条评论

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