1. 程式人生 > >解析|累計造成千萬損失的EOS智慧合約漏洞,該如何避免?

解析|累計造成千萬損失的EOS智慧合約漏洞,該如何避免?

自六月份EOS主網之後,Joe老師親身經歷和見證過EOS DApp一路走來的艱辛。被黑客以各種各樣方式攻擊過的DAPP,總的損失規模至少上千萬了。一直以來,都想對所有被攻擊過的DAPP,以及攻擊方式做一個彙總。因為前人花大的代價踩的坑,得到的教訓,應該總結好,避免後人再犯。因此上週日,一塊鏈習·區塊鏈技術100講—第3講由資深全棧工程師,區塊鏈DApp開發老司機Joe老師向大家傾心分享——如何避免累計造成千萬損失的EOS智慧合約漏洞。以下是老師分享的詳細內容 。

這些造成損失的漏洞,可以歸為合約程式碼漏洞,和隨機數演算法使用不當的兩種問題。

合約程式碼漏洞

1.、程式碼溢位類漏洞


(狼人殺案例)這個就不多說了,漏洞細節,就是當時eos版本中的 asset 類的乘法存在檢查溢位無效的問題。 而 程式碼中,依賴 asset 的程式碼做溢位檢查,或者壓根就沒考慮做溢位檢查。 導致了整型溢位,出現 了致命而無法修復的bug。這點,只要主要在合約程式碼中的數值計算部分,做詳細的 eosio_assert 斷言檢查就可避免。關鍵還 是細心寫程式碼哈。

2、未檢查 code == eosio.token
這個問題受害面還是比較廣的,從 EOS bet、newdex、 以及後來的 cast 專案,都是因為沒有檢查 轉賬的來源,而蒙受大額損失,從以上的列表看出,這條經驗是好百萬鉅款買來的。

專案方,利用轉賬通知回撥transfer,來執行業務,但是這個 transfer通知,有可能來自 非 eosio.token的 (俗稱假幣 假EOS), 所以對 code == eosio.token 的判斷,就非常的重要。

3、轉賬通知 未判斷 to == _self
這個問題,暴漏在 EOS bet 第二次被攻擊的時候。損失也是500萬以上。轉賬通知,有個特點,就是不僅僅你收到轉賬的會收到通知,任何人收到轉賬的時候,都可以通 知你,他收到了轉賬。 比如,張三收到了李四 100 EOS 的轉賬,於是張三 告訴王五,“收到 100 EOS 轉賬”。王五,沒有判斷 “收款人 等於王五”,於是以為是自己收到了 100 EOS的轉賬。

原因就是這樣。 專案方,實際上沒有收到 真正的 EOS, 只是收到了 一條EOS 到賬通知,但收款 人還不是專案方。如何對任意人發起轉賬通知呢?只需要在轉賬中加入一條程式碼即可:require_recipient(N(eosbetdice11));這樣,你想通知誰,就通知誰。 此時要是對面沒判斷收款人,那麼很可能就被你黑了噢。

偽隨機程式碼中存在的一些問題

偽隨機數演算法中的可計算引數。之前不少概率隨機遊戲的專案方,採用了鏈上偽隨機方案,也即純粹使用區塊鏈上的資料來做遊 戲開獎的隨機數種子。但是,由於使用不當,有了種種被黑的結局。類似的鏈上偽隨機演算法,我貼幾張圖作為參考:

以及廣泛被引用的github程式碼:以上案例,都有一個共同的特點,就是採用了 tapos_block_prefix, tapos_block_num 做為隨機數種子。但是啊,這兩個值,其實是依賴於過去的區塊。也就是說,它們是能算出來的,當這個關鍵的參 數,能夠被計算出來的時候,所謂的隨機數演算法,就不是隨機了。黑客可以提前計算好結果,保證百分百勝率。除了,tapos_block_prefix, tapos_block_num, 還有當前 transacation_id 下面就分享下,這幾個值如何算。
1、tapos_block_prefix, tapos_block_num 以及 transacation id 怎麼計算 ?
   tapos的計算方法:
    tapos 有個定義是 Transactions as Proof-of-Stake (TaPOS)。它是指定一個過去的區塊( ref_block_num ),用來做 Proof-of-     Stake的。而程式碼中使用的 tapos_block_prefix 和tapos_block_num, 正是由這個 ref_block_num 算出來的。

檢視push action 說明,可以看到發起一個普通的action時,其中 ref_block_num, 是由客戶端指定的!有人會說,我平時發起action的時候,並沒有指定過 ref_block_num啊?其實,當你沒指定 ref_block_num 的時候,我們使用的 cleos 或eosjs 客戶端,會幫我們預設指定一個:

翻客戶端的程式碼,它告訴你,如果使用者沒指定ref_block_num,會幫你取一個, 取的是 last_irreversible_block_id,也就是get info 中返回的,上一個不可逆區塊的id。也就是,有了ref_block_num ,我們就可以拿到 tapos_block_prefix 和tapos_block_num 了:tapos_block_num = ref_block_num & 0xffff tapos_block_prefix = getblock(ref_block_num).ref_block_prefix

transacation_id的計算方法 
transacation_id ,是一筆交易的唯一id,是一個hash值,看起來好像是隨機不可預測的啊。於是有人拿它做隨機數,然後就沒然後了。這個值,跟你發起的 transacation 有唯一關聯, 所以是個可計算的值。計算方法如下:

