公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141)

收藏待读

公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141)

公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141)

本期DVP漏洞專題是比特幣漏洞CVE-2010-5141,這個漏洞可以導致攻擊者盜取任何人的比特幣,危害十分嚴重。萬幸的是該漏洞並沒有被利用過,而且修復速度極快。該漏洞與比特幣的腳本引擎有關,對公鏈開發者具有參考意義;從當下市場上的公鏈來看,大多數都內置了虛擬機或腳本引擎,以此來打造DApp生態,同時也是區塊鏈的大趨勢之一。

一、比特幣當中的UTXO模型是什麼?

Tips:在漏洞代碼片段中會涉及一些UTXO的相關知識、概念,所以對該漏洞進行理論分析之前需要先了解一下這些知識點,已經了解的可以直接跳過。

Ⅰ、賬戶模型與UTXO模型

我們在看 UTXO模型 之前先說說常見的 賬戶模型 ,什麼是 賬戶模型賬戶模型 的數據結構簡單可以理解為「賬號=>餘額」,每個賬號都對應一個餘額。舉個例子:若賬號A向賬號B轉賬200,在賬戶模型中完成這個轉賬操作只需要A-200然後B+200;目前大部分軟件都採用的是賬戶模型,比如銀行系統、以太坊等等。

而比特幣卻使用了自行研發的UTXO模型,UTXO中是沒有「賬號=>餘額」這樣的數據結構的,那怎麼進行轉賬?

Ⅱ、比特幣如何操作轉賬

以上面A向B轉賬為例,在UTXO中完成這個轉賬需要以下操作:

  1. 找到A賬號下200餘額的來源,也就是意味着要找到A收款200的這筆交易x
  2. 以x交易為輸入,以向B轉賬200的交易y為輸出,x與y對應且x與y的轉賬金額必須相等
  3. x交易被標記為已花費,y交易被標記為未花費

兩筆交易的轉賬金額必須相等,簡單解釋就是收到多少就只能轉出多少,實際上確實是這樣。

但是又必須只給別人轉一部分的時候怎麼辦?答案是只向他人轉一部分,然後剩下的一部分轉給自己另外一個號。

Ⅲ、引用兩張來自網絡的圖文:

公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141)

公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141)

在本文當中比特幣為什麼採用UTXO模型不是重點,我們了解UTXO的原理即可。

二、比特幣的腳本引擎

比特幣腳本是非圖靈完備的。比特幣使用自行定義的一種腳本進行交易和其他的操作,為比特幣提供有限的靈活性。實現諸如多重簽名、凍結資金等簡單功能,但更多的就不行了。

比特幣這麼做的原因是犧牲一定的完備性來保障安全性。比特幣腳本的原理是先定義了一堆操作碼,然後腳本引擎基於堆棧來逐個執行每個操作碼。

堆棧很好理解,隊列是先進後出,而堆棧正好相反,是先進先出,將一個元素壓入(push)堆棧後該元素會被最先彈出(pop)。

在比特幣早期的版本中發送一筆標準轉賬( pay-to-pubkey )交易需要腳本簽名( scriptSig )和公鑰驗證腳本( scriptPubKey ),具體處理流程如下:

公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141)

先填入要執行的腳本( Script ),然後簽名( sig )和公鑰( pubKey )被壓入堆棧,然後操作碼 OP_CHECKSIG 會去驗證簽名等,若驗證通過就將true壓入堆棧,否則就將false壓入堆棧。

三、CVE-2010-5141漏洞分析

了解以上知識後就可以開始分析 CVE-2010-5141 漏洞了。筆者下載了存在漏洞的版本 0.3.3 ,下載地址在github的bitcoin倉庫中找release.

script.cpp 代碼片段 VerifySignature 函數:

公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141)

執行每個交易都會調用 VerifySignature 函數,該函數用於執行腳本以及驗證簽名,然後給交易標註是否被花費。

