1. 程式人生 > >區塊鏈安全—詳談共識攻擊(二)

區塊鏈安全—詳談共識攻擊(二)

上一篇文章中我們講述了傳統的Pow以及Pos相關的延伸共識演算法。而在這篇文章中,我們講述一下區塊鏈的精髓PBFT以及其延伸演算法。

在安全方面,我們給大家科普一下日蝕攻擊已經賄賂攻擊。並且一步一步帶大家復現一下前些日子爆出的區塊鏈整數溢位攻擊。希望大家能夠從文章中瞭解到區塊鏈的共識演算法,並對相應的攻擊手段有所掌握。

為了便於初學者接受,文章選擇從基礎開始講起。有需要討論的同學下方留言即可。

一、拜占庭容錯演算法(PBFT)

1、 演算法簡介

學習區塊鏈的同學都知道“挖礦”這個名詞,而根據我們前文講述的Pow機制我們也瞭解了其挖礦的原理。然而,比特幣的發起人-中本聰為什麼選擇使用Pow作為其共識演算法?而Pow是被用來解決什麼樣子的問題呢?下面我們就來講述一下區塊鏈的原始思想——拜占庭將軍問題。

根據維基百科:拜占庭將軍問題(Byzantine Generals Problem),是由萊斯利·蘭波特在其同名論文中提出的分散式對等網路通訊容錯問題。在分散式計算中,不同的計算機通過通訊交換資訊達成共識而按照同一套協作策略行動。但有時候,系統中的成員計算機可能出錯而傳送錯誤的資訊,用於傳遞資訊的通訊網路也可能導致資訊損壞,使得網路中不同的成員關於全體協作的策略得出不同結論,從而破壞系統一致性。拜占庭將軍問題被認為是容錯性問題中最難的問題型別之一。

而簡單來說,下面我們假設一個場景。假設班級裡有五個人,其中一個是小霸王(就是塊頭又大有喜歡欺負別人的熊孩子),而其他四個人飽受那個小霸王的欺負。然而他們四個加起來也許可以打過小霸王。有一天他們約好了一起去找小霸王說理,也就是意味著只要四個人中有一個人不到場那他們就輸了,而他們其中任何一個人都可以選擇去或者不去,那我們如何讓這四個人針對這一個問題達成共識呢?這其實就是著名的拜占庭將軍問題,分析一下,它有幾個特點:
1、敵人很強大;
2、作為弱者的幾個人實力均等(以上四個小夥伴);
3、明確弱者聯合起來可以打敗強大的敵人;
4、只要有一個弱者退出,合作勢必失敗。


簡單來說,拜占庭將軍問題就是“如何讓眾多完全平等的節點針對某一狀態達成共識”

而拜占庭將軍問題的原型就是在戰爭中的相關方案。問題的原型如下:

拜占庭帝國為了擴張領域需要攻打一個強大的敵人,為此他們派去了10支隊伍,然而這個帝國雖然打不過10支隊伍,但是也足以抵抗5支軍隊。然而拜占庭帝國的軍隊必須要同時開始攻擊才能夠攻破城池。那麼拜占庭如何讓他們10支隊伍中的半數以上(6+)同時進攻呢?假設通訊兵沒有任何阻礙,但是對於這些將軍來說,他們不確定他們中是否有叛徒,叛徒可能擅自變更進攻意向或者進攻時間。在這種狀態下,拜占庭將軍們如何才能保證有多於6支軍隊在同一時間一起發起進攻,從而贏取戰鬥?

假設團隊中沒有出現叛徒,那麼假設將軍A提議早上8點攻擊,此時通訊兵通知了其他將軍,並獲得了全部統一,那戰役成功。然而其中部分將軍不同意並同時提出了其他的提議。(例如早上9點攻擊、11點攻擊、下午3點攻擊等......)。除此之外,由於時間上並不是同步到,不同將軍收到的提議的時間也是不同的。上述情況是相對理想一些的,分散式中的時間延遲也是可以理解的。倘若我們將問題複雜一些看待,其中的節點中出現了叛徒(惡意節點),並且他故意搗亂向將軍發出了不同的進攻提議(例如在同一了其中一個時間後又同一了其他時間;或者隨意傳送提議等)。