直接 read_transaction 拿到當前 transacation的資料,然後 sha256 就得到了 transacation_id了。

2、為什麼利用延時交易,tapos_block_prefix, tapos_block_num 依然被破解 了?
有的專案,採用延時交易來非同步開獎,偽隨機演算法裡,用到tapos_block_prefix,tapos_block_num, 時間,使用者名稱,獎池金額等資訊。這樣的偽隨機演算法,看起來好像沒毛病,用到了未來的資料,似乎無法預測,但是真的是這樣麼?時間的話,知道了你延遲多少秒後,直接往上加就行了,使用者名稱是確定的,獎池金額這個雖然是 動態的,但是如果變動的頻率不是非常快,快到每分每妙都在變,那麼在短時間內,可以視為是 定值。所以關鍵還在於 tapos_block_prefix, tapos_block_num 。延遲交易中的tapos_block_prefix, tapos_block_num, 和普通交易中的 tapos_block_prefix, tapos_block_num 計算方式有所不同。普通交易,通過客戶端制定 ref_block_num ,然後計算出來。而延遲交易中,看EOS相關程式碼得知,直接使用head_block_id 做為 ref_block_num 。也就是說,當前區塊頭,就是ref_block_num。

知道這點後,就好辦了,有多種辦法可以拿到head_block ,比如說 直接 get_info

拿到了 head_block_num 之後,在get_block 資訊,可以直接得到

拿到了這兩個引數,又知道了隨機數演算法,理論上,就能夠預測能不能中獎了。
直接在合約裡寫程式碼判斷,能中獎就傳送交易,不能就跳過。
總結
tapos_block_prefix, tapos_block_num,慎用為隨機數演算法引數,如果非要用,可以採用發起 連續的兩次延時交易。因為延遲交易中的 head_block_id 跟你延遲多少秒沒關係,是構造這個延遲 交易的時候,就設定好了的。

3、合約鉤子的利用
同步開獎時被利用智慧合約鉤子,回滾交易,早期被黑的專案中,還存在一些 “錯誤的實踐” , 也值得提一下。
A、第一種,是同步開獎:
同步開獎有個問題,就是黑客可以輕鬆的破解你,一個簡單的思路是,直接拉取你的專案code,然後部署一個合約A, 然後用合約B跟這個A合約玩,假如和A合約玩贏了, A合約會給B轉賬,在B的轉賬通知那邊寫一個轉賬通知回撥,一旦贏了,就和你的專案玩。 這樣是必勝的。
B、第二種,是發回執,被對方拒絕,導致回滾整個交易:
一個錯誤案例是: 開獎 -> 給對方發一個 receipt action, 告訴對方你中獎沒中獎, 於是對方,直接針對這個receipt 寫一個回撥,當你告訴他輸了之後,他可以拒絕,回滾這次開獎, 這個好像被 叫做 “重放攻擊” , 避免的方法,就是 receipt 之類的通知,可以採用非同步的。
4、為何不開源也被擼?
目前大多數菠菜類專案方,是不敢開源的,一是怕競爭,二是怕暴漏問題。這個不好評判,我個人還是喜歡開源的。被擼的專案方,其實大部分也都沒有開源,那麼為什麼還是被擼了呢? 這裡簡單分享下。
首先,你合約程式碼都部署都上鍊了,對於有能力擼你的那些技術大神,這裡其實沒多少祕密可言。無非是讀原始碼和編譯後代碼的區別而已。專案的程式碼,在編譯過後,部署到了鏈上。取回的步驟如下:
1、 第一步,通過 get code 取回。可以得到 abi 檔案和 wast 檔案。abi檔案可以看到介面資訊可猜測整個合約程式碼的大致結構。
2、翻譯 wast 檔案了,找出你的隨機數演算法。 下面幾張圖,是簡單的示例。

簡而言之,你開源或不開源,對那些黑客來說,其實都一樣的。關鍵還是寫程式碼時,不要留下問題。不要去踩那些坑。不要輕易使用可以被拿到的引數做關鍵部分的隨機數演算法引數。

5、onchain呼叫

EOS Bet 在第一次被攻擊的時候,表面上是沒有檢查code == eosio.token , 實際上還有一個值得一提的細節。那就是對方這個 transfer action,並沒有暴漏在 abi中,也就是客戶端是沒辦法直接呼叫的。但是黑客是直接呼叫的。那麼是如果做到的呢?原來,abi並不是必須的。一旦你的code中有這個action,那麼就算在abi檔案中,把這個 action介面刪除了,企圖留下外面的人不知道的 “後門”。實際上這個 action 還是可以被呼叫。有兩種方法:

第一種,就是直接在另一個智慧合約裡發起呼叫。

第二種,就是改造下你的客戶端。不要在push aciton的時候,去呼叫abi。

改造的細節過於技術,有興趣的去參考這個:
https://zhuanlan.zhihu.com/p/42903901?utm_source= wechat_session&utm_medium=social

總結
abi 只是介面和資料描述檔案,就算你程式碼不部署 abi,也不影響合約程式碼正常工作。所以,如果你的智慧合約,僅僅是用來和其他智慧合約互動,而不是面向客戶的使用者(cleos、 eosjs),那 abi 都可以不用部署。