EOSIO智能合约的安全性分析

 

EOSIO区块链是具有代表性的权益证明(DPoS)区块链平台之一,最近发展迅速。同时,在野还发现并观察到针对热门EOSIO DApp及其智能合约的许多漏洞和攻击,造成了严重的经济损失。大多数EOSIO智能合约不是开源的,它们通常会编译为WebAssembly(Wasm)字节码,因此很难分析和检测可能存在的漏洞。在本文中提出了EOSAFE,这是第一个可用于自动检测EOSIO智能合约中字节码级别漏洞的静态分析框架工具。框架包括一个用于Wasm的实用符号执行引擎,一个用于EOSIO智能合约的定制库模拟器,以及四个启发式扫描器,用于识别EOSIO智能合约中四个最流行的漏洞的存在。实验结果表明,EOSAFE在检测漏洞方面取得了较优结果,F1-measure为98%。截至2019年11月15日,已将EOSAFE应用于生态系统中所有有效的53,666份智能合约。结果表明,超过25%的智能合约易受攻击。进一步分析了对这些易受攻击的智能合约的可能的利用尝试,并确定了48种在野攻击(其中25种已由DApp开发人员确认),造成的财务损失至少为170万美元。

 

0x01 Introduction

随着加密货币(例如比特币)的繁荣发展,区块链技术变得越来越有吸引力,并在许多领域被采用。由于工作证明共识的带来的吞吐量有限(例如TPS),因此不能使用传统的区块链平台(例如比特币和以太坊)来支持高性能应用程序。研究人员提出了不同的共识协议,例如权益证明(PoS)和委托权益证明(DPoS)以解决性能问题。

作为最具代表性的DPoS平台之一,EOSIO已成为最活跃的全球社区之一。 EOSIO采用了基于DPoS共识协议的多线程机制,能够实现数百万的TPS。 EOSIO的性能优势使其在去中心化应用程序(DApps)开发人员中很受欢迎。 EOSIO在其于2018年6月推出的三个月内,在DApp交易中成功超过了以太坊,并在接下来的几个月中将其主导地位进一步提高了数十倍,EOSIO的交易量平均是以太坊的100倍以上。截至2019年,EOSIO的链上交易总价值已超过60亿美元。

智能合约是一种计算机协议,允许用户以便捷安全的方式进行数字协商。与传统合约法相比,智能合约的交易成本大大降低,并且共识协议确保了其执行的正确性。 EOSIO智能合约可以用C ++编写,然后将其编译为WebAssembly(又名Wasm)并在EOS虚拟机(EOS VM)中执行。 Wasm是一个Web标准,为基于堆栈的VM指定二进制指令格式。它可以在现代Web浏览器和其他环境中运行。

但是,要保证执行智能合约的安全性并不容易,特别是EOSIO。 EOSIO智能合约中发现了许多漏洞,而在野发现了严重的攻击,这造成了巨大的经济损失。例如,在2018年秋天,由于虚假EOS(fake EOS)和虚假收据(fake receipt)漏洞,一个DApp EOSBet在短短一个月中遭受了两次攻击,分别造成40,000和65,000 EOS损失。因此,有必要识别智能合约的安全性问题,以防止此类攻击。

不幸的是,EOSIO上的大多数智能合约都不是开源的,并且很少有分析工具来分析Wasm字节码,这使得自动检测EOSIO智能合约的漏洞更加困难。尽管已经有很多工具可以分析以太坊智能合约,但是它们都不能应用于EOSIO智能合约,因为这两个生态系统完全不同,从虚拟机,字节码结构到漏洞的类型。

具体来说,分析EOSIO智能合约存在一些挑战。首先,就数量和种类而言,EOS VM比以太坊VM更复杂。例如,EOS VM支持浮点操作,类型转换和br_table之类的高级跳转指令。其次,与以太坊字节码相比,Wasm字节码本身由于函数中的多层嵌套结构而使分析更加复杂,从而导致基本块之间的跳转关系复杂。第三,到目前为止发现的大多数EOSIO漏洞比以前发现的简单漏洞(例如整数溢出)更为复杂。因此,它通常需要更多的语义信息,例如平台特定的数据结构的字段作为索引,以对其进行建模和分析。例如要检测到虚假EOS,需要检查apply函数中参数code的特定值。

本研究已经实现了EOSAFE,这是第一个用于检测EOSIO智能合约漏洞的系统静态分析框架。具体来说,首先为Wasm字节码实现一个符号执行引擎,然后通过应用启发式引导剪枝方法来缓解路径爆炸问题。其次,为了分析EOSIO智能合约并模拟其外部交互环境,实现了一个模拟器来模拟关键EOSIO库特征的行为,这些特征对于检测漏洞至关重要。第三,提出了一个通用的漏洞检测框架,该框架允许安全分析人员轻松地将自己的漏洞扫描器实现为插件。在这项工作中,实现了四个扫描器,旨在检测四个重要的漏洞,包括虚假EOS,虚假收据,回滚(rollback)和缺失权限检查(missing permission check)。

为了评估EOSAFE的有效性,首先手动设计了一个基准套件,其中包括52个智能合约,由从公开验证的攻击中收集的易受攻击的智能合约及其相应的修补补丁组成,实验结果表明EOSAFE在识别现有漏洞方面取得了出色的性能。为了衡量EOSIO生态系统中漏洞的存在,进一步将EOSAFE应用于生态系统中的所有智能合约(总计53,666)。实验结果表明,安全漏洞在EOSIO生态系统中十分普遍:超过25%的智能合约(包括历史版本)易受攻击,并且其中很大一部分没有及时修补。为了进一步衡量漏洞的影响,收集了交易记录(总计超过25亿笔交易),并精心设计了一套保守的启发式策略,以识别针对这些脆弱智能合约的攻击。已经识别出48起攻击,以及183起缺少权限检查的行为。截至撰写本文时,DApp开发者已确认25起攻击,已造成超过170万美元的经济损失。

 

