1. 程式人生 > >智慧合約中的“座霸” | 成都鏈安漏洞分析連載第七期 ——儲存器區域性變數未初始化

智慧合約中的“座霸” | 成都鏈安漏洞分析連載第七期 ——儲存器區域性變數未初始化

針對區塊鏈安全問題,成都鏈安科技團隊每一週都將出智慧合約安全漏洞解析連載,希望能幫助程式設計師寫出更加安全牢固的合約,防患於未然。

行身踐規矩,甘辱恥媚灶。——韓愈

前情提要

上回講到,

區塊鏈遊戲江山如畫,

安全防護未規劃,

一片殘陽西掛。

我們在上一期的區塊鏈遊戲漏洞的彙總和分析中將目前遊戲合約出現的問題與前幾期的漏洞連載分析進行了聯動,發現遊戲合約的漏洞很大一部分是在重複之前代幣合約的重大錯誤。被鮮亮外衣包裹的遊戲合約在吸引更多眼球的同時,也需要對安全問題提高重視,才能獲得更長遠的發展。

本期話題

第七回, 本地變數儲存措手不及,意外變數覆蓋易幟拔旗

最近新聞上的“座霸”事件,在社會中引起了強烈的反響,一個理應對號入座的乘車環境,在某些人不守規矩的情況下,導致買了票的乘客沒有座位,以及車廂內的秩序混亂。

成都鏈安科技的小夥伴們在辦公室對此事件各抒己見,熱烈討論,一致反對這種無理的行為。但是在討論之餘,同時也連聯想到,沒有對號入座而引起混亂這個問題,其實在智慧合約漏洞問題當中有類似的情況。

基礎小知識

大家都清楚,談到儲存,變數被儲存時都會被分配一個儲存位置。這個位置可以被理解為乘車時的座位。

在智慧合約語言 Solidity當中,存在Storage(儲存器)和 Memory(記憶體)兩個不同的概念。Storage變數是指永久儲存在區塊鏈中的變數。Memory變數是臨時的,這些變數在外部呼叫結束後會被移除。

但是Solidity目前對複雜的資料型別,比如array(陣列)和struct(結構體),在函式中作為區域性變數時,會預設儲存在Storage當中。

此外,Solidity對於狀態變數,儲存次序一般是按照出現的先後順序依次排列的。這些狀態變數的位置就相當於它們的座位。

問題出在哪

Solidity與傳統語言有個很明顯的不同,就是允許定義一個指向Storage的引用。未初始化的外部指標(引用)會預設指向起始地址,如果不加以初始化,直接進行賦值,0地址上的狀態變數就會被覆寫。

拿乘坐列車打個比方,第一批乘客上車時沒有安排相應的座位號,於是大家都是按照上車順序,從前往後坐。到了新的目的地,第二批乘客上車時沒有被指定座位號,坐剩餘的座位,又想從前往後坐,第一批乘客原本坐在座位上,現在直接被“座霸”趕走,無位可坐。

合約中的“坐霸”例項

開發中的缺一手

成都鏈安科技技術團隊翻開之前的漏洞案例冊,從上面發現這種漏洞原先體現為由於開發者疏忽遺留出的已被攻擊者利用的漏洞,例如有關安全公司發現的BancorLender相關的指標問題。

如圖所示,狀態變數agreements一開始被宣告在第一個黃色框內,進入起始位置slot 0x00。

第二個黃色框框是在函式offerToLend()中試圖宣告一個新的區域性變數agreement,但其未做初始化處理,所以起始位置slot 0x00會被新的區域性變數agreement佔據。更具體些講,從粉紅色劃線處開始的後面三項賦值操作都會覆蓋slot 0x00到slot 0x03上原有的值。

最後導致了程式碼邏輯紊亂,功能無法正常實現[1]。

蜜罐中的留一手