首先 txFrom 參數是上一筆交易, txTo 是正在處理的這筆交易,如果理解了上面的章節中講解過的 UTXO模型 ,這裡就不難理解了。重點看 1125行 代碼,調用了 EvalScript 函數,第一個參數是 txin.scriptSig (包含簽名信息)+分隔操作碼 OP_CODESEPARATOR + txout.scriptPunKey (包含公鑰信息、 OP_CHECKSIG 指令),這些就是 EvalScript 函數要執行的腳本,後面的參數可以暫時不用管,只要 EvalScript 函數返回true那麼這筆驗證簽名就通過了。 EvalScript 函數如何才能返回true?

公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141)

首先堆棧不能是空的,然後棧頂強轉bool後必須是true。筆者簡單解讀為必須要有棧頂而且值不能是0。

然後再看關鍵的 OP_CHECKSIG 操作碼

(註:由於操作碼太多,本文針對 OP_CHECKSIG 操作碼)

公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141) 上面代碼不難理解,調用 Checksig 函數來驗證簽名,然後返回給FSuccess變量,如果為真就壓一個vchTrue(非0)進棧,否則就壓一個vchFalse(0)進棧;如果opcode是 OP_CHECKSIGVERIFY 而不是 OP_CHECKSIG 的話就讓vchTrue出棧,並開始執行後面的操作碼。

按照 OP_CHECKSIG 的正常邏輯,驗證簽名不成功的話一定會有一個vchFalse留在棧頂,雖然堆棧不為空,但是棧頂的值是0,還是會返回false。

回到之前的代碼, EvalScript 函數執行的腳本主要由以下變量組成:

  1. txin.scriptSig
  2. OP_CODESEPARATOR
  3. txout.scriptPubKey

第一個簽名信息可控,第二個不用管只是一個分割符,會被刪掉,第三個不可控,因為是來自上一個交易。

第一個變量可控,而且是作為腳本執行,所以這個變量可以不僅僅是簽名信息,還能是opcode,這就好辦了,下面需要引用一個神奇的操作碼 OP_PUSHDATA4 ,我們看看比特幣 0.3.3 是怎麼處理這個操作碼的:

公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141)

首先獲取操作碼。如果操作碼的值小於或者等 於OP_PUSHDATA4 的值就把vchPushValue全壓入堆棧,再跟進 GetOp 函數

公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141)

經翻閱源碼,發現 OP_PUSHDATA4 指令被定義為 78 ,所以當函數遇到 OP_PUSHDATA4 時,指針會向又移 78+4=82 位,其中 78 位數據會被壓入棧,所以只要在 txin.scriptSig 中注入一個 OP_PUSHDATA4 操作碼,後面的公鑰信息以及 OP_CHECKSIG 指令都會被」吃掉」並作為參數入棧,當指針走到最後時,進行最後的判斷:

  1. 堆棧是否為空?不是
  2. 棧頂元素是否為0?不是

於是滿足條件 EvalScript 函數返回true,然後 VerifySignature 函數也返回true,既然簽名驗證都繞過了,那別人的比特幣便可以任意花費了。

四、CVE-2010-5141漏洞修復方案

筆者下載了比特幣版本0.3.8,直接看關鍵部分代碼

公鏈安全之比特幣任意盜幣漏洞淺析(CVE-2010-5141)

修復方案也很明確,把 scriptSigscriptPubkey 分開執行,無論你 scriptSig 裏面有什麼也不會影響到後面的 scriptPubkey 執行。

寫在最後:

因為該比特幣漏洞來自2010年,素材可以說是相當古老,目前漏洞分析主要存在以下難點:

  1. 漏洞相關資料非常少,大多數漏洞都只有一個CVE編號和簡介,不查閱大量資料無從入手。
  2. 環境搭建難,難在編譯、私鏈搭建(早期的比特幣甚至沒有私鏈這個概念)等,比特幣早期的源碼編譯需要的依賴現在很多都已經不維護並下線了。

基於這些原因,所以筆者僅做了理論研究,並未進行實踐驗證,如有錯誤之處還請指正。

五、參考鏈接

https://bitcoin.stackexchange.com/questions/37403/which-release-fixed-cve-2010-5141-attacker-can-spend-any-coin

https://en.bitcoin.it/wiki/Script

原文 : 安全客

相關閱讀

免责声明:本文内容来源于安全客,已注明原文出处和链接,文章观点不代表立场,如若侵犯到您的权益,或涉不实谣言,敬请向我们提出检举。