Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

ContractFuzzer: Fuzzing Smart Contracts for Vulnerability Detection

作者:Bo Jiang, Ye Liu, W.K. Chan

单位:Beihang University

出处:ASE’18

原文:http://delivery.acm.org/10.1145/3240000/3238177/ase18main-p52-p.pdf

摘要

智能合约是运行在区块链共识协议之上的程序代码,其能够使人们在最小化彼此之间信任的同时达成协定。如今,数百万的智能合约被部署在各种各样的去中心化应用中,然而存在于智能合约中的安全漏洞却对这些去中心化应用造成了巨大的威胁。

本文提出了ContractFuzzer工具,该工具是一个全新的针对于以太坊智能合约安全漏洞检测的模糊测试工具,其能够根据智能合约的ABI规范生成模糊测试输入,定义检测安全漏洞的测试预言(test oracle),通过对以太坊虚拟机(EVM)插桩记录智能合约的运行时状态,分析日志并报告安全漏洞。

文章使用ContractFuzzer工具对6991个智能合约实例进行了模糊测试,以极高的精度发现了这些合约中存在的459个安全漏洞。

1 介绍

文章总结了智能合约容易受到安全攻击的几个原因:

  • 智能合约的运行依赖于底层的区块链平台以及其它协作合约,合约开发者没能完全理解这些合约之间以及合约与底层区块链平台之间隐含的关系;
  • 智能合约的编程语言与运行环境对于合约开发者来说都是全新的,且这些工具还不够成熟,合约开发者没能很好的处理这些工具自身的不足;
  • 区块链具有不可篡改的性质,智能合约在被部署至区块链平台之后难以更新。

虽然以往的工作中提出了几种检测智能合约安全漏洞的验证工具,但这些工具依旧存在着一些局限性:

  • 检测策略可能不精确,容易导致高误报率;

  • 符号验证所有可行的路径可能存在路径爆炸的问题,如果仅验证某些路径容易导致漏报。

本文的主要贡献如下:

  • 文章实现了第一个用于检测以太坊智能合约安全漏洞的模糊测试框架;
  • 文章提出了一系列用于精确检测真实智能合约安全漏洞的测试预言(test oracle);
  • 文章系统地对以太坊平台上的6991个真实的智能合约进行了模糊测试,ContractFuzzer工具发现了459个合约中存在的安全漏洞。

2 智能合约综述

2.1 以太坊智能合约基础

从概念上来说,以太坊平台可以被视为一个基于交易的状态机,其状态在每次交易时进行更新。交易的有效性由底层区块链平台的共识协议进行验证。

2.2 以太坊智能合约安全漏洞

基于区块链的去中心化应用中的安全漏洞可能存在于区块链层面、以太坊虚拟机(EVM)层面以及智能合约层面。文章的关注点主要集中在以太坊智能合约中的安全漏洞。

Gasless Send

当合约使用Send函数发送以太币至某一合约时,接收合约中的后备函数(fallback function)将会被调用,该函数能够使用的Gas是固定的。如果接收合约中的后备函数执行所需要的Gas超过该限制,发送合约将会收到一个ErrOutOfGas的异常。如果该异常没有被捕获或者没有被正确地传递,一个恶意的发送合约就可以在看似无辜的情况下非法占有接收合约所应得的以太币。

Exception Disorder

在Solidity语言中,不同的函数调用方式对于异常的处理是不一致的。对于一个嵌套调用链,如果其中的每一个调用都是对合约函数的直接调用,当异常发生时,所有的交易包括以太币转账都会被还原。然而,如果其中的某一个调用是通过Call、DelegateCall或者Send这些低级调用方法进行的,交易的回滚便会在该调用函数处停止并返回False。这种异常处理的不一致性将可能导致调用合约无法知晓整个嵌套调用链中发生的异常。

Reentrancy

由于合约开发者的疏忽,合约的某些函数并没有被设计为可以重复进入,一个恶意的合约能够通过刻意重复进入这些函数造成合约损失以太币。

Timestamp Dependency & Block Number Dependency

当一个智能合约使用区块的时间戳或高度作为发送以太币等关键操作的执行条件,或者作为生成随机数的熵的来源时,一个恶意的矿工能够通过修改区块的时间戳或高度以利用该漏洞攫取利益。

Dangerous DelegateCall