此外,聯絡上一期我們提到的遊戲合約,這個漏洞不出意外的在遊戲合約中也出現了,但是出現的形式是蜜罐,蜜罐我們之前也提到過,是故意放置明顯的破綻讓略懂技術的玩家以為有機可趁,但實際上更深處有合約擁有者留給自己的不公平獲利操作空間。所以我們將這種情況歸為此類漏洞的第二種具體情況。

案例來源於一個名為OpenAddressLottery的博彩合約。

這部分程式碼中的“s”被宣告但是並沒有做相應的初始化處理,所以實際上之後的賦值操作都會覆蓋原有地址上重要的值。

會代替哪些值呢?我們來看誰“坐在”最初始Storage地址上:

所謂的可預測的最終答案LuckyNumber佔據的最初始的位置,所以實際上是會被tx.gasprice*7所覆蓋的,而address owner會被msg.sender代替,但這兩個值實際上是一樣的。

由於luckyNumberOfAddress的結果以模8(二進位制)的形式計算,而被覆蓋後tx.gasprice*7真實的結果一定會大於7,所以玩家想贏是絕對不可能的[2]。

其中我們還要注意一點,第一部分程式碼中,require(msg.send==owner),表示只有合約擁有者才能呼叫這個能覆蓋原有值的函式,所以合約的蜜罐意圖非常明顯。

最終的結果也符合了我們的推斷:

合約建立者在轉出所有參與者的資金後,啟動自毀,逃之夭夭。

表現形式總結與修復建議

總結上述具體案例的情況,我們可以說:

未初始化的儲存器區域性變數可以指向合約中的狀態變數,從而導致故意(即開發人員故意將它們放在那裡進行攻擊)或無意的漏洞。

我們將一些典型的預設儲存在Storage中的變數分為結構體(struct)和陣列(Array)展示出錯誤範例[3]。

典型結構體(struct)錯誤範例:

當輸入_name="0x0000000000000000000000000000000000000000000000000000000000000001"(63個0),地址任意地址時,會覆蓋unlocked的值,使其變為true。

典型陣列(Array)錯誤範例:

當輸入elements=["0x0000000000000000000000000000000000000000000000000000000000000001"](63個0),會覆蓋frozen的值,使其變為true。

漏洞修復建議

Remix-ide等編譯器會對未初始化的儲存器區域性變數進行告警,開發人員不能忽略這個警告,在宣告變數時,應對這些儲存器區域性變數進行初始化,或者根據其使用情況,將其安排在暫時的儲存空間Memory上,避免安全漏洞。

良好的秩序,良好的心態

本期介紹的漏洞,是由於Solidity語言的預設儲存規則,以及引用未初始化變數的特殊性共同導致的。在傳統語言當中,這個情況會在編譯器當中報錯,無法通過。目前的Solidity版本(0.4.24)卻沒有進行相同嚴格的禁止,只會在編譯器中給出告警。

所以成都鏈安科技團隊在這裡針對智慧合約開發和使用兩方面再次強調:

  1. 遵守合約開發規範,縝密籌備安全防護,是我們屢次三番提到的合約開發精神,在區塊鏈這個新興的技術應用時遵守規範、周全規劃,才能更好的幫助新興技術穩步發展。

  2. 對於蓄謀欺騙大眾的投機合約,當發現破綻時,一定要提防合約建立者的蜜罐手段,多留一個心眼,避免上當受騙。

目前的區塊鏈是一個尚未成熟,有待發展的產業,追逐機遇的同時,做到冷靜思考,心態平和是成功的必備素養,請大家給變數分配位置的同時,也給自己的心態調整位置。

好了,本期的漏洞分析就到這裡,預知後事如何,請看下回:

轉賬過程紛繁複雜

安全應對各個擊破

引用:

[1]:

Solidity缺陷易使合約狀態失控 

https://zhuanlan.zhihu.com/p/41412333

[2]: 

How does this honeypot work? 

https://www.reddit.com/r/ethdev/comments/7wp363/how_does_this_honeypot_work_it_seems_like_a/

[3]: 

建構函式失控、未初始化的儲存指標

https://ethfans.org/posts/comprehensive-list-of-common-attacks-and-defense-part-7

相關閱讀: