1. 程式人生 > >比特幣DoS漏洞和超發漏洞技術分析,攻擊者或無法獲利

比特幣DoS漏洞和超發漏洞技術分析,攻擊者或無法獲利

上週,bitcoin core 0.16.3 版本客戶端的突然釋出,以及開發者敦促大家儘快升級一事,令比特幣世界的人們感到了驚訝。表面上的原因,在於0.14-0.16.2版本客戶端中存在一個拒絕服務 (DoS) 向量需要被修補。到後來,我們才發現,在0.15-0.16.2版本core客戶端中的另一個漏洞,可能會引起比特幣的超發問題。

在這篇文章中,作者試圖說明:到底發生了什麼?潛在的危險是什麼?以及如果有人利用這個漏洞,還將會發生什麼?

btc

(圖片來自:Mowie Jak Jest)

 

雙重支付的兩種方式

 

 

在我們接觸實際的漏洞之前,我們需要解釋一些東西。我們首先需要定義一下雙重支付,因為這個漏洞就可以用於雙重支付。

所謂雙重支付的情況,就比如說愛麗絲(Alice)向鮑勃(Bob)支付了一筆幣,然後她又把相同的幣再一次支付給了查利(Charlie),愛麗絲基本上試圖進行兩次支付,其中的一筆她知道會被拒回。當然,當我們考慮支付時,愛麗絲的某些賬戶通過寫這兩次支付被透支了。這很接近比特幣的工作原理,但並不是十分準確。

比特幣並不是基於帳戶模型的,而是基於未花費交易輸出(UTXO)。一筆交易的輸出基本包含了一個地址以及數量。一旦輸出被使用了,它就無法再次被花費。試想一下一個UTXO(作為一筆傳送給你的幣),它可以是任意數量的,比如說0.413 BTC。

比特幣的雙重支付意味著一筆幣(UTXO)被花費了兩次。通常,這意味著愛麗絲將她的0.413 BTC傳送給了鮑勃,然後她又把同一筆比特幣又傳送給了查利。

比特幣的解決方法是,其中一筆交易會納入一個區塊,由此來決定實際誰得到了報酬。如果兩筆交易不知何故都傳遞到了多個區塊,那麼後面發生的區塊,就會被軟體給拒絕掉。如果兩筆交易都在同一個區塊當中,那麼這個區塊也會遭到軟體的拒絕。

基本上,比特幣軟體會檢測到雙重支付行為,如果有雙重支付行為的發生,則應該拒絕掉相應的區塊。

然而,在兩筆不同的交易中傳送同一個UTXO,並不是唯一的雙花方法。實際還存在著同一UTXO在同一交易進行雙重支付的病態情況。在這種情況下,愛麗絲向鮑勃發送同一筆幣兩次。所以,愛麗絲實際支付的是0.413 BTC,但鮑勃收到的卻是0.826 BTC。這顯然不是一個有效的交易,因為只有一筆價值0.413 BTC的UTXO 是被髮送的。這就相當於,愛麗絲用同一10美元向鮑勃發送了兩次,而鮑勃收到的則是20美元。

 

定義漏洞

 

 

因此,總結一下我們所定義的兩種型別的雙重支付嘗試:

  1. 使用兩筆或更多的交易,來花費相同的UTXO;
  2. 使用一筆交易花費同一UTXO多次;

結果表明,Bitcoin Core 軟體正確地處理了第一個問題,而第二個問題,正是我們要關心的。任何人都可以像這樣構造出一筆雙花交易,但要讓節點接受這種交易,又是另一回事了。

 

目前有兩種方法可以讓交易被納入一個區塊當中: A. 支付足夠的費用,將交易廣播到網路上,那麼礦工會負責把交易納入區塊當中;

B. 作為一名礦工,把交易納入一個區塊;

(A) 除了建立交易,並將其廣播到網路上的節點之外,你不需要做太多的工作。 (B) 需要你找到足夠的工作量證明。這也是這次漏洞的關鍵。

(A) 不是一個可能的攻擊向量,因為這些交易會立即被標記為無效的,網路上的節點會拒絕它們。沒有礦工們的合作,這種交易就無法進入礦工們的記憶庫,因為它們不會得到傳播。

(B)是漏洞顯現的唯一情況。換句話說,想要利用這個漏洞,你就需要工作量證明,或者說足夠的礦機裝置和電力。

為了明確起見,雙花交易有4種情況需要處理:

1A — 多筆 mempool交易花費了同一UTXO ;

1B — 多筆區塊交易花費了同一UTXO ;

