安全事件

最近,智能合約漏洞很火吟温。

讓我們再來看一下4月22日BeautyChain(BEC)的智能合約中一個毀滅性的漏洞序仙。

BeautyChain團隊宣布,BEC代幣在4月22日出現(xiàn)異常鲁豪。攻擊者通過智能合約漏洞成功轉(zhuǎn)賬了10^58 BEC到兩個指定的地址潘悼。

具體交易詳情https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f

攻擊者到底是怎么攻擊的?為什么能轉(zhuǎn)賬這么大的BEC爬橡?

智能合約代碼

首先我們來看BEC轉(zhuǎn)賬的智能合約代碼

function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {

? ?uint cnt = _receivers.length;

? ?uint256 amount = uint256(cnt) * _value;

? ?require(cnt > 0 && cnt <= 20);

? ?require(_value > 0 && balances[msg.sender] >= amount);

? ?balances[msg.sender] = balances[msg.sender].sub(amount);

? ?for (uint i = 0; i < cnt; i++) {

? ? ? ?balances[_receivers[i]] = balances[_receivers[i]].add(_value);

? ? ? ?Transfer(msg.sender, _receivers[i], _value);

? ?}

? ?return true;

}

以上的代碼是Solidity語言治唤,是一門面向合約的,為實現(xiàn)智能合約而創(chuàng)建的高級編程語言糙申。

變量類型

在讀代碼之前我們先來簡單了解一下以下幾個變量類型(Solidity):

address

160位的值宾添,且不允許任何算數(shù)操作。

unit 8

8位無符號整數(shù),范圍是0到2^8減1?(0-255)

unit256

256位無符號整數(shù)缕陕,范圍是0到2^256減1

(0-115792089237316195423570985008687907853269984665640564039457584007913129639935)

敲黑板粱锐,玩手機的同學注意看這里,這里是考試重點哦

那么扛邑,我們請看如下神奇的化學反應(yīng)

定義變量uint a

a的取值范圍是0到255

當a=255怜浅,我們對a加 1,a會變成 0蔬崩。

當a=255恶座,我們對a加 2,a會變成 1沥阳。

當a=0跨琳,我們對a減 1,a會變成 255桐罕。

當a=0脉让,我們對a減 2,a會變成 255冈绊。

a的值超過了它實際的取值范圍侠鳄,然后會得出后面的值,這種情況叫溢出死宣。

代碼解讀

知道了這幾個變量類型伟恶,下面我們一行一行的來讀這段代碼。

第一行

function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool)

函數(shù)有兩個參數(shù):

_receivers —————轉(zhuǎn)賬接收人毅该,address類型的變量數(shù)組博秫,是一個160位的值。

_value ———————-轉(zhuǎn)賬數(shù)量眶掌,uint256的狀態(tài)變量挡育,256位的無符號整數(shù)。

定義函數(shù)batchTransfer朴爬,功能主要是實現(xiàn)轉(zhuǎn)賬即寒,接收兩個參數(shù),定義了參數(shù)的取值范圍召噩。

第二行

uint cnt = _receivers.length;

計算接收人地址對應(yīng)地址數(shù)組的長度母赵,即轉(zhuǎn)賬給多少人。

第三行

uint256 amount = uint256(cnt) * _value;

把unit類型的cnt參數(shù)值強制轉(zhuǎn)換為uint256然后乘以轉(zhuǎn)賬數(shù)量_value 并賦值給uint256類型的amount變量具滴。

第四行

require(cnt > 0 && cnt <= 20);

require函數(shù)

require的入?yún)⑴卸?b>false凹嘲,則終止函數(shù),恢復所有對狀態(tài)和以太幣賬戶的變動构韵,并且也不會消耗 gas 周蹭。

判斷cnt是否大于0且cnt是否小于等于20

第五行

require(_value > 0 && balances[msg.sender] >= amount);

參數(shù)解讀:

_value—————————————轉(zhuǎn)賬數(shù)量

balances[msg.sender]————-轉(zhuǎn)賬人余額

amount————————————轉(zhuǎn)賬總數(shù)量

判斷_value是否大于0且轉(zhuǎn)賬人的余額balances[msg.sender]大于等于轉(zhuǎn)賬總金額amount

第六行

balances[msg.sender] = balances[msg.sender].sub(amount);

計算轉(zhuǎn)賬人的余額趋艘,使用當前余額balances[msg.sender]減去轉(zhuǎn)賬總數(shù)量

第七行

