在傳統(tǒng)的client-server網(wǎng)路架構(gòu)中,存在著所謂的中間人攻擊:攻擊者可以攔截通訊雙方的通話并插入新的內(nèi)容。而在區(qū)塊鏈的世界中,也存在著類似的攻擊手法,稱為日蝕攻擊。本文將以波士頓大學(xué)團隊的一篇論文為基礎(chǔ),對日蝕攻擊進行一個基本的介紹。
這篇介紹文預(yù)計分成兩部分,第一部分(即本文)將會對Ethereum的P2P做一個簡單的介紹,如此才能了解日蝕攻擊為何能夠生效。第二部分將介紹波士頓大學(xué)團隊論文中關(guān)于日蝕攻擊的實現(xiàn)原理。
在撰寫此篇文章的時候,最新geth版本為1.8.0,而日蝕攻擊論文的實驗對象為geth 1.6.6 ,本文所描述的各種Ethereum環(huán)境,若無特別提及,則視作與日蝕攻擊論文一致。
除此之外,Ethereum的重大改版在即,此文中所描述的諸多Ethereum P2P特性都可能面臨巨大的改變,請多加注意。
Ethereum的P2P特色
根據(jù)日蝕攻擊此篇論文的整理,Ethereum的P2P有以下幾點特色(A~G):
A. 基于Kademlia設(shè)計的P2P協(xié)定
Kademlia是一種用于檔案共享的結(jié)構(gòu)化P2P協(xié)定,其目標(biāo)為有效率的儲存及定位檔案。BitTorrent以及eMule等檔案共享工具就是基于Kademlia設(shè)計而成。
在Kademlia的協(xié)定中,每一個檔案以及每一個節(jié)點都被賦予一個唯一的識別碼( ID ),這個ID是以長度為b的位元組所形成(在原始的Kademlia中,b =160)。此ID會用來計算所謂的距離參數(shù)( d,distance,個人習(xí)慣形容為相異度 ),d值越大表示距離越遠(yuǎn)(相異度越大),計算方式是對兩個ID ( ID?及ID? )進行異或運算 ( XOR ),其定義如下:
d = ID ? ⊕ ID ?
一個簡單范例如下:
ID? = 10011.。.01
ID? = 10011.。.10
ie ID?及ID?的前158個位元皆一致
d = ID ? ⊕ ID ? = 10011.。.01 ⊕ 10011.。.10 = 00000.。. 11 = 3
另外,在每一個Kademlia的節(jié)點里,包含一種稱之為桶(bucket)的資料結(jié)構(gòu)。桶里所儲存的是網(wǎng)路中的節(jié)點資訊,桶的數(shù)量即為識別碼的長度( b ),每一個桶里最多會儲存k個節(jié)點的資訊,因此這種資料結(jié)構(gòu)又被稱為k桶( k-bucket ),如下圖:
Ethereum沿用了Kademlia的XOR運算及bucket概念,另外在Ethereum的設(shè)計中,尋找檔案的機制不再需要,只留下更新節(jié)點資訊的機制,詳細(xì)內(nèi)容可參考下面敘述(E.節(jié)點資訊的更新方式) 。
在此篇論文中,作者提到無法理解Ethereum的P2P協(xié)定為何要使用Kademlia。
由于Ethereum中的底層資料結(jié)構(gòu)(chaindata等資料),并未分割成不同的小片段去作分散式儲存,因此也就無法利用到Kademlia中尋找檔案的特性。
個人猜測或許是Ethereum當(dāng)初在設(shè)計開發(fā)時,曾經(jīng)有考慮要將底層資料結(jié)構(gòu)進行分散式儲存?此點還待高手協(xié)助解惑。
B. ECDSA公鑰作為節(jié)點ID
Ethereum以ECDSA公鑰作為節(jié)點ID,其ID長度為512bits。同一臺機器(同一個IP)可以運行數(shù)個Ethereum節(jié)點,亦即一個IP可以產(chǎn)生數(shù)個節(jié)點ID,這是日蝕攻擊能夠生效的因素之一。
C. UDP協(xié)定與TCP協(xié)定負(fù)責(zé)不同層級的訊息交換
在Ethereum中,P2P資訊的交換是藉由UDP來進行,而區(qū)塊資訊的交換則是在TCP上進行。
Ethereum UDP所傳遞的資訊可分為四種:ping,pong,findnode以及neighbor。
· ping and pong: ping訊息用來嘗試探知某個節(jié)點,若是該節(jié)點接受到 ping訊息即會返回 pong訊息。
· findnode and neighbor:findnode訊息會附帶一個節(jié)點ID(假設(shè)為 t ), findnode將會向另一個節(jié)點(假設(shè)為 x )詢問距離 t最近的節(jié)點資訊,亦即詢問 x的節(jié)點列表中距離t最近的節(jié)點。x會將這些距離 t最近的節(jié)點資訊,以 neighbor訊息返回,最多會返回16筆節(jié)點資訊。
為了防御重放攻擊(replay attacks),UDP傳遞的這些資訊,都會加上時間戳記(timestamp),當(dāng)節(jié)點發(fā)現(xiàn)某一個UDP訊息其時間比本機時間還老舊(比本機時間早20秒) ,就會丟棄該訊息。
Ethereum的TCP連線可分為兩種,一種是由本機節(jié)點發(fā)出請求給其他節(jié)點的outgoing連線,另一種是其他節(jié)點發(fā)起請求給本機節(jié)點的incoming連線。一個Ethereum節(jié)點同一時間最多可允許25個TCP連線存在( maxpeers ),而在geth 1.8.0版之前,這25個TCP連線可以全部都是incoming連線,這也是日蝕攻擊能夠生效的其中一個因素。
D. 儲存P2P節(jié)點資訊的資料結(jié)構(gòu)
Ethereum有兩種儲存節(jié)點資訊的資料結(jié)構(gòu),一種用于長期儲存,日蝕攻擊論文的作者稱之為db。db會將節(jié)點資訊持久化的儲存于硬碟內(nèi),因此每次節(jié)點重啟時,db的資料仍會保存。db內(nèi)儲存了數(shù)筆節(jié)點的資訊,每一筆資訊包含以下的欄位:
· nodeIP:節(jié)點IP
· tcpPort:TCP Port number
· udpPort:UDP Port number
· latestPing:最近一次嘗試接觸該節(jié)點的時間(亦即ping訊息送出的時間)
· latestPong:最近一次該節(jié)點回應(yīng)的時間(亦即收到pong訊息的時間)
· failedTimes:以findnode嘗試詢問該節(jié)點卻失敗的次數(shù)
以上欄位名稱并非Ethereum geth原始碼中所定義的名稱,只是為了方便本文后續(xù)說明而定義的名稱。
Ethereum本機節(jié)點會定期檢查db內(nèi)的每筆節(jié)點資訊,若是某節(jié)點的latestPong與當(dāng)下時間間隔已經(jīng)超過一天,則將該節(jié)點從db中移除。
另一種結(jié)構(gòu)是短期儲存,稱之為table,每次重啟節(jié)點時,table都會是空的。table含有256個bucket,每個bucket又包含16筆其他節(jié)點的資料,每筆資料的欄位如下:
· nodeID:節(jié)點ID
· nodeIP:節(jié)點IP
· tcpPort:TCP Port number
· udpPort:UDP Port number
bucket內(nèi)的每筆節(jié)點資料都會按照加入順序而排序,當(dāng)一個bukcet已經(jīng)額滿卻還有新節(jié)點資料要加入的時候,本機節(jié)點會傳送ping訊息給bukcet中最早加入的節(jié)點,若是最早加入的節(jié)點沒有回應(yīng)pong訊息,則移除最早加入的節(jié)點,并將新節(jié)點加入,反之則丟棄新節(jié)點。
為了決定某一個節(jié)點資訊要放到哪一個bucket之中,需配合logdist函式來進行計算,過程如下,其中ID?及ID?是logdist函式的輸入?yún)?shù):
H? = SHA3(ID?) ,where ID? is the ID of local node
H? = SHA3(ID?) ,where ID? is the ID of other node
r = similarity(H?,H?) ,where 0 ≤ r ≤ 255
i.e. r is number of most significant bits that H? and H? are the same
經(jīng)過上面的計算得到r值后,就可算出節(jié)點會被儲存在編號為bucket_num (等于256﹣ r )的bucket之內(nèi)。由于logdist函式的特性,節(jié)點在不同bucket_num的分配會呈現(xiàn)高度偏移(highly-skewed)的狀況,即任一節(jié)點有較高機率被分配到bucket_num較大的bucket中,其機率分布如下式:
P? = 1 / ( 2^( r +1) ) ,where 0 ≤ r ≤ 255 , bucket_num = 256﹣ r
根據(jù)上式,機率分布圖如下:
理論上table結(jié)構(gòu)最大可容納4096筆節(jié)點資料(一個bucket最多容納16筆,共有256個bucket ) ,但是由于這種高度偏移狀況,實際上table大部分的時間只會儲存少量的節(jié)點資料(大約150~200筆,參考下圖,出自日蝕攻擊論文)。
E. 節(jié)點資訊的更新方式
Ethereum綜合了下述的預(yù)設(shè)節(jié)點資訊以及運算模式,以新增或更新其節(jié)點資訊紀(jì)錄( table及db ) :
1.預(yù)設(shè)節(jié)點( Bootstrap nodes)
Ethereum內(nèi)建(hardcoded)了六筆預(yù)設(shè)節(jié)點資訊。當(dāng)一個Ethereum節(jié)點初始化并第一次啟動時,db沒有儲存任何資料,此時就會用到預(yù)設(shè)節(jié)點資訊。
2.聯(lián)結(jié)更新運算( Bonding)
聯(lián)結(jié)更新運算是本機節(jié)點嘗試新增或更新某一個遠(yuǎn)端節(jié)點資訊的過程( db以及table ),其具體過程如下:
step1-a:檢查該遠(yuǎn)端節(jié)點是否已存在于db中
step1-b:檢查failedTimes是否為0
step1-c:檢查latestPong是否小于24小時
step2:若是第一個步驟的三個條件都成立,則將該節(jié)點存入table中對應(yīng)的bucket,具體存入過程請參考上述D.儲存P2P節(jié)點資訊的資料結(jié)構(gòu)。 若是有任一條件不成立,則進到step3。
step3:嘗試傳送ping訊息給該節(jié)點,若收到該節(jié)點的pong訊息,則于db加入或更新該節(jié) 點資訊,并且將該節(jié)點存入table中對應(yīng)的bucket。
3. ping訊息觸發(fā)運算( Unsolicited pings )
當(dāng)本機節(jié)點收到一個遠(yuǎn)端節(jié)點傳送過來的ping訊息時,除了回應(yīng)pong訊息給該遠(yuǎn)端節(jié)點,本機節(jié)點也會針對該遠(yuǎn)端節(jié)點執(zhí)行上述的聯(lián)結(jié)更新運算( Bonding )。
4.尋訪運算( Lookup )
尋訪運算即針對某一個目標(biāo)節(jié)點找尋其最近節(jié)點,并且紀(jì)錄這些最近節(jié)點,步驟如下:
step1:給定一個目標(biāo)節(jié)點,稱之為t。
step2:從本機節(jié)點table中的所有節(jié)點中選出16個最接近t (根據(jù)logdist函式)的節(jié)點,
這16個節(jié)點組成“待訪節(jié)點列表”。
step3:本機分別去訪問( findnode )“待訪節(jié)點列表”的每個節(jié)點,每一個被訪問的節(jié)點會再
各自返回多個(最多16個)更接近t的節(jié)點( neighbor )。
step4: step3完成后,本機可得到數(shù)個新節(jié)點(最多256個),接著對這數(shù)個新節(jié)點進行聯(lián)結(jié)
更新運算( bonding )。
step5:從step2所選出的16個節(jié)點加上step4完成聯(lián)結(jié)更新運算的新節(jié)點(最多16+256
個)中再度組成新的待訪節(jié)點列表,并取代step2原本的待訪節(jié)點列表。
step6:本機不斷重復(fù)step2 ~ step5,直到節(jié)點資訊不再變化,表示尋訪機制完成(當(dāng)
step5新的待訪節(jié)點列表與step2原本的待訪節(jié)點列表相同時,視作不再變化),此
時會將這組最新的待訪節(jié)點列表放到一個lookup_buffer的資料結(jié)構(gòu)中。
F. 種子運算(Seeding)
種子運算皆由本機自主觸發(fā),是以近期之內(nèi)保持在線上的節(jié)點資訊作為更新table資訊的依據(jù),具體行為是將預(yù)設(shè)節(jié)點以及db之中的節(jié)點資訊復(fù)制到table中,有三種情況會觸發(fā)種子運算:
· 當(dāng)Ethereum節(jié)點啟動時
· 每一小時定期觸發(fā)
· 尋訪運算( Lookup )啟動時
種子運算的具體步驟如下:
step1:本機節(jié)點檢查是否table為空;若為空,才繼續(xù)執(zhí)行后續(xù)步驟。
step2:本機節(jié)點對六筆預(yù)設(shè)節(jié)點執(zhí)行聯(lián)結(jié)更新運算。
step3:從本機db中,取出latestPong小于或等于120小時的一組節(jié)點,若這一組節(jié)點的數(shù)
量少于30個,則全部保留;若數(shù)量多于30個,則隨機留下其中30個。接著本機節(jié)點針
對這些節(jié)點去執(zhí)行聯(lián)結(jié)更新運算。
step4:本機節(jié)點以自己為目標(biāo)執(zhí)行尋訪運算。
G. Outgoing TCP連線的建立
Ethereum節(jié)點會持續(xù)建立Outgoing TCP連線,Outgoing TCP連線數(shù)最多可達13個(0.5×(1+ maxpeers ))。Ethereum節(jié)點會準(zhǔn)備兩種執(zhí)行緒:
第一種執(zhí)行緒是discover_task,此執(zhí)行緒之任務(wù)是以一個隨機節(jié)點為目標(biāo)去進行尋訪運算以持續(xù)更新節(jié)點資訊。
第二種執(zhí)行緒是dial_task,此執(zhí)行緒之任務(wù)用于對某個目標(biāo)節(jié)點建立TCP連線,嘗試建立連線前,首先會檢查幾個條件:
· 該節(jié)點不在黑名單內(nèi)
· 與該節(jié)點的TCP連線尚未建立
· 是否正在對該節(jié)點進行撥號( dial );若否,則成立
· 是否最近曾對該節(jié)點進行撥號;若否,則成立
整體而言,Ethereum會由lookup_buffer (經(jīng)由尋訪運算得出)及table取得節(jié)點資訊,再嘗試去和這些節(jié)點建立TCP連線。
綜合上述,可以整理出Ethereum更新節(jié)點資訊以及連結(jié)節(jié)點的過程,如下圖:
以上是關(guān)于Ethereum P2P的簡介,而日蝕攻擊便是針對Ethereum建立節(jié)點資訊和連結(jié)節(jié)點的過程進行攻擊。
日蝕攻擊手法
接下來的敘述中,只要受害目標(biāo)的所有TCP連線都被攻擊者占據(jù),我們都定義為完成日蝕攻擊。
A. Monopolizing Connections — 主動占據(jù)受害目標(biāo)(victim)的連線
此攻擊的概念就是攻擊者主動去占據(jù)受害目標(biāo)的所有TCP連線。這種攻擊能夠成功的原因,是基于Ethereum節(jié)點的幾個特性:
· 一個節(jié)點的所有TCP連線可以全部是Incoming連線,也就是由其他遠(yuǎn)端節(jié)點發(fā)起請求所形成的連線。
· 當(dāng)一個節(jié)點重啟時,Outgoing及Incoming的連線數(shù)皆為0。
· 當(dāng)一個節(jié)點重啟時,需要一段頗長的時間才會建立起Outgoing連線。
根據(jù)論文的實驗數(shù)據(jù)顯示,在一臺2 vCPU及2GB RAM的云端服務(wù)器上啟動Ethereum節(jié)點,要8秒鐘之后種子運算才會開始進行,也就是8秒鐘之后才會開始嘗試進行Outgoing TCP連線。
攻擊細(xì)節(jié)如下:首先,攻擊者會產(chǎn)生N個節(jié)點ID,N的數(shù)量遠(yuǎn)大于一個Ethereum節(jié)點所能允許的最大TCP連線數(shù)( maxpeers,預(yù)設(shè)25個)。接下來,等到受害節(jié)點重啟之后,立即以這N個節(jié)點對受害節(jié)點進行Incoming連線。當(dāng)受害者的全部TCP連線都被攻擊者的Incoming連線占據(jù)時,即完成日蝕攻擊。
論文作者嘗試了兩個實驗,在第一個實驗中,攻擊者在兩臺主機上建立了1000個攻擊節(jié)點,接著重啟受害者節(jié)點再去攻擊。這個實驗重復(fù)了50次,每次都會將受害者回復(fù)到攻擊前的狀態(tài)。最后發(fā)現(xiàn)50次攻擊里有49次可以完成日蝕攻擊。在失敗的那一次中,受害者節(jié)點成功建立了一條Outgoing連線。
在第二個實驗中,論文作者測試了網(wǎng)路延遲(latency)的影響。作者這次將受害者節(jié)點安排在距離攻擊者節(jié)點較遠(yuǎn)的地方(受害者在新加坡,攻擊者則在紐約),接著一樣建立1000個攻擊節(jié)點,最后發(fā)現(xiàn)53次的攻擊里只有43次會完成日蝕攻擊。
根據(jù)實驗結(jié)果,作者認(rèn)為Ethereum節(jié)點的Incoming連線沒有限制數(shù)量是關(guān)鍵弱點所在,因此建議應(yīng)該限制Incoming連線數(shù),以保證Ethereum節(jié)點能夠進行Outgoing連線。
這一個建議已經(jīng)被Ethereum社群所采納,在geth 1.8.0版本中, Incoming連線的數(shù)量變成是可以限制的,預(yù)設(shè)上限是maxpeers /3。
B. Table Poisoning — 竄改受害者的節(jié)點資訊列表
在第一種攻擊中,作者在最后提出了防御補強的建議,假設(shè)受害者采納了這個建議,使得本機能夠限制Incoming連線數(shù),那么攻擊者是否仍有機會完成日蝕攻擊?
在這種前提下,作者設(shè)想了另一種可能的攻擊方式— Table Poisoning。在Table Poisoning這種攻擊手法中,除了主動占據(jù)受害者的Incoming連線,攻擊者還必須設(shè)法侵占受害者的節(jié)點資訊列表,使其列表中的大多數(shù)節(jié)點都是屬于攻擊者所控制的節(jié)點,那么當(dāng)受害者嘗試進行Outgoing連線時,仍然會連接到攻擊者的節(jié)點,借此達成占據(jù)其所有TCP連線的意圖,此攻擊經(jīng)由以下幾個步驟完成。
首先第一步,由于攻擊者知道受害者的節(jié)點ID,攻擊者便可計算出受害者table的不同bucket中,可能會儲存哪些節(jié)點資訊( logdist函式)。由于Ethereum 節(jié)點儲存方式的特性,攻擊者并不需要占滿受害者的所有bucket,攻擊者只要嘗試占滿受害者的最后n個bucket ( bucket_num從256~256﹣ n ),即可以極高的機率占據(jù)其TCP連線。所以攻擊者的第一步工作,便是去算出多個匹配bucket_num的攻擊用節(jié)點ID (以下簡稱IDattack )。
可預(yù)期的是當(dāng)n越大,需要越多時間去算出能夠匹配bucket_num的IDattack。
在本論文中,作者選擇n=17作為其bucket攻擊數(shù)量,總共需要計算出272個IDattack,作者使用一臺MacBook Pro進行計算(CPU:i5 2.9 GH;RAM:16 GB),總共需要15分鐘去算出272個IDattack。
接下來第二步,在計算出IDattack之后,便是設(shè)法讓這些IDattack被插入到受害者的db之中。攻擊者會藉由這些IDattack發(fā)出ping訊息給受害者,受害者收到ping訊息之后,除了回應(yīng)pong訊息,還會對這些IDattack進行Bonding,如此便可讓這些IDattack被插入到受害者的db之中。
在這個過程中,攻擊者每24小時便會由這些IDattack發(fā)出ping訊息給受害者,并且攻擊者也要確實回應(yīng)受害者的ping訊息(返回pong )以及findnode訊息(攻擊者會返回空的neighbor訊息),如此這些IDattack便具備了快速占據(jù)受害者table的條件(詳細(xì)內(nèi)容請參考Bonding運算)。
第三步是此攻擊的最后步驟,設(shè)法讓受害者節(jié)點重新啟動。Ethereum節(jié)點重新啟動時,其table是空的,攻擊者在這時會不斷的藉由已經(jīng)產(chǎn)生的IDattack發(fā)出ping訊息給受害者,由于在第二步中,我們使這些IDattack具備了快速占據(jù)受害者table的條件,此時便可很快的占據(jù)受害者table。
Ethereum節(jié)點在啟動時,UDP監(jiān)聽程序會先運作(此時就可以連接其他節(jié)點的Incoming連線,并接受其他節(jié)點傳來的ping訊息進行Bonding運算),然后才進行種子運算程序,兩個程序之間大約間隔一秒。
藉由種子運算的特性,我們可以得知,在table已存有節(jié)點資訊的情況下,種子運算并不會發(fā)生作用,也就是db中沒有任何節(jié)點資訊會轉(zhuǎn)移到table中(妨礙種子運算),如此受害者的Outgoing連線將全部連接到攻擊者的節(jié)點,到這個時候,攻擊者只要再配合Monopolizing Connections攻擊的方法想辦法占據(jù)剩余的Incoming連線,就有機會完成日蝕攻擊。
table的更新可能是外部觸發(fā)(Unsolicited pings ,遠(yuǎn)端節(jié)點發(fā)出ping給本機)或是自主觸發(fā)(本機的種子運算)。Table Poisoning的作用是嘗試讓自主觸發(fā)失效。
IDattack只是具備快速占據(jù)受害者table的條件,并無法完全保證table只儲存IDattack。若是有其他誠實節(jié)點嘗試發(fā)出ping訊息給受害者,table中就有可能儲存了誠實節(jié)點。
針對這種攻擊,作者也執(zhí)行了兩種實驗。第一個實驗,攻擊者先準(zhǔn)備了一個已經(jīng)運行33天的受害者(地點在紐約),此受害者的db中儲存了25580筆節(jié)點資訊,在那個時間點,這已經(jīng)可以視為Ethereum全網(wǎng)的全部節(jié)點數(shù)量。接著作者依上述步驟開始進行攻擊,第一步和第二步各執(zhí)行一次,第三步則重復(fù)執(zhí)行51次,每次重復(fù)之前都會將受害者主機重新啟動。
攻擊者準(zhǔn)備了兩臺攻擊用主機,一臺主機(位于波士頓)以272個IDattack傳送ping訊息給受害者,另一臺主機(位于紐約)則依照Monopolizing Connections攻擊以1000個攻擊節(jié)點對受害者進行Incoming連線。在這51次的實驗里,有49次受害者的Outgoing連線皆被攻擊者占據(jù)(其中只有19次成功地妨礙種子運算),然而這51次中卻只有34次完成日蝕攻擊。
為何此次攻擊的成功率明顯較為低落(比起單純Monopolizing Connections攻擊)?
作者解釋到,由于此實驗仍是使用舊版geth來進行(在他們實驗的時候,尚未發(fā)行g(shù)eth 1.8.0,也就是尚未發(fā)行針對第一種攻擊進行修正的版本),為了模擬出限制Incoming連線數(shù)的效果(也就是25個TCP連線中,一部分必須是屬于Outgoing),攻擊者一開始不會積極搶占受害者的Incoming連線,直到確認(rèn)受害者建立了Outgoing連線,才會嘗試搶占受害者的Incoming連線,而在這段等待確認(rèn)Outgoing的時間,受害者可能已經(jīng)連接了其他誠實節(jié)點的Incoming連線,導(dǎo)致日蝕攻擊無法完成。
而在第二個實驗中,作者測試了網(wǎng)路延遲(latency)的影響。攻擊者準(zhǔn)備了一個已經(jīng)運行1小時的受害者(地點在新加玻),此受害者的db中儲存了7000筆節(jié)點資訊。兩臺攻擊用主機則與第一個實驗相同,不再贅述。第二個實驗重復(fù)了50次,最后有44次完成了日蝕攻擊,6次的失敗中,有1次是因為受害者建立了Outgoing連線,另外5次則是其他誠實節(jié)點建立了Incoming連線。
在實際應(yīng)用上,Table Poisoning仍有缺陷。為了匹配某個受害者的節(jié)點ID去產(chǎn)生攻擊用節(jié)點ID,攻擊者需要耗費可觀的運算資源,如果攻擊者想攻擊多個不同的受害者,就必須產(chǎn)生大量的攻擊用節(jié)點ID,資源的消耗也會急遽增加。
針對這種資源消耗的缺陷,作者設(shè)想了另一種產(chǎn)生攻擊用節(jié)點ID的方式,稱之為建立尋訪表( lookup table )。
針對Table Poisoning的攻擊手法,作者建議了幾種補強方式,大致上可區(qū)分為兩種策略—改良節(jié)點ID的管理方式以及改良種子運算:
1.改良節(jié)點ID的管理方式
此策略的目的在于讓攻擊者無法輕易的算出IDattack,以及讓攻擊者在同一臺機器上(單一個IP)無法操縱多個不同節(jié)點ID。
作者建議,應(yīng)該嚴(yán)格要求同一臺機器(同一個IP)只能同時與一個節(jié)點ID相關(guān),也就是在table及db的儲存結(jié)構(gòu),以及nieghbor訊息的結(jié)構(gòu)中應(yīng)該去設(shè)定這個限制。此外,為了讓攻擊者無法輕易的算出IDattack,應(yīng)該要改變logdist函式的輸入?yún)?shù),而尋訪運算也應(yīng)該改變其運算目標(biāo)。
2. 改良種子運算
作者建議,即使table不是空的,仍應(yīng)執(zhí)行種子運算。此外,當(dāng)一個節(jié)點在重新啟動之后,在被動的執(zhí)行聯(lián)結(jié)更新運算之前(由Unsolicited pings所觸發(fā)),應(yīng)優(yōu)先執(zhí)行尋訪運算。如此以來,就可以盡量避免被攻擊用節(jié)點ID占據(jù)。
在geth 1.8.0版,作者所建議的改良種子運算已經(jīng)實作。
C. Manipulating Time — 時間操作攻擊
這個攻擊利用了Ethereum防御重放攻擊的機制—若是某個UDP訊息的時間戳記比節(jié)點的本機時間還要早20秒以上,該訊息將被丟棄。
攻擊者借著操弄NTP等方式,使得受害者的本機時間總是比外界真實時間還要遲緩20秒以上(也就是比其他誠實節(jié)點遲緩20秒)。如此一來,受害者將不再理會其他誠實節(jié)點傳送過來的pong及neighbor訊息,而經(jīng)過數(shù)日之后,由于過期檢查機制,受害者的db將會清除掉所有的誠實節(jié)點。
另外,由于neighbor訊息都被丟棄,也會讓尋訪機制失去效用,導(dǎo)致table內(nèi)的誠實節(jié)點都被清除,最后就會使得受害者節(jié)點不再儲存任何誠實節(jié)點的資訊。而相對的,因為受害者也不再理會其他誠實節(jié)點傳送過來的ping及findnode訊息,在一段時間之后,其他誠實節(jié)點也就不會再儲存受害者節(jié)點的資訊。
依據(jù)以上的敘述,作者設(shè)計了一個實驗。作者準(zhǔn)備了一臺已經(jīng)運行34天的受害者節(jié)點(在紐約),接著便開始對受害者模擬NTP竄改時間攻擊(具體做法是直接改變受害者節(jié)點的本機時間),實驗進行6次,每一次受害者節(jié)點的本機時間都比真實時間延遲許多(6個不同的延遲時間:25秒,70秒,5分鐘,7分鐘,9分鐘,13分鐘),整個實驗歷時數(shù)日(從8月17日至9月4日)。實驗結(jié)果顯示,到了攻擊第3天,受害者db中紀(jì)錄的節(jié)點數(shù)量顯著的減少,而且之后便趨于平緩幾乎不再增加(從一開始的數(shù)萬個變成10幾個),而table中的數(shù)量也有一樣的變化趨勢。
雖然db及table中的節(jié)點數(shù)量大幅減少并且?guī)缀醪辉僭黾樱桥c受害者連接的節(jié)點數(shù)量卻呈現(xiàn)大幅波動,有時可以達到預(yù)設(shè)上限25個,有時又不到10個,進一步分析之后發(fā)現(xiàn),這些與受害者連接的節(jié)點,極大部分不屬于geth版本,而是諸如parity,Ethereum(J)等其他Ethereum的實作版本,在實驗的最后11天里,作者統(tǒng)計了與受害者連結(jié)的節(jié)點數(shù)量與類別,其中屬于geth版本僅有130個,而非geth版本的則高達64374個。
Manipulating Time攻擊能夠弱化受害者節(jié)點,使得受害者幾乎無視其他節(jié)點,也讓其他節(jié)點幾乎遺忘了受害者節(jié)點,這樣一來,就能夠以更少的資源消耗來執(zhí)行Table Poisoning攻擊。
而針對Manipulating Time攻擊,作者建議應(yīng)該改良防御重放攻擊的方式,也就是每一個UDP消息不再以時間戳記(timestamp)進行標(biāo)記,而是以隨機的nonce進行標(biāo)記,當(dāng)送出ping或findnode訊息時附加一個nonce,而之后送回的pong或neighbor也應(yīng)該附加相對應(yīng)的nonce才視作合法訊息。
作者自己也坦承,這樣的改良只能保證pong或neighbor不會重放,卻無法保證ping或findnode本身重放。
以上便是波士頓大學(xué)團隊研究所提出的幾種日蝕攻擊手法,在論文中,作者還有提出一些更深入的分析及實驗,例如,怎么樣去放大攻擊的規(guī)模,怎么樣做出最有效率(最省成本)的攻擊。
評論