成都鏈安漏洞分析連載第十一期 :留心合約中“你的名字”—— 繼承變數覆蓋及建構函式失配
針對區塊鏈安全問題,成都鏈安科技團隊每一週都將出智慧合約安全漏洞解析連載,希望能幫助程式員寫出更加安全牢固的合約,防患於未然。
引子: 《易》曰:‘君子慎始,差若毫釐,繆以千里。’ – 《禮記·經解》
前情提要
上回書,機制依賴引數主導,礦工操縱投機取巧。
區塊引數作為區塊屬性的資料,對於挖掘區塊的礦工來說,並不具有完全的隨機性,因此將其作為隨機數生成的依據是有侷限性和危險性的。
而以太坊本身又沒有提供類似於傳統語言的rand()函式,所以隨機數生成的來源儘量來源於區塊鏈外部或者利用新的信任模型RanDAO來完成。隨機數生成作為目前以太坊遊戲的核心,在原理的定製上直接決定了專案質量和專案壽命。
本期話題
繼承變數名同實不同,建構函式名異失其義。
廣義的名字,指明瞭一個特定的人或物,將其與相似的其他人或物區別開來。我們把名字作為對一個人或物的稱呼,初次瞭解人或物,我們都會先嚐試記住他們的名字。
計算機也是如此,在區塊鏈開發,合約的編寫當中,我們給予不同函式、不同變數以不同的名字,程式才能按照編寫的意願呼叫和執行。
正確書寫名稱、正確宣告函式自然就成為智慧合約安全開發的基礎。然而,這樣的問題在區塊鏈發展到近期依然屢次出現,導致安全事件的發生,例如Morphtoken, B2X, DoubleOrNothinglmpl等多個合約中出現的Owned合約建構函式Owned大小寫問題。
我們在連載開啟之前的一篇文章( ofollow,noindex">注意!3份合約又存在Owner許可權被盜問題 )中著重講過這個問題。本期我們再次全面的概括由於名稱書寫,宣告語句,繼承中變數覆蓋等細節問題引起的巨大安全隱患。
基礎知識
Solidity中的建構函式
Solidity的使用與面向物件程式語言非常相似。建構函式(constructor)用於初始化合約物件。一個合約的建構函式的方法名與合約的名字相同,在合約建立時,對於狀態變數的資料初始化操作是通過呼叫建構函式完成的,一般包括:設定代幣名稱、識別符號、發幣、將所有代幣傳送給owner,注意此呼叫僅存在於合約部署時。
此外,合約的所有者(owner)的設定一般也放在建構函式當中。因此,建構函式相當於合約啟動的引擎。
以太坊solidity0.4.22引入了新的建構函式宣告形式constructor(),該函式引入的目的是避免程式設計人員在編寫建構函式時的命名錯誤。
Solidity中的繼承
Solidity支援多繼承和多型,其原理是程式碼拷貝。換句話說,繼承的寫法總是能夠寫成一個單獨的合約。當一個合約從多個合約繼承時,只有一個合約(子類)會被部署到鏈上,而其他的程式碼都會被拷貝到這個單一的合約當中去。
因小失大
MorphToken出現的安全漏洞只是因為在建構函式中Owned大小寫沒有注意,Owned寫成的owned,使owned函式失去建構函式僅在部署時才能呼叫的特殊性,導致任何賬戶都能呼叫,來實現更改owner變數,轉移合約所有權的惡性事件。
攻擊者在初次刺探時可能以為要黑建構函式可能相當於打李逵,結果細看之後發現對方不過是個李鬼。
繼承的情況有許多種,在合約繼承中出現的漏洞是因為:子類重新定義的變數繼承父類的函式,而且還取了同樣的名字來方便理解,而其實呼叫父類函式並不會操作子類的這個變數。
開發者認為函式操作的是子合約的變數,沒想到操作的父合約的變數。(更多的閱讀請參閱:Solidity原理(一):繼承(Inheritance)。)這個失誤還曾被當作蜜罐手段偽裝成漏洞吸引想要改變合約許可權、偷取合約內資金的玩家上鉤。
建構函式失配漏洞
上面講到如果建構函式在宣告時出錯,變成了一個普通函式,那麼,合約將存在重大安全風險。我們建構函式失配的情況分為兩大類:
一、建構函式名和合約名不一致
· 案例合約:
在這個合約中,ownerWallet和合約的函式名不一致,變成了普通的函式,導致使用者可以執行此函式,變成合約的owner,然後取出合約地址下的Ether。
· 漏洞修復:
Solidity 0.4.22提出了建構函式的新的寫法constructor() public {},如果可能,推薦這種寫法,如果版本低於0.4.22,那麼一定要著重檢測建構函式的名稱是否和合約名相同。
二、constructor宣告形式錯誤
· 案例合約
其中,owned合約的function constructor()函式的功能是將建立者地址賦予owner,用於後續的身份驗證。
但是,在使用constructor宣告建構函式時,開發者錯誤的在其前面添加了一個function關鍵字,導致其變成一個名為constructor普通的函式。任意賬戶地址都可以呼叫constructor()函式,並修改owner的值,導致合約管理許可權被盜用。
· 漏洞修復
Solidity 0.4.22 提出的新的建構函式的完整宣告形式如下,注意:constructor前無function
合約繼承中的變數覆蓋漏洞
這裡我們拿Owned合約做一個簡單的例子:
呼叫useEmergencyCode函式,只會更改TestBank合約中的owner,並不會更改Owned中的owner,onlyOwner中的owner仍是合約的部署者地址。
換句話說,TestBank合約中的owner與Owned中的owner是不一樣的兩個變數。根據上面提到的Solidity原理的解釋:對於EVM來說,每個Storage 變數都會有一個唯一標識的slot id。在這裡,雖然都叫做owner,但是從bytecode的角度來看,他們都是由不同的slot id來確定的,因此也和變數的名字沒有什麼關係。
關於Storage變數以及slot的相關知識我們也曾在 第七期儲存器區域性變數未初始化 中講到過,在此就不贅述。
失之毫釐,差之千里
正確記住對方的名字,在社交禮儀中是非常重要的一點,代表著對他人的尊重。
在合約編寫的過程中,規範書寫,正確宣告,辨析不同變數也是對程式碼的尊重,更是對工作的尊重。在做到這份尊重的同時,也能帶來專案質量和資金安全的提升,當大部分開發者都做到這一點,這個產業的良性迴圈也就慢慢啟動。
本回結語:藺相如,司馬相如,名相如,實不相如;魏無忌,長孫無忌,人無忌,爾勿無忌。
引用:
[1]: Solidity原理(一):
繼承(Inheritance):https://blog.csdn.net/Programmer_CJC/article/details/80042261
[2]: 以太坊蜜罐智慧合約分析:https://paper.seebug.org/631/
[3]: Solidity語法—以太坊智慧合約生命週期:https://www.jianshu.com/p/61e2d9e31aab
[4]: 深入理解Solidity:https://solidity-cn.readthedocs.io/zh/develop/solidity-in-depth.html
[5]: 注意!3份合約又存在Owner許可權被盜問題——低階錯誤不容忽視:https://mp.weixin.qq.com/s/xPwhanev-cjHhc104Wmpug