for (uint i = 0; i < cnt; i++) {

這里是一個循環(huán),循環(huán)次數(shù)為cnt(遍歷轉(zhuǎn)賬地址)

第八行

balances[_receivers[i]] = balances[_receivers[i]].add(_value);

當i有具體的值時凶朗,balances[_receivers[i]]表示轉(zhuǎn)賬接收人瓷胧,這里是表示轉(zhuǎn)賬人給轉(zhuǎn)賬接收人_value數(shù)量的幣。

第九行

Transfer(msg.sender, _receivers[i], _value);

保存轉(zhuǎn)賬記錄

第十行

return true;

函數(shù)范圍為True

代碼流程

OK俱尼,我們讀了完整的代碼抖单,接下來請看一個流程圖

函數(shù)的流程是這樣,那么攻擊者到底是怎么攻擊的呢遇八?他為什么這么秀?同樣都是九年義務(wù)教育……

攻擊過程

其實耍休,他只是細心了一點刃永,所使用的攻擊方法并不高明啊,你且聽我慢慢道來羊精,注意看斯够,別走神啊。

交易詳情

我們首先看這筆詳細的交易:

好了喧锦,我們從圖可以看到

轉(zhuǎn)賬接收人有兩個地址读规,即balances[_receivers]:

000000000000000000000000b4d30cac5124b46c2df0cf3e3e1be05f42119033

0000000000000000000000000e823ffe018727585eaf5bc769fa80472f76c3d7

轉(zhuǎn)賬數(shù)量為_value:

8000000000000000000000000000000000000000000000000000000000000000(十六進制)

轉(zhuǎn)10進制為

57896044618658097711785492504343953926634992332820282019728792003956564819968

實戰(zhàn)

OK,接下來我們來走函數(shù)流程

第一行

function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool)

正常執(zhí)行

第二行

uint cnt = _receivers.length

由于這里有兩個轉(zhuǎn)賬接收人地址燃少,address數(shù)組長度為2束亏,所以cnt為2,類型為uint

第三行

uint256 amount = uint256(cnt) * _value;

_value=57896044618658097711785492504343953926634992332820282019728792003956564819968

cnt=2

兩者相乘得到amount阵具,類型為uint256

即amount=115792089237316195423570985008687907853269984665640564039457584007913129639936

考試重點用上了碍遍,記不住的同學去前面看看。

amount的類型為uint256阳液,那么按照理論怕敬,它的最大取值是0到2^256減1,即

115792089237316195423570985008687907853269984665640564039457584007913129639935

所以A泵蟆6颉!

amount瞬間從115792089237316195423570985008687907853269984665640564039457584007913129639936變成了0?

第三行得到的結(jié)果:amount=0

第四行

require(cnt > 0 && cnt <= 20);

cnt=2鹰溜,2肯定大于0虽填,2當然也小于等于20

所以這個條件成立,require函數(shù)返回值為True奉狈。

第五行

require(_value > 0 && balances[msg.sender] >= amount);

_value=57896044618658097711785492504343953926634992332820282019728792003956564819968

_value肯定是大于0卤唉,轉(zhuǎn)賬人的余額balances[msg.sender]肯定是大于等于0的。

所以這個條件同樣成立仁期,require函數(shù)返回值為True桑驱。

第六行

balances[msg.sender] = balances[msg.sender].sub(amount);

前面的條件都成立竭恬,那么代碼會執(zhí)行到這。

這行代碼是求轉(zhuǎn)賬人轉(zhuǎn)完賬以后剩下的余額熬的,amount為0 痊硕,那么轉(zhuǎn)賬人的余額其實沒變!Q嚎颉岔绸!

第七行

for (uint i = 0; i < cnt; i++)

cnt=2,該行代碼表示執(zhí)行兩次后面的操作

第八行

balances[_receivers[i]] = balances[_receivers[i]].add(_value);

i=0時橡伞,轉(zhuǎn)賬接收人balances[_receivers[0]]的余額加_value

i=1時盒揉,轉(zhuǎn)賬接收人balances[_receivers[1]]的余額加_value

看到這里其實我們就很明白了吧。

攻擊者給以下兩個轉(zhuǎn)賬接收人

000000000000000000000000b4d30cac5124b46c2df0cf3e3e1be05f42119033

0000000000000000000000000e823ffe018727585eaf5bc769fa80472f76c3d7

轉(zhuǎn)了

_value=57896044618658097711785492504343953926634992332820282019728792003956564819968個幣

更可惡的是兑徘,攻擊者執(zhí)行完這個操作刚盈,轉(zhuǎn)賬人的余額根本沒變,看代碼第六行的執(zhí)行結(jié)果挂脑。

第九行

Transfer(msg.sender, _receivers[i], _value);

這里只是把上面兩個轉(zhuǎn)賬記錄保存藕漱。

第十行

return true;

函數(shù)返回為True

小結(jié)

千里之堤毀于蟻穴!

就一個溢出漏洞崭闲,導致BEC的市值瞬間變0

