首先需要說明的一點是,這個世界上沒有絕對安全的技術。在區塊鍊發展的十年裡,各種基于區塊鍊的數字貨币引發的安全事故層出不窮,這些安全威脅主要來源有三個方面:
- 自身安全機制的問題,類似智能合約。
- 生态安全問題,交易所,礦池,網站等等。
- 使用者安全問題,包括個人賬号密碼的洩露,被釣魚等。
作為普通的開發人員或者有一定程式設計知識的從業人員,我們首先應該確定的是自身安全機制沒有問題,當然這個“沒有問題”是一個相對的概念。智能合約的安全為什麼這麼重要,這很大原因在于智能合約程式設計和傳統程式設計的巨大差別:
- 智能合約本身開發簡單,但是卻能夠存儲幾千萬到幾十億的的資産。
- 智能合約部署的過程是一次共識的過程,如果部署以後發現了安全問題,不能通過傳統的打更新檔或者更新的方式來避免。必須在設計和編碼的過程中處理好這些容錯和異常終止邏輯。
- 智能合約的代碼都是開放的,多任何人可見。這其中就包括了一些不懷好意的黑客,沒有傳統開發過程中的加密,通路控制。
本系列希望通過對過往發生的一些安全事故的回顧,來提醒或者說警醒各位開發者,在開發的過程中,即便不能做到百分百安全,那麼起碼能做到“吸取前人的教訓”,避免已經發生過的安全事故再次發生。
本文介紹的是對以太坊影響深遠的The Dao 智能合約漏洞事件。
事件介紹
The Dao 是一個去中心化的自治風險投資基金,通過釋出的智能合約來募集資金,參與者可以通過投票的方式來投資以太坊上的應用,如果盈利,參與者就能獲得回報。2016年6月17日,一名黑客發現了The Dao募資合約的漏洞,使得他可以無限的從合約中轉出資金,短短幾小時,360萬的以太币被轉出。這件事對以太坊的發展産生了巨大的影響,最後為了彌補使用者的損失V神智能采用軟分叉的方式,即所有通過這個The Dao的合約來減少新增使用者餘額的方式都被視為無效。
漏洞原因
首先請讀者看一下合約中的代碼,這端代碼的業務邏輯是:如果使用者不同意其他使用者的投票,可以選擇分裂出去。簡單的說就是使用者拿錢給基金會投資,中間使用者如果反悔可以随時退錢。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | |
上面的代碼在了解業務很容易明白:
使用者提出分裂--》合約計算應該退給使用者的金額--》調用call函數發送金額給使用者--》使用者的賬戶餘額歸為0,即先是調用splitDAO,splitDao中調用withdrawRewardFor,withdrawRewardFor中調用payOut執行轉賬。
乍一看沒什麼問題,講述黑客的攻擊手段之前,回顧一下solidity程式設計中的知識點:如果call函數的調用結果是true就一定是執行成功的嗎?答案是NO,因為有可能是執行了回調函數。當調用call.value的時候,會把所有的gas發送到合約位址上并執行預設函數。是以這個預設函數将會有足夠的gas執行任何操作,包括重新調用原合約的接口。本次攻擊的黑客正式利用了這一點。
攻擊手段
- 黑客先是通過自己建立了一個合約Child Dao,這個合約擁有一個回調函數,這個函數的作用就是去調用The Dao中的splitDao。
- 黑客送出了splitDao,位址是Child Dao的位址,當然在此之前的操作都是合法的操作,滿足The Dao定義的調用splitDao的條件。
- 結合上面的代碼,你會發現,開發者的代碼先是在函數withdrawRewardFor中把金額退還給了使用者,然後在退出函數之後将使用者的餘額置為0。那麼如果攻擊者在withdrawRewardFor和餘額置空之間在此調用withdrawRewardFor,将會再次向攻擊者送出的位址轉移賬戶金額。結合剛才介紹的call函數知識點,聰明的讀者應該能夠想到攻擊的原理了。黑客利用了call函數的機制,在合約中再次調用轉賬申請,由于上一次轉賬申請的餘額還沒有更新,是以第二次也會成功。相當于在循環中的重複調用自己,程式設計中的遞歸。
如何防範
其實The Dao的開發者的漏洞代碼在傳統的程式設計中沒有任何問題,傳統程式設計為了應對事務處理的結果,往往在轉賬之後進行餘額的更新,因為有可能因為網絡等原因導緻轉賬不成功,如果程式提前把使用者的賬戶餘額置為0則容易引發資料丢失的問題。本次The Dao事件的代碼修複可以從多方面來考慮:
- 調整代碼順序,在轉賬之前執行餘額減扣。
- 避免不可控的函數調用,黑客利用call函數fallback的調用機制來攻擊,這個場景其實在很多别的攻擊事件中也可能發生,後面介紹的DOS攻擊中黑客也利用了這一點。一方面應該避免這種方式調用,其實還應該避免在合約中直接使用轉賬操作,可以在設計的時候提供一個轉賬mapping,每個使用者可以提現金額的多少對應其中的key value,讓使用者主動去操作這個接口完成調用。因為合約主動調用本身就存在安全隐患,合約的權限大于所有人。
|