Checkmarx安全研究團隊成員Paulo Silva和Guillaume Lopes進行了本研究。
根據官方文件資料的定義,Solidity“是一種面向合約的、為實現智能合約而創建的高級語言。”早在2014年, Gavin Wood就提出了Solidity的概念。當時,Solidity由幾個人共同負責開發,其中大多數人是以太坊平臺的核心構建者。Solidity的設計目的是為了能夠在以太坊等區塊鏈平臺上編寫智能合約。
Solidity是圍繞ECMAScript語法設計的語言,其靜態類型類似C++。Solidity的設計目的是為了讓網絡開發人員熟悉如何給inheritance、庫、用戶定義的數據類型提供支持。
Solidity的概念被提出時,它與mappings、structs、inheritance等其他同樣以EVM(比如Serpent 、LLL,Viper以及Mutan等)為目標的語言有著顯著的不同,甚至與自然語言規范NatSpec也有著本質的區別。
像其他以虛擬機(VM)為目標的編程語言一樣,Solidity使用編譯器solc編譯成字節碼。
智能合約可視為一種計算機協議,旨在根據合約規則完成某些任務。在加密貨幣的背景下,智能合約加強了交易的可追溯性和不可逆轉性,讓交易雙方擺脫了對銀行等第三方監管機構的需要。智能合約概念于1994年由Nick Szabo提出。
本文從安全角度介紹了Checkmarx安全研究團隊創建的Solidity。
如今,越來越多的人/企業將區塊鏈視為一項有前途的技術,愿意開發區塊鏈技術。在此背景下,如果他們需要創建智能合約,則必須采用代碼審查、測試和審核等軟件開發最佳做法。由于智能合約是在公開的條件下執行的,其源代碼通常可以利用,因此上述做法變得尤為關鍵。
我們很難保證軟件的使用能夠完全滿足預期要求,因此了解軟件最常出現的問題以及智能合約運行環境的可及性至關重要。軟件漏洞攻擊的目標可能不是智能合約本身,而是編譯器或虛擬機(例如EVM)。
我們將在接下來的章節中討論這些內容,提供概念驗證,證實所討論的主題。
前言
在以太坊(簡稱Eth)的環境中,智能合約是可以處理資金的腳本。Miners(多臺計算機)負責執行和認證這些智能合約,將交易(執行智能合約或支付加密貨幣)添加到公共分類賬(區塊)中。人們將多個區塊稱為區塊鏈。
Miners使用“Gas”完成作業(舉例而言,發布智能合約、運行智能合約函數,或執行賬戶間轉賬)。這種“Gas”是用以太坊支付的。
常見問題
隱私
在Solidity中,隱私的概率可能和您想象的相距甚遠。如果您習慣使用類似Java等語言面向對象編程更是如此。
私有變量并不表示他人無法查閱該內容,只是說該內容的訪問途徑只有合約。您應該記得,區塊鏈存儲在許多計算機上,讓其他人能夠看到存儲在這些“私有”變量中的內容。
請注意,私有函數不會被其他合約繼承。為了啟用私有函數繼承機制,Solidity提供了內部關鍵字。
純函數/視圖函數
要防止函數在EVM級別讀取狀態是無法實現的,但防止這些函數寫入狀態是可行的(換言之,視圖函數可在EVM級別執行,而純函數則不能)。
編譯器開始執行純函數并未讀取版本0.4.17中的狀態。
資料來源
可重入性
可重入性是一個眾所周知的計算概念,也是2016年6月導致7,000萬美元損失的黑客攻擊的原因(這起黑客攻擊事件也被稱為分散式自治組織(DAO)攻擊事件)。David Siegel在他的書中《Understanding The DAO Hack for Journalists》詳細介紹了整個事件的時間軸,并對所發生的事情進行了全面說明。
“就計算而言,如果某個計算機程序或子程序在執行過程中被中斷,然后在完成其前次調用前被再次安全引入(‘重入’),這樣的程序就稱為可重入程序。”(維基百科)
憑借通用計算模式,開發智能合約是可行的,仍然有希望。調用()函數是這次攻擊的核心,值得注意的是調用函數:
以下警告信息摘自Solidity文檔:
“與其他合約直接發生任何交互都會構成潛在危險,如果事先并不了解合約的源代碼,更容易產生問題。當前合約將控制權移交給被調用的合約,這可能會導致任何事情發生。即使被調用合約繼承了已知母合約,繼承合約只需要有一個正確的接口。然而,用戶可能任意執行該合約,從而構成危險。此外,用戶應做好準備,避免合約首次調用返回前,系統的其他合約被調用,或甚至返回到調用合約。這意味著被調用合約可通過自身的函數改變調用合約的狀態變量。舉例而言,采用以下方式編寫函數:在合約狀態變量發生變化后,對外部函數進行調用,這樣您的合約就不會受到可重入性攻擊。”
以上粗體突出顯示的內容正是網絡攻擊者如何利用可重入性的漏洞對智能合約進行攻擊的原因所在。在下面概念驗證內容和隨附視頻中,我們準備了一個可運行的示例。為了避免這種攻擊,請確保:
<address>.transfer(uint256 amount)/ <address>.send(uint256 amount) return (bool)
當前設置的Gas限值為2300,能夠有效避免智能合約因可重入性的問題受到攻擊;
溢位
由于256位虛擬機(EVM)的存在,Solidity數據類型很難處理。該語言不提供浮點表示,短于32字節的數據類型被打包放置于同一個32字節槽中。字面量0類型表示字節,而不是我們預期的整數。
限于256位,上限溢位和下限溢位是我們可以預料的。最大值為255(2?8-1或11111111)的uint8的情況可能會發生
OverflowUint8.sol源代碼或最大值為1.157920892×10 (2?256-1)的uint256
OverflowUint256.sol源代碼
盡管uint256不太可能發生溢位,人們認為它(更)安全,但它和其他數據類型一樣,也存在相同的問題。batchOverflow bu?(CVE-2018–10299)是uint256發生溢位的很好證明。
概念驗證
請參考以下銀行智能合約,該合約持續跟蹤簽約地址的余額。
仔細看看函數withdraw()函數,就會發現內部狀態更新前,上文通信常見問題>可重入部分外部調用中突出顯示的可重入模式。
現在我們需要一個惡意設計的智能合約攻擊銀行的漏洞:
盜竊
我們用Solidity開發環境預演“打劫”。要運行上述惡意程序,我們只需要一個支持docker的環境。
克隆solidity-ddenv項目,在solidity-ddenv文件夾中移動
$ git clone https://github.com/Checkmarx/solidity-ddenv && cd solidity-ddenv
我們啟動開發環境
$ ./ddenv
使用默認驅動程序創建網絡“solidityddenv _ default”
創建ganache …完成
創建truffle…完成
如果ddenv啟動正確,您將進入workspace文件夾(您可以看它運行pwd)。
我們進入銀行智能合約和盜竊智能合約所在的可重入目錄
$ cd可重入
現在,是編譯源代碼的時候了
$ ddenv truffle編譯
啟動ganache…完成
編譯./contracts/Bank.sol…
編譯./contracts/Migrations.sol…
編譯./contracts/Thief.sol…將任務圖寫入./build/contracts
并將智能合約部署到我們的開發網絡中:
現在,我們已經做好發起攻擊的準備。我們給自己的開發網絡生成一個控制臺,這樣我們就可以發出一些命令。
$ ddenv truffle控制臺——網絡開發
啟動ganache…完成
truffle(開發)>
truffle(開發) >是提示。如果您想自己運行攻擊程序,只需從下面的腳本中復制提示旁邊的命令,并將其粘貼到您之前啟動的控制臺提示中。
現在,我們執行以下操作
Checkmarx安全研究團隊開展調查研究的原因正是因為發現了上述的漏洞。我們團隊持續開展上述研究活動的目的是為了讓全球各地的企業都能在軟件安全實踐方面進行必要的變革。更多內容,關注Checkmarx微信公眾號或官網。