2A — 單筆mempool交易花費了同一UTXO多次

2B — 單筆區塊交易花費了同一UTXO多次

該漏洞有兩種表現形式。在0.14.x版本客戶端中,存在著一個拒絕服務(DoS)的漏洞,而在0.15.x - 0.16.2版本的客戶端,則存在一個超發漏洞。接下來,我們會分別分析它們。

 

拒絕服務攻擊

 

 

故事始於2009年的Bitcoin 0.1版本客戶端,這一版本的程式碼通過拒絕案例1B和案例2B(檢查區塊沒有雙重支付)來強制達成共識。

p1

為了清晰起見,作者刪除了很多程式碼

你可以看到“檢查衝突”的註釋,其程式碼負責檢查每個輸入沒有被花費。“將輸出標記為已使用”註釋下面的程式碼,標記了UTXO的使用。如果任何UTXO的花費超過一次,則會導致錯誤。

 

在2011年,PR 443被合併到了比特幣程式碼庫。這一改變是為了處理通過mempool (上面的情況2A)傳輸單筆交易雙重支付的情況。這個合併請求註釋的目的非常明確:

“而且,沒有具有重複輸入的交易會被納入區塊當中......幾個星期前,有人嘗試過了,但這些交易並沒有被納入區塊。我假設某個地方存在了一個檢查關,它會阻止這些重複交易進入區塊,雖然我沒有對這個問題進行任何挖掘。這實際上是為了防止這種明顯無效的交易得到中繼。”

p2

 

實際的程式碼更改,或多或少與上面的ConnectInputs 中的“檢查衝突”註釋下的程式碼執行了相同的操作,但位於的是不同的位置。程式碼更改是在CheckTransaction 中執行的,其負責了所有上述的4種情況(1A, 1B, 2A, 2B)。因此,我們在區塊雙重支付共識程式碼中有了一些冗餘,正如案例1B和2B都被檢查了兩次,其中一次檢查是在CheckTransaction,另一次則發生在ConnectInputs。

到了2013年,PR 2224被納入了比特幣軟體。這一改變的目的是區分共識錯誤(例如雙重支付)和系統錯誤(例如磁碟空間耗盡)之間的差別,正如PR註釋中所表明的那樣:

“它引入了CValidationState,它會儲存關於區塊的元資料,或者正在執行的交易驗證資料。它被用於區分驗證錯誤(例如,未能滿足網路規則)和執行時錯誤(比如磁碟空間的不足),從前這些可能會產生混淆,因磁碟空間用完會導致區塊被標記為無效。此外,CValidationState還承擔了跟蹤 DoS級別的角色(因此它不需要儲存於交易或區塊當中...)”

實際的相關程式碼更改如下:

 

p3

在那個時候,ConnectInputs已經被模組化成多個方法,並且這個函式成為了檢查雙重支付的函式。這裡的關鍵改變是,曾經的error被改為了 assert

assert在C++中是做什麼的?它會完全中止程式。程式設計師為什麼要在這裡停止程式?這就是Pull請求的目的所在。下面就是那個時候的程式碼片段:

p4

它會像以前一樣處理案例1B和2B。函式名則從ConnectInputs更改為ConnectBlock,但檢查案例1B和2B的冗餘性仍然在PR 443中保留。正如我們已經看到的,UpdateCoins做了第二次雙重支付檢查。其中CheckBlock通過呼叫CheckTransaction進行了第一次雙重支付檢查:

p5

p6

由於這是第二次檢查相同的內容,所以要讓UpdateCoins的雙花檢查失敗的唯一方法,就是存在某種UTXO資料庫或交易儲存損壞。事實上,這似乎是改成assert的原因。因為CheckBlock通過CheckTransactionUpdateCoins之前已經進行了檢查,我們已知道某筆交易並不是雙花交易。因此,PR 2224正確地推測到,UpdateCoins中的這個狀態必然是一個系統錯誤,而不是一個共識錯誤。在這種情況下,為了防止進一步的資料損壞,正確的做法就是停止程式。

到了2017年,PR 9049作為Bitcoin 0.14的一部分被引入比特幣網路。隨著隔離見證(Segwit)的納入,它是加快區塊驗證時間的諸多更改的其中之一,其程式碼更改實際是非常少的:

p7

你可以看到布林函式 fCheckDuplicateInputs被添加了進去,用於加快區塊檢查。我們將在下面看到,這是一個被認為是冗餘的檢查。不幸的是, UpdateCoins中的程式碼在PR 2224中被更改為系統損壞檢查,而不是共識檢查。到了0.14.0版本客戶端,其程式碼進行了更多的模組化更改,而assert也發生了一些改變:

