作者:小白
編輯:Liz
背景概述
在以太坊生態中,合約地址的確定性生成機制爲開發者提供了便利,但同時也引入了新的攻擊面。本期我們將分析通過使用 CREATE 與 CREATE2 操作碼在不同時間部署不同合約到同一地址的攻擊手法及防禦策略。往期智能合約安全審計入門文章見合集。
前置知識
我們先來了解以太坊地址生成的兩種規則:
1. CREATE
CREATE 是以太坊虛擬機(EVM) 中用於動態部署智能合約的原生操作碼。自以太坊創世區塊起,所有合約部署均依賴此機制。其核心特點是地址生成依賴於部署者賬戶的 nonce,因此地址是非確定性的(無法在部署前精確預知)。
CREATE 生成的合約地址由部署者的地址和地址的 nonce 決定:

2. CREATE2
CREATE2 是以太坊在君士坦丁堡硬分叉(2019 年 2 月)中引入的新合約創建操作碼(EIP-1014)。與傳統的 CREATE 不同,在合約部署前,鏈下參與者就可預先計算出合約地址,這使得鏈下交互(如狀態通道)和複雜合約架構成爲可能。
CREATE2 生成的合約地址由以下四個參數決定:

到這裏,相信大家已經瞭解了以太坊生成合約地址的兩種方式,心細的讀者可能會想,如果計算出的合約地址已經存在了怎麼辦?這一點不必擔心,在以太坊中,無論通過 CREATE 還是 CREATE2 生成的地址,只要已經在鏈上存在(無論是外部賬戶 EOA 還是合約賬戶),EVM 都會拒絕創建合約的請求。以下是地址衝突的兩種場景:
1. 目標地址是外部賬戶(EOA)
規則:如果目標地址是一個已存在的 EOA(例如,用戶錢包地址),EVM 會拒絕合約部署請求。
結果:交易失敗,Gas 被消耗,合約不會被創建,且不會覆蓋該地址的任何數據。
2. 目標地址是合約賬戶
規則:如果目標地址是一個已部署的合約賬戶,EVM 同樣會拒絕部署請求。
結果:交易失敗,Gas 被消耗,原有合約的代碼和存儲數據保持不變。
當然,凡事都有例外,如果目標地址爲合約並且已通過 selfdestruct 自毀,此時就可以重新在該地址部署新合約。
至此,CREATE 與 CREATE2 這兩種操作碼的各種特性就介紹完了,下面我們看看如何利用這兩個操作碼的特性打出一套組合拳,完成一次合約攻擊。
漏洞示例

漏洞分析
這個 DAO 合約實現了一個基本的治理機制:Owner 通過 approve 函數審覈並記錄提案合約地址到 Proposals 數組中,任何用戶隨後可通過 execute 函數執行已審覈的提案。看似嚴密的權限控制(僅 Owner 可審覈提案)結合執行檢查(提案需審覈且未執行),實則存在一個隱蔽的邏輯漏洞:已審覈的提案地址可能在執行時指向完全不同的合約代碼。攻擊者可分如下三步實施攻擊:
1. 部署正常合約,獲取授權
攻擊者首先部署一個包含無害 executeProposal() 函數的合約 A,通過 Owner 審覈將地址加入提案列表。
2. 自毀原合約,搶佔地址
合約 A 執行自毀操作(selfdestruct) 清空代碼,隨後攻擊者使用 CREATE2 操作碼在同一地址部署惡意合約 B(包含危險邏輯的 executeProposal())。
3. 觸發執行,劫持控制權
當用戶調用 execute 時,合約會通過 delegatecall 執行新部署的惡意合約 B 代碼。由於 delegatecall 會保留當前合約的上下文,攻擊者可通過此操作篡改 DAO 合約狀態(如修改 Owner)或轉移資產。
下面我們結合攻擊合約來看看具體的攻擊流程。
攻擊合約

攻擊流程如下:
1. Alice 部署了 DAO 合約。
2. Evil 部署 DeployerDeployer 合約,地址爲 DD。
3. Evil 調用 DD.deploy(),使用 CREATE2 部署 Deployer 到地址 D(固定 salt)。
4. 調用 D.deployProposal(),創建 Proposal 合約地址 P,此時 D 的 nonce 是 0。
5. Alice 批准地址 P。
6. 攻擊者調用 D.kill(),銷燬 D,此時地址 D 的賬戶被清除,包括 nonce 也被重置爲 0。
7. 攻擊者再次調用 DD.deploy(),重新部署 Deployer 到地址 D(同樣的 CREATE2 參數)。
8. 調用 D.deployAttack(),因爲此時 D 的 nonce 被重置爲 0 且 D 的地址沒變,所以創建的 Attack 合約地址與之前的 Proposal 合約地址 P 是相同的。
9. 此時,DAO 的 proposals 數組中有一個提案指向地址 P,但現在地址 P 已經被重新部署爲 Attack 合約。因此,當調用 execute() 時,會執行 Attack 的 executeProposal(),最終通過 delegatecall 修改 DAO 的 Owner 爲 msg.sender,也就是 Attack 合約。
攻擊原理可以總結爲攻擊者利用 CREATE2 可以重新部署合約到同一地址的特性,先部署一個合法的 Proposal 合約,讓 DAO 批准。然後銷燬 Deployer 合約,重新部署同一地址的 Deployer,重置該地址的 nonce ,最終成功部署惡意合約到與之前 Proposal 相同的地址。由於 DAO 存儲的提案地址現在指向惡意合約,當執行提案時,惡意代碼在 DAO 的上下文中執行,成功將 Owner 修改爲攻擊者。
修復建議
作爲開發者:
在批准提案時,不僅要記錄地址,還要記錄該地址的代碼哈希,並在執行時驗證代碼哈希是否一致。
避免使用 delegatecall 調用外部合約,除非有充分的安全措施。
考慮合約自毀後重新部署到同一地址的可能,進行外部調用時,需要檢查外部合約是否可信,如果需要調用陌生的外部合約,建議檢查合約中是否存在防自毀機制。
作爲審計者:
識別外部調用的合約中是否存在 selfdestruct 自毀功能,如果存在,則需要警惕外部合約自毀後部署惡意代碼到同一地址的攻擊風險。
檢查合約部署方式是否使用了 CREATE2,確保生成合約地址用到的 salt 值足夠隨機,防止因爲合約地址被提前預測導致合約地址被攻擊者搶先部署佔用。
檢查 DAO 執行提案時是否驗證目標地址的代碼一致性,比如比較當前代碼哈希與批准時的哈希。
謹慎分析 delegatecall 的使用場景,確認目標地址是否可信,是否存在任意地址調用的風險。
審查提案的生命週期管理,比如是否有機制防止提案被修改或替換,例如在批准後鎖定目標地址的狀態。
