以太坊智能合約攻擊研究綜述

以太坊智能合約攻擊研究綜述

論文:Nicola Atzei , Massimo Bartoletti , Tiziana Cimoli

A survey of attacks on Ethereum smart contracts - International Conference on Principles of Security & Trust - 2017

論文摘要

智能合約是由節點組成的網絡可以正確執行的計算機程序,不需要外部可信中介的授權。由於智能合約處理和轉移的資產價值相當大,因此,除了正確執行它們之外,確保它們的實現不受竊取或篡改資產的攻擊也是至關重要的。我們在Ethereum中研究了這個問題,Ethereum是迄今為止最著名和使用最多的智能契約框架。我們分析了Ethereum智能合約的安全漏洞,給出了常見程序的分類。

論文介紹:

一、介紹

比特幣是一種去中心化的加密貨幣,自2009年問世以來,其市值已達到100億美元。比特幣的成功引發了業界和學術界的極大興趣。簡單來說,區塊鏈是由P2P網絡的節點維護的只支持追加數據的數據結構。加密貨幣使用區塊鏈作為一個公共賬本,在那裡他們記錄所有的貨幣轉移。時至今日,區塊鏈的使用範圍不僅僅是比特幣,它還涉及金融產品、服務行業、跟蹤各種財產的所有權、數字身份驗證、投票等各個產業。智能合約的框架有很多種,其中最出名的是以太坊。智能合約被正確執行是以太坊有效性的必要條件;否則,對手可以篡改合約執行,例如從合約參與者那裡轉移一些錢給攻擊者自己。然而,僅僅保證執行的正確性並不足以保證智能合約的安全性。實際上,通過實際的開發經驗和對Ethereum區塊鏈上的所有合約的靜態分析,我們已經發現了Ethereum智能契約中的幾個安全漏洞。這些漏洞已經被一些黑客利用,其中最出名的一次攻擊造成了6000萬美元的財產損失。這也引發了人們對於區塊鏈安全性的激烈討論。

導致智能合約在以太坊中的執行容易出錯的原因有很多個。其中一個重要的原因與solidity(以太坊支持的高級編程語言)有關。Solidity的編程語法與程序員的直覺之間的不一致性導致了很多的漏洞。問題在於,儘管solid看起來類似javascript語言(除了異常和函數)。與此同時,該語言沒有引入處理區域特異性方面的構造,例如,計算步驟被記錄在公共區塊鏈上,而在公共區塊鏈上他們會被故意的重新排序或者延遲執行。另一個含有漏洞合約依然再被擴散的主要原因是,很多關於漏洞的文檔分佈在不同的文檔之中。

我們的貢獻是:本文首次系統地闡述了以太坊及其高級編程語言Solidity的安全漏洞。我們將造成漏洞的原因歸類,目的有兩方面:(i)作為智能合約開發者的參考,以瞭解和避免常見的陷阱;(ii)作為研究人員的指南,促進發展智能合同的分析和驗證技術。對於分類法中的大多數漏洞原因,我們給出了一個實際的利用了這些漏洞進行攻擊的例子(通常是在實際合約中進行的)。

二、以太坊智能合約的背景介紹

智能合約的編寫:

以太坊智能合約攻擊研究綜述

圖1 一個簡單的實現錢包功能的合約

我們通過一個小例子(圖1中的AWallet)來說明智能合約,它實現了一個與所有者關聯的個人錢包。我們沒有直接將其作為EVM字節碼進行編程,而是使用了一種類似javascript的編程語言solidity,它可以編譯為EVM字節碼。直觀地說,合約可以從其他用戶接收以太幣,它的所有者可以通過功能pay將一部分以太幣發送給其他用戶。一個哈希表outflow記錄了它發送金錢到的所有地址,並將轉賬總額關聯到每個地址。所有接收到的以太幣都由合約保存。它的金額自動記錄在餘額中,這是一個特殊的變量,不能由程序員更改。