這么傻的問題肋联,寫代碼的人是寫睡著了嗎?刁俭?橄仍?

不,其實他根本沒睡著啊薄翅,人家還用了SafeMath里的add函數(shù)和sub函數(shù)

我們看看什么是SafeMath函數(shù)

/**

* @title SafeMath

* @dev Math operations with safety checks that throw on error

*/

library SafeMath {

?function mul(uint256 a, uint256 b) internal constant returns (uint256) {

? ?uint256 c = a * b;

? ?assert(a == 0 || c / a == b);

? ?return c;

?}

?function div(uint256 a, uint256 b) internal constant returns (uint256) {

? ?// assert(b > 0); // Solidity automatically throws when dividing by 0

? ?uint256 c = a / b;

? ?// assert(a == b * c + a % b); // There is no case in which this doesn't hold

? ?return c;

?}

?function sub(uint256 a, uint256 b) internal constant returns (uint256) {

? ?assert(b <= a);

? ?return a - b;

?}

?function add(uint256 a, uint256 b) internal constant returns (uint256) {

? ?uint256 c = a + b;

? ?assert(c >= a);

? ?return c;

?}

}

注意看這一段

function mul(uint256 a, uint256 b) internal constant returns (uint256) {

? ?uint256 c = a * b;

? ?assert(a == 0 || c / a == b);

? ?return c;

?}

這里是乘法計算沙兰,計算出乘法的結(jié)果后會用assert函數(shù)去驗證結(jié)果是否正確。

回到我們前面的dis第三行代碼執(zhí)行后的結(jié)果

_value=57896044618658097711785492504343953926634992332820282019728792003956564819968

cnt=2

兩者相乘得到amount翘魄,類型為uint256

由于溢出鼎天,amount=0

賦值給mul函數(shù)即

c=amount,而amount=0暑竟,則c=0

a=cnt, 而cnt=2斋射,則a=2

b=_value

得出

b=57896044618658097711785492504343953926634992332820282019728792003956564819968

那么c/a==b這個式子不成立,導致assert函數(shù)執(zhí)行會報錯但荤,assert報錯罗岖,那么就不會執(zhí)行后面的代碼,也就不會發(fā)生溢出腹躁。

也就是說桑包,寫這段代碼的人,加減法他用了SafeMath里面的add函數(shù)和sub函數(shù)纺非,但是卻沒有用里面的乘法函數(shù)mul

如何防止這樣的漏洞哑了?

肯定是要用SafeMath函數(shù)啊赘方,你加減法用了,乘法不用弱左,你咋這么皮呢

代碼上線前要做代碼審計啊親窄陡,強調(diào)多少遍了!

合理使用變量類型拆火,了解清楚變量的范圍

一定要考慮到溢出跳夭!一定要考慮到溢出!一定要考慮到溢出们镜!重要的事情說三遍币叹。

原文地址:https://mp.weixin.qq.com/s?__biz=MzU0NDg1MjQ0Nw==&mid=2247483672&idx=1&sn=2d7aad9e1645c7218161f8561664c102&chksm=fb7491a8cc0318be802db628097bf3da59cccf979fbc4af498e0209abb5bee3b17b9ccfd0f5b&mpshare=1&scene=1&srcid=0531lVTfE6DeC04XZTF6c8qu#rd

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市模狭,隨后出現(xiàn)的幾起案子套硼,更是在濱河造成了極大的恐慌,老刑警劉巖胞皱,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異九妈,居然都是意外死亡反砌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門萌朱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宴树,“玉大人,你說我怎么就攤上這事晶疼【票幔” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵翠霍,是天一觀的道長锭吨。 經(jīng)常有香客問我,道長寒匙,這世上最難降的妖魔是什么零如? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮锄弱,結(jié)果婚禮上考蕾,老公的妹妹穿的比我還像新娘。我一直安慰自己会宪,他們只是感情好肖卧,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著掸鹅,像睡著了一般塞帐。 火紅的嫁衣襯著肌膚如雪拦赠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天壁榕,我揣著相機與錄音矛紫,去河邊找鬼。 笑死牌里,一個胖子當著我的面吹牛颊咬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播牡辽,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼喳篇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了态辛?” 一聲冷哼從身側(cè)響起麸澜,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奏黑,沒想到半個月后炊邦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡熟史,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年馁害,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹂匹。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡碘菜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出限寞,到底是詐尸還是另有隱情忍啸,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布履植,位于F島的核電站计雌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏静尼。R本人自食惡果不足惜白粉,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鼠渺。 院中可真熱鬧鸭巴,春花似錦、人聲如沸拦盹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽普舆。三九已至恬口,卻和暖如春校读,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背祖能。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工歉秫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人养铸。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓雁芙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钞螟。 傳聞我的和親對象是個殘疾皇子兔甘,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容