
Optimism盗難事件の深層解析:Layer2ネットワークにおけるコントラクト展開のリプレイ攻撃
TechFlow厳選深潮セレクト

Optimism盗難事件の深層解析:Layer2ネットワークにおけるコントラクト展開のリプレイ攻撃
CoboはOptimismの盗難事件について分析と再現を行い、タイムライン、攻撃経路、イーサリアムのコントラクトアドレス生成、技術的詳細など、複数の観点から包括的な技術解説を実施しました。
事件概要
今年5月末,Optimism基金会聘请做市商Wintermute为OP代币提供流动性,并向Wintermute团队提供了2000万枚OP代币用于做市。在此过程中发生了沟通失误:Wintermute团队向Optimism基金会提供了以太坊主网(Layer1)上的收款地址,但该地址尚未在Optimism网络(Layer2)上部署。Optimism基金会在Layer2向该地址转账后,Wintermute团队才发现问题。然而在修复账户权限之前,攻击者已抢先获取了该账户的控制权,并开始抛售账户中的OP代币。
时间线
● 05.26 & 05.27 — Optimism基金会向Wintermute团队提供的地址 0x4f3a120e72c76c22ae802d129f599bfdbc31cb81 分别发送1枚和100万枚OP代币进行测试。
● 05.27 — Optimism基金会向该地址转入剩余的1900万枚OP代币。
● 05.30 — Wintermute团队发现账户错误,立即联系Optimism基金会,并请求Gnosis Safe团队协助资金回收。经与Optimism基金会及Gnosis Safe团队协商评估,认为该账户仍处于安全状态,仅可通过授权方控制,且可在Gnosis Safe团队协助下恢复访问权限。计划于06.07完成账户权限修复。
● 06.01 — 攻击者部署攻击合约,其内部硬编码了factory地址,表明此时攻击者已制定完整的攻击方案。
● 06.05 — 攻击者发起攻击,成功取得目标账户控制权,并将100万枚OP代币转至Tornado Cash进行混币。
● 06.09 — Wintermute团队发表声明,对此次事件承担全部责任,承诺回购所有被抛售的代币,并呼吁攻击者归还剩余资金。声明发布4小时后,攻击者再次转移100万枚OP代币至某私人账户。
攻击路径
1. 所有Gnosis Safe钱包合约均由Gnosis Safe Proxy Factory合约部署。若要获得目标地址控制权,需通过调用Proxy Factory在该地址部署代理合约(proxy contract)。
2. 攻击发生前,Optimism网络(Layer2)上尚未部署Proxy Factory合约。攻击者通过重放以太坊主网(Layer1)上的Factory部署交易,在Layer2上成功部署了同地址的Factory合约。
3. 在Layer2上多次调用Factory合约的createProxy方法部署代理合约,逐步增加Factory合约的nonce值,直至在目标地址成功部署proxy合约。
4. 调用createProxy部署时,将masterCopy参数设置为攻击者控制的合约地址,使该地址成为新proxy的逻辑实现(implementation)。至此,攻击者完全掌控目标地址。
以太坊合约地址生成机制
为理解为何攻击者能在特定地址部署合约,需了解以太坊的合约地址生成规则。
合约地址无对应私钥,其地址在部署时确定。部署方式有两种:CREATE 与 CREATE2。CREATE 的地址生成算法如下:将部署者地址与其关联的nonce进行RLP编码,再对该结果执行SHA3哈希运算,取最后20字节作为新合约地址。
new_address = hash(sender, nonce)
可用JavaScript代码表示为:
const Web3 = require("web3");
const RLP = require("rlp");
const nonce = 0;
const account = "0xa990077c3205cbdf861e17fa532eeb069ce9ff96";
var e = RLP.encode(
[
account,
nonce,
],
);
const nonceHash = Web3.utils.sha3(Buffer.from(e));
console.log(nonceHash.substring(26));
对于外部账户(EOA),每发起一笔交易,nonce加1;对于合约账户,每次创建新合约时nonce加1。
CREATE2通常由智能合约调用,其地址生成算法如下(编码方式与CREATE相同):
new_address = hash(0xFF, sender, salt, bytecode)
● 0xFF 是固定常量
● sender 为部署发起地址
● salt 为sender指定的任意值
● bytecode 为待部署合约的字节码
● CREATE2避免使用递增的nonce,改用由sender控制的salt,从而更精确地控制部署地址。
技术细节
Layer1 上 Proxy 的创建过程
根据上一节所述,若要在Layer2特定地址部署合约,需先确认其在Layer1的部署方式。Wintermute多签钱包的proxy由以下交易创建:
https://etherscan.io/tx/0xd705178d68551a6a6f65ca74363264b32150857a26dd62c27f3f96b8ec69ca01
使用的是 Proxy Factory 1.1.1,调用方法为 createProxy。其实现代码如下:
function createProxy(address masterCopy, bytes memory data)
public
returns (Proxy proxy)
{
proxy = new Proxy(masterCopy);
if (data.length > 0)
// solium-disable-next-line security/no-inline-assembly
assembly {
if eq(call(gas, proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) { revert(0, 0) }
}
emit ProxyCreation(proxy);
}
可见此处使用了 new 关键字部署Proxy合约,底层调用的是 CREATE 操作码,而非 CREATE2。
因此,只要在Layer2上使用相同地址的Proxy Factory 1.1.1合约并调用createProxy方法,且部署时nonce值一致,即可在相同地址部署出相同的proxy合约。
在 Layer2 重放部署 Gnosis Safe Proxy Factory 1.1.1
攻击发生前,Optimism网络尚未部署 Gnosis Safe Proxy Factory 1.1.1。因此需首先将该Factory合约部署到与Layer1相同的地址。
Layer1 上该Factory合约地址为 0x76E2cFc1F5Fa8F6a5b3fC4c8F4788F0116861F9B,由以下交易于2019年创建:
https://etherscan.io/tx/0x75a42f240d229518979199f56cd7c82e4fc1f1a20ad9a4864c635354b4a34261
解码该交易:
https://www.ethereumdecoder.com/?search=0x75a42f240d229518979199f56cd7c82e4fc1f1a20ad9a4864c635354b4a34261
原始数据如下:
{
"nonce": 2,
"gasPrice": {
"_hex": "0x02540be400"
},
"gasLimit": {
"_hex": "0x114343"
},
"to": "0x00",
"value": {
"_hex": "0x00"
},
"data": "xxxx...xxxx",
"v": 28,
"r": "0xc7841dea9284aeb34c2fb783843910adfdc057a37e92011676fddcc33c712926",
"s": "0x4e59ce12b6a06da8f7ec7c2d734787bd413c284fc3d1be3a70903ebc23945e8c"
}
注意到 v = 28,根据EIP-155,v值为27或28的交易未采用EIP-155签名标准,因此不包含ChainID信息。
因此,该Layer1的合约部署交易可直接在Layer2(Optimism)上重放。重放交易为:
https://optimism.etherscan.io/tx/0x75a42f240d229518979199f56cd7c82e4fc1f1a20ad9a4864c635354b4a34261
成功在Layer2部署了新的Gnosis Safe Proxy Factory,地址与Layer1一致:
https://optimism.etherscan.io/address/0x76e2cfc1f5fa8f6a5b3fc4c8f4788f0116861f9b

图中可见,该合约在攻击开始前4天才创建,但此前已有大量调用记录。
此时该Factory合约的nonce为0。对于合约账户,每次部署新合约都会使nonce加1。因此,持续调用createProxy方法部署proxy合约,即可累加nonce,最终在目标地址成功部署所需的proxy。
在 Layer2 目标地址部署 Proxy
攻击合约地址:0xe7145dd6287ae53326347f3a6694fcf2954bcd8a[1]
攻击者通过该合约批量调用ProxyFactory[2]的createProxy方法,并将masterCopy参数设为自己控制的合约地址,从而大量创建以自身为implementation的proxy合约,最终实现对目标地址的控制。
具体过程:
攻击者从EOA地址1[3]多次调用攻击合约,每笔交易创建162个Proxy合约,最终在以下交易中成功部署出目标地址的合约:
https://optimism.etherscan.io/tx/0x00a3da68f0f6a69cb067f09c3f7e741a01636cbc27a84c603b468f65271d415b
随后,攻击者使用EOA地址2[4]作为proxy的所有者(owner),完全控制该合约。
> eth_call 0x4f3a120E72C76c22ae802D129F599BFDbc31cb81 owner()
0x0000000000000000000000008bcfe4f1358e50a1db10025d731c8b3b17f04dbb
经验教训
随着越来越多以太坊二层解决方案(如Optimism、Arbitrum)和侧链(如xDai、Polygon)的出现,资产与DApp逐渐分散于不同网络中。然而,外部账户(EOA)与Gnosis Safe等智能合约账户在跨链行为上存在巨大差异。
EOA基于私钥,可跨网络使用;而合约账户无私钥,需复杂的部署与初始化流程才能确立控制权与功能,无法直接跨链使用。
同样,各网络中的基础设施合约并非天然存在或自动同步复制。
因此,在处理多层网络时,涉及合约的操作必须格外谨慎。应首先确认目标网络与以太坊主网在合约地址、代码内容、状态等方面的一致性。在复杂场景下,还需验证网络特性与合约功能的兼容性。切勿假设Layer1地址会无差别映射至Layer2或侧链,以免再次发生类似Wintermute的资金损失事件。
参考资料
[1] 攻击合约: https://optimism.etherscan.io/address/0xe7145dd6287ae53326347f3a6694fcf2954bcd8a
[2] ProxyFactory: https://optimism.etherscan.io/address/0x76e2cfc1f5fa8f6a5b3fc4c8f4788f0116861f9b#code
[3] 攻击者EOA地址1: https://optimism.etherscan.io/address/0x60B28637879B5a09D21B68040020FFbf7dbA5107
[4] 攻击者EOA地址2: https://optimism.etherscan.io/address/0x8bcfe4f1358e50a1db10025d731c8b3b17f04dbb
TechFlow公式コミュニティへようこそ
Telegram購読グループ:https://t.me/TechFlowDaily
Twitter公式アカウント:https://x.com/TechFlowPost
Twitter英語アカウント:https://x.com/BlockFlow_News