而叛徒傳送前後不一致的進攻提議,被稱為“拜占庭錯誤”,而能夠處理拜占庭錯誤的這種容錯性稱為「Byzantine fault tolerance」,簡稱為BFT。

2 、演算法詳情

而此時我們就很容易進行解釋為何中本聰選擇了使用 Pow作為區塊鏈的共識,我們從上述內容瞭解到,由於惡意節點在某個時間段的不斷作惡行為,導致了節點中存在了大量的錯誤資訊,所以我們引入了工作量證明來增加發送資訊的成本,並降低傳送資訊的速率以達到在一個時間只有一個節點(或是很少)在進行廣播。

而運用演算法機制已經密碼學知識。當將軍A提出一個提議後,他就要先發布了一個訊息“進攻”並附上了自己的簽名“將軍A” 。例如【3點進攻+將軍A簽名】。其他人看到後也要在其後面附上內容,例如將軍B看到後回覆:【3點進攻 + 將軍B簽名】。當訊息達到6個後他們便可以達成進攻共識進行進攻。

這時也會出現一種情況,將軍A發出訊息後,可能會有兩個或多個將軍同時跟上“進攻+簽名”的訊息,這時,各個節點會嚴格按照廣播的精確時間進行排序,確保一條鏈的完整性。

而這就是區塊鏈存在的分叉機制,在比特幣中我們採用了在最長節點後新增區塊的共識來解決分叉問題。

一個分散式節點中假設存在“3f+1”節點,而最大惡意節點數的容忍數量則是f個。而整個演算法的流程如下:

  • 1客戶端向主節點請求服務

  • 2主節點廣播給副節點

  • 3副節點執行內容並反饋

  • 4客戶端收到f+1個相同答案的資料

二、授權拜占庭容錯演算法(DBFT)

1、 DBFT簡介

DBFT稱為delegated BFT,它是在Castro 和 Liskov提出的“實用拜占庭容錯演算法”的基礎上經過改進的演算法。下面我們看一下演算法的詳細過程:

  • 1、獲取議員名單:這種共識機制與議會制度十分相似,而每個區塊的生成都是在議長的主持下由議會成員共同協商而生成的。

  • 2、確定議長:確定議長的演算法是當前區塊高度+1 再減去當前的檢視編號,結果mod上當前的議員人數,結果就是議長的下標。

  • 3、議長髮起共識:議長在更新完檢視編號後,如果當前時間距離上次寫入新區塊的時間超過了預定的每輪共識的間隔時間(15s)則立即開始新一輪的共識,否則等到間隔時間後再發起共識。

  • 4、議員參與共識:當議長髮起共識後會廣播給議員節點,此時議員收到訊息後對訊息進行公鑰驗證,之後進行共識。

  • 5、檢視更新:一個檢視生存週期完成的時候,如果共識還沒有被達成,則議員會發送廣播請求進入下一個檢視週期並重新選擇議長,當請求更新檢視的請求大於議員數量的2/3的時候,全網達成共識進入下一個檢視週期重新開始共識過程。

詳細原始碼分析見:從NEO原始碼分析看DBFT共識協議

相比其他演算法,DBFT在以下方面做了改進:

  • 1、將C/S架構的請求響應模式,改進為適合P2P網路的對等節點模式

  • 2、將靜態的共識參與節點改進為可動態進入、退出的動態共識參與節點

  • 3、為共識參與節點的產生設計了一套基於持有權益比例的投票機制,通過投票決定共識參與節點(記賬節點)

  • 4、在區塊鏈中引入數字證書,解決了投票中對記賬節點真實身份的認證問題

2 、DBFT安全隱患

根據DBFT的白皮書陳述,總節點數量的要求: n >= 3f + 1 (f為作惡節點)。而在區塊鏈中需要一位議長並且負責提議過程,而議員的工作是對提議進行投票和驗證,如果投票數量 >= 2f + 1,就代表該區塊最終被確定下來。即投票數是惡意節點的兩倍多。在區塊鏈系統的正常運轉過程中可能存在議長離線的情況。此時議員需要重新選舉議長。然而此時威脅就已經存在了。

我們知道區塊鏈作為分散式系統其資訊在傳播的過程中肯定存在延遲的現象。例如一些議員收到2f + 1個投票時,部分節點未必也能收到2f+1這麼多的投票訊息。而節點與節點之間並不知道對方是否收到了相應的訊息。此時當部分議員收到、部分議員沒有收到訊息時,超時的一方就會發起change-view過程,而此過程被用於重新選舉議長。此時就會存在兩方資訊不匹配的情況(會去不同的區塊上新增訊息,即分叉為兩個部分)。

單單是網路延遲就會導致這個問題產生,假設有黑客從中作亂呢?
當一個作惡的人或者黑客混入了小蟻的記帳人節點時,那黑客就可以故意改寫原始碼,在快要超時重選議長的時候,黑客再發起提議,此時就有很大的概率復現上面說的分叉現象。

三、日蝕攻擊

1 、攻擊介紹

日蝕攻擊是對區塊鏈的一種網路級攻擊,它與我前文層提到的P2P網路相關連,並且屬於P2P網路中的一種攻擊。這種攻擊的物件相對比較豐富,在比特幣與以太坊中均存在此類攻擊。

簡單來說,在日蝕攻擊中,攻擊者控制所有來自以目標受害者的節點及將要連線目標受害者的節點。這樣,攻擊者就可以防止受害者獲得關於網路其他部分的完整資訊。而如果能夠成功利用日蝕攻擊,那麼攻擊者只需40%的算力就可以達到51%攻擊的效果。

攻擊方法如下:

  • 1、攻擊者通過某種方法將正常的比特幣節點的輸出連線連線到攻擊者控制的節點,並使用攻擊節點聯結器輸入節點(全部連滿)。而我們知道,在比特幣節點的本地中存有新、舊兩張節點地址的表格。而區塊鏈節點每次建立的輸出連線均是在這兩張表中尋找到最新的一個節點。而攻擊者要做的就是不斷的與該節點進行連線,以便不斷重新整理表格以達到控制的目的。

  • 2、攻擊者使用DDos使目標節點宕機並重啟,這樣就控制了該節點的輸入輸出,也就變相的控制了該節點。

2 、各平臺應用情況

我們知道,比特幣設計之初只能生產8個輸出的TCP連線,而以太坊需要13個。同時以太坊使用了點對點加密的安全通道,比特幣卻沒有。所以我們一定會下意識的以為以太坊在日蝕攻擊中肯定是比比特幣平臺安全的。但是事實不是這樣。

對於比特幣來說,攻擊者可以控制足夠數量的 IP 地址來壟斷所有受害節點之間的有效連線。然後攻擊者可以徵用受害者的挖掘能力,並用它來攻擊區塊鏈的一致性演算法或用於 “重複支付和私自挖礦”。

對於以太坊,攻擊者可以壟斷受害節點所有的輸入和輸出連線,從而將受害節點與網路中其他正常節點隔離開來。 然後攻擊者日食攻擊可以誘騙受害者檢視不正確的乙太網交易細節,誘騙賣家在交易其實還沒有完成的情況下將物品交給給攻擊者。

然而以太坊的點對點網路中的節點由其公鑰所標識。也就是說,以太坊的版本允許使用者在同一個IP下執行無限數量的節點,每個節點都有一個不同的公鑰。而比特幣相對於的一個IP地址只能對應一個節點。這也就是為何曾經國外實驗室使用了兩天PC機就可以模擬日蝕攻擊的原因。

