一夜回到解放前?溢出漏洞類型全面分析區塊鏈
溢出漏洞類型主要分為乘法溢出、加法溢出、減法溢出。
4月發生的BEC事件以及SMT事件已經沉淀一段時間了,具體的情況也被多方媒體所報道,相關的漏洞根源問題也有很多大神團隊的分析和指正,成都鏈安科技團隊將各種已經發生或可能發生的類似溢出漏洞原理進行整理,再次將全方位的原理分析與大家分享。
事件回顧
2018年4月22日,黑客對BEC智能合約發起攻擊,憑空取出57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000.792003956564819968 個BEC代幣并在市場上進行拋售,BEC隨即急劇貶值,價值幾乎為0,該市場瞬間土崩瓦解[1]。
2018年4月25日,SMT項目方發現其交易存在異常,黑客利用其函數漏洞創造了65,133,050,195,990,400,000,000,000,000,000,000,000,000,000,000,000,000,000,000
50,659,039,041,325,800,000,000,000,000,000,000,000,000,000,000,000,000,000,000
的SMT幣,火幣Pro隨即暫停了所有幣種的充值提取業務[2]。
讓我們一起以沉痛的心情緬懷以上一夜歸零的代幣
僅僅在BEC事件過去后的12小時內,就有多達12個項目的智能合約存在類似的整數溢出類型的漏洞,黑客可以利用這一漏洞轉賬“無中生有”的巨大數量代幣,也就是我們所說的“增發”。
代幣增發為什么就會貶值
人們常說“物以稀為貴”,意思是事物因稀少而且有益,從而顯得珍貴[3]。
黃金被選為最早時期全世界公認的貨幣的原因就是因為它稀缺,地球上儲量有限,易分割,屬性穩定等特點[4]。
中國人首先發明了“交子”,來代替不便交易的黃金。
所以就出現了金本位的概念,很多國家發行自己貨幣的時候都是按照其黃金儲量來發行,并不是想發多少就發多少。
試想一下如果憑空出現了一顆黃金做的大隕石,撞擊地球后黃金撒了一地,黃金還會值錢嗎?
所以當一個代幣的數量在有了額定發行總量之后,突然肆意的增加,必然會導致代幣的貶值,甚至失去在市場上流通的意義。這就是“代幣增發”的不良后果。
整數溢出漏洞分析
前面提到,黑客是利用整數溢出漏洞繞過了轉賬數額的相關規則,進而增發代幣。
那什么是整數溢出呢?為什么能用整數溢出來實現增發來秀一波空手套白狼的操作?
我來舉個例子
比如有一個國家的人不會復雜的數學運算,只會從0數到9,每次數到9之后又從0開始數,最后以數到的數作為結果。大家都這樣平安無事的生活著,但有一天,有一個從別的國家來的小黑,他發現了這個問題,于是他去金庫拿金錠,拿出來的個數超過了9個,于是金庫管理員幫他數,數到9之后又從0開始了,最后結算發現他取出來的結果是0個金錠,但實際上他已經把金庫里的金錠幾乎都取完了。
黑客利用類似的機制憑空向一個賬戶中轉賬了超級大數額的代幣,而合約中的邏輯只要求他花費很小的代價。
成都鏈安科技審計組對過往代幣增發事件漏洞類型進行整理,概括出整型溢出類型漏洞的全面分析。
以太坊虛擬機(EVM)為整數指定固定大小的數據類型。這意味著一個整型變量只能有一定范圍的數字表示。例如,一個 uint8 ,只能存儲在范圍 [0,255] 的數字。試圖存儲 256 到一個 uint8 將變成 0。不加注意的話,只要沒有檢查用戶輸入又執行計算,導致數字超出存儲它們的數據類型允許的范圍,Solidity 中的變量就可以被用來組織攻擊。
整數溢出的類型包括乘法溢出,加法溢出,減法溢出三種,
鏈安科技團隊對BEC事件進行分析后,發現可將其漏洞歸類于乘法溢出,原理如下:
乘法溢出
案例(CVE-2018-10299)
上述合約代碼中,存在漏洞的代碼為uint256 amount = uint256(cnt) * _value;,計算轉出總額度amount未使用SafeMath也未對溢出進行檢查,直接將轉賬地址數量乘以轉賬額度,如果輸入極大的_value,那么amount計算結果就可能產生溢出,導致代幣增發。
在Remix-ide中測試如下:
1、部署合約;
2、調用batchTransfer函數,向batchTransfer函數傳入地址數組["0xb4D30Cac5124b46C2Df0CF3e3e1Be05f42119033","0x0e823fFE018727585EaF5Bc769Fa80472F76C3d7"],以及_value"0x8000000000000000000000000000000000000000000000000000000000000000"即2*255,使得amount=2\*255 * 2,超出uint256類型的范圍[0,2**256-1],溢出為0,發送者賬戶余額不減少,并且,本例中,發送者的代幣可以為零,實現"無中生有"。
3、查看余額:
而針對SMT事件進行分析后,發現其漏洞屬于加法溢出類型,其原理如下:
加法溢出
案例
上述合約代碼中,mintToken函數的功能是owner向指定賬戶增發mintedAmount數量的代幣,但是在對balanceOf[target]與totalSupply進行加法操作未做溢出檢查,導致其可能存在溢出,并且,通過溢出,惡意owner可以任意增減target賬戶的余額,或者增發實際遠遠超過totalSupply的代幣。
在Remix-ide中測試如下:
1、部署合約;
2、向target預先轉一部分代幣,模擬目標賬戶中已有的代幣:調用transfer函數,傳入target地址:
0x14723a09acff6d2a60dcdf7aa4aff308fddc160c,以及轉賬額度,比如2000000000000000000(2 * 10**uint256(decimals));
3、如果owner想控制target的余額減半,那么,他只需要向target增發2*256-balanceOf[target] 10\*18=0xfffffffffffffffffffffffffffffffffffffffffffffffff21f494c589c0000,現在調用mintToken函數,向target地址轉入上述數量的代幣:
4、查詢target余額:
另外還有減法溢出的操作,雖然未出現相關的漏洞攻擊事件,我們也提供了相關的原理分析:
減法溢出
案例
上述合約代碼中,distribute函數的功能是從owner賬戶向指定的地址列表轉入2000 * 10**8代幣,但是在對balances[owner]的計算中未使用SafeMath,也未判斷owner賬戶是否有足夠的代幣,當轉出代幣總量大于owner賬戶余額的時候,balances[owner]產生減法溢出,變成一個極大值。
在Remix-ide中測試如下:
部署合約
調用distribute函數,傳入地址數組:
["0x14723a09acff6d2a60dcdf7aa4aff308fddc160c","0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db"]
owner分別向這兩個地址發送2000 * 10**8代幣,超過owner余額,balances[owner]產生減法溢出;
查詢owner賬戶余額,等于2**256-2000*10**8:
黑客雖然無孔不入,但鏈安科技團隊對具有此類漏洞的合約進行綜合性調研后,提出了“亡羊補牢”的修復措施:
漏洞修復
OpenZeppelin提供了一套很好的SafeMath庫,使用SafeMath庫函數能夠有效避免溢出漏洞,SafeMath庫源碼如下:
子曰:吾日三省吾身
我們從這些慘痛的教訓中能總結出:
基于ERC20協議編寫的這些智能合約給予開發者的權力過大了,這些溢出問題本應該放在底層檢測。
編寫合約的開發者沒有以嚴謹敬業的精神去遵守開發規范,使用SafeMath去做相關功能,或者進行溢出測試,在這里提出批評。
沒有嚴謹的邏輯,單單憑借創造力并不是都能出成果,還是要穩中求勝,能使用庫就使用庫。
所以,鏈安科技團隊建議,為了避免程序結果中產生溢出,破壞智能合約執行邏輯,建議開發者在所有數學運算中都使用SafeMath(敲黑板)。
引用:
[1]:http://bc.jrj.com.cn/2018/04/26132824455908.shtml
[2]: https://zhuanlan.zhihu.com/p/36116810
[3]: https://baike.baidu.com/item/物以稀為貴
[4]: http://www.woshipm.com/blockchain/945232.html
1.TMT觀察網遵循行業規范,任何轉載的稿件都會明確標注作者和來源;
2.TMT觀察網的原創文章,請轉載時務必注明文章作者和"來源:TMT觀察網",不尊重原創的行為TMT觀察網或將追究責任;
3.作者投稿可能會經TMT觀察網編輯修改或補充。