合約由字段和函數組成。用戶可以通過向Ethereum節點發送合適的事務來調用函數。交易必須包括給礦工的執行費用,並可能包括以太從調用方到合同的轉賬。Solidity也有例外的情況。當拋出異常時,無法捕獲異常:執行停止,費用丟失,所有副作用(包括以太的轉賬)都被恢復。第5行中的函數AWallet是一個構造函數,在創建契約時只運行一次。函數pay將金額wei (1 wei =

ether)從合同發送給收件人。在第8行,如果調用者(msg.sender)不是所有者,或者一些ether (msg.value)附加到調用並傳輸到契約,合約將拋出異常。因為異常會恢復副作用,所以這個以太會返回給調用者(但是調用者會損失執行費用)。在第9行,如果以太幣數量不足,調用將終止;在這種情況下,不需要恢復異常狀態。在第10行,契約更新outflow,然後將以太幣傳輸給接收方。第11行中用於此目的的send函數出現了一些特殊限制,例如,如果收件人是合同,它可能會失敗。

執行費用:

理想情況下,每個函數調用都由Ethereum網絡中的所有礦工執行。調用函數的用戶所支付的執行費用激勵礦工進行此類工作。除了被用作獎勵,執行費用還可以防止服務攻擊,即對手試圖通過請求耗時的計算來降低網絡速度。需要指出的是,執行費用由gas和gas的單位價格決定的。大略地說,用戶支付的費用越高,那麼他的調用有越大的可能被礦工執行。

挖礦過程:

礦工的挖礦難度由很大因素構成。其中一個條件要求礦工解決一箇中等難度的“工作證明”難題,這取決於前一個塊和新塊中的事務。謎題的難度是動態更新的,所以平均挖掘速度是每12秒挖礦1塊。當一個採礦者解決了這個難題並向網絡廣播了一個新的有效塊時,其他採礦者將放棄他們的嘗試,通過添加新的塊來更新他們的本地區塊鏈副本,並在其上開始“挖礦”。解決這一難題的礦工將獲得新區塊交易費用的獎勵。

如果有兩名(或兩名以上)礦工幾乎同時解決了這個難題,區塊鏈將分叉為兩個(或更多)分支,新塊指向相同的父塊。共識協議規定採礦者要擴展最長的分支。因此,即使兩個分支都可以暫時繼續存在,最終也會為最長的分支解析fork。只有其中的事務將成為區塊鏈的一部分,而最短分支中的事務將被丟棄。在GHOST協議中,獎勵將全部費用分配給最長分支中區塊的採礦者,並將部分費用分配給開採廢棄分支的根的採礦者。例如,假定塊A和B有同樣的父節點,一個礦工添加了一個新的節點在A上面。那麼這個礦工可以貢獻出一些獎勵費用給B。

編譯Solidity為EVM二進制字節碼:

雖然契約以函數集的形式呈現,但是EVM字節碼不支持函數。因此,solability編譯器翻譯合約,以便實現函數調度機制。更具體地說,每個函數都由一個簽名唯一地標識,簽名基於函數的名稱和類型參數。在函數調用時,此簽名作為輸入傳遞給被調用的契約:如果它匹配某個函數,則執行跳轉到相應的代碼,否則將跳轉到回調函數。回調函數是一個沒有名稱和參數的特殊函數,可以任意編程。回調函數也會在合同被傳遞一個空簽名時執行:例如,當向合同發送以太幣時。

三、智能合約安全漏洞分類

在本節中,我們將Ethereum智能合約的安全漏洞系統化。我們根據引入漏洞的級別將漏洞分為三類。此外,我們還通過一小段代碼來說明每個漏洞在可靠性級別上的表現。所有這些漏洞都可以(實際上,大部分漏洞已經被利用)進行攻擊,例如從合同中竊取資金。表1總結了我們的漏洞分類。

以太坊智能合約攻擊研究綜述

表1 以太坊智能合約中漏洞分類

Call to the unknown.

在solidity中用於調用函數和傳輸以太幣的一些原語可能具有調用被調用方/接收者的回調函數函數的副作用。我們將在下面對此進行說明。

  • call調用一個函數(另一個合約的函數,或本身的函數),並將以太幣傳輸給被調用方。例如,可以調用合同c的函數ping:
以太坊智能合約攻擊研究綜述