DeletegateCall函数调用其它合约的代码在当前合约的上下文环境中执行。当DelegateCall函数的参数是由当前合约调用者指定时,一个攻击者便可以利用该漏洞使得当前合约执行其它合约的任意函数。

20181229133404

Freezing Ether

一个存在Freezing Ether漏洞的合约能够接收以太币,但是它自己的代码中没有任何能够发送以太币的函数,只能通过DelegateCall函数调用其它合约的代码发送以太币,即该合约完全依赖于其它合约中的代码向外发送以太币。如果该被依赖的合约执行了自杀或自毁操作,则原合约将没有任何方法向外发送以太币,其持有的所有以太币都被冻结了。

3 针对智能合约漏洞定义测试预言

Gasless Send

测试预言GaslessSend检查:

  • 函数调用是通过Send函数进行的,即该函数调用的输入为0,Gas限制为2300;
  • 函数调用在执行时返回错误码ErrOutOfGas。

Exception Disorder

对于一个嵌套调用链,测试预言ExceptionDisorder检查:

  • 由原始调用开始的嵌套调用链中的调用抛出异常,但是原始调用没有抛出异常。

Reentrancy

测试预言Reentrancy包括两个子预言ReentrancyCall与CallAgentWithValue:

Reentrancy = ReentrancyCall & CallAgentWithValue

子预言ReentrancyCall检查: + 原始函数调用在由其开始的嵌套调用链中出现了不止一次。

子预言CallAgentWithValue检查: + 函数调用所发送的以太币大于0; + 被调用函数拥有充足的Gas执行复杂的代码,即函数调用不是通过Send函数或Transfer函数进行的; + 被调用合约由原始合约调用者指定,而不是硬编码在原始合约中的。

Timestamp Dependency & Block Number Dependency

测试预言TimestampDependency/BlockNumDependency包括三个子预言TimestampOp/BlockNumOp、SendCall与EtherTransfer:

TimestampDependency/BlockNumDependency = TimestampOp/BlockNumOp & (SendCall EtherTransfer)

子预言TimestampOp/BlockNumOp检查:

  • 当前合约的执行过程中执行了TIMESTAMP/NUMBER操作符。

子预言SendCall检查:

  • 函数调用是通过Send函数进行的。

子预言EtherTransfer检查

  • 函数调用所发送的以太币大于0。

Dangerous DelegateCall

测试预言DangerDelegateCall检查:

  • 当前合约执行过程中通过DelegateCall函数进行了函数调用;
  • DelegateCall函数的参数是由当前合约调用者指定的。

Freezing Ether

测试预言FreezingEther检查:

  • 当前合约能够接收以太币;
  • 当前合约执行过程中通过DelegateCall函数进行了函数调用;
  • 当前合约自己的代码中没有transfer/send/call/suicide函数。

4 智能合约模糊测试工具

20181229144311

4.1 ContractFuzzer概述

ContractFuzzer工具包括一个线下的EVM插桩工具以及一个线上的模糊测试工具。线下的EVM插桩工具通过对EVM进行插桩,使得模糊测试工具能够监视智能合约的执行并提取执行日志用于漏洞分析。

文章实现了一个网络爬虫从Etherscan网站上爬取以太坊平台上已经部署的智能合约,爬取内容包括合约创建代码、ABI接口与构造函数参数。文章将爬取的智能合约重新部署在自己搭建的以太坊测试网络中,一方面作为之后模糊测试的对象,另一方面作为使用合约地址作为参数的合约调用的输入。

线上的模糊测试过程如下:

  • 分析测试智能合约的ABI接口以及字节码,提取ABI函数的每一个参数的数据类型以及ABI函数中所使用到的函数签名;
  • 对于所有从以太坊平台上爬取的智能合约进行ABI签名分析,并根据各个智能合约所支持的函数签名将其进行索引;
  • 生成与ABI规范相符的合法模糊测试输入以及越过有效边界的突变输入;
  • 启动模糊测试,通过随机的函数调用,使用生成的输入调用相应的ABI接口;
  • 分析模糊测试过程中生成的执行日志,检测安全漏洞。

4.2 静态分析智能合约

分析合约池中的ABI函数签名

ContractFuzzer工具从每个合约导出的JSON格式的ABI中提取所有声明的函数签名,并对于每个函数签名计算函数选择器的值(function selector),最后生成一个以不同的函数选择器作为键,以具有相同函数选择器的智能合约的地址向量作为值的映射。

