上周,bitcoin core 0.16.3 版本客戶(hù)端的突然發(fā)布,以及開(kāi)發(fā)者敦促大家盡快升級(jí)一事,令比特幣世界的人們感到了驚訝。表面上的原因,在于0.14-0.16.2版本客戶(hù)端中存在一個(gè)拒絕服務(wù) (DoS) 向量需要被修補(bǔ)。到后來(lái),我們才發(fā)現(xiàn),在0.15-0.16.2版本core客戶(hù)端中的另一個(gè)漏洞,可能會(huì)引起比特幣的超發(fā)問(wèn)題。
在這篇文章中,作者試圖說(shuō)明:到底發(fā)生了什么?潛在的危險(xiǎn)是什么?以及如果有人利用這個(gè)漏洞,還將會(huì)發(fā)生什么?
雙重支付的兩種方式
在我們接觸實(shí)際的漏洞之前,我們需要解釋一些東西。我們首先需要定義一下雙重支付,因?yàn)檫@個(gè)漏洞就可以用于雙重支付。
所謂雙重支付的情況,就比如說(shuō)愛(ài)麗絲(Alice)向鮑勃(Bob)支付了一筆幣,然后她又把相同的幣再一次支付給了查利(Charlie),愛(ài)麗絲基本上試圖進(jìn)行兩次支付,其中的一筆她知道會(huì)被拒回。當(dāng)然,當(dāng)我們考慮支付時(shí),愛(ài)麗絲的某些賬戶(hù)通過(guò)寫(xiě)這兩次支付被透支了。這很接近比特幣的工作原理,但并不是十分準(zhǔn)確。
比特幣并不是基于帳戶(hù)模型的,而是基于未花費(fèi)交易輸出(UTXO)。一筆交易的輸出基本包含了一個(gè)地址以及數(shù)量。一旦輸出被使用了,它就無(wú)法再次被花費(fèi)。試想一下一個(gè)UTXO(作為一筆發(fā)送給你的幣),它可以是任意數(shù)量的,比如說(shuō)0.413 BTC。
比特幣的雙重支付意味著一筆幣(UTXO)被花費(fèi)了兩次。通常,這意味著愛(ài)麗絲將她的0.413 BTC發(fā)送給了鮑勃,然后她又把同一筆比特幣又發(fā)送給了查利。
比特幣的解決方法是,其中一筆交易會(huì)納入一個(gè)區(qū)塊,由此來(lái)決定實(shí)際誰(shuí)得到了報(bào)酬。如果兩筆交易不知何故都傳遞到了多個(gè)區(qū)塊,那么后面發(fā)生的區(qū)塊,就會(huì)被軟件給拒絕掉。如果兩筆交易都在同一個(gè)區(qū)塊當(dāng)中,那么這個(gè)區(qū)塊也會(huì)遭到軟件的拒絕。
基本上,比特幣軟件會(huì)檢測(cè)到雙重支付行為,如果有雙重支付行為的發(fā)生,則應(yīng)該拒絕掉相應(yīng)的區(qū)塊。
然而,在兩筆不同的交易中發(fā)送同一個(gè)UTXO,并不是唯一的雙花方法。實(shí)際還存在著同一UTXO在同一交易進(jìn)行雙重支付的病態(tài)情況。在這種情況下,愛(ài)麗絲向鮑勃發(fā)送同一筆幣兩次。所以,愛(ài)麗絲實(shí)際支付的是0.413 BTC,但鮑勃收到的卻是0.826 BTC。這顯然不是一個(gè)有效的交易,因?yàn)橹挥幸还P價(jià)值0.413 BTC的UTXO 是被發(fā)送的。這就相當(dāng)于,愛(ài)麗絲用同一10美元向鮑勃發(fā)送了兩次,而鮑勃收到的則是20美元。
定義漏洞
因此,總結(jié)一下我們所定義的兩種類(lèi)型的雙重支付嘗試:
使用兩筆或更多的交易,來(lái)花費(fèi)相同的UTXO;
使用一筆交易花費(fèi)同一UTXO多次;
結(jié)果表明,Bitcoin Core 軟件正確地處理了第一個(gè)問(wèn)題,而第二個(gè)問(wèn)題,正是我們要關(guān)心的。任何人都可以像這樣構(gòu)造出一筆雙花交易,但要讓節(jié)點(diǎn)接受這種交易,又是另一回事了。
目前有兩種方法可以讓交易被納入一個(gè)區(qū)塊當(dāng)中: A. 支付足夠的費(fèi)用,將交易廣播到網(wǎng)絡(luò)上,那么礦工會(huì)負(fù)責(zé)把交易納入?yún)^(qū)塊當(dāng)中;
B. 作為一名礦工,把交易納入一個(gè)區(qū)塊;
(A) 除了創(chuàng)建交易,并將其廣播到網(wǎng)絡(luò)上的節(jié)點(diǎn)之外,你不需要做太多的工作。 (B) 需要你找到足夠的工作量證明。這也是這次漏洞的關(guān)鍵。
(A) 不是一個(gè)可能的攻擊向量,因?yàn)檫@些交易會(huì)立即被標(biāo)記為無(wú)效的,網(wǎng)絡(luò)上的節(jié)點(diǎn)會(huì)拒絕它們。沒(méi)有礦工們的合作,這種交易就無(wú)法進(jìn)入礦工們的記憶庫(kù),因?yàn)樗鼈儾粫?huì)得到傳播。
(B)是漏洞顯現(xiàn)的唯一情況。換句話(huà)說(shuō),想要利用這個(gè)漏洞,你就需要工作量證明,或者說(shuō)足夠的礦機(jī)設(shè)備和電力。
為了明確起見(jiàn),雙花交易有4種情況需要處理:
1A?— 多筆 mempool交易花費(fèi)了同一UTXO ;
1B?— 多筆區(qū)塊交易花費(fèi)了同一UTXO ;
2A?— 單筆mempool交易花費(fèi)了同一UTXO多次;
2B?— 單筆區(qū)塊交易花費(fèi)了同一UTXO多次;
該漏洞有兩種表現(xiàn)形式。在0.14.x版本客戶(hù)端中,存在著一個(gè)拒絕服務(wù)(DoS)的漏洞,而在0.15.x - 0.16.2版本的客戶(hù)端,則存在一個(gè)超發(fā)漏洞。接下來(lái),我們會(huì)分別分析它們。
拒絕服務(wù)攻擊
故事始于2009年的Bitcoin 0.1版本客戶(hù)端,這一版本的代碼通過(guò)拒絕案例1B和案例2B(檢查區(qū)塊沒(méi)有雙重支付)來(lái)強(qiáng)制達(dá)成共識(shí)。
你可以看到“檢查沖突”的注釋?zhuān)浯a負(fù)責(zé)檢查每個(gè)輸入沒(méi)有被花費(fèi)?!皩⑤敵鰳?biāo)記為已使用”注釋下面的代碼,標(biāo)記了UTXO的使用。如果任何UTXO的花費(fèi)超過(guò)一次,則會(huì)導(dǎo)致錯(cuò)誤。
在2011年,PR 443被合并到了比特幣代碼庫(kù)。這一改變是為了處理通過(guò)mempool (上面的情況2A)傳輸單筆交易雙重支付的情況。這個(gè)合并請(qǐng)求注釋的目的非常明確:
“而且,沒(méi)有具有重復(fù)輸入的交易會(huì)被納入?yún)^(qū)塊當(dāng)中。..。..幾個(gè)星期前,有人嘗試過(guò)了,但這些交易并沒(méi)有被納入?yún)^(qū)塊。我假設(shè)某個(gè)地方存在了一個(gè)檢查關(guān),它會(huì)阻止這些重復(fù)交易進(jìn)入?yún)^(qū)塊,雖然我沒(méi)有對(duì)這個(gè)問(wèn)題進(jìn)行任何挖掘。這實(shí)際上是為了防止這種明顯無(wú)效的交易得到中繼。”
實(shí)際的代碼更改,或多或少與上面的ConnectInputs 中的“檢查沖突”注釋下的代碼執(zhí)行了相同的操作,但位于的是不同的位置。代碼更改是在CheckTransaction 中運(yùn)行的,其負(fù)責(zé)了所有上述的4種情況(1A, 1B, 2A, 2B)。因此,我們?cè)趨^(qū)塊雙重支付共識(shí)代碼中有了一些冗余,正如案例1B和2B都被檢查了兩次,其中一次檢查是在CheckTransaction,另一次則發(fā)生在ConnectInputs。
到了2013年,PR 2224被納入了比特幣軟件。這一改變的目的是區(qū)分共識(shí)錯(cuò)誤(例如雙重支付)和系統(tǒng)錯(cuò)誤(例如磁盤(pán)空間耗盡)之間的差別,正如PR注釋中所表明的那樣:
“它引入了CValidationState,它會(huì)存儲(chǔ)關(guān)于區(qū)塊的元數(shù)據(jù),或者正在執(zhí)行的交易驗(yàn)證數(shù)據(jù)。它被用于區(qū)分驗(yàn)證錯(cuò)誤(例如,未能滿(mǎn)足網(wǎng)絡(luò)規(guī)則)和運(yùn)行時(shí)錯(cuò)誤(比如磁盤(pán)空間的不足),從前這些可能會(huì)產(chǎn)生混淆,因磁盤(pán)空間用完會(huì)導(dǎo)致區(qū)塊被標(biāo)記為無(wú)效。此外,CValidationState還承擔(dān)了跟蹤 DoS級(jí)別的角色(因此它不需要存儲(chǔ)于交易或區(qū)塊當(dāng)中。..)”
實(shí)際的相關(guān)代碼更改如下:
在那個(gè)時(shí)候,ConnectInputs已經(jīng)被模塊化成多個(gè)方法,并且這個(gè)函數(shù)成為了檢查雙重支付的函數(shù)。這里的關(guān)鍵改變是,曾經(jīng)的error被改為了 assert
assert在C++中是做什么的?它會(huì)完全中止程序。程序員為什么要在這里停止程序?這就是Pull請(qǐng)求的目的所在。下面就是那個(gè)時(shí)候的代碼片段:
它會(huì)像以前一樣處理案例1B和2B。函數(shù)名則從ConnectInputs更改為ConnectBlock,但檢查案例1B和2B的冗余性仍然在PR 443中保留。正如我們已經(jīng)看到的,UpdateCoins做了第二次雙重支付檢查。其中CheckBlock通過(guò)調(diào)用CheckTransaction進(jìn)行了第一次雙重支付檢查:
由于這是第二次檢查相同的內(nèi)容,所以要讓UpdateCoins的雙花檢查失敗的唯一方法,就是存在某種UTXO數(shù)據(jù)庫(kù)或交易存儲(chǔ)損壞。事實(shí)上,這似乎是改成assert的原因。因?yàn)镃heckBlock通過(guò)CheckTransaction在UpdateCoins之前已經(jīng)進(jìn)行了檢查,我們已知道某筆交易并不是雙花交易。因此,PR 2224正確地推測(cè)到,UpdateCoins中的這個(gè)狀態(tài)必然是一個(gè)系統(tǒng)錯(cuò)誤,而不是一個(gè)共識(shí)錯(cuò)誤。在這種情況下,為了防止進(jìn)一步的數(shù)據(jù)損壞,正確的做法就是停止程序。
到了2017年,PR 9049作為Bitcoin 0.14的一部分被引入比特幣網(wǎng)絡(luò)。隨著隔離見(jiàn)證(Segwit)的納入,它是加快區(qū)塊驗(yàn)證時(shí)間的諸多更改的其中之一,其代碼更改實(shí)際是非常少的:
你可以看到布爾函數(shù) fCheckDuplicateInputs被添加了進(jìn)去,用于加快區(qū)塊檢查。我們將在下面看到,這是一個(gè)被認(rèn)為是冗余的檢查。不幸的是, UpdateCoins中的代碼在PR 2224中被更改為系統(tǒng)損壞檢查,而不是共識(shí)檢查。到了0.14.0版本客戶(hù)端,其代碼進(jìn)行了更多的模塊化更改,而assert也發(fā)生了一些改變:
曾經(jīng)是作為一個(gè)冗余檢查,現(xiàn)在卻成了負(fù)責(zé)區(qū)塊單筆交易雙重支付檢查(案例2B),并負(fù)責(zé)停止程序。從技術(shù)上來(lái)說(shuō),它仍然是強(qiáng)制執(zhí)行共識(shí)規(guī)則。只是在中止程序問(wèn)題上,它表現(xiàn)地非常糟糕。
PR 9049是如何獲得通過(guò)的? Greg Maxwell 給了我IRC上的聊天記錄。
長(zhǎng)話(huà)短說(shuō),開(kāi)發(fā)者們?cè)谟懻揚(yáng)R 9049時(shí),傾向于認(rèn)為區(qū)塊級(jí)單筆交易雙重支付(案例2B)會(huì)在PR 443處遭到檢查,而沒(méi)有考慮PR 2224。這使得開(kāi)發(fā)者們并沒(méi)有密切關(guān)注PR 9049;
總而言之:
1、在2011年引入用于防止雙重支付交易中繼(案例2A)的 PR 443,實(shí)際產(chǎn)生了一個(gè)副作用,即對(duì)區(qū)塊的雙重支付共識(shí)規(guī)則檢查創(chuàng)造了冗余校驗(yàn)(案例1B和 2B)。
2、PR 2224是在2013年引入的,作為一種副作用,將(1)中用于區(qū)塊驗(yàn)證的代碼,從冗余升級(jí)到了共識(shí)層;
3、PR 9049是在2017年被引入的,并且它跳過(guò)了(1)中用于單個(gè)區(qū)塊單筆交易雙重支付(案例1B)檢查的代碼。開(kāi)發(fā)人員錯(cuò)誤地認(rèn)為代碼是多余的,因?yàn)樗麄儧](méi)有考慮到(2)。事實(shí)上,這種改變跳過(guò)了共識(shí)的關(guān)鍵部分。
公平地講,這些事的匯合導(dǎo)致了這次漏洞。
DoS漏洞的嚴(yán)重性
這意味著 0.14.x 版本的Core軟件可能會(huì)因?yàn)橐粋€(gè)奇怪的區(qū)塊而崩潰。而要讓軟件崩潰,攻擊者需要做的事是:
1.創(chuàng)建一筆花費(fèi)兩次同一UTXO的交易;
2.通過(guò)足夠的工作量證明,將(1)中的交易納入一個(gè)比特幣區(qū)塊;
3.將這個(gè)區(qū)塊廣播到0.14.x版本軟件的節(jié)點(diǎn);
(1) 和 (3) 的成本并不高,而步驟(2)的最小成本為12.5 BTC。
如果你認(rèn)為從博弈論的角度來(lái)看,分裂網(wǎng)絡(luò)并不是那么好,那么利用這個(gè)漏洞的動(dòng)機(jī)就相當(dāng)?shù)土恕3淦淞?,作為攻擊者,你花費(fèi)了12.5 BTC將部分全節(jié)點(diǎn)給搞崩潰。由于不可能從分裂網(wǎng)絡(luò)中獲利,攻擊者無(wú)法輕易地補(bǔ)償自己的攻擊成本。
如果這是唯一的漏洞,那么攻擊者可能給很多人帶來(lái)一些不便,但這不會(huì)是持續(xù)的,因?yàn)檫@些被攻擊的節(jié)點(diǎn)可以簡(jiǎn)單地重啟,并連接到其它誠(chéng)實(shí)節(jié)點(diǎn)。一旦有一個(gè)較長(zhǎng)的鏈,那么惡意區(qū)塊攻擊就會(huì)完全失去它的威脅。除非攻擊者以每區(qū)塊12.5 BTC的代價(jià)繼續(xù)創(chuàng)建區(qū)塊,并將其傳播給 0.14.x版本軟件的節(jié)點(diǎn),否則攻擊就是不可持續(xù)的。
換句話(huà)說(shuō),雖然這個(gè)漏洞的確存在著,但對(duì) DoS攻擊的經(jīng)濟(jì)刺激卻是相當(dāng)?shù)偷摹?/p>
超發(fā)漏洞
從0.15.0版本軟件開(kāi)始,core軟件引入了一個(gè)新的特性,以便更快地查找和存儲(chǔ)UTXO,而這恰恰又引入了另一個(gè)漏洞。當(dāng)一筆具有雙重支付單個(gè)交易的區(qū)塊納入區(qū)塊鏈時(shí),軟件會(huì)將其視為有效,而不會(huì)出現(xiàn)崩潰的現(xiàn)象。
這就意味著一筆病理性交易(相同UTXO在同一交易中被使用多次,即案例2B),0.14版本的節(jié)點(diǎn)會(huì)因此而崩潰,而使用0.15版本軟件的節(jié)點(diǎn)卻會(huì)認(rèn)為交易是有效的,這基本上是憑空在創(chuàng)建比特幣。
談?wù)勊侨绾伟l(fā)生的。在0.15中出現(xiàn)的PR 10195 ,引入了很多內(nèi)容,但它的主要要點(diǎn)在于改變了 UTXO的存儲(chǔ)方式,使得它們更有效地進(jìn)行查找。因此,它出現(xiàn)了很多變化,包括對(duì)早期UpdateCoins函數(shù)的更改:
注意,assert(false) 周?chē)拇a是如何被完全取出的。注意這一點(diǎn),0.15.0中的PR 10537也更改了代碼。
assert失敗的條件現(xiàn)在取決于inputs.SpendCoin,它看起來(lái)是這樣子的:
本質(zhì)上,SpendCoin返回“ false”值的唯一方法,就是讓幣不存在于UTXO集中。但正如你所看到的,這需要幣是FRESH的,而不是DIRTY的。這些不是常見(jiàn)的術(shù)語(yǔ),但值得慶幸的是,core開(kāi)發(fā)者Andrew Chow給出了解釋?zhuān)?/p>
“現(xiàn)在的問(wèn)題是,什么時(shí)候UTXO會(huì)被標(biāo)記為FRESH?當(dāng)它們被添加到UTXO數(shù)據(jù)庫(kù)時(shí),它們就會(huì)被標(biāo)記為FRESH。但是,UTXO數(shù)據(jù)庫(kù)仍然只存在于存儲(chǔ)當(dāng)中的(作為緩存)。當(dāng)它被保存到磁盤(pán)時(shí),存儲(chǔ)中的條目將不再被標(biāo)記為FRESH……”
標(biāo)記為FRESH的幣,是進(jìn)入交易存儲(chǔ)池(memory pool)中的幣。而攻擊者可以通過(guò) UpdateCoins函數(shù)中的assert語(yǔ)句來(lái)破壞節(jié)點(diǎn)。更糟的是,如果幣是屬于DIRTY的(基本上從磁盤(pán)上讀取的),那么這就會(huì)導(dǎo)致比特幣的超發(fā)。
因此,攻擊者可以欺騙那些運(yùn)行 0.15.0- 0.16.2版本軟件的礦工接受一個(gè)奇怪的、無(wú)效的區(qū)塊,從而導(dǎo)致比特幣的供應(yīng)超發(fā)。
超發(fā)漏洞的嚴(yán)重性
這種攻擊的經(jīng)濟(jì)誘因似乎明顯高于DoS攻擊,因?yàn)楣粽呖赡軙?huì)憑空制造出比特幣。但你仍然需要有挖礦設(shè)備來(lái)執(zhí)行攻擊,但考慮到潛在的經(jīng)濟(jì)誘因,這可能是值得的,或者看起來(lái)是這樣的。
下面是使用這種漏洞的一種簡(jiǎn)單攻擊方式:
1.創(chuàng)造一筆帶有雙重支付交易的區(qū)塊,其會(huì)向自己支付兩次,比方說(shuō) 50 BTC →100 BTC;
2.將該區(qū)塊廣播給0.15/0.16版本客戶(hù)端的所有礦工;
下面是會(huì)發(fā)生的一些事:
1.0.14.x 版本節(jié)點(diǎn)會(huì)崩潰;
2.較舊版本的節(jié)點(diǎn)及其它替代客戶(hù)端會(huì)拒絕這個(gè)區(qū)塊;
3.很多區(qū)塊鏈瀏覽器是運(yùn)行在自定義軟件上的,而不是基于core,因此,至少有一些瀏覽器會(huì)拒絕該區(qū)塊,并且不會(huì)顯示來(lái)自該區(qū)塊的任何交易。
4.取決于礦工們運(yùn)行的軟件,我們可能會(huì)迎來(lái)鏈分裂;
有可能,所有的礦工都是運(yùn)行的Bitcoin Core 0.15+版本軟件,在這種情況下,不受攻擊的客戶(hù)端可能會(huì)停滯不前。也有可能礦工會(huì)運(yùn)行其它東西,在這種情況下,當(dāng)他們發(fā)現(xiàn)一個(gè)區(qū)塊時(shí),鏈就會(huì)發(fā)生分叉。
由于這些違規(guī)行為,網(wǎng)絡(luò)上的人們很快就會(huì)追蹤到這一點(diǎn),可能已提醒一些開(kāi)發(fā)人員,并且core開(kāi)發(fā)者已經(jīng)修復(fù)了它。如果存在分叉,那么在那個(gè)時(shí)候,關(guān)于哪條鏈?zhǔn)钦_的共識(shí)鏈,將開(kāi)始得到討論,而出現(xiàn)意外超發(fā)的鏈,可能會(huì)遭到拋棄。如果真的發(fā)生了,那么社區(qū)可能會(huì)自愿進(jìn)行一次回滾,以懲罰攻擊者。
所以對(duì)于攻擊者來(lái)說(shuō),這不會(huì)帶來(lái)50 BTC的收入,更可能的是失去12.5 BTC。如果攻擊者加倍花費(fèi),比如說(shuō)200 BTC,那么超發(fā)漏洞將持續(xù)存在的可能性會(huì)更小,因?yàn)楣魰?huì)更明顯。
因此,從攻擊者的角度來(lái)看,這并不是一種好的獲利方式。
攻擊者可以獲利的另一種方式,就是事先做空比特幣,然后再執(zhí)行攻擊。這也是具有風(fēng)險(xiǎn)的,因?yàn)楣舨荒鼙WC比特幣價(jià)格會(huì)下跌,特別是當(dāng)危機(jī)得到迅速和果斷處理時(shí)。此外,考慮到大多數(shù)交易所提供的杠杠交易,都需要AML/KYC,這可能導(dǎo)致攻擊者很快暴露。
攻擊者不僅面臨著巨大的資金風(fēng)險(xiǎn),而且還會(huì)有身體危險(xiǎn)。從經(jīng)濟(jì)角度來(lái)看,這并不是一個(gè)容易獲利的漏洞。
當(dāng)然,有一些別有用心的人,可能會(huì)用這種漏洞來(lái)嚇唬那些比特幣持有者。投資回報(bào)率將變得更抽象,因此從理論上來(lái)講,這可能會(huì)達(dá)到這些別有用心者的目的。
結(jié)論
毫無(wú)疑問(wèn),這是一個(gè)相當(dāng)嚴(yán)重的漏洞。盡管我和Awemany之間有著分歧,但我很感激他選擇公開(kāi)地和我辯論。也就是說(shuō),考慮到經(jīng)濟(jì)博弈理論,我不認(rèn)為這個(gè)漏洞會(huì)像他所描述的那樣嚴(yán)重。
即使這個(gè)漏洞在被發(fā)現(xiàn)之前被壞人所利用,攻擊者可能也不會(huì)選擇利用它,因?yàn)閺慕?jīng)濟(jì)學(xué)上來(lái)講,它是沒(méi)有意義的??梢钥隙ǖ氖?,這一技術(shù)漏洞應(yīng)該被修復(fù),并且開(kāi)發(fā)者應(yīng)該做得更好,但真正能夠利用這種漏洞的對(duì)象其實(shí)是非常少的,基本上,只有那些想要摧毀比特幣的組織才會(huì)這么干。
Bitcoin Core開(kāi)發(fā)者的教訓(xùn)有很多:
1、任何共識(shí)變化(即使是微小的變化,例如9049),也需要更多的人進(jìn)行審查; 2、需要對(duì)病理交易進(jìn)行更多的檢查; 3、代碼庫(kù)中哪些檢查是冗余的,哪些檢查是不冗余的,以及實(shí)際代碼將要做些什么,需要變得更加清晰; 4、過(guò)去存在漏洞,將來(lái)也會(huì)存在漏洞?,F(xiàn)在重要的是,學(xué)習(xí)并反思這一教訓(xùn);
評(píng)論
查看更多