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