静态分析测试智能合约

ContractFuzzer工具通过解析JSON格式的合约ABI接口,以提取合约的函数描述以及每个参数的数据类型。虽然大部分数据类型的输入域都是确定的,但是考虑到合约函数的参数可能是一个合约地址,函数通过调用Call函数与其它合约进行交互。因此,在给具有地址数据类型参数的ABI函数生成输入时,工具必须选择支持当前合约Call函数调用的函数选择器的合约的地址作为输入。

基于以上观察,ContractFuzzer工具对于测试智能合约的字节码进行了静态分析,以确定每个公开ABI函数所调用的函数选择器。

20181229154951

ContractFuzzer工具对测试智能合约的每一个ABI函数都生成了一个私有的合约池,合约池中存放所有支持该ABI函数所调用的函数选择器的合约的地址。当ContractFuzzer工具需要给某一ABI函数生成地址数据类型的模糊测试输入时,便会选择该ABI函数的私有合约池中存放的合约地址作为输入。

4.3 生成模糊测试输入

基于ABI接口生成输入

ContractFuzzer工具使用不同的策略生成定长参数和不定长参数的输入。

对于定长参数,ContractFuzzer工具首先随机地从参数的合法输入域中选择一组数值作为输入集,随后再根据合约的静态分析结果,把在合约中经常使用到的该类型数据的数值添加进输入集中。

对于不定长参数,ContractFuzzer工具首先随机地生成一个正数作为该参数的长度,随后再对参数中的各个元素随机地从合法输入域中选择一组数值作为输入集。

针对Reentrancy漏洞生成输入

Reentrancy漏洞的触发需要两个智能合约之间的交互调用,所以简单地通过一个外部账户调用测试合约是没有办法触发Reentrancy漏洞的。

因此,文章构造了一个AttackerAgent合约,该合约通过与测试合约的每个ABI函数进行交互并尝试重复进入这些函数,以实现对于测试合约Reentrancy漏洞的检测。

20181229162453

4.4 插桩EVM收集测试预言

ContractFuzzer工具通过插桩EVM,以收集三种类型的信息,包括合约调用Call函数与DelegateCall函数的各种属性,合约执行过的操作码以及执行过程中的合约状态。

收集Call/DelegateCall/Send函数的信息

基于Call函数、DelegateCall函数以及Send函数行为上的相似性,ContractFuzzer工具在插桩EVM的过程中使用相同的数据结构记录以上函数的信息,同时也记录由原始函数调用开始的嵌套调用链的信息。

20181229163513

收集操作码的信息

ContractFuzzer工具通过对EVM中的解释器实现,即interpreter.Run()函数进行插桩,以监视每个操作码的解释执行以及操作码的执行对合约状态的改变。

5 实验和结果分析

文章从Etherscan网站上爬取了共6991个互不相同且包含经过验证的源代码的智能合约作为实验对象。

对于测试智能合约的每个ABI函数,ContractFuzzer工具使用三种类型的账户对其进行调用,包括:创建合约的外部账户,与合约无关的普通外部账户,AttackerAgent合约账户。此外,通过每种类型的账户,工具分别使用发送以太币和不发送以太币两种模式对ABI函数进行调用。对于每个包含参数的ABI函数,工具生成k个输入作为函数的输入集。

ContractFuzzer工具把对于某个合约生成的所有调用合并成一个调用池,并随机从调用池中选择调用进行对该合约进行模糊测试,以模拟合约中ABI函数的不同调用序列。文章使用ContractFuzzer工具对所有测试智能合约共进行了80个小时的模糊测试,直至实验结果逐渐趋同。

20181229164413

Timestamp Dependency与Block Number Dependency漏洞的误报率来源于二者测试预言定义的不精确,ContractFuzzer工具没有对函数中的涉及这两个漏洞的数据流做进一步的检查。

20181229164433

ContractFuzzer工具的漏报率来源于:

  • 一些合约把特定的时间信息硬编码在代码中并使用区块的时间戳与其进行比较,工具的模糊测试输入无法触发漏洞;
  • 一些条件判断难以在有限的模糊测试时间内触发。

20181229164459

Oyente工具的漏报率来源于符号执行过程中约束求解器无法分析散列函数等密码操作。

20181229164517