2 Background

作为第一个工业级规模的分布式操作系统,EOSIO平台可以实现高性能,即数百万个TPS,以有效执行复杂的DApp。它如此高效地执行的事实很大程度上归功于它使用的共识算法,即DPoS。与传统的PoW(比特币和以太坊采用)相比,它不会在不必要的挖掘过程中花费大量的计算资源。接下来,介绍一些关键概念以促进对这项工作的理解。

A.账户管理

EOSIO中的帐户是识别实体的基本单位,它可以触发与EOSIO中其他帐户的交易。此外,为了确保帐户安全并防止身份欺诈,EOSIO实施了基于权限的高级访问控制系统。具体而言,该帐户可以将公共/专用密钥分配给特定的操作,并且特定的密钥对将只能执行相应的操作。默认情况下,EOSIO帐户附加到两个公用密钥:ownerkey(用于指定帐户的所有权)和active key(用于授予对帐户活动的访问权限)。这两个密钥授权两个本机命名权限:owner和active权限,以管理帐户。除了本机权限,EOSIO还允许自定义命名权限以进行高级帐户管理。

与以太坊不同,EOSIO智能合约不被视为单独的实体。智能合约只是帐户中存储的一小段代码,这可以轻松解释为什么EOSIO中的智能合约是可更新的,而不是所有者无法自由更改和销毁的东西,因此,当一个帐户被另一个人调用时,它所驻留的智能合约将负责处理收到的调用。这样,合约中最关键的组件就是dispatcher,它可以将请求调度到相应的函数。由EOSIO官方定义,智能合约中的调度程序被命名为apply,如下所示。

B.EOSIO交易

事务是要由节点验证的基本单位,它被打包在块中。如下图所示,一个事务由一个或多个action组成。动作是触发函数的基本单位。例如,上面第1行的操作指定了目标函数名称。一个动作负责携带调用者的权限。

一个动作可以通过两种方式在同一上下文中触发其他动作:inlined和deferred。具体来说,内联动作可以看作是继承其父级上下文(包括所携带的权限)的普通动作。至于延迟动作,引入该动作的原因是每个事务的执行时间上限为30毫秒,并且所有不必要的动作都可以分为延迟动作以执行。因此,如上图所示,在不同的事务中执行了延迟的操作。

除了交易和动作外,还有另一种排他性机制,即notifification。如上图所示,EOS是帐户eosio.token发行的正式token。它维护一个表格以记录持有人及其余额。因此,要将EOS转移到DApp,用户必须在eosio.token中请求transfer函数。对于上图中的步骤1,指示实际调用其代码的代码是eosio.token;代表动作或通知的接收者的接收者也是eosio.token。更新余额表后,eosio.token将通知付款人和收款人(参阅步骤2和3)。请注意,步骤3中的代码仍然是eosio.token,因为通知根本不是一个动作,而接收者是DApp。最后,通知也将由调度程序处理,就像使用相同的动作调用一样姓名。

C.Wasm字节码和EOS VM

EOSIO智能合约用C ++编写,然后编译为WebAssembly(Wasm)字节码,该字节码将在EOS VM中执行。Wasm是基于堆栈的虚拟机的二进制指令格式。尽管它被设计为支持高性能Web应用程序的开放标准,但它也可以用于支持其他环境,例如区块链。由于其高效性和可移植性,除了EOSIO之外,其他流行的区块链(例如,以太坊2.0)也将支持Wasm。

EOSIO Wasm二进制文件称为模块。 在模块内部,存在许多部分。 具体而言,在Function”部分中确定函数的顺序,该顺序与Code部分中函数(在低级指令中)的实现顺序相对应。 出现在Element部分的所有函数索引都可以视为当前模块的条目。 此外,字符串文字常用于初始化“内存”部分并存储在Data部分中。

像以太坊VM一样,EOS VM支持Stack,Local和Global,它们通过多个指令(例如local_set,global_get)从虚拟堆栈中推送和弹出。 同样,EOS VM具有一个称为Memory的区域,这是一个随机可访问的线性字节数组,只能通过使用特定指令(例如load和store)进行访问。

 

0x03 Vulnerabilities in EOSIO Smart Contracts

在合约执行的生命周期内,可以随时进行攻击。因此首先游戏以DApp为例,介绍智能合约执行的一般生命周期,如下图所示。首先,玩家在eosio.token中调用转移以参与游戏。然后,当DApp收到通知时,它将分派要通过分派器进行transfer的请求。在那之后,transfer将调用reveal以计算一个随机数,以确定玩家是否在本回合中中了头奖。如果是,DApp将触发eosio.token中的transfer,以将奖品返还给玩家。但是,攻击者可以在每个步骤中利用这些漏洞来获取利润。例如,在第3步和第4步中,攻击者可能无法严格验证输入参数的值。最重要的是,整个游戏过程都有可能被恶意回滚。在本节中讨论与合约执行生命周期有关的四种常见漏洞。

A.虚假EOS

任何人都可以创建和发行称为EOS的token,因为token名称和符号在EOSIO中不需要符合唯一性。因此,在上图中的步骤3对code进行的不正确验证可能会导致漏洞。

漏洞描述:由于eosio.token的源代码是完全公开的,因此任何人都可以复制其源代码并发行具有相同名称,符号和代码的token。虚假EOS与官方的EOS唯一的区别在于,它们的发行人不同。因此,如果攻击者通过复制的合约的transfer函数将虚假EOS转移到游戏DApp,则项目方收到的通知代码将不会是beeosio.token。此外,如果DApp碰巧不检查code的值,那么将绕过调度程序中的验证。为了缓解上述问题,一些开发人员缩小了接受code的范围,如前文的第6行所示“ code == self”用于处理来自其他帐户的直接调用,而“ code == N(eosio.token )”仅接受来自官方帐户的通知。但是,由于短路评估,如果攻击者直接在DApp中调用transfer,则验证也将是无效的,因为两个实体的余额都没有变化。

由于这两种情况仅与虚假EOS token有关,因此在本文中,将两者都称为虚假EOS漏洞。

B.虚假收据

如果DApp开发人员对code进行了全面检查,则通知将由调度程序转发到transfer,如上图中的步骤4所示。但是,如果开发人员在此步骤中未执行验证,则DApp可以被攻击。

漏洞描述:通知可以转发,并且code不会更改。因此,DApp可能会被同时扮演发起者和共犯双重角色(帐户)的攻击者欺骗。具体来说,启动器通过eosio.token调用对同伙(由to表示,transfer函数的参数)的常规转移。当eosio.token通知共犯时,它将立即将通知转发给DApp,而无需进行任何修改。这样,code就不会更改,这是官方发行者:eosio.token。因此,调度器将不会发现任何异常情况。但是,如果不检查transfer中的参数to,则DApp将被欺骗,因为token的转移已在攻击者控制的两个帐户之间完成。这会给DApp开发人员造成直接的财务损失。由于通知是由require_recipient触发的,因此将此漏洞称为虚假收据。

C.回滚

在前图中,transfer和reveal是关键函数。在transfer中,DApp处理随玩家转帐而收到的压注;在reveal中,开发人员经常使用各种链上状态值作为种子(例如current_time,该值表示执行操作的时间戳)来生成伪随机数,并最终通过将生成的数字与玩家的输入进行比较来获得结果。请注意,通常,回滚情况只能在游戏DApp中找到。假设reveal函数始终存在,并且对于每个游戏DApp而言都可以从入口点(即apply函数)到达。

漏洞描述:即使开发人员仔细检查了输入的每个参数,并在采取任何敏感措施之前检查了调用者的权限,与前图中的模型匹配的游戏仍然可能受到攻击。具体而言,所有动作都是内联调用的,即位于单个事务中。因此,当玩家在步骤8之后收到通知时,他可以立即调用eosio.token的另一个内联动作来检查其余额。如果他的平衡减少,则意味着他没有赢得本轮比赛。他可以使用断言语句来强制当前操作失败。动作失败可能导致整个交易恢复。这样,玩家可以继续尝试,直到他中奖为止。将此恶意回滚称为回滚漏洞。

D.缺少权限检查

在执行任何敏感操作之前,开发人员应检查动作是否携带了相应的权限。例如,在前图中的步骤5之前,DApp应检查调用者是否可以代表实际付款人参加游戏。

漏洞描述:权限检查由EOSIO中的require_auth(acct)强制执行,用于检查调用者是否已被acct授权来触发相应的函数。请注意,内联操作继承了其父级的上下文,包括权限(请参见第2.1节)。因此,如果携带不足权限的攻击者调用了一个函数,该函数通过内联动作并且没有权限检查就执行敏感操作,则可能会发生意外行为。将所有没有权限检查的函数视为缺少权限检查漏洞。

 

0x04 Technical Challenges and Our Solutions

A.路径爆炸

在EOSIO中,此问题主要是由于两种情况造成的:执行条件跳转指令(例如br_if)或调用函数调用。具体而言,与仅生成两个新分支的普通条件跳转指令不同,EOSIO中的br_table将其元素为目标指针的数组作为参数。结果,单个br_table可以导致n个新分支,其中n是数组的长度。除了这些条件跳转指令外,函数调用还强加了许多新分支来表示已发送所有可能的被调用者。显然,如果存在深度调用堆栈,则分支的数量将成倍增加。不幸的是,在EOSIO合约中,多个深度调用堆栈的串联是很常见的。因此,实际需要缓解此问题,否则符号执行解决方案将不适用。
为此,采用启发式指导的剪枝方法(heuristic-guided pruning approach)来解决挑战。一方面,依靠几种常规的修剪策略来缓解分支和深层函数调用带来的问题。例如,操作观察结果表明,根据场景确定的特定深度阈值下的路径丢弃不会影响(几乎)所有情况下结果的精度。具体来说公开:1)一个名为call depth的选项,它限制了调用堆栈的深度; 2)名为timeout的选项,供用户限制符号执行的过程。

但是,一般缓解策略的有效性在实践中是有限的。幸运的是,执行漏洞检测时,EOSIO中的此问题可以得到进一步(部分)解决,因为只需要注意易受攻击的代码片段的某些特定函数/结构即可。例如,在检测到虚假EOS和虚假收据漏洞时,仅考虑apply和transfer函数。

B.内存重叠

Wasm的存储区可以看作是未解释字节的向量,这意味着用户可以通过load和store以不同的值类型来解释这些原始位。模拟内存的传统方法是使用线性数组,但是由于模拟了EOSIO智能合约的稀疏内存布局,因此非常耗费内存。因此决定使用key-value映射来模拟内存,其中键是用于指定地址范围的元组,而值是要存储的数据:(lower-bound,upper-bound) → data。

但是,此策略可能导致内存重叠(参见下图)。如果使用映射而不进行优化,则可以直接存储键(A + 2,A + 3),(A + 3,A + 4)和(A + 2,A + 4),而不会发生冲突。结果,如果使用键(A,A + 4)检索数据,则存在两种满足条件的情况(下图中的情况1和2),这可能导致检索错误的数据。此外,在情况3中,如果要更新(A + 1,A + 3)中的数据,则必须遍历键空间以确定是否只有一个实体包含提供的地址范围,必须保证确保数据一致性。在情况4中,必须串联相邻的存储数据块,以确定如何将数据加载到存储区中。简而言之,所有这些问题都是由于内存重叠和映射策略不正确造成的。

本文提出了一种内存合并方法,通过合并分配的内存来解决此问题。如前所述,Wasm提供了20多个与内存访问相关的指令。将首先为遇到的所有与存储相关的指令创建键-值映射,其中值是按位存储的数据。之后,可以根据所提出的内存合并算法处理两个键的范围相邻或重叠的情况,该算法将更新相应的数据块以保证执行的准确性。简而言之,尽力确保任意两个任意键之间的间隔至少为一位。通过这样做可以成功克服上图中提出的挑战。

C.库依赖

为了促进智能合约的开发,EOSIO允许将外部函数作为库导入,这意味着这些导入函数的主体将不会编译为Wasm字节码。 EOSIO官方为DApp开发人员提供了大量诸如系统库之类的函数。它们已在许多(如果不是大多数)智能合约中得到广泛使用。结果,由于缺少那些导入的函数调用的主体,因此分析将被错误地终止。

为了解决依赖关系,提出了一种按需且语义感知的方法来模拟导入的函数。 仅关注其功能和副作用与本文的分析有关的功能。 必须正确模拟这些函数,以保证最终结果的正确性。 模拟的强度和覆盖范围取决于执行分析的需要。 对于某些函数,必须涵盖参数,返回值和副作用。 例如,与内存有关的函数memmov,在其中必须考虑其对符号内存的所有副作用。 对于其他一些可能只需要考虑可能的副作用。 例如,对于那些没有返回值且对漏洞检测没有影响的与表相关的函数,例如db_store_i64,可以平衡堆栈而无需模拟其行为。

 

0x05 System Design

下图描绘了EOSAFE的总体架构,该架构以EOSIO智能合约的Wasm字节码作为输入,并最终确定该字节码是否易受攻击。具体来说,EOSAFE基于Octopus(用于Wasm模块的安全分析框架)来启动预处理。每个智能合约将被发送到Octopus,以使用反汇编的Wasm指令构建其相应的控制流图(CFG)。

EOSAFE主要由三个模块组成,即Wasm符号执行引擎(简称Engine),EOSIO 库模拟器(简称Emulator)和漏洞扫描程序(Scanner)。如上图所示,将预处理后的输入(CFG)输入到Scanner,以使用Engine和Emulator通过两步过程(定位可疑函数和检测漏洞)执行漏洞检测。具体来说,Engine会相应地执行符号执行以及路径约束,Scanner将使用路径约束来执行漏洞检测。此外,当Engine遇到对导入函数的调用时,Engine会请求Emulator实现建模的行为。

A.Wasm符号执行引擎

该引擎被设计为通用框架,可在基于堆栈的EOS VM上模拟智能合约的执行。它接受CFG和反汇编的Wasm指令作为输入,并象征性地在基本块内执行指令,以获取所有可行的路径。在此过程中,会相应地生成路径约束。具体来说,该模块必须维护两个关键组件:路径树和状态。对于路径树,不仅记录由符号执行产生的约束,而且记录沿路径导入的函数的所有参数和返回值,这有助于分析漏洞检测。关于状态,维护一些必要的状态相关信息,包括局部/全局变量,线性存储器,堆栈以及后续指令及其相应的程序计数器。

(1)通过通用策略缓解路径爆炸

本研究提供了两个选项,包括call depth和time-out,供用户通过牺牲准确性来缓解此问题。一方面,选项call depth用于限制调用堆栈的深度,以防止分析陷入处理复杂分支或深度函数调用的麻烦。众所周知,单个函数可能具有与该函数内可行路径相对应的几组约束,这可能导致路径数量呈指数增长。因此,限制了调用堆栈的深度以提高覆盖范围。另一方面,遇到一些非常耗时的情况时可能仍然会遇到麻烦。为了保证整个系统的进度,引擎提供了另一个名为time-out的选项,以控制路径级分析的最大执行时间。当然,超时结果将被记录以供进一步调查。请注意,路径爆炸问题将在漏洞扫描器中进一步解决,因为只需要注意易受攻击的代码片段的某些特定函数即可。

(2)消除内存重叠

本文实现了一个符号存储器来表示Wasm的存储器,并且还提出了一种存储器合并算法来模拟存储指令。该算法将符号存储器,地址,字节数据长度和数据作为输入,最后返回合并的符号存储器,而没有重叠/相邻的键作为输出。具体来说,给定要存储的新键,将检查现有密钥的地址范围是否与新密钥的地址范围重叠。如果是这样,将直接执行插入操作;否则,它将相应地更新重叠的部分并连接不重叠的部分。此外,按键以起始位置的升序排列。如果两个相邻的键不重叠,它们将合并在一起以形成一个新的键值对。例如,现有的键值对是:symbolic memory := {(0,2) → a0|a1,(3,4) → a3}。当(2,4)→a2 | a’3到达时,它将更新重叠的部分并在必要时将不重叠的部分连接起来:symbolic memory := {(0,2) → a‘|a1,(2,4) → a2|a’3}。之后,它将合并相邻的键:symbolic memory := {(0,4) → a0|a1|a2|a‘3}。简而言之,该算法通过强制所有有效地址在键空间中仅出现一次来保证数据的一致性。

B.EOSIO库模拟器

使用按需和语义感知的方法来解决EOSIO库依赖关系。已经手动分析了排名前100位的流行DApp的智能合约和现有已知的易受攻击的智能合约,以从Function部分提取所有导入的函数。然后,根据其主要函数(如下表所示)将所有导入的函数分为五类进行模拟。最后,可以从模拟的导入函数中检索副作用。

区块链状态函数:这些函数返回与区块链系统相关的常量,这些常量通常由智能合约用作种子以生成伪随机数。由于它们不会带来任何副作用,因此只是通过直接返回一个代表区块链状态的符号值来模拟它们。

与内存相关的函数:顾名思义,此类函数与已实现的符号内存有关。因此,将这些行为作为其初始进行模拟,并在插入新数据时应用内存合并算法。由于约束解决,为未定义的行为(例如memcpy函数的负长度)抛出了异常。

控制流相关函数:这些函数是可以根据其返回结果更改或终止智能合约的控制流的函数。因此如有必要,将分叉两条路径。例如,如果eosio_assert函数的谓词是符号值而不是特定的布尔值,则将生成两条路径。

与授权相关的函数:由于权限系统仅与缺少权限检查漏洞的检测有关,因此只需要检查这些函数的存在,而无需考虑特定的权限。因此,只返回一个符号值来平衡堆栈。

与表相关的函数: EOSIO中有一个特殊的数据结构,可以持久存储数据。与以太坊中的存储概念类似,此类数据保存在称为表的区块链中。通过一些特定于平台的指令,可以将Table视为支持CRUD操作(即,创建,检索,更新和删除)的数据库。对于这些函数只需要关注内存的副作用,而不是内部操作。具体来说,使用用于更新内存的返回值实现了它们,如下所示:

请注意,对于没有任何返回值但修改表内容的函数(例如db_update_i64),将其函数名称和参数记录在约束中。

C.漏洞扫描器

为了检测多个漏洞,Scanner被设计为执行检测的通用框架。它主要包括两个步骤,即查找可疑函数和检测漏洞。

只需要关注有价值的函数,这些函数可以调用具有更改链上状态的外部函数,包括send_inline,db_update_i64和db_store_i64。根据观察,在大多数情况下,这些有价值的函数可以试探性地视为目标函数,从而可以显着减少分析时间。结果,借助CFG和路径树(由约束和有价值的函数组成),可以高效、准确地识别漏洞。具体来说,定义以下公式:

这里conX表示给定函数的约束,其中X是要分析的函数的名称。仅当conX满足三个条件中的至少一个条件时,X才是有价值的函数。

结果,检测框架的两个步骤可以进一步转移并简化为以有价值的函数为中心的过程:1)定位有价值的函数; 2)验证其可发起攻击的能力。请注意,该过程的第二步是可选的,因为在某些情况下始终可以保证可达性。基于此框架,将介绍四个扫描器的详细信息。

(1)虚假EOS检测

如前文所述,虚假EOS漏洞只能通过调用传递函数来触发,该传递函数成为可以导致财务损失的有价值的函数,它满足以下条件:

而且,攻击者必须能够从条目(即apply函数)访问transfer函数,这意味着在pply函数中没有适当的code验证。具体地说,扫描器遍历通过象征性地执行apply函数生成的所有可行路径,以检查当前路径的约束条件是否符合以下条件:

具体来说,它限制了只能分析与transfer函数关联的路径。为了加速分析,引擎将提前终止不相关的路径(如果目的地不是transfer),以避免进一步执行。然后,扫描器将检查code值。因此,满足与code相关的任何条件都意味着存在不正确的验证。综上所述,满足以上条件的智能合约被认为是易受攻击的。

(2)虚假收据检测

此漏洞是由于transfer函数内部的验证不足所致。但是,相应的send_inline函数非常深,这总是导致调用深度溢出来破坏引擎。因此,直接通过符号执行从条目(即apply函数)中定位有价值的函数(即transfer函数)是不可行的。

为了解决该问题,改为采用基于启发式的方法。具体而言,扫描器首先识别apply函数,然后枚举所有相关的基本块以验证其跳转目标的索引可能指向可疑的transfer函数。找到可疑的transfer函数后,扫描器将根据1到3的标准来确定有价值的transfer函数。请注意,对于给定的智能合约,应确切地存在一个transfer函数,这意味着transfer函数是可疑函数之一,或内联在apply函数中,如下所示:

对于以上两种情况中的任何一种,将进一步检查以下保护的存在:

满足以上条件的智能合约被视为容易受到伪虚假收据漏洞的攻击。此外,该扫描器还采用尽早终止的方法来加速整个过程。具体来说,对于有价值的transfer,应在更新有关链上状态的更改之前验证保护。因此,在遇到三个标准中的任何一个而没有沿途保护的情况下终止分析是合理的。

(3)回滚检测

reveal函数通常会生成随机数来确定头奖中奖者,并通过内联动作调用eosio.token中的传递函数以返还奖品。因此,根据标准1,reveal函数成为有价值的函数。但是,在某些情况下,处理reveal函数时必须考虑计算负担,即send_inline函数的调用深度对于引擎来说太深了。

幸运的是,由于没有必要考虑任何目标游戏DApp的路径中send_inline函数的可达性,因此能够应用两种策略来加快定位reveal函数的过程。具体而言,第一种策略是按需遍历可行的路径。除了枚举所有路径外,仅检查可用于解析目标send_inline函数的数据/变量依赖关系的路径。另一方面,第二种策略在提取有价值的函数后减小了引擎正在检查的路径集的大小,即删除了基本路径完全是其他路径的子集的冗余路径。因此,可以获得最小的路径集,以覆盖尽可能多的基本块。

最后,检测逻辑与两个属性相关联。首先,研究表明,reveal函数将使用rem指令沿构造的路径集中的路径生成随机数。其次,如果模运算的操作数(部分)是由区块链状态函数生成的,则智能合约将受回滚漏洞的影响。总之,检测逻辑必须满足:

这里operand2始终是与区块链状态无关的常量或变量。如果满足上述条件,可以确认该合约容易受到回滚漏洞的影响。请注意,将删除EOSIO官方库(例如eoslib)生成的所有rem指令,以减少误报。

(4)缺少权限检查检测

将重点放在那些在敏感操作之前是有价值的和缺乏权威验证的函数上。同样,这些函数应该可以通过apply函数实现。根据1到3的标准对所有有价值的函数进行过滤后,将检查约束条件是否符合以下条件:

具体而言,第一个条件首先要求该函数(由func标识)是攻击者可以通过apply函数实现的。然后,第二个条件意味着可以直接触发函数。最后,第三个条件检查函数是否缺乏权限验证。如果满足上述条件,则智能合约容易受到缺少权限检查漏洞的影响。

 

0x06 Implementation and Experimental Setup

实现:利用Octopus构建Wasm字节码的CFG,并使用Z3定理证明(版本4.8.6)作为约束求解器。所有其他主要组件,包括Wasm符号执行引擎,库模拟器和漏洞扫描程序,均由自己设计和实现。该实现基于Python,其中包括超过5.5k行代码。

实验设置:实验是在运行Debian的服务器上执行的,该服务器具有2.10GHz @ 64G RAM的四个Intel®Xeon®E5-2620 v4。Wasm引擎提供了两个配置选项(即调用深度和超时)来部分解决路径爆炸问题。在实验过程中,根据经验将调用深度设置为2层,它足以识别大多数漏洞。关于检测时间,由于以下两个主要原因,根据经验将上限设置为5分钟。首先,在5分钟内,可以对基准中的所有智能合约进行全面分析并检测出有希望的结果。其次,当试图将EOSAFE应用于所有EOSIO智能合约时,必须在准确性和可扩展性之间进行权衡。因此,每个合约的检测时间最多设置为5分钟。请注意,所有这些设置都可以在工具中轻松配置和自定义,以满足不同的要求。

研究问题:本文评估以下三个研究问题(RQ)

RQ1: EOSAFE在检测EOSIO智能合约的漏洞方面的准确性如何?

RQ2:这些漏洞在生态系统中是否普遍存在?

RQ3:攻击者利用了多少智能合约,这些攻击的影响是什么?

为了回答RQ1,在研究社区中没有建立基准的情况下,本文收集实际攻击并手动检查受害智能合约以制定可靠的基准。为了回答RQ2,收集了EOSIO上所有可用的智能合约及其历史版本。然后,应用EOSAFE来检测安全漏洞的存在,并描述漏洞的演变。为了回答RQ3,进一步收集与已标记的易受攻击合约相关的所有链上交易,然后提出启发式方法以查明可能的攻击。

 

0x07 Experimental Results

A.RQ1:漏洞检测的准确性

基准:为了评估EOSAFE,首先努力制定一个基准,并将其提供给社区。不时报告EOSIO攻击。因此诉诸于知名区块链安全公司发布的安全报告,以收集所有相关的公开验证的攻击作为数据。已经收集了38次攻击,针对总共34个不同的易受攻击智能合约。尽管这些攻击已由相应DApp的官方团队确认,但发现某些攻击与智能合约本身无关,但与其他外部因素有关,例如服务器的问题。因此,进一步手动检查了所有涉及的智能合约。具体来说,发现10种虚假EOS攻击中有3种与服务器问题有关。对于回滚,21种攻击中有11种是由于服务器的错误显示策略所致。此外,其中两个是回滚的变体,它们与EOS MainNet上某些节点的配置有关。最后,将上述所有合约从基准中排除,以确保所有攻击均来自智能合约本身中的代码。

基准的分布显示在上表中,还收集了相应的修补智能合约(无漏洞)作为评估EOSAFE有效性的比较。此外,据报告,仅有两个与缺少权限检查漏洞有关的易受攻击的智能合约,并且尚未对其进行修补。因此,进一步手动创建了4对智能合约(有和没有缺少权限检查漏洞)以补充基准。最终总共将52个智能合约标记为基准。

结果:在52份智能合约中,EOSAFE将26份标记为易受攻击,只有一个漏报性案例(属于回滚),没有误报性,导致准确率和召回率分别为100%和96.97%。上表显示了详细的结果。对于回滚的唯一错误否定情况,即fair-dogegame / betdogewallt,根本原因是可疑的揭示数量过多,无法建立路径,并且在给定的超时时间(此处为5分钟)内象征性地执行了它们。手动定位易受攻击的函数(即func73)后,可以获得正确的结果。因此,优化策略引入了漏报性,这是准确性和可伸缩性之间的折衷。例如,通过探索更多路径并增加分析时间,很容易调整本文的方法以涵盖该方法。尽管如此,由于大多数智能合约并不太复杂,因此在实验过程中很少见到这种例外情况。

B.RQ2:漏洞的普遍程度

数据集:考虑了从2018年6月9日(EOS MainNet的最开始)到2019年11月15日的所有53,666份智能合约(包括历史版本)。请注意,与以太坊智能合约一旦部署便无法修改的不同,EOSIO合约可以按照前文中的说明更新并绑定相同的帐户。因此使用EOSIO帐户来标记每个唯一的智能合约,即一个帐户可能对应于多个合约版本。结果有53,666个不同版本的合约,它们属于5,574个EOSIO帐户。由于回滚漏洞仅与游戏DApp有关,因此可以在此处缩小候选列表。使用DAppTotal-一种可靠的多平台DApp浏览器来标记游戏DApp,并使用此类合约(17,394)进行回滚漏洞检测。对于其他三种漏洞,将扫描器应用于所有53,666个合约(请参见下表)。

(1)总体结果

上表显示了总体结果。令人惊讶的是,在53,666个智能合约中,有25%以上是易受攻击的(请参阅第3列)。缺少权限检查漏洞是最普遍的漏洞,影响了超过15%的智能合约。伪造的收据漏洞也很普遍(13%)。对于回滚漏洞,尽管仅分析了游戏DApp的17,000个智能合约,但其中有1000多个易受攻击。伪造的EOS漏洞影响大约2.7%的智能合约。这表明EOSIO智能合约生态系统中普遍存在安全漏洞,这表明识别和防止此类漏洞的紧迫性。

存在漏洞的唯一智能合约:由于一个智能合约可能对应多个版本,因此从唯一合约(帐户)的角度进一步描述漏洞的分布。如上表的第5列所示,对于5,574个唯一合约,大约有一半具有至少一个易受攻击的版本。 10%的唯一智能合约占易受攻击版本的61.24%,这表明大多数易受攻击的版本是由一小部分智能合约导入的。此外,有1,793个唯一智能合约,它们的版本都容易受到攻击(其中41%的版本至少具有两个版本)。合同eossanguoone是一种流行的游戏DApp,具有最多易受攻击的版本(356个版本)。通过手动检查,发现2019年9月4日之前发布的所有版本都遭受了虚假收据漏洞,然后该漏洞由开发人员修补。自2019年8月以来,已发现缺少权限检查漏洞,这可能是由于未经授权就导入了新函数所致。

(2)修复漏洞的时间

当分析了不同版本的漏洞的演变时,因此有必要进一步研究修复每个唯一智能合约的漏洞的时间,这些时间可用于衡量攻击者利用这些漏洞的窗口期。

结果:如上表所示,截至本研究,对于具有漏洞版本的2759个唯一智能合约,其中超过75%的最新版本仍至少存在一个安全漏洞。 679个唯一的智能合约在其演变过程中修补了所有漏洞,平均窗口期为16.84天。

补丁率:进一步分析漏洞的补丁率。回滚漏洞的补丁程序率最高(超过66%),平均窗口期约为4天。其及时响应的原因可能是回滚漏洞仅存在于游戏DApp中,这些DApp通常在其帐户中具有较高的余额。如果开发人员不理会该漏洞,则财务损失可能是灾难性的。对于缺少的权限检查,有349个智能合约对其所有缺少的检查操作进行了修补。请注意,在此处测量了操作级别的平均补丁时间,因为一个易受攻击的合约可能有多个缺少权限检查操作。总共有647个补丁操作,其中大约500个补丁补丁在一天之内被修复,而总补丁时间为4.38天。这表明大多数丢失的权限检查操作都会得到及时修补,而少数合约则需要相对较长的时间才能解决。相反,虚假EOS和虚假收据漏洞具有最低的补丁率(即大约20%),并且补丁时间相对较长(即平均2到3周)。手动检查发现,与虚假收据相关的智能合约中有一半是在24小时内修补的,这进一步表明一些不活跃的智能合约拖延了平均修补时间。大多数不活动的智能合约(帐户)没有余额,交易很少,通常不是攻击者的目标。

C.RQ3:存在攻击

(1)方法

探索攻击者成功利用了多少个易受攻击的智能合约并不是一件容易的事。直到最近,安全研究人员仍需要大量临时工作(通常是手动工作)来进行验证。因此,在存在易受攻击的智能合约的情况下,首先收集其所有相关的链上交易,然后设计一套启发式方法来定位可疑攻击,这将用于促进进一步的手动验证以确定实际攻击。总计,已经收集了超过25亿笔交易记录。

虚假EOS攻击:此攻击的最重要行为是通过使用伪造的EOS token从易受攻击的智能合约中欺骗官方EOS token,可以通过存储token发行者信息的交易记录来识别这些伪造的EOS token。根据观察,将首先过滤掉所有token符号为“ EOS”的token转移交易。然后,将根据以下定义对这些交易进行分组:

•发送伪造的EOS token的伪造交易;

•发送真实EOS token的真实发送交易;

•接收真实EOS token的真实接收交易。

结果,可以将潜在的攻击定义为假冒发送交易之后是真实接收交易的序列。请注意,假冒发送交易A可以与真实接收交易B合并,前提是且仅当它们出现在同一时期,而A发生在B之前。对于所有这些潜在交易,主要关注那些获得了更多收益的交易。真正的EOS代币所花费的金额为此,进一步检查了攻击者与易受攻击的合约之间的投入产出比,以确定可疑的攻击。最后,基于可疑攻击,将在收到伪造的EOS令牌后验证易受攻击的智能合约是否将恢复正常执行(例如,为真实玩家开奖)。如果是这样,会将可疑交易标记为伪造的EOS攻击。

虚假收据攻击:攻击的主要特征是易受攻击的智能合约被虚假通知误导以接收token,而实际的token转移发生在属于同一攻击者的两个帐户之间。为简单起见,下面将使用from_account和to_account表示这两个帐户,其中to_account将伪造的收据发送给易受攻击的合约,而from_account是最终的受益人。

因此,将首先查询其token由eosio.token发行且token符号为“ EOS”的所有token转移交易,以获取所有真实的EOS token转让。然后,将筛选出既不是eosio.token也不是from_account或to_account的接收者的交易。这些交易将被视为带有虚假通知的伪造收据。接下来,如果from_account在从易受攻击的合约获利之前发送了虚假收据,会将相应的交易标记为潜在交易。之后,通过消除无关的EOS支出交易(例如,出于攻击者发起的测试目的),主要关注那些获得了比其花费更多的真实EOS代币的人。如果投入产出比仍然很高,则将相应的交易标记为可疑。最后将手动检查可疑交易,在收到虚假收据后,易受攻击的智能合约是否将恢复正常执行。如果是这样,会将此类交易标记为伪造的收据攻击。

回滚攻击:此攻击的事务由动作的顺序调用组成,可以用作识别攻击的模式。具体来说,将首先筛选出至少包含四个动作作为潜在交易的所有交易。接下来,将选择可满足以下四个条件的可疑交易:(1)必须在同一合约中调用第一个和最后一个动作,第一个动作是发起攻击的手段,最后一个动作将决定是否发起攻击。从易受攻击的智能合约中获得奖励后,必须进行回滚。 (2)中间的两个动作必须是通过eosio.token进行的token转移,两个动作的发送者和接收者(必须是易受攻击的智能合约)彼此相对。 (3)交易方中的至少一个,即发送者或接收者,被标记为游戏DApp。 (4)从易受攻击的智能合约转移的代币数量超过其收到的数量。此外,值得注意的是,回滚的事务将不会记录在链上。因此,必须手动检查玩家每单位时间的成功率,即如果该成功率比其他人高,会将可疑交易标记为回滚攻击。

缺少权限检查攻击:由于授权信息与调用的事务一起存在,因此可以检查它是否属于被调用方合约以识别此攻击。更准确地说,将首先筛选出所有目标操作为易受攻击操作的交易,以获取可疑交易。然后,如果交易的权限不属于该操作所属的智能合约,会将其标记为缺少权限检查攻击。

(2)结果

总体结果如上表所示。总共确定了48个攻击,包括9个虚假EOS攻击,27个虚假收据攻击和12个回滚攻击。此外,还确定了183个未执行权限检查的调用操作(属于144个合约)。请注意,对于这些缺少权限检查的操作,其中一些是有意设计的,而不是意外的实现。很难区分它们是否是攻击,并且无法估计经济损失。因此将其视为滥用行为,而不是攻击。

攻击影响:识别出的48种攻击导致EOS损失超过341K,按攻击日期的收盘价计算,大约相当于170万美元。请注意,已经与一家领先的区块链安全公司合作,向DApp开发人员报告了这些攻击,其中27起已得到确认,占总损失的99%以上。所有未确认的可疑攻击事件仅与少数EOS有关,并且大多数不再活跃。上表列出了前5大已确认的攻击事件。

未被漏洞利用的合约:有趣的是,尽管成千上万个合约易受攻击,但攻击者仅成功利用了其中的几个合约。手动采样了一些智能合约以进行逆向工程并检查其交易,发现主要原因有两个。首先,流行的智能合约(具有高余额)是攻击者的主要目标,但是这些易受攻击的合约可以及时修补,并且为攻击者留出了很短的窗口期。根据交易发现攻击者一直在尝试利用流行的合约,并且某些攻击确实是成功的(请参见上表),而大多数攻击都失败了。第二,正如前文中提到的那样,考虑到低利润和攻击成本之间的折衷,大多数未打补丁的智能合约都是余额较低的非活动智能合约,因此吸引了攻击者较少的注意力。

 

0x08 Threats to Validity

首先,本文系统继承了符号执行的局限性,即路径爆炸。尽管已经实施了几种优化策略,但是EOSAFE仍然报告错误否定情况。但是,这对于系统来说不是一个大问题。一方面,大多数智能合约不像其他软件那样复杂。可以在短时间内全面分析大部分智能合约。另一方面,在搜索可以消除大多数不相关路径的漏洞时,提出了特定的优化方法。尽管如此,可以利用先进的符号执行技术来缓解这一问题。其次,依靠试探法和半自动方法来验证攻击。当然,这可能无法扩展,这意味着本文仅提供了较低级别的攻击的检测。但是,确定的大部分攻击已由DApp团队确认,这表明本文方法相当可靠。不过,可以采用某些技术(例如动态测试)来帮助本文工具自动识别攻击。在本文中主要贡献是自动检测安全漏洞,而攻击验证不是这项工作的主要重点。第三,可能存在一些当前版本中未涵盖的新漏洞,以及其他软件系统中的常规漏洞,例如缓冲区溢出。在本文中仅关注EOSIO特定的漏洞,主要原因是缺乏其他安全漏洞的真实性。尽管如此,将本文系统扩展为涵盖其他漏洞很容易,因为符号执行引擎和扫描程序框架是通用的。

 

0x09 Conclusion

本文是首个有关检测EOSIO智能合约中的安全漏洞工作。在文中提出了EOSAFE,这是一个准确且可扩展的框架,该框架能够检测EOSIO特定的漏洞。实验结果表明,EOSAFE具有良好的性能。本研究的大规模评估研究进一步揭示了生态系统中的严重安全问题,即超过25%的智能合约易受攻击,并且成功造成了许多著名攻击事件。

(完)