區塊鏈專案中,一個交易一般是由未簽名的部分(UnsignedTx,交易要執行的內容)和簽名的部分(交易的witness)構成的。在如比特幣之類的區塊鏈專案中,交易的hash計算實際上是包含了該交易的簽名部分的。而在如NEO、ONT等多種區塊鏈平臺中,交易的計算公式為hash=SHA256(UnsignedTx)。即交易的雜湊是由未簽名的部分計算的來的,與交易的witness無關。而NEO智慧合約在執行的時候,能夠透過Transaction_GetWitnesses方法,從一個交易中獲得該交易的witnesses。其具體實現如下:
某個合約交易獲得自己的witness之後,還能夠透過Witness_GetVerificationScript方法獲得該witness中的驗證指令碼。如果攻擊者針對同一個未簽名交易UnsignedTx1,可以構造兩個不同的驗證指令碼。則可以造成該合約執行的不一致性。正常情況下,合約的VerificationScript是由合約的輸入等資訊決定的,攻擊者無法構造不同的驗證指令碼並透過驗證。但是我們發現在VerifyWitness方法中,當VerificationScript.length=0的時候,系統會呼叫EmitAppCall來執行目標指令碼hash。
所以當VerificationScript=0,或者VerificationScript等於目標指令碼的時候,均可滿足witness驗證條件。即攻擊者可以對於同一個未簽名的交易UnsignedTx_1,構造兩個不同的VerificationScript。攻擊者利用這個性質,可以對NEO智慧合約上的所有代幣資產進行雙花攻擊,其具體攻擊場景如下:
步驟1:攻擊者構造智慧合約交易Tx_1(未簽名內容UnsignedTx_1,驗證指令碼為VerficationScript_1)。在UnsignedTx_1的合約執行中,合約判斷自己的VerficationScript是否為VerficationScript_1。如果為VerficationScript_1,擇傳送代幣給A使用者。如果VerficationScript為空,則傳送代幣給B使用者。
步驟2:Tx_1被打包到區塊Block_1中。
步驟3: 攻擊者收到Block_1後,將Tx_1替換成Tx_2(Tx_1具有與Tx_1相同的未簽名內容UnsignedTx_1,但驗證指令碼為空)從而形成Block_2。攻擊者將Block_1傳送給A使用者,將Block_2傳送給B使用者。
步驟4:當A使用者收到Block_1時,發現自己收到攻擊者傳送的代幣。當B使用者收到Block_2時,也會發現自己收到了攻擊者傳送的代幣。雙花攻擊完成。
可見,該漏洞的利用門檻非常低,且可以對NEO智慧合約上的所有代幣資產進行雙花攻擊。危害非常嚴重。
3.2 NEO MerlkeTree繫結繞過造成交易雙花攻擊漏洞:
智慧合約交易與區塊的繫結,通常透過MerkleTree來完成。如果攻擊者能繞過該繫結,則能實現對任意交易的雙花。這裡我們看看NEO的MerkleTree的實現如下:
在MerkleTreeNode函式中,NEO進行了MerkleTree葉節點到父節點的計算。但這裡存在一個問題,當leaves.length為奇數n的時候。NEO的MerkleTree會將最後一個葉節點複製一次,加入到MerkleTree的計算中。也就是說當n為奇數時,以下兩組交易的MerkleRoot值會相等:
【Tx_1 Tx_2 …… Tx_n】
【Tx_1 Tx_2 …… Tx_n Tx_】其中 Tx_= Tx_n
利用這個特性,攻擊者可以實現對任意NEO資產的雙花攻擊。其具體攻擊場景如下:
步驟1:假設正常的一個合法Block_1,包含的交易列表為【Tx_1 Tx_2 … Tx_n】。攻擊者收到Block_1後,將交易列表替換為【Tx_1 Tx_2 … Tx_n Tx_】,形成 Block_2。然後將Block_2釋出到網路中去。
步驟2:一個普通節點收到Block_2後,會對Block_2的合法性進行校驗。然而因為【Tx_1 Tx_2 … Tx_n Tx_】與【Tx_1 Tx_2 … Tx_n】具有相同的MerkleRoot。所以Block_2能夠透過區塊合法性校驗,從而進如區塊持久化流程。NEO本地取消了普通節點對合法區塊中交易的驗證(信任幾個共識節點)。則Tx_n交易可以被普通節點執行兩次,雙花攻擊執行成功。
可見,該漏洞的利用門檻非常低,且可以對NEO上的所有資產進行雙花攻擊。危害非常嚴重。
4. 虛擬機器不一致性執行
智慧合約平臺共識機制,本質上是將所有的交易【Tx_1 Tx_2 ……. Tx_n】按順序作用到初始State_0上,使全網始終保持相同的狀態。在狀態機複製過程中,我們要求State_n × Tx_èState_是決定性的。State_n × Tx_èState_實質上就是智慧合約虛擬機器對Tx_的執行過程,如果智慧合約虛擬機器中存在設計或者實現漏洞,導致虛擬機器不一致性執行(對相同的輸入State_n 和Tx_,輸出State_不一致)。則攻擊者可以利用該問題在網路中產生分叉和並進行雙花攻擊。下面我們介紹多個EOS和NEO上我們發現的虛擬機器不一致執行漏洞和其產生原因。
4.1 EOS虛擬機器記憶體破壞RCE漏洞:
此前,我們公開了文章《EOS Node Remote Code Execution Vulnerability --- EOS WASM Contract Function Table Array Out of Bound》(http://blogs.360.cn/post/eos-node-remote-code-execution-vulnerability.html)。在該文中,我們發現了一個EOS WASM虛擬機器的一個記憶體越界寫漏洞,針對該漏洞我們編寫的利用程式可以成功利用該漏洞使EOS虛擬機器執行任意指令,從而完全控制EOS所有出塊和驗證節點。
究其本質而言,是在State_n × Tx_{n+1} → State_{n+1}過程中。攻擊者能讓EOS虛擬機器完全脫離原本執行路徑,執行任意指令,自然可以完成雙花攻擊。其攻擊流程如下:
步驟1:攻擊者構造能夠實現RCE的惡意智慧合約,並將該合約釋出到EOS網路中。
步驟2:EOS超級節點解析到該合約後,觸發漏洞,執行攻擊者自定義的任意指令。
步驟3:攻擊者實現雙花攻擊。
該漏洞的危害非常嚴重,且是第一次智慧合約平臺受到遠端程式碼執行攻擊事件。讀者可以閱讀該文章瞭解相關細節,在此不再贅述。
4.2 EOS虛擬機器記憶體未初始化造成雙花攻擊:
我們在編寫《EOS Node Remote Code Execution Vulnerability --- EOS WASM Contract Function Table Array Out of Bound》的利用程式的過程中,還利用了EOS中當時的一個未公開的記憶體未初始化漏洞。在記憶體破壞攻擊中,記憶體未初始化漏洞通常能夠造成資訊洩露、型別混淆等進一步問題,從而輔助我們繞過如ASLR之類的現代二進位制程式的緩解措施,進一步實現攻擊。然而在智慧合約虛擬機器中,記憶體未初始化漏洞有更直接的利用方式,可以直接造成雙花攻擊。以下為我們在EOS RCE中利用的一個記憶體未初始化漏洞的細節,其可以被用來直接實現EOS智慧合約代幣資產雙花攻擊。
當WASM虛擬機器透過grow_memory虛擬碼來申請記憶體新的記憶體。在EOS WASM grow_memory最初的實現中,未對申請到的記憶體進行清零操作。該塊記憶體的空間實際上是隨機的(依賴於合約執行機器的記憶體狀態)。則攻擊者可以構造惡意合約,實現對EOS上任意合約資產的雙花攻擊。其攻擊流程如下:
步驟1: 攻擊者構造惡意智慧合約。合約中透過grow_memory獲得一塊新的記憶體地址。
步驟2:合約中讀取該地址中的某個bit內容(此時該bit可能為0,也可能為1,依賴於合約執行機器的狀態)。
步驟3:合約判斷該bit的內容,如果為1則傳送代幣給A使用者,如果為0則傳送代幣給B使用者,從而實現雙花攻擊。
4.3 EOS虛擬機器記憶體越界讀造成雙花攻擊:
在傳統的記憶體破壞中,記憶體越界讀漏洞主要將會導致資訊洩露,從而輔助我們繞過如ASLR之類的現代二進位制程式的緩解措施,進一步與其他漏洞一起實現攻擊。然而在智慧合約虛擬機器中,記憶體越界讀漏洞有更直接的利用方式,可以直接造成雙花攻擊。下面為一個我們發現的EOS記憶體越界讀漏洞,我們可以利用該漏洞實現雙花攻擊。
當EOS WASM將一個offset轉換內WASM記憶體地址時,其邊界檢查過程如下:
在這裡|ptr|的型別實際上是一個I32型別,它可以是一個負數。那麼當:-sizeof(T) < ptr < 0
的時候,ptr+sizeof(T)是一個很小的數可以透過該邊界檢查。在之後的定址中,我們看到程式碼:T &base = (T)(getMemoryBaseAddress(mem)+ptr);
|base|的地址將會超過WASM的記憶體基址,從而讓智慧合約實現記憶體越界讀(讀到的記憶體地址內容取決於虛擬機器當前執行狀態,可被認為使隨機的)。攻擊者可以利用該漏洞實現雙花攻擊。其攻擊過程如下:
步驟1: 攻擊者構造惡意智慧合約。合約中利用記憶體越界讀漏洞,讀取超越WASM記憶體基址的某個bit)此時該bit可能為0,也可能為1,依賴於合約執行機器的狀態)。
步驟2:合約判斷該bit的內容,如果為1則傳送代幣給A使用者,如果為0則傳送代幣給B使用者,從而實現雙花攻擊。
4.4 標準函式實現不一致造成雙花攻擊:
總結上面雙花攻擊兩個例子的本質,實際上是EOS合約在執行過程中因為某些記憶體漏洞原因讀取到了隨機變數,從而打破了原本虛擬機器執行的一致性,造成了雙花攻擊。事實上,合約執行的不一致性,不一定完全依賴於隨機性。這裡我們介紹一個因為各個平臺(版本)對標準C函式實現不一致造成的雙花攻擊。
在C語言標準定義中,memcmp函式的返回時被要求為:小於0,等於0,或者大於0。然而各種不同的C版本實現中,具體返回的可能不一樣(但依然符合C標準)。攻擊者可以利用該標準實現的不一致性,造成執行在不同系統上的EOS 虛擬機器執行結果不一致,進而實現雙花攻擊。其攻擊流程如下:
步驟1:攻擊者構造惡意智慧合約,在合約中呼叫memcmp函式,並獲取返回值。
步驟2:此時,不同的平臺和版本實現Memcmp的返回值不一致(即使EOS虛擬機器的二進位制程式碼是相同的)。惡意合約判斷Memcmp的返回值,決定轉賬給A或B,從而完成雙花。
該漏洞的具體修復如下:
EOS強制將memcmp的返回值轉換為0,-1或者1,從而抵抗這種不一致執行。
Memcmp這個問題,是同一種語言對相同標準實現的不一致性造成的。事實上,同一個區塊鏈專案經常會有多個不同版本語言的實現。不同語言對相同標準的實現通常也會有偏差,比如一個我們發現的因標準定義實現不一致造成不一致執行是ECDSA函式。ECDSA簽名標準中要求私鑰x不為0。如python、JS中的多個密碼學庫中對該標準由嚴格執行,但是我們發現部分golang的ECDSA庫允許私鑰x=0進行簽名和驗證計算,惡意攻擊者利用該問題可以對同一個區塊鏈平臺的不同版本實現(比如golang實現和python實現)構造不一致執行惡意合約,從而進一步完成雙花攻擊。
4.5版本實現不一致造成雙花攻擊:
同一個區塊鏈專案經常會有多個不同版本程式語言的實現。不同程式語言的實現同樣存在著各種這樣的不一致執行的可能性。上面ECDSA是一個例子。大整數運算也是一個常見的例子。比如在曾經的NEO的C#版本實現和python版本實現中,大整數(BigInteger)除法運算可導致不同程式語言實現版本見的不一致執行現象,從而造成雙花攻擊。類似的現象在多個區塊鏈專案中產生過。
4.6其他問題不一致性問題
系統時間、隨機數、浮點數計算等因素也是可以造成虛擬機器不一致執行的原因。但是在我們的審計中,並沒有發現此類漏洞在大公鏈專案中出現。多數區塊鏈專案在設計之初就會考慮到這些明顯可能造成的問題。
但可能造成不一致執行的因素可能遠遠超過我們上面發現的這些問題。事實上,一些主觀因素(取決於機器當前執行狀態的因素,我們稱之為主觀因素)都可能造成虛擬機器的不一致執行。舉個例子,比如在4G記憶體,8G記憶體的機器在執行過程中產生記憶體溢位(OOM)的主觀邊界就不一樣,攻擊者利用OOM可能造成虛擬機器的不一致執行。
5. 共識機制造成的雙花攻擊
共識機制造成的雙花攻擊實際上是在業界中獲得充分討論的一個問題,然而各種公鏈方案在共識機制實現上仍然可能存在分叉問題,從而造成雙花攻擊。
5.1 ONT vBFT VRF隨機數繞過漏洞
Long range attack 是目前所有PoS共識機制都面臨的一種分叉攻擊方法。攻擊者可以選擇不去分叉現有的鏈,而實回到某個很久之前的鏈狀態(攻擊者在這個狀態曾佔有大量貨幣),造一跳更長的新鏈出來讓網路誤以為是主鏈,從而完成雙花。目前業界針對Long range attack並沒有根本的解決辦法,只能保證在“Weak Subjectivity”不發生的情況下,防止分叉發生。
ONT的vBFT共識演算法提出了一種依靠可驗證隨機函式(VRF)來防止惡意分叉擴充套件的方法。網路首先基於VRF在共識網路中依次選擇出一輪共識的備選區塊提案節點集,區塊驗證節點集和區塊確認節點集,然後由選出的節點集完成共識。由於每個區塊都是由VRF確定節點的優先順序順序的,對於惡意產生的分叉,攻擊者很難持續維持自己的高優先順序(如果攻擊者沒有控制絕大多數股權的話),因此惡意產生的分叉將很快消亡,從而使vBFT擁有快速的狀態終局性。
然而我們發現vBFT中的VRF實現存在一個漏洞,導致私鑰為0的使用者的可對任意區塊資料生成相同的vrfValue。具體的,vBFT中的vrf是對由波士頓大學提出的VRF標準草稿(https://hdl.handle.net/2144/29225)的一個實現。具體在該草案的5.1和5.2章節中,我們可以看到證明生成,和隨機數計算的演算法。如圖:
漏洞在於x=0時候,此時從計算上 y仍然為一個合法的公鑰,且能透過vBFT實現中ValidatePublicKey的校驗。gamma為橢圓曲線上固定的點(無窮遠點)。即對任意輸入alpha,該vrf產生的值為固定一個值。完全沒有隨機性。該問題可導致攻擊者利用固定vrf破壞共識演算法隨機性,從而長期控制節點選舉。
5.2 NEO dBFT共識分叉
NEO的dBFT共識機制,本質上可以看成是一個POS+pBFT方案。在原版NEO程式碼中,我們發現NEO和ONT在實現其dBFT共識機制的時候存在分叉問題。惡意的共識節點可以產生一個分叉塊,從而造成雙花的發生。具體細節可以參考我們之前的文章:《Analysis and Improvement of NEO’s dBFT Consensus Mechanism》(http://blogs.360.cn/post/NEO_dBFT_en.html),在此我們不做贅述。
6. 一種針對虛擬機器執行不一致雙花問題的高效減緩措施
對於校驗繞過之類的邏輯漏洞和共識機制問題產生的雙花漏洞,還是需要深入到業務邏輯中具體問題具體分析。這裡我們提出一種針對虛擬機器執行不一致的減緩措施。
一種簡單的解決虛擬機器執行不一致造成的雙花問題的方法是由出塊者將執行完交易後的全域性狀態State_進行雜湊雜湊,然後將該雜湊打包到區塊中。普通節點在收到區塊後,將本地執行完交易後的狀態State’_的雜湊雜湊與State_的雜湊雜湊進行對比。如果相等,則說明沒有分叉產生。然而由於本地資料是先行增長的,所以每次對全域性狀態進行雜湊計算的開銷極大。針對這個問題,以太坊使用了MekleTree的結構來提高效能,同時應對分叉回滾問題。但以太坊的方案並不適用於採用其他資料結構儲存狀態資訊的區塊鏈專案。這裡我們提出一種新的解決方案,其工作流程如下:
1. 區塊生產者在區塊打包階段,將該區塊中所有的交易執行過程中的對資料庫的寫操作序列【write_db_1 write_db_2 …. write_db_n】記錄下來,並計算該序列的雜湊值write_db_hash。
2. 普通節點收到新的區塊後,對區塊進行校驗。然後在虛擬機器中執行交易。同時本地記錄這些交易對資料庫的寫操作序列【write_db_1’ write_db_2’ …. write_db_n’】,然後計算write_db_hash’。判斷其與write_db_hash是否相等。如果相等,則認為沒有不一致執行發生。如果不等,則拒絕對該寫操作序列進行commit。
本方法的核心思路在於,智慧合約平臺虛擬機器執行不一致產生的原因在於:合約中各種功能函式和圖靈完備性的支援中,可能引入多種不確定因素,從而造成執行不一致。各種各樣複雜的小原因,可能導致這種不一致執行防不勝防。但是我們退一步看,雙花攻擊的本質是要對全域性狀態State_進行修改,本質上就是一系列的簡單寫操作(簡單的寫操作往往並不會產生二義性)。要防止雙花,只需要對所有的寫操作序列進行匹配校驗便可。本地對這些寫操作進行匹配和記錄的開銷非常小,同時本地記錄這些寫操作序列,也方便應對分叉回滾等其他因素。
7. 後記
在本文中,我們透過介紹我們發現的針對EOS、NEO等大公鏈平臺的多個雙花攻擊漏洞的案例發現,總結出多種造成數字貨幣雙花攻擊的多種原因,並提出了一種通用的安全減緩措施。從上面的分析中,我們可以看到,區塊鏈安全目前的形式仍然十分嚴峻。各種大公鏈專案實際上都產生過能夠產生雙花攻擊之類的嚴重安全問題。我們的職業道德經受住了無數次的考驗。 make a billion or work hard? Of course, work hard. 不過幸運的是,在幾個月的區塊鏈安全研究中,我們收到了來自各個專案方價值超過30萬美金的數字貨幣漏洞報告獎勵,感謝。hard work pay off。
本文中所有提到的漏洞均已被修復。在漏洞報告和解決的過程中我們發現EOS與NEO專案方對於安全問題處理專業高效,反應及時。專案安全性也一步一步得到完善。我們會繼續關注和研究區塊鏈相關技術的安全問題,推動區塊鏈技術向前發展。
本文作者:Zhiniang Peng from Qihoo 360 Core Security & YukiChen of Qihoo 360 Vulcan Team
來源:360區塊鏈實驗室
更多數字貨幣資訊:www.qukuaiwang.com.cn/news