如何看待BEC合約出現重大漏洞,攻擊者可以生成無限代幣?

技術上的原因是怎樣的?其它項目方應當如何避免類似問題?


Ethereum Accounts, Address and Contracts?

etherscan.io

合約第257行有個整數乘法溢出

兩個數乘完是轉出方應支付的總額,但是如果轉超大額給多個地址的話,兩個數乘完了就會超出uint256的範圍,結果繞回0(或者一個很小的值),接著259行、261行按這個錯誤的值驗證轉出方的餘額並進行扣除,隨後262到265行對每個接收方增加原本指定的金額

至於防禦

INT30-C. Ensure that unsigned integer operations do not wrap?

wiki.sei.cmu.edu

最尷尬的不是這點,而是合約上來第8行就有個檢查溢出的乘法計算方法卻從來沒見哪裡有用過

怕不是哪抄來的代碼,改的時候沒意識到整數溢出也是個攻擊途徑(火狐在07年的時候吃過這個虧,具體信息見CVE-2007-0776)

CVE編號也有了:CVE-2018-10299


這個不是什麼新鮮的bug。

8年前就有過類似的事情,只能感嘆時間過得飛快,那時候不知道比特幣這回事,要不然早就發大財了。

大整數溢出漏洞其實是非常常見的,比特幣在2010年8月15號的時候被發現過類似的bug,bitcointalk論壇上面的討論至今還能找到。

https://bitcointalk.org/index.php?topic=822.0

922億個比特幣被發送到2個地址,合計刷出來1844億個。

好在及早發現並得到修補。現在看來比特幣可以說是最安全的了。

另外,遇到這種事BEC還可以通過分叉的方式回滾,原有地址投資者的持倉,過段時間可能又要死灰復燃。


解釋前先跟大夥提個醒,不是帶節奏。就是現在!可以適當地拋掉一些名不見經傳的山寨幣,並不是說因為有些幣代碼寫得次,就會又來一發漏洞歸0了,而是因為群眾的恐慌心理,炒幣說到底還是心理博弈。

原文:http://www.8btc.com/bec-bug?from=timelineisappinstalled=0

1

這個數額(5.8x10^58)已經長的連屏幕也裝不下了…

那筆操作記錄是 0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f

數量:57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000.792003956564819968 個 BEC

2

下面我來帶大家看看,黑客是如何實現的!

我們可以看到執行的方法是 batchTransfer

那這個方法是幹嘛的呢?(給指定的幾個地址,發送相同數量的代幣)

整體邏輯是

你傳幾個地址給我(receivers),然後再傳給我你要給每個人多少代幣(value)

然後你要發送的總金額 = 發送的人數* 發送的金額

然後 要求你當前的餘額大於 發送的總金額

然後扣掉你發送的總金額

然後 給receivers 裡面的每個人發送 指定的金額(value)

從邏輯上看,這邊是沒有任何問題的,你想給別人發送代幣,那麼你本身的餘額一定要大於發送的總金額的!

但是這段代碼卻犯了一個很傻的錯!

3

代碼解釋

這個方法會傳入兩個參數

_receivers

_value

_receivers 的值是個列表,裡面有兩個地址

0x0e823ffe018727585eaf5bc769fa80472f76c3d7

0xb4d30cac5124b46c2df0cf3e3e1be05f42119033

_value 的值是 8000000000000000000000000000000000000000000000000000000000000000

我們再查看代碼(如下圖)

4

我們一行一行的來解釋

uint cnt = _receivers.length;

是獲取 _receivers 裡面有幾個地址,我們從上面可以看到 參數裡面只有兩個地址,所以 cnt=2,也就是 給兩個地址發送代幣

uint256 amount = uint256(cnt) * _value;

uint256

首先 uint256(cnt) 是把cnt 轉成了 uint256類型

那麼,什麼是uint256類型?或者說uint256類型的取值範圍是多少…

uintx 類型的取值範圍是 0 到 2的x次方 -1

也就是 假如是 uint8的話

則 uint8的取值範圍是 0 到 2的8次方 -1

也就是 0 到255

那麼uint256 的取值範圍是

0 – 2的256次方-1 也就是 0 到115792089237316195423570985008687907853269984665640564039457584007913129639935

python 算 2的256次方是多少

5

那麼假如說 設置的值超過了 取值範圍怎麼辦?這種情況稱為 溢出

舉個例子來說明

因為uint256的取值太大了,所以用uint8來 舉例。。。

從上面我們已經知道了 uint8 最小是0,最大是255

那麼當我 255 + 1 的時候,結果是啥呢?結果會變成0

那麼當我 255 + 2 的時候,結果是啥呢?結果會變成1

那麼當我 0 – 1 的時候,結果是啥呢?結果會變成255

那麼當我 0 – 2 的時候,結果是啥呢?結果會變成254

那麼 我們回到上面的代碼中,

amount = uint256(cnt) * _value

則 amount = 2* _value

但是此時 _value 是16進位的,我們把他轉成 10進位

(python 16進位轉10進位)

6

可以看到 _value = 57896044618658097711785492504343953926634992332820282019728792003956564819968

那麼amount = _value*2 = 115792089237316195423570985008687907853269984665640564039457584007913129639936