四、區塊鏈賄賂攻擊(Bribery Attacks)

而這種攻擊不同於上述的P2P網路型別的攻擊。此類攻擊運用了區塊鏈中的“社會學”。簡單來說,惡意節點沒有必要在Pow機制下為了使自己能做惡而瘋狂提升算力。反而,我可以在一個區塊鏈協議之外的賄賂者通過一個條件來收購代幣或者挖礦算力,從而達到攻擊原有區塊鏈的目的。

舉例一個簡單的投票機制。假設所有參與者均可以投票0或者1。而機制規定只有所有節點全部為0或者1才給與獎勵。此時有下圖:

即當自己投反對點1時,攻擊方可以基於你賄賂,也就是給與你E價值的獎勵。而原本全為0的時候,所有節點均處於納什均衡(博弈論知識),而當其中1個人被賄賂的時候,這個均衡還不會被打破。倘若攻擊者給所有的節點均進行了賄賂,則所有人均由原來的P到了現在的P+E。而吃虧的是區塊鏈系統。

五、整數溢位攻擊

下面我們講述一下利用區塊鏈合約中的系統漏洞導致的攻擊行為。

事件回顧:

4月25日早間,火幣Pro公告,SMT專案方反饋今日凌晨發現其交易存在異常問題。該漏洞代理的直接經濟損失高達上億元人民幣,間接產生的負面影響目前無法估量。

而這個漏洞取源於整數溢位問題。

我們來看一個例子:

pragma solidity ^0.4.10;

/**
這是一個測試整數型別上溢和下溢的例子
*/
contract Test{

  // 整數上溢
  //如果uint8 型別的變數達到了它的最大值(255),如果在加上一個大於0的值便會變成0
  function test() returns(uint8){
    uint8 a = 255;
    uint8 b = 1;

    return a+b;// return 0
  }

  //整數下溢
  //如果uint8 型別的變數達到了它的最小值(0),如果在減去一個小於0的值便會變成255
  function test() returns(uint8){
    uint8 a = 0;
    uint8 b = 1;

    return a-b;// return 255
  }
}

而根據上面的內容,我們知道這中漏洞就是由於檢測不嚴格而導致的,這也會在現實的協議中產生致命的危害。

而在曾經出現過嚴重問題的SMT合約中,整數溢位問題就給了其一個大的教訓。

比如在原始碼transferProxy()程式碼中:

function transferProxy(address _from, address _to, uint256 _value, uint256 _feeSmt,
        uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){

        if(balances[_from] < _feeSmt + _value) revert();

        uint256 nonce = nonces[_from];
        bytes32 h = keccak256(_from,_to,_value,_feeSmt,nonce);
        if(_from != ecrecover(h,_v,_r,_s)) revert();

        if(balances[_to] + _value < balances[_to]
            || balances[msg.sender] + _feeSmt < balances[msg.sender]) revert();
        balances[_to] += _value;
        Transfer(_from, _to, _value);

        balances[msg.sender] += _feeSmt;
        Transfer(_from, msg.sender, _feeSmt);

        balances[_from] -= _value + _feeSmt;
        nonces[_from] = nonce + 1;
        return true;
    }

我們知道_feeSmt_value均是由使用者傳入的變數引數。而我們看第一行程式碼,if(balances[_from] < _feeSmt + _value) revert();當賬戶餘額小於_feeSmt + _value時會跳出if語句。也就是無法進行下一步的轉賬操作。然而我們根據上面的內容知道,加入_feeSmt = 9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff並且value = 6000000000000000000000000000000000000000000000000000000000000001此時兩者相加使數字溢位為0。

簡單的說由於溢位而使if語句沒有執行,直接跳過了檢查而使下面的語句執行成功。

詳細內容可以參考整數溢位安全漏洞

這也就與傳統的web漏洞非常相似,例如PHP中的弱型別等等均可以看做類似的利用模式。

六、參考資料