p8

曾經是作為一個冗餘檢查,現在卻成了負責區塊單筆交易雙重支付檢查(案例2B),並負責停止程式。從技術上來說,它仍然是強制執行共識規則。只是在中止程式問題上,它表現地非常糟糕。

PR 9049是如何獲得通過的? Greg Maxwell 給了我IRC上的聊天記錄。

p9

長話短說,開發者們在討論PR 9049時,傾向於認為區塊級單筆交易雙重支付(案例2B)會在PR 443處遭到檢查,而沒有考慮PR 2224。這使得開發者們並沒有密切關注PR 9049;

總而言之:

1、在2011年引入用於防止雙重支付交易中繼(案例2A)的 PR 443,實際產生了一個副作用,即對區塊的雙重支付共識規則檢查創造了冗餘校驗(案例1B和 2B)。

2、PR 2224是在2013年引入的,作為一種副作用,將(1)中用於區塊驗證的程式碼,從冗餘升級到了共識層;

3、PR 9049是在2017年被引入的,並且它跳過了(1)中用於單個區塊單筆交易雙重支付(案例1B)檢查的程式碼。開發人員錯誤地認為程式碼是多餘的,因為他們沒有考慮到(2)。事實上,這種改變跳過了共識的關鍵部分。

公平地講,這些事的匯合導致了這次漏洞。

 

DoS漏洞的嚴重性

 

 

這意味著 0.14.x 版本的Core軟體可能會因為一個奇怪的區塊而崩潰。而要讓軟體崩潰,攻擊者需要做的事是:

  1. 建立一筆花費兩次同一UTXO的交易;
  2. 通過足夠的工作量證明,將(1)中的交易納入一個比特幣區塊;
  3. 將這個區塊廣播到0.14.x版本軟體的節點;

(1) 和 (3) 的成本並不高,而步驟(2)的最小成本為12.5 BTC。

 

如果你認為從博弈論的角度來看,分裂網路並不是那麼好,那麼利用這個漏洞的動機就相當低了。充其量,作為攻擊者,你花費了12.5 BTC將部分全節點給搞崩潰。由於不可能從分裂網路中獲利,攻擊者無法輕易地補償自己的攻擊成本。

如果這是唯一的漏洞,那麼攻擊者可能給很多人帶來一些不便,但這不會是持續的,因為這些被攻擊的節點可以簡單地重啟,並連線到其它誠實節點。一旦有一個較長的鏈,那麼惡意區塊攻擊就會完全失去它的威脅。除非攻擊者以每區塊12.5 BTC的代價繼續建立區塊,並將其傳播給 0.14.x版本軟體的節點,否則攻擊就是不可持續的。

換句話說,雖然這個漏洞的確存在著,但對 DoS攻擊的經濟刺激卻是相當低的。

 

超發漏洞

 

 

從0.15.0版本軟體開始,core軟體引入了一個新的特性,以便更快地查詢和儲存UTXO,而這恰恰又引入了另一個漏洞。當一筆具有雙重支付單個交易的區塊納入區塊鏈時,軟體會將其視為有效,而不會出現崩潰的現象。

這就意味著一筆病理性交易(相同UTXO在同一交易中被使用多次,即案例2B),0.14版本的節點會因此而崩潰,而使用0.15版本軟體的節點卻會認為交易是有效的,這基本上是憑空在建立比特幣。

談談它是如何發生的。在0.15中出現的PR 10195 ,引入了很多內容,但它的主要要點在於改變了 UTXO的儲存方式,使得它們更有效地進行查詢。因此,它出現了很多變化,包括對早期UpdateCoins函式的更改:

p10

注意,assert(false) 周圍的程式碼是如何被完全取出的。注意這一點,0.15.0中的PR 10537也更改了程式碼。

p11

assert失敗的條件現在取決於inputs.SpendCoin,它看起來是這樣子的:

p12本質上,SpendCoin返回“ false”值的唯一方法,就是讓幣不存在於UTXO集中。但正如你所看到的,這需要幣是FRESH的,而不是DIRTY的。這些不是常見的術語,但值得慶幸的是,core開發者Andrew Chow給出瞭解釋:

“現在的問題是,什麼時候UTXO會被標記為 FRESH?當它們被新增到UTXO資料庫時,它們就會被標記為 FRESH。但是,UTXO資料庫仍然只存在於儲存當中的(作為快取)。當它被儲存到磁碟時,儲存中的條目將不再被標記為 FRESH……”

