返回论坛

智能合约安全审计:CREATE vs CREATE2 —— 同一地址部署不同合约的攻击向量分析

查找币 学术研究 安全研究 Web3安全 区块链安全

查找币安全研究院

钱包恢复评估 | 链上取证分析 | Web3 事件响应
以合法授权、证据保全、隐私保护和可复核流程为前提,不要求用户在线提交完整私钥或助记词。

查看研究院 研究报告中心
## 概述 在以太坊生态中,合约地址的确定性生成机制为开发者提供了便利,但同时也引入了新的攻击面。查找币安全团队在近期审计中发现,部分项目因未充分理解 `CREATE` 与 `CREATE2` 操作码的底层差异,导致攻击者能够利用地址复用技术在不同时间点将不同合约部署到同一地址,进而实施破坏性操作。本文将深入剖析这一攻击手法的技术原理、漏洞示例及审计防御策略。 ## 前置知识:以太坊合约地址生成机制 ### 1. CREATE 操作码 `CREATE` 是以太坊虚拟机(EVM)中最基础的合约部署操作码,自创世区块起便存在。其核心特点是地址生成依赖于部署者账户的 `nonce` 值,因此地址具有**非确定性**——无法在部署前精确预知。 地址生成公式如下: ``` contract address = last 20 bytes of keccak256(RLP(sender, nonce)) ``` 其中: - `sender`:部署合约的账户地址 - `nonce`:部署者账户的当前交易次数 - `RLP`:递归长度前缀编码 ### 2. CREATE2 操作码 `CREATE2` 是以太坊在君士坦丁堡硬分叉(2019年2月,EIP-1014)中引入的新型合约创建操作码。与 `CREATE` 不同,`CREATE2` 允许参与者在链下预先计算合约地址,从而支持状态通道、反事实部署等复杂架构。 地址生成公式如下: ``` contract address = last 20 bytes of keccak256(0xff || sender || salt || keccak256(init_code)) ``` 其中: - `0xff`:固定前缀,用于区分 `CREATE` - `sender`:部署者地址 - `salt`:用户自定义的32字节盐值 - `init_code`:合约初始化代码(包括构造函数参数) ### 地址冲突规则 无论使用 `CREATE` 还是 `CREATE2`,EVM 都会确保目标地址未被占用: - **目标地址为外部账户(EOA)**:若地址已被用户钱包等外部账户占用,EVM 拒绝部署,交易失败并消耗 Gas。 - **目标地址为合约账户**:若地址已存在合约代码,EVM 同样拒绝部署,原合约代码和存储保持不变。 **例外情况**:如果目标地址上的合约已通过 `selfdestruct` 自毁,则该地址变为可重新部署的状态。这正是攻击者可利用的关键点。 ## 攻击原理:地址复用与 delegatecall 结合 攻击者利用 `CREATE2` 的地址可预测性,结合 `selfdestruct` 自毁机制,实现以下攻击路径: 1. **首次部署**:攻击者使用 `CREATE2` 部署一个看似无害的合约(如包含 `selfdestruct` 的合约)到特定地址。 2. **获取信任**:该合约地址被项目方批准(如 DAO 的提案系统)作为可执行目标。 3. **自毁重置**:攻击者触发 `selfdestruct`,清除该地址上的代码和存储。 4. **重新部署**:攻击者使用相同的 `salt` 和 `init_code` 参数,通过 `CREATE2` 将恶意合约部署到同一地址。 5. **执行攻击**:项目方通过 `delegatecall` 调用该地址,执行恶意逻辑。 ## 漏洞示例:DAO 合约 以下是一个存在漏洞的 DAO 合约示例: ```solidity // 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 合约存在以下安全缺陷: 1. **地址未验证**:`approve` 函数仅记录目标地址,未验证该地址上的代码内容或哈希。 2. **无代码一致性检查**:`execute` 函数执行 `delegatecall` 时,未验证目标地址的代码是否与批准时一致。 3. **依赖外部合约**:合约完全信任已批准的地址,未考虑合约自毁后重新部署的风险。 ### 攻击流程 1. 攻击者部署一个包含 `selfdestruct` 的辅助合约,并通过 `CREATE2` 计算目标地址。 2. 攻击者将辅助合约地址提交给 DAO 的 `approve` 函数,Owner 批准该提案。 3. 攻击者触发辅助合约的 `selfdestruct`,清除该地址上的代码。 4. 攻击者使用相同的 `salt` 和 `init_code` 参数,通过 `CREATE2` 部署恶意合约(如包含 `selfdestruct(address(this))` 或转移资金逻辑)到同一地址。 5. 攻击者调用 `execute` 函数,DAO 通过 `delegatecall` 执行恶意合约的 `executeProposal()` 函数,导致资金损失或合约状态被破坏。 ## 防御策略 ### 作为开发者 1. **代码哈希验证**:在批准提案时,不仅记录目标地址,还要记录该地址的 `codehash`(使用 `extcodehash` 操作码)。执行时验证当前代码哈希与记录的一致。 2. **避免滥用 delegatecall**:仅在完全信任且经过严格审计的外部合约上使用 `delegatecall`。优先考虑使用 `call` 或接口调用。 3. **防范自毁攻击**:对外部合约进行调用时,检查合约是否包含 `selfdestruct` 机制。必要时,要求合约实现防自毁保护(如通过 `Ownable` 限制自毁权限)。 4. **使用 CREATE2 时注意 salt 随机性**:确保 `salt` 值足够随机且不可预测,防止攻击者通过暴力计算占用合约地址。 ### 作为审计者 - **识别 selfdestruct 风险**:检查外部调用的合约中是否存在 `selfdestruct` 功能。若存在,评估攻击者通过自毁后重新部署恶意代码的可能性。 - **验证代码一致性**:检查 DAO 或治理合约在执行 `delegatecall` 时是否验证目标地址的代码哈希与批准时一致。 - **分析 delegatecall 使用场景**:确认目标地址是否可信,是否存在任意地址调用的风险(如用户可控制的地址)。 - **审查提案生命周期**:确保提案被批准后,目标地址的状态不可被修改或替换。例如,在批准后锁定目标地址的代码哈希,直到执行完成。 ## 总结 `CREATE` 与 `CREATE2` 的地址生成机制差异,结合 `selfdestruct` 自毁功能,构成了一个危险的攻击向量。开发者需要深入理解 EVM 的底层行为,在涉及外部合约调用时实施严格的代码一致性检查。审计过程中,应重点关注合约的 `delegatecall` 使用场景、代码哈希验证逻辑以及提案的生命周期管理。 通过本文的分析,希望开发者能够意识到地址复用攻击的潜在风险,并在实际项目中采取相应的防御措施。 --- **本文由查找币安全团队整理发布**
在论坛中查看和回复