前言
北京時間2021年8月10日,跨鏈橋項目Poly Network遭遇攻擊,損失超過6億美金。雖然攻擊者在後續償還被盜數字貨幣,但是這仍然是區塊鏈歷史上涉及金額最大的一次攻擊事件。由於整個攻擊過程涉及到不同的區塊鏈平台,並且存在合約以及Relayer之間的複雜交互,對於攻擊的完整過程和漏洞的根本原因,現有分析報告並未能梳理清楚。
整個攻擊分為兩個主要階段,包括修改keeper簽名和最終提幣。對於第二階段,由於keeper簽名已經被修改,因此攻擊者可以直接構建惡意提幣交易,具體可以參見我們之前的報告。然而對於修改keeper簽名的交易是如何最終在目標鏈執行的,目前並沒有詳細的文章闡明。而這一步是攻擊的最核心步驟。
本報告從修改keeper簽名交易入手(Ontology鏈上交易0xf771ba610625d5a37b67d30bf2f8829703540c86ad76542802567caaffff280c),分析了背後的原理和漏洞的本質。我們發現以下幾個原因是Keeper能被修改的原因:
-
源鏈上(Ontology)的relayer沒有對上鍊的交易做語義校驗,因此包含修改keeper惡意交易可以被打包到poly chain上
-
目標鏈上(以太坊)上的relayer雖然對交易做了校驗,但是攻擊者可以直接調用以太坊上的EthCrossChainManager合約最終調用EthCrossChainData合約完成簽名修改
-
攻擊者精心夠著了能導致hash衝突的函數簽名,從而調用putCurEpochConPubKeyBytes完成對簽名的修改 [2]
涉及交易和合約
整個過程中的交互流程如下:
本體交易 -> 本體中繼器 -> 多鏈 -> 以太坊中繼器 -> 以太坊
以太坊
0x838bf9e95cb12dd76a54c9f9d2e3082eaf928270:EthCrossChainManager
0xcf2afe102057ba5c16f899271045a0a37fcb10f2:EthCrossChainData
0x250e76987d838a75310c34bf422ea9f1ac4cc906:LockProxy
0xb1f70464bd95b774c6ce60fc706eb5f9e35cb5f06e6cfe7c17dcda46ffd59581: 修改keeper的交易
本體
0xf771ba610625d5a37b67d30bf2f8829703540c86ad76542802567caaffff280c: 修改keeper的交易
保利
0x1a72a0cf65e4c08bb8aab2c20da0085d7aee3dc69369651e2e08eb798497cc80: 修改keeper的交易
攻擊流程
整個攻擊大致可以分為三個步驟。第一個步驟是在Ontology 鏈生成一條惡意交易(0xf771ba610625d5a37b67d30bf2f8829703540c86ad76542802567caaffff280c),第二個步驟是修改以太坊EthCrossChainData合約中的keeper簽名,第三個步驟構造惡意交易發起最終攻擊和提幣。
步驟一
攻擊者首先在Ontology發起了一筆跨鏈交易(0xf771ba610625d5a37b67d30bf2f8829703540c86ad76542802567caaffff280c),裡麵包含了一個攻擊payload:
可以看出交易包含了精心設計的函數名(圖中以6631開頭的數字,轉換後即 f1121318093),目的在於通過造成哈希衝突(hash collision)的方式調用putCurEpochConPubKeyBytes函數(屬於以太坊上的EthCrossChainData合約)。關於哈希函數衝突的細節在網絡上已有很多討論,可以參考[2].
隨後,該筆交易被Ontology Relayer 接收,注意這裡並沒有很嚴格的校驗。該交易會通過Relayer在Poly Chain成功上鍊(0x1a72a0cf65e4c08bb8aab2c20da0085d7aee3dc69369651e2e08eb798497cc80)。 Ethereum Relayer會感知到新區塊的生成。
然而,這筆交易被Ethereum Relayer拒絕了。原因在於Ethereum Relayer對目標合約地址有校驗,只允許LockProxy合約作為目標地址,而攻擊者傳入的是EthCrossChainData地址。
因此,攻擊者攻擊之路在此中斷。但如前所述,包含惡意payload的攻擊交易已經在Poly Chain成功上鍊,可被進一步利用。
步驟二
攻擊者手動發起交易,調用EthCrossChainManager合約中的verifyHeaderAndExecuteTx函數,將之前一步保存在Ploy Chain區塊中的攻擊交易數據作為輸入。由於該區塊是poly chain上的合法區塊,因此可以通過verifyHeaderAndExecuteTx中對於簽名和merkle proof的校驗。然後執行EthCrossChainData合約中的putCurEpochConPubKeyBytes函數,將原本的4個keeper修改為自己指定的地址(0xA87fB85A93Ca072Cd4e5F0D4f178Bc831Df8a00B)。
步驟三
在keeper被修改之後,攻擊者直接調用目標鏈上的verifyHeaderAndExecuteTx函數(而不需要再通過poly chain — 因為keeper已經被修改,攻擊者可以任意簽署在目標鏈看來合理的poly chain上的塊),最終調用至Unlock函數(屬於LockProxy合約),大量地轉移資金,給項目方帶來了嚴重的損失。具體的攻擊細節可參考我們之前的報告[1]。
Relayer代碼分析
在本攻擊過程中,Ontology方和以太坊方均有Relayer負責將來自Ontology的交易在poly Chain上鍊,以及將poly chain上的交易放到以太坊。這兩個Relayer是由Go語言實現的服務進程。
然而我們發現,這兩個Relayer都缺乏有效的校驗。這導致
-
攻擊者可以在Ontology構造一條惡意的跨鏈交易,並且成功打包到poly chain上。
-
雖然在以太坊的Relayer具有校驗功能,但是攻擊者可以直接同以太坊上的鏈上合約進行交互,直接執行惡意的函數。
Ontology Relayer完全信任來自Ontology上的跨鏈交易
Poly Network 的 ont_relayer(https://github.com/polynetwork/ont-relayer) 負責監聽Ontology 鏈上的跨鏈交易並將其打包入傳入Poly Chain.
注:
-
在Ontology Relayer中,Side 指Ontology Chain; Alliance 指Poly Chain.
-
CrossChainContractAddress 是Ontology 鏈上原生編號為09 的智能合約.
上圖中,Ontology Relayer啟動時開啟三個Goroutines 分別負責監聽Ontology Chain 和Poly Chain 的跨鏈交易,以及對Poly Chain 上的跨鏈交易做狀態檢查。在本報告中,我們只關注69行的監聽Side的代碼邏輯。
在上圖中,Ontology Relayer 調用Ontology 鏈提供的RPC 接口(第215 行,調用SDK函數GetSmartContractEventByBlock) 獲取區塊中觸發的智能合約事件;然後在第228 和232 行表明Ontology Relayer 只監聽Ontology Chain 上由CrossChainContractAddress 觸發的makeFromOntProof 事件;
上圖中,在處理Ontology Chain 上的跨鏈交易時,Ontology Relayer 總共做了五次校驗,分別是兩次向Ontology Chain 發送的RPC 請求校驗(check 1 和check 4), 以及三次參數是否為空的校驗(check2, check3, 和check5)。這五次校驗都屬於常規校驗,並未對來自Ontology Chain 上的跨鏈交易做語義上的校驗; 第167 和171 行取出了在目標鏈上執行所需要的交易參數信息(proof, auditPath);第183 行向Poly Chain 發送交易;
Ontology Relayer 在構造了Poly Chain 上的交易後便向Poly Chain 發起RPC 請求發送交易(第164 行,函數調用SendTransaction);
這個名為ProcessToAliianceCheckAndRetry 的Goroutine 也僅僅是做了重發失敗交易的工作,仍然未對來自Ontology Chain 上的跨鏈交易做任何語義上的校驗。
至此,我們可以看出ont-relayer 監聽所有來自Ontology Chain 由CrossChainContractAddress 觸發的makeFromOntProof 事件,並未對其做任何語義上的校驗,便向Poly Chain 轉發了交易。而任何人向Ontology 發送的任何跨鏈交易都會觸發CrossChainContractAddress 的makeFromOntProof 事件,所以Ontology Relayer 會將所有來自Ontology 上的跨鏈交易都轉發到Poly chain 上。
Ethereum Relayer中的無效校驗
Ethereum Relayer 負責監聽Poly Chain 並將目標鍊為Ethereum 的跨鏈交易轉發到Ethereum 上。
Ethereum Relayer 啟動一個Goroutine 來監控Poly Chain;
Ethereum Relayer監聽所有Poly Chain 上目標鍊為Ethereum 的跨鏈交易(第275 至278 行); Ethereum Relayer會校驗跨鏈交易的目標合約是否為config.TargetContracts中指定的合約之一,如果不是則不會發送這筆跨鏈交易到Ethereum 上(第315 行)。
雖然Ethereum Relayer 對Poly Chain 上的跨鏈交易做了部分校驗,比如限制了目標合約,但是與Poly Chain 不同,任何人都可以向Ethereum 上的EthCrossChainManager合約發送交易。換句話說,Ethereum Relayer 在這裡做的校驗沒有實際的意義,只要包含惡意payload的跨鏈交易被成功打包進了Poly Chain(雖然沒有被relay轉發到以太坊鏈上), 那麼任何人都可以直接使用已經打包好的區塊數據將payload發送到以太坊EthCrossChainManager合約並執行(這個過程中,可以通過merkle proof的校驗,因為是已經正常上鍊的poly chain區塊數據)。
攻擊者正是利用了上述兩個缺陷,完成了攻擊流程中的步驟一和步驟二。
寫在最後
通過對整個攻擊流程的完整梳理和詳盡分析,我們認為Relayer的不完整校驗是攻擊得以發生的根本原因。其它(諸如利用hash衝突等)方面則更多地屬於比較精彩的攻擊技巧。總而言之,跨鏈的校驗和鑑權是跨鏈系統安全的關鍵所在,值得社區付出更多的努力。