標記為FRESH的幣,是進入交易儲存池(memory pool)中的幣。而攻擊者可以通過 UpdateCoins函式中的assert語句來破壞節點。更糟的是,如果幣是屬於DIRTY的(基本上從磁碟上讀取的),那麼這就會導致比特幣的超發。

 

因此,攻擊者可以欺騙那些執行 0.15.0- 0.16.2版本軟體的礦工接受一個奇怪的、無效的區塊,從而導致比特幣的供應超發。

 

超發漏洞的嚴重性

 

 

這種攻擊的經濟誘因似乎明顯高於DoS攻擊,因為攻擊者可能會憑空製造出比特幣。但你仍然需要有挖礦裝置來執行攻擊,但考慮到潛在的經濟誘因,這可能是值得的,或者看起來是這樣的。

下面是使用這種漏洞的一種簡單攻擊方式:

  1. 創造一筆帶有雙重支付交易的區塊,其會向自己支付兩次,比方說 50 BTC →100 BTC;
  2. 將該區塊廣播給0.15/0.16版本客戶端的所有礦工;

下面是會發生的一些事:

  1. 0.14.x 版本節點會崩潰;
  2. 較舊版本的節點及其它替代客戶端會拒絕這個區塊;
  3. 很多區塊鏈瀏覽器是執行在自定義軟體上的,而不是基於core,因此,至少有一些瀏覽器會拒絕該區塊,並且不會顯示來自該區塊的任何交易。
  4. 取決於礦工們執行的軟體,我們可能會迎來鏈分裂;

有可能,所有的礦工都是執行的Bitcoin Core 0.15+版本軟體,在這種情況下,不受攻擊的客戶端可能會停滯不前。也有可能礦工會執行其它東西,在這種情況下,當他們發現一個區塊時,鏈就會發生分叉。

 

由於這些違規行為,網路上的人們很快就會追蹤到這一點,可能已提醒一些開發人員,並且core開發者已經修復了它。如果存在分叉,那麼在那個時候,關於哪條鏈是正確的共識鏈,將開始得到討論,而出現意外超發的鏈,可能會遭到拋棄。如果真的發生了,那麼社群可能會自願進行一次回滾,以懲罰攻擊者。

所以對於攻擊者來說,這不會帶來50 BTC的收入,更可能的是失去12.5 BTC。如果攻擊者加倍花費,比如說200 BTC,那麼超發漏洞將持續存在的可能性會更小,因為攻擊會更明顯。

因此,從攻擊者的角度來看,這並不是一種好的獲利方式。

攻擊者可以獲利的另一種方式,就是事先做空比特幣,然後再執行攻擊。這也是具有風險的,因為攻擊不能保證比特幣價格會下跌,特別是當危機得到迅速和果斷處理時。此外,考慮到大多數交易所提供的槓槓交易,都需要AML/KYC,這可能導致攻擊者很快暴露。

攻擊者不僅面臨著巨大的資金風險,而且還會有身體危險。從經濟角度來看,這並不是一個容易獲利的漏洞。

當然,有一些別有用心的人,可能會用這種漏洞來嚇唬那些比特幣持有者。投資回報率將變得更抽象,因此從理論上來講,這可能會達到這些別有用心者的目的。

 

結論

 

 

毫無疑問,這是一個相當嚴重的漏洞。儘管我和Awemany之間有著分歧,但我很感激他選擇公開地和我辯論。也就是說,考慮到經濟博弈理論,我不認為這個漏洞會像他所描述的那樣嚴重。

即使這個漏洞在被發現之前被壞人所利用,攻擊者可能也不會選擇利用它,因為從經濟學上來講,它是沒有意義的。可以肯定的是,這一技術漏洞應該被修復,並且開發者應該做得更好,但真正能夠利用這種漏洞的物件其實是非常少的,基本上,只有那些想要摧毀比特幣的組織才會這麼幹。

Bitcoin Core開發者的教訓有很多:

1、任何共識變化(即使是微小的變化,例如9049),也需要更多的人進行審查; 2、需要對病理交易進行更多的檢查; 3、程式碼庫中哪些檢查是冗餘的,哪些檢查是不冗餘的,以及實際程式碼將要做些什麼,需要變得更加清晰; 4、過去存在漏洞,將來也會存在漏洞。現在重要的是,學習並反思這一教訓;

原文:https://medium.com/@jimmysong/bitcoin-core-bug-cve-2018-17144-an-analysis-f80d9d373362
作者:Jimmy Song
編譯:灑脫喜
稿源(譯):巴位元資訊(http://www.8btc.com/bitcoin-core-bug)