其中,被調用的函數由其哈希簽名的前4個字節標識,amount決定需要向c傳輸多少個wei, n是ping的實際參數。值得注意的是,如果在地址c處不存在具有給定簽名的函數,那麼將執行c的回退函數。

  • send用於將以太幣從正在運行的合同傳輸到某些接收者r,如r.send(amount)。傳輸以太幣之後,send執行接收者的回退。其他與發送相關的漏洞將在“exception disorders”和“gasless send”。
  • delegatecall與call非常相似,不同之處在於被調用函數的調用是在調用者環境中運行的。例如,執行c.delegatecall(bytes4(sha3("ping(uint256)"),n),如果ping包含變量this,它指的是調用者的地址而不是c,如果以太通過d.send(amount)傳輸給某個接收者d,以太從調用者餘額中取出。
  • 除了上述基本類型外,還可以使用以下直接調用:
以太坊智能合約攻擊研究綜述

第一行聲明Alice的合約接口,最後兩行包含Bob的合約:其中,pong通過直接調用調用Alice的ping。現在,如果程序員輸入錯誤了contract Alice的接口(例如,通過聲明參數的類型為int而不是uint),並且Alice沒有具有該簽名的函數,那麼對ping的調用實際上會導致對Alice回調函數的調用。

Exception disorder.

在Solidity中,有幾種可能引發異常的情況,例如(i)執行耗盡氣體;(ii)調用棧達到極限;(iii)執行命令拋出。然而,Solidity在處理異常的方式上並不一致:有兩種不同的行為,這取決於合約如何調用彼此。例如,考慮:

以太坊智能合約攻擊研究綜述

現在,假設某個用戶調用Bob的pong, Alice的ping拋出一個異常。之後,執行會停止,並且整個事務的副作用會被回退。因此,字段x在事務之後包含0。現在,假設Bob通過call調用ping。在這種情況下,只有調用的副作用被恢復,call返回false,執行繼續。因此,x在調用之後的值為2。

更一般地,假設拋出異常時存在一個嵌套調用鏈。然後,異常處理如下:

  • 如果鏈中的每個元素都是直接調用,那麼執行就會停止,所有副作用(包括以太幣的轉賬)都會恢復。此外,原始事務分配的所有gas均被消耗;
  • 如果鏈中的至少一個元素是一個調用(案例delegatecall和send是類似的),那麼異常將沿著鏈傳播,恢復被調用合約中的所有副作用,直到它到達一個調用。從那時起,執行將繼續,調用返回false。此外,調用分配的所有gas都被消耗掉了。

異常處理方式的不規範可能會影響合約的安全性。例如,僅僅因為沒有異常就相信以太幣的傳輸是成功的可能會導致攻擊。分析顯示,∼28%的契約並不控制call/send調用的返回值。

Gasless send.

當使用send函數將以太幣傳輸到一個合約時,可能會導致一個out-of-gas異常。我們通過一個小例子來說明發送行為,包括合同C通過函數支付發送以太幣,以及兩個收件人D1、D2。

以太坊智能合約攻擊研究綜述

執行pay有三種可能的情況:

- n

0, d = D1。send失敗,原因是out-of-gas異常,2300個單位的gas不足以執行更新狀態D1的回調函數

- n

0, d = D2。send成功.

- n = 0, d∈{D1, D2}。對於編譯器版本< 0.4.0, send會失敗,出現out-of-gas異常,因為該氣體不足以執行任何回調函數,甚至不能執行空回調函數。對於≥0.4.0的編譯器版本,無論是d = D1還是d = D2,其行為都與前兩種情況相同。

Type casts.

再次考慮如下情況:

以太坊智能合約攻擊研究綜述

pong的簽名通知編譯器c符合接口Alice。但是,編譯器只檢查接口是否聲明瞭函數ping,而沒有檢查:(i) c是契約Alice的地址;(ii) Bob聲明的接口與Alice的實際接口相匹配。類似的情況也發生在顯式類型轉換中。實際上,直接調用是在用於編譯調用的EVM字節碼指令中編譯的(除了異常管理)。因此,如果類型不匹配,在運行時可能會發生三種不同的情況:

  • 如果c不是合同地址,調用返回而不執行任何代碼。
  • 如果c是具有與Alice 's ping相同簽名的函數的任何合約的地址,則執行該函數。
  • 如果c是一個沒有匹配Alice ping簽名的函數的合約,那麼執行c的回調函數。

在很多情況下,沒有錯誤拋出,調用者無法察覺異常的產生。

Reentrancy.

事務的原子性和順序性可能會使程序員相信,當調用非遞歸函數時,它不能在終止之前重新輸入。然而,情況並非總是如此,因為回調機制可能允許攻擊者重新輸入調用方函數。這可能導致意想不到的行為,也可能導致循環調用,最終消耗所有的氣體。例如,假設契約

Bob已經在區塊鏈上了,當攻擊者發佈Mallory契約時:

以太坊智能合約攻擊研究綜述

Bob中的ping函數使用一個空簽名且沒有gas限制的調用,將2 wei發送到某個地址c。現在,假設已經使用Mallory的地址調用了ping。如前所述,call具有調用Mallory的回調函數的副作用,而回調函數又調用ping。由於變量sent尚未被設置為true, Bob再次將2wei發送給Mallory,並再次調用她的回滾,從而開始循環。此循環將在執行最終耗盡資源或達到堆棧限制時結束。

Keeping secrets.

合約中的字段可以是公共的,即每個人都可以直接讀取,也可以是私有的,即其他用戶/合約不能直接讀取。儘管如此,宣佈字段為私有並不能保證其保密性。這是因為,要設置字段的值,用戶必須向採礦者發送一個合適的事務,採礦者將在區塊鏈上發佈它。因為區塊鏈是公共的,所以每個人都可以檢查事務的內容,並推斷字段的新值。為了確保字段在特定事件發生之前保持機密,合約必須使用適當的加密技術。

Immutable bugs.

如果一個合同包含一個bug,就沒有直接的方法來修補它。因此,程序員必須預期在實現時更改或終止合同的方法——儘管這與Ethereum的原則的一致性存在爭議.

Ether lost in transfer.

發送以太幣時,必須指定接收地址,它的形式是160 bit的序列。然而,這些地址中有許多是孤立的,也就是說,它們不與任何用戶或合約相關聯。如果某個以太幣被髮送到一個孤立地址,它將永遠丟失(注意,沒有方法檢測一個地址是否是孤立地址)。由於丟失的以太無法恢復,程序員必須手動確保接收地址的正確性。

Stack size limit.

每當合約調用另一個約(甚至是通過this.f()調用自己)時,與事務關聯的調用堆棧將增長一幀。調用堆棧被限制到1024幀:當達到此限制時,進一步的調用將拋出異常。

Unpredictable state.

合約的狀態由其字段的值和餘額決定。通常,當用戶向網絡發送事務以調用某個合約時,他不能確保事務將在發送該事務時合約的相同狀態下運行。

在某些情況下,不知道事務將在何處運行可能會導致漏洞。例如,在調用可動態更新的合約時就是這種情況。

Generating randomness.

EVM字節碼的執行是確定的:在沒有不當行為的情況下,所有執行事務的礦工將得到相同結果。因此,為了模擬非確定性的選擇,許多合約(例如彩票、遊戲等)生成偽隨機數,其中初始化種子是為所有礦工唯一選擇的。然而,由於礦工控制哪些事務被放在一個塊中,以及按照什麼順序放置,惡意礦工可以嘗試構造自己的塊,從而對偽隨機數的結果產生控制。

Time constraints.

合約可以檢索塊被挖掘的時間戳;一個塊中的所有事務共享相同的時間戳。這保證了執行後與合約狀態的一致性,但也可能使合約暴露於攻擊之下,因為創建新塊的礦工可以選擇具有一定任意性的時間戳。如果一個礦業公司持有一個合同的股份,他可以通過為他正在開採的區塊選擇合適的時間戳來獲得優勢。

四、對智能合約的攻擊方式

現在我們舉例說明一些利用本節中提供的漏洞進行攻擊的攻擊——其中許多攻擊的靈感來自真實的用例

The DAO attack.

我們提供了DAO的簡化版本,它與原始版本有一些相同的漏洞。然後我們展示了利用它們的兩種攻擊。

以太坊智能合約攻擊研究綜述

SimpleDAO允許參與者根據自己的選擇捐贈以太幣來資助合同。然後合同可以收回他們的投資。

攻擊1:這種攻擊與實際DAO上使用的攻擊類似,允許對手從SimpleDAO竊取所有的以太幣。攻擊的第一步是發佈合同Mallory。

以太坊智能合約攻擊研究綜述

後,對手為Mallory捐獻了一些以太幣,並調用了馬洛裡的回調函數。回退函數調用withdraw,它將以太幣傳輸到Mallory。現在,用於此目的的函數調用具有再次調用Mallory回退的副作用(第5行),它惡意地回調withdraw。直到gas被消耗完、棧溢出或者DAO的賬戶餘額變為0。

攻擊2:同樣,我們的第二次攻擊允許對手從SimpleDAO竊取所有的以太幣,但是它只需要調用兩次回退函數。第一步是發佈Mallory2,為它提供少量以太幣(例如,1 wei)。然後,對手發動攻擊,捐出1wei給自己,然後收回。該函數提取用戶信用是否足夠的檢查,如果足夠,則將以太傳輸到Mallory2。

以太坊智能合約攻擊研究綜述

與前面的攻擊一樣,call調用Mallory2的回調函數,後者反過來調用back withdraw。由此DAO會第二次發送1 wei給Mallory2。然而,這一次回調函數什麼也不做,嵌套調用開始關閉。其結果是,Mallory2的信用被更新了兩次:第一次更新為零,第二次更新為(

- 1)wei。

King of the Ether Throne.

“King of the Ether Throne”是一款玩家為獲得“King of the Ether Throne”稱號而競爭的遊戲。如果有人想成為國王,他必須向現任國王支付一定數額的以太幣,並向合同支付一小筆費用。成為國王的獎賞單調地增加。我們討論了一個簡化版的遊戲(具有相同的漏洞),實現為合同KotET:

以太坊智能合約攻擊研究綜述

合同看上去似乎是合理的:實際上並非如此,因為不檢查send的返回代碼可能導致竊取以太幣。事實上,由於send配備了一些gas,如果國王的地址是具有昂貴回調函數的合同地址,那麼第17行上的send將失敗。在這種情況下,由於send不傳播異常,補償由合同保留。

Multi-player games.

考慮一個合同,它實現了兩個玩家之間簡單的“賠率和平手”遊戲。每個玩家選擇一個數字:如果總和是偶數,第一個玩家獲勝,否則第二個玩家獲勝。

以太坊智能合約攻擊研究綜述

合約記錄了兩名場上隊員的賭注。由於該字段是私有的,其他合約不能直接讀取它。要加入遊戲,每個玩家在調用函數play時必須傳輸1以太幣。如果傳輸的數量不同,則通過拋出異常將其發送回玩家。

對手可以進行攻擊,這總是讓她贏得比賽。為此,對手模擬第二個玩家,並等待第一個玩家下注。現在,雖然場上的玩家是私有的,但是對手可以通過查看第一個玩家加入遊戲的區塊鏈交易來推斷他的賭注。然後,對手可以通過調用適當的賭注來贏得比賽。這個攻擊利用了”keeping secrets”這個漏洞。

Rubixi.

Rubixi是一種實施龐氏騙局的合約,龐氏騙局是一種欺詐性的高收益投資項目,參與者從新來者的投資中獲利。此外,合同所有人可以收取一些費用,在投資時支付給合同。下面的攻擊允許對手從契約中竊取一些以太幣,利用“immutable bugs”漏洞。

在合同的發展過程中,它的名字從DynamicPyramid變成了Rubixi。但是,程序員忘記了相應地更改構造函數的名稱,這樣構造函數就變成了任何人都可以調用的函數(相反,構造函數在創建合約時只運行一次)。動態金字塔函數設置所有者地址;業主可以通過收取費用收回利潤。

以太坊智能合約攻擊研究綜述

GovernMental.

GovernMental是另一個有缺陷的龐氏騙局。參加計劃的人士必須在合約內加入一定量的以太幣。如果12小時內沒有人參加這個計劃,最後一個參與者將獲得合同中所有的以太幣(業主保留的費用除外)。參與者列表及其信用記錄存儲在兩個數組中。當12小時結束時,最後一名參加者可以領取款項,數值聲明如下:

我們現在給出了Governmental的一個簡化版本,它與原始合約有一些相同的漏洞:

以太坊智能合約攻擊研究綜述

攻擊1:這種攻擊利用了“exception disorder”和“stack size limit”漏洞。

攻擊者的目標是不付錢給獲勝者,這樣以太幣就可以按照合同保存,並在以後的時間由擁有者贖回。為了實現這個目標,所有者必須使第24行send失敗。他的第一步是公佈以下合同:

以太坊智能合約攻擊研究綜述

然後,所有者調用Mallory的攻擊,該攻擊開始遞歸地調用自己,使堆棧增長。當調用堆棧的深度達到1022時,Mallory調用Governmental的resetInvestment,然後以堆棧大小1023執行。此時,由於調用堆棧限制(第二個發送也失敗),第24行上的發送失敗。由於政府沒有檢查發送的返回代碼,執行繼續,重置合同狀態,然後開始另一輪。每次運行此攻擊時,合約的餘額都會增加,因為合法的贏家沒有得到報酬。

攻擊2:在本例中,攻擊者是一個礦工,他還模擬一個玩家。作為一名礦工,她可以選擇不把指向政府的交易包括在區塊中,除非是她自己的交易,以便在這輪交易中成為最後一名參與者。此外,攻擊者可以重新排序事務,使她的事務首先出現:實際上,通過先玩並選擇合適數量的以太進行投資,她可以阻止其他玩家加入該方案(第14行),從而導致最後一個玩家出現在輪中。這種攻擊利用了“不可預測狀態”漏洞。

攻擊3:同樣在這種情況下,攻擊者是一個模擬玩家的礦工。假設攻擊者設法加入了該方案。為了在一分鐘內成為最後一名選手,她可以利用時間戳的漏洞。更具體地說,攻擊者設置新塊的時間戳,使其至少在一分鐘後成為當前塊的時間戳。正如我們在討論“time constraints”漏洞時所討論的,對時間戳的選擇有一個容忍度。如果攻擊者設法發佈帶有延遲時間戳的新塊,那麼她將是輪中的最後一個玩家。

Dynamic libraries.

現在我們考慮一個合約,它可以動態地更新它的一個組件,這是一個集合上的操作庫。因此,如果開發了這些操作的更有效的實現,或者修復了錯誤,則合約可以使用庫的新版本。

以太坊智能合約攻擊研究綜述

契約SetProvider的所有者可以使用函數updateLibrary用一個新的庫地址替換庫地址。任何用戶都可以通過getSet獲取庫的地址。庫集實現了一些基本的集操作。庫是特殊的合約,例如不能有可變字段。當用戶聲明一個接口是一個庫時,通過delegate atecall直接調用它的任何函數。標記為存儲的參數通過引用傳遞。

假設Bob是SetProvider誠實用戶的合約。特別是,Bob通過getSetVersion查詢庫版本:

以太坊智能合約攻擊研究綜述

現在,假設setProvider的所有者也是一個攻擊者。她可以這樣攻擊Bob,目的是竊取他所有的以太幣。在攻擊的第一步中,對手發佈一個新的庫MaliciousSet,然後調用SetProvider的updateLibrary函數使其指向MaliciousSet。

以太坊智能合約攻擊研究綜述

注意MaliciousSet在第4行執行一個sned操作,將以太幣轉賬給攻擊者。由於Bob已經將接口集聲明為一個庫,所以對version的任何直接調用都將作為delegatecall實現,從而在Bob的環境中執行。因此,這個。第4行發送中的餘額實際上是Bob的餘額,導致發送將所有以太幣發送給對手。之後,函數將正確返回版本號。

本文的主要貢獻:

  1. 整理了目前以太坊中智能合約的主要漏洞類型
  2. 結合實例對這些漏洞所可能產生的攻擊進行了剖析
  3. 系統化的總結了智能合約漏洞的產生原因和攻擊方式

致謝

本文由南京大學軟件學院2016級本科生徐光耀翻譯轉述


分享到:


相關文章: