Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

Sereum Protecting Existing Smart Contracts Against Re-Entrancy Attacks

作者:Michael Rodler, Wenting Li, Ghassan O. Karame, Lucas Davi

单位:University of Duisburg-Essen, NEC Laboratories Europe

出处:NDSS’19

原文:https://arxiv.org/pdf/1812.05934.pdf


摘要

近来,越来越多的现有区块链系统中被曝出存在着具有严重缺陷和漏洞的智能合约。尽管各类文献中提出了许多保护智能合约的方案,但这些方案均主要侧重于证明合约中某种特定类型漏洞的存在与否,无法保护那些已被部署的合约不被攻击者利用。

本文针对重入(Re-entrancy)攻击提出了一种新的智能合约安全技术,称为Sereum(Secure Ethereum),其能够通过运行时监测和验证,以向后兼容的方式保护已被部署的合约不被攻击者利用。Sereum既不需要对合约进行任何修改,也不需要掌握合约的任何语义信息。

文章在以太坊(Ethereum)区块链上实现并评估了Sereum,其能够覆盖合约的实际执行流,以低至0.06%的误报率和可被忽略的运行时开销精确地检测和防御重入攻击。

1 介绍

修复在合约中发现的缺陷和漏洞面临着以下三个关键挑战:

  1. 合约代码在被部署之后是不可更改的;
  2. 合约所有者是匿名的,负责任的披露(responsible disclosure)通常是不可行的;
  3. 现有合约保护方案通常是基于离线分析的,容易遗漏未知的运行时攻击模式。

因此,现有合约保护方案只对未来合约的开发具有重要意义,那些已被部署的合约依旧能够被攻击者利用。

重入攻击的攻击模式是适合于进行运行时检测的,文章通过修改和加固以太坊虚拟机(EVM)实现了Sereum原型。Sereum利用污点跟踪(taint tracking)监测合约的执行,通过引入写入锁(write lock)的方式,防止合约在重入执行的过程中非法更新合约状态,回滚恶意交易,保证合约状态的一致性,从而防御可能的重入攻击。

文章通过重放执行以太坊区块链中已有的大部分交易,从而对Sereum原型进行了广泛的评估。结果表明,Sereum检测到了所有与The DAO攻击有关的恶意交易,并且只产生了9.6%的运行时开销与0.06%的误报率。

2 背景

2.1 智能合约与以太坊虚拟机

以太坊虚拟机遵循堆栈机器的架构,指令或者从数据栈中弹出操作数,或者使用常量操作数作为运算对象。

2.2 重入攻击

由于重入是以太坊智能合约中常见且官方支持的编程模式,所以在正常合约的执行过程中经常会出现合法的重入。

20190218134234

尽管重入在提款以及一些其它的编程模式中是必须的,但如果在实现时存在疏漏,便有可能被攻击者利用,造成重入攻击。当一个合约被恶意重入时,其内部状态尚未得到更新,合约在不一致的内部状态下运行,重入攻击便发生了。

20190218142451

2.3 常见防护与分析工具

  • Oyente
  • Mythril
  • Manticore
  • teEther
  • Maian
  • Zeus
  • SmartCheck
  • Securify
  • KEVM
  • ECFChecker(运行时监测工具)

3 问题陈述与新的攻击

文章发现了三种新的重入模式,包括跨函数重入(cross-function re-entrancy)、委托重入(delegated re-entrancy)与基于创建的重入(create-based re-entrancy)。这三种新的重入模式均能够被攻击者利用实现重入攻击,但却无法被现有的检测工具准确检测出来。

3.1 跨函数重入(cross-function re-entrancy)

一个合约通常会提供多个读写同一内部状态变量的函数接口,攻击者能够通过重入读写同一内部状态变量的不同的函数接口,实现跨函数重入攻击。

一般而言,检测跨函数重入攻击对于任何静态分析工具都是非常困难的,因为这要求工具检查合约的每个函数接口的任意外部调用都是安全的,存在潜在状态爆炸(potential state explosion)的问题。

20190218150841

3.2 委托重入(delegated re-entrancy)

DELEGATECALL指令与CALLCODE指令允许一个合约在当前合约的上下文环境中调用其它合约的代码,攻击者能够利用这两条指令绕过现有检测工具的检测,实现委托重入攻击。

现有静态分析工具在进行离线分析时,无法确知合约在实际执行时会委托调用的合约,故无法检测委托重入攻击。

20190218150910

3.3 基于创建的重入(create-based re-entrancy)

一个合约既能够被外部账户创建,也能够被其它合约创建,此外,合约的构造函数也能够发起更进一步的外部调用,攻击者能够利用CREATE指令,绕过现有工具的检测,实现基于创建的重入攻击。

与委托重入攻击类似,现有静态分析工具在进行离线分析时,无法确知合约在实际创建时会调用的合约,故无法检测基于创建的重入攻击。

4 Sereum的设计

Sereum通过引入攻击检测(Attack Detector)模块与污点引擎(Taint Engine)模块对标准的以太坊虚拟机进行了加固扩展,这也是第一个将动态污点跟踪(dynamic taint tracking)应用于智能合约领域的解决方案。

20190218163226

一般来说,重入攻击的目标是利用合约的不一致状态,绕过目标合约业务逻辑中的合法性检查,执行恶意操作。因此,检测重入攻击所需要关注的重点在于目标合约中的条件跳转以及影响这些条件跳转的状态变量。

Sereum的核心思想便是在合约调用执行的过程中检测导致合约状态不一致,继而影响合约业务逻辑中合法性检查的状态更新。

文章总结了导致合约状态不一致的三个条件:

  1. 一个合约外部调用了另一个合约;
  2. 在外部调用过程中,导致合约状态不一致的状态变量被用于控制流决策;
  3. 在外部调用返回之后,该状态变量被更新。

20190218163243

对于一笔交易中同一合约的多次执行,Sereum通过动态污点跟踪,分析并记录每次执行过程中用于控制流决策的状态变量,并对这些状态变量添加写入锁,以防止进一步的更新,并实时构造当前交易的动态调用树(dynamic call tree)。

在之后的交易执行过程中,如果检测到违反写入锁的状态更新,Sereum就会报告发现重入漏洞,并立即回滚当前交易。

20190218173643

5 实现

Sereum是基于go-ethereum项目的以太坊网络客户端geth实现的。

5.1 污点跟踪EVM

文章修改了geth的字节码解释器(bytecode interpreter)模块,通过维护影子内存(shadow memory)的方式将污点值与实际数据值分离存储,从而确保动态污点跟踪对合约实际执行的完全透明。

由于EVM的架构是完全确定的,合约代码只能使用专用的指令访问区块链状态,所以Sereum能够通过跟踪EVM指令级别的数据流来完全模拟系统级别的数据流。

20190218173721

对于重入攻击检测,Sereum只跟踪一种类型的污点,即DependsOnStorage,其污点源(taint source)是SLOAD指令,污点槽(taint sink)是JUMPI指令。

20190218204754

5.2 攻击检测

在一笔交易的执行过程中,Sereum通过动态构造并不断遍历动态调用树,检测是否存在违反写入锁的状态更新,及时报告并回滚恶意交易。

6 评估

6.1 运行时检测重入攻击

文章使用geth提供的RPC API的debug模块,重放执行了以太坊区块链前4,500,000个区块所包含的共计77,987,922条交易。Sereum将其中49,080条交易(0.063%)标记为重入攻击。

通过人工分析这些交易涉及到的合约,并去除相同或相似的合约,Sereum总共检测到了16个互不相同且曾被符合重入攻击模式的交易调用过的合约,这16个合约中有6个合约是开源的。

这16个合约中有6个合约是开源的,文章对于这些合约的Solidity源代码进行了代码审计,而对于剩下的10个不开源的合约,文章使用ethersplay 反汇编工具对于这些合约进行了逆向分析。

分析结果确认了其中有2个合约是确实曾遭受过重入攻击的,其中之一便是众所周知的The DAO合约,而另一个合约则是DSEthToken合约,其所遭受的重入攻击则是其开发者在发现合约存在有重入漏洞之后有意为之。

此外,文章还使用Oyente工具与Securify工具对这16个合约进行了分析,并比较了分析结果的误报率和漏报率。

20190218231325

20190218234954

6.2 误报分析

文章从误报的合约中总结出了一些代码模式,如何准确分析这些代码模式,是任何运行在EVM字节码层面的分析工具都极难解决的问题。

I. EVM层面缺少字段敏感性(Lack of Field-Sensitivity on the EVM Level)

20190219001523

II. 存储释放(Storage Deallocation)

20190219002148

III. 构造函数回调(Constructor Callbacks)

20190219002413

IV. 合约紧密耦合(Tight Contract Coupling)

V. 手动重入锁定(Manual Re-Entrancy Locking)

20190219002526

6.3 性能与内存开销

文章通过重复执行10,000次同样的50个区块所包含的全部交易来测量Sereum的性能开销。geth的平均运行时间为2277.0ms,而Sereum的平均运行时间为2494.5ms,Sereum带来了217.6ms或9.6%的平均性能开销。在文章的测试过程中,geth需要平均9252MB的内存,而Sereum需要平均9767MB的内存。