DAO攻擊事件回顧

博客轉(zhuǎn)移新地址
渣渣學(xué)習(xí)以太坊,參考大佬文章并綜述,綜述2333
參考列表:
區(qū)塊鏈安全 - DAO攻擊事件解析(模擬過程)
智能合約初體驗(yàn)(基礎(chǔ)概念)
從技術(shù)角度剖析針對(duì)THE DAO的攻擊手法(實(shí)際攻擊分析)

基礎(chǔ)概念

?1.以太坊兩種不同的賬戶

??外部賬戶,由人類控制(通過擁有私鑰)
??合約賬戶,由代碼控制(代碼即法律)只有合約賬戶才有fallback功能

?2.合約編程語言

??Solidity: 類JavaScript,這是以太坊推薦的旗艦語言,也是最流行的智能合約語言。具體用法參加Solidity文檔

?3.以太幣和Gas

??以太幣: ETH,以太坊中的虛擬貨幣,可以購買和使用,也可以與真實(shí)貨幣交易。以太幣的走勢圖
??Gas:相當(dāng)于手續(xù)費(fèi)。在以太坊執(zhí)行程序以保存數(shù)據(jù)都要消耗一定量的以太幣。這個(gè)機(jī)制可以控制區(qū)塊鏈中計(jì)算的數(shù)量,保證效率。

?4.智能合約與DApp

??DApp——Dimensional Assessment Of Personality Pathology
DApp流程:
??用Solidity(或其他語言)編寫智能合約(后綴為.sol)
??用solc編譯器將.sol合約編譯成EVM字節(jié)碼
??編譯好的字節(jié)碼回送給dapp前端
??前端將編譯好的智能合約部署到區(qū)塊鏈中
??區(qū)塊鏈返回智能合約地址+ABI(合約接口的二進(jìn)制表示。合約接口用JSON表示,包括變量,事件和可以調(diào)用的方法)
??前端通過Address+ABI+nonce,調(diào)用智能合約。智能合約開始處理。

基礎(chǔ)知識(shí)說明

?1.跨合約調(diào)用問題

??智能合約之間的調(diào)用本質(zhì)上是外部調(diào)用,可以使用message call或者創(chuàng)建智能合約對(duì)象的形式進(jìn)行調(diào)用。

  1. 使用message call
    比如合約1調(diào)用合約2的某個(gè)方法:
    bytes4 methodId = bytes4(keccak256("increaseAge(string,uint256)"));
    return addr.call(methodId,"jack", 1);

  2. 還原智能合約對(duì)象 如果已知合約的地址,可以通過如下方式獲取到合約對(duì)象:
    Contract1 c = Contract1(AddressOfContract1) ; c.foo() ; //跨合約調(diào)用

?2.智能合約發(fā)送ETH

??我們可以在智能合約中用代碼向某個(gè)地址(這個(gè)地址可以是人,也可以是智能合約)發(fā)送以太幣,比較常見的兩個(gè)方式是:

  1. 調(diào)用send函數(shù)
    比如:msg.sender.send(100)

  2. 使用message call
    msg.sender.call.value(100)()

??這兩個(gè)方式不同的是發(fā)送的gas數(shù)量,gas就是執(zhí)行opcode需要花費(fèi)的一種幣,稱作為gas也特別形象。當(dāng)調(diào)用send方法時(shí),只會(huì)發(fā)送一部分gas,準(zhǔn)確地來講,是2300gas,一旦gas耗盡就可能拋出異常。
??而使用message call的時(shí)候,則是發(fā)送全部的gas,執(zhí)行完之后剩余的gas會(huì)退還給發(fā)起調(diào)用的合約。
??我理解的是,發(fā)多少用多少全部發(fā)返剩余的關(guān)系。

?3.fallback函數(shù)

??智能合約中可以有唯一的一個(gè)未命名函數(shù),稱為fallback函數(shù)。該函數(shù)不能有實(shí)參,不能返回任何值。如果其他函數(shù)都不能匹配給定的函數(shù)標(biāo)識(shí)符,則執(zhí)行fallback函數(shù)。
??當(dāng)合約接收到以太幣但是不調(diào)用任何函數(shù)的時(shí)候,就會(huì)執(zhí)行fallback函數(shù)。如果一個(gè)合約接收了以太幣但是內(nèi)部沒有fallback函數(shù),那么就會(huì)拋出異常,然后將以太幣退還給發(fā)送方。
下面就是一個(gè)fallback函數(shù)的代碼示例:
?contract Sample{ function () payable{ // your code here } }

一般單純使用message call或者send函數(shù)發(fā)送以太幣給合約的時(shí)候,沒有指明調(diào)用合約的某個(gè)方法,這種情況下就會(huì)調(diào)用合約的fallback函數(shù)。

攻擊模擬

首先是存在漏洞的智能合約代碼,Bank:

contract bank

??說明:用戶可以通過addToBalance方法存入一定量的以太幣到這個(gè)智能合約,通過withdrawBalance方法可以提現(xiàn)以太坊,通過getUserBalance可以獲取到賬戶余額。
感受一下gas的代價(jià):


代碼操作費(fèi)用表

??出問題的是withdrawBalance方法,特別是在修改保存在區(qū)塊鏈的balances的代碼是放在了發(fā)送以太幣之后。 攻擊代碼如下:

image.png

??這里的deposit函數(shù)是往Bank合約中發(fā)送10wei。withdraw是通過調(diào)用Bank合約的withdrawBalance函數(shù)把以太幣提取出來。注意看這里的fallback函數(shù),這里循環(huán)調(diào)用了兩次Bank合約的withdrawBalance方法。

攻擊模擬過程

(1)假設(shè)Bank合約中有100wei,攻擊者Attack合約中有10wei

(2)Attack合約先調(diào)用deposit方法向Bank合約發(fā)送10wei

(3)之后Attack合約調(diào)用withdraw方法,從而調(diào)用了Bank的withdrawBalance方法。

(4)Bank的withdrawBalance方法發(fā)送給了Attack合約10wei

(5)Attack收到10wei之后,又會(huì)觸發(fā)調(diào)用fallback函數(shù)

(6)這時(shí),fallback函數(shù)又調(diào)用了兩次Bank合約的withdrawBalance,從而轉(zhuǎn)走了20wei

(7)之后Bank合約才修改Attack合約的balance,將其置為0

通過上面的步驟,攻擊者實(shí)際上從Bank合約轉(zhuǎn)走了30wei,Bank則損失了20wei,如果攻擊者多嵌套調(diào)用幾次withdrawBalance,完全可以將Bank合約中的以太幣全部轉(zhuǎn)走。

攻擊手法還原

節(jié)選自從技術(shù)角度剖析針對(duì)THE DAO的攻擊手法
??實(shí)際DAO攻擊的產(chǎn)生是由于攻擊者創(chuàng)建了childDAO并將Ether持續(xù)轉(zhuǎn)入其中,這是目前唯一可行的提取Ether的機(jī)制,所以關(guān)注點(diǎn)從splitDAO函數(shù)開始。
??splitDAO會(huì)創(chuàng)建childDAO(如果不存在的話),將分裂者擁有的Ether轉(zhuǎn)入childDAO中,根據(jù)白皮書的設(shè)計(jì),splitDAO的本意是要保護(hù)投票中處于弱勢地位的少數(shù)派防止他們被多數(shù)派通過投票的方式合法剝削。通過分裂出一個(gè)小規(guī)模的DAO,給予他們一個(gè)有效投票的機(jī)制,同時(shí)仍然確保他們可以獲取分裂前進(jìn)行的對(duì)外資助產(chǎn)生的可能收益。但通往地獄的道路就這樣用鮮花鋪就了。根據(jù)BLOG 2,在DAO.sol中,function splitDAO函數(shù)有這樣一行:

splitDAO代碼片段

??先回退,后歸零,和模擬案例的情況就是一樣的。來看看withdrawRewardFor函數(shù):

withdrawRewardFor

??paidOut[_account] += reward 在原來代碼里面放在payOut函數(shù)調(diào)用之后,最新github代碼中被移到了payOut之前。
??再看payOut函數(shù)調(diào)用。rewardAccount的類型是ManagedAccount,在ManagedAccount.sol中可以看到:

payOut函數(shù)

??對(duì)_recipient發(fā)出call調(diào)用,轉(zhuǎn)賬_amount個(gè)Wei,call調(diào)用默認(rèn)會(huì)使用當(dāng)前剩余的所有g(shù)as,此時(shí)call調(diào)用所執(zhí)行的代碼步驟數(shù)可能很多,基本只受攻擊者所發(fā)消息的可用的gas限制。

攻擊就很簡單了,黑客創(chuàng)建自己的黑客合約HC,該合約帶有一個(gè)匿名的fallback函數(shù)。根據(jù)solidity的規(guī)范,fallback函數(shù)將在HC收到Ether(不帶data)時(shí)自動(dòng)執(zhí)行。此fallback函數(shù)將通過遞歸觸發(fā)對(duì)THE DAO的splitDAO函數(shù)的多次調(diào)用(但不會(huì)次數(shù)太多以避免gas不夠),過程中還應(yīng)該需要記錄當(dāng)前調(diào)用深度以控制堆棧使用情況。
提交split proposal:
?—> splitDAO函數(shù)(No. 1)
?—> withdrawRewardFor函數(shù) (No. 1,黑客的dao余額和dao總量此時(shí)沒變!)
?—> payOut 函數(shù)(No. 1,向HC發(fā)送以太第一次)
?—> HC的fallback函數(shù) (No. 1)
?—->如果遞歸未達(dá)預(yù)設(shè)深度:調(diào)用splitDAO函數(shù)(No. 2)
?—> withdrawRewardFor函數(shù)(No. 2, 黑客dao余額等仍然沒變?。?br> ?—> payOut函數(shù)(No. 2, 向HC發(fā)送以太第二次)
?—> HC的fallback函數(shù) (No. 2)
?—> (繼續(xù)遞歸)

轉(zhuǎn)入childDAO的錢在一定時(shí)間后根據(jù)原合約可以提取,黑客收割韭菜的時(shí)候到了。

防范和思考

攻擊得逞的因素有二:一是dao余額扣減和Ether轉(zhuǎn)賬這兩步操作的順序有誤,二是不受限制地執(zhí)行未知代碼。
應(yīng)用代碼順序方面,應(yīng)先扣減dao的余額再轉(zhuǎn)賬Ether,因?yàn)閐ao的余額檢查作為轉(zhuǎn)賬Ether的先決條件,要求dao的余額狀況必須能夠及時(shí)反映最新狀況。在問題代碼實(shí)現(xiàn)中,盡管最深的遞歸返回并成功扣減黑客的dao余額,但此時(shí)對(duì)黑客dao余額的扣減已經(jīng)無濟(jì)于事,因?yàn)槠渖细鲗舆f歸調(diào)用中余額檢查都已成功告終,已經(jīng)不會(huì)再有機(jī)會(huì)判斷最新余額了。

不受限制地執(zhí)行未知代碼方面,雖然黑客當(dāng)前是利用了solidity提供的匿名fallback函數(shù),但這種對(duì)未知代碼的執(zhí)行原則上可以發(fā)生在更多場景下,因?yàn)楹霞s之間的消息傳遞完全類似于面向?qū)ο蟪绦蜷_發(fā)中的方法調(diào)用,而提供接口等待回調(diào)是設(shè)計(jì)模式中常見的手法,所以完全有可能執(zhí)行一個(gè)未知的普通函數(shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容