可以在查看上面看到 uint256取值範圍最大為 115792089237316195423570985008687907853269984665640564039457584007913129639935

此時,amout已經超過了最大值,溢出 則 amount = 0

下一行代碼 require(cnt &> 0 cnt &<= 20); require 語句是表示該語句一定要是正確的,也就是 cnt 必須大於0 且 小於等於20 我們的cnt等於2,通過! require(_value &> 0 balances[msg.sender] &>= amount);

這句要求 value 大於0,我們的value是大於0 的 且,當前用戶擁有的代幣餘額大於等於 amount,因為amount等於0,所以 就算你一個代幣沒有,也是滿足的!

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

這句是當前用戶的餘額 – amount

當前amount 是0,所以當前用戶代幣的餘額沒有變動

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

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

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

}

這句是遍歷 _receivers中的地址, 對每個地址做以下操作

balances[_receivers[i]] = balances[_receivers[i]].add(_value); _receivers中的地址 的餘額 = 原本餘額+value

所以 _receivers 中地址的餘額 則加了57896044618658097711785492504343953926634992332820282019728792003956564819968 個代幣!!!

Transfer(msg.sender, _receivers[i], _value); } 這句則只是把贈送代幣的記錄存下來!!!

總結1

就一個簡單的溢出漏洞,導致BEC代幣的市值接近歸0

那麼,開發者有沒有考慮到溢出問題呢?

其實他考慮了,

7

可以看如上截圖

除了amount的計算外, 其他的給用戶轉錢 都用了safeMath 的方法(sub,add)

那麼 為啥就偏偏這一句沒有用safeMath的方法呢。。。

這就要用寫代碼的人了。。。

啥是safeMath

8

safeMath 是為了計算安全 而寫的一個library

我們看看他幹了啥?為啥能保證計算安全.

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

uint256 c = a * b;

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

return c;

}

如上面的乘法. 他在計算後,用assert 驗證了下結果是否正確!

如果在上面計算 amount的時候,用了 mul的話, 則 c / a == b 也就是 驗證 amount / cnt == _value

這句會執行報錯的,因為 0 / cnt 不等於 _value

所以程序會報錯!

也就不會發生溢出了…

那麼 還有一個小問題,這裡的 assert 好 require 好像是乾的同一件事

都是為了驗證 某條語句是否正確!

那麼他倆有啥區別呢?

用了assert的話,則程序的gas limit 會消耗完畢

而require的話,則只是消耗掉當前執行的gas

總結2

那麼 我們如何避免這種問題呢?

我個人看法是

只要涉及到計算,一定要用safeMath

代碼一定要測試!

代碼一定要review!

必要時,要請專門做代碼審計的公司來 測試代碼

這件事後需要如何處理呢?

目前,該方法已經暫停了(還好可以暫停)所以看過文章的朋友 不要去測試了…


謝邀。問我那就問錯人了,我已經慢慢變成eth黑了……

eth本來就是滿地的坑。解決方法兩個:

1)棄用。

2)不棄用,花錢買教訓。

現在幣圈涌動著一股暗流——到底是敏捷迭代還是嚴格保障安全的前提下進行不同版本的發布。


技術分析前面寫的很詳細;實際上eth的智能合約虛擬機本身也存在溢出的問題,所以需要個額外的safemath庫。

新一代區塊鏈Aeternity則嘗試從智能合約虛擬機本身來解決這類溢出問題;介紹直接copy過來:

FTWVM

The Functional Typed Warded Virtual Machine is used to efficiently and safely execute contracts written in the Sophia language.

The FTWVM machine is Warded. This means that all arithmetic operations are checked for overflow and underflow if applicable. The machine has a signed arbitrary large number type so for most operations there should be no overflow.

The FTWVM machine is typed. Every instruction and every instruction argument has a type. All argument types are checked when a contract is called. All data is tagged with the types.

The FTWVM machine is functional. The machine supports the execution of functional languages with tagged data, automatic memory management and garbage collection.

The FTWVM is a virtual machine the instructions are on a higher level than pure memory references.


  1. 乘法運算溢出
  2. 使用SafeMath Library OpenZeppelin/zeppelin-solidity


好像是溢出。圍觀天價交易https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f


技術方面我不懂,所以想從另一個角度說說我的看法。BEC這次出事,讓我想到區塊鏈技術的去中心化雖然有它的優勢,但是否也意味著出了問題沒人擔責?BEC的幣值因為這起事件狂跌,那些花了真金白銀的人找誰彌補損失?

我記得在看《圖說區塊鏈》這本書的時候,作者一直強調比特幣去中心化的好處。但是你有沒有想過真的都只有好處嗎?你用支付寶錢少了,至少可以找支付寶賠。

基於區塊鏈技術的各類錢幣,本質上還是一種貨幣。如果你這麼一想,那BEC作為一種貨幣,和你錢包里、手機里的人民幣是一樣的,你能接受因為有人印假鈔,它的價值一下子少一半嗎?少了一半你要不要找人民銀行鬧去?就算你不鬧人民銀行也弄死他啊。但是換成區塊鏈的幣,很多人就糊塗了。


推薦閱讀:

TAG:區塊鏈Blockchain | 美圖公司 | 以太坊 | 智能合約 | 首次代幣發行ICO |