Evaluating Smart Contract Static Analysis Tools Using Bug Injection

SolidiFI

论文题目:(2020-ISSTA) How Effective are Smart Contract Analysis Tools? Evaluating Smart Contract Static Analysis Tools Using Bug Injection

论文引用:Ghaleb A, Pattabiraman K. How Effective are Smart Contract Analysis Tools? Evaluating Smart Contract Static Analysis Tools Using Bug Injection[J]. arXiv preprint arXiv:2005.11613, 2020.


一、主要内容

本研究的主要目标是建立一种系统的方法来评估检测智能合约的静态分析,关键思想是将漏洞注入到智能合约源代码中的所有有效位置。本文提出了SolidiFI用于评估智能合约静态分析工具的自动化系统方法。 SolidiFI基于将 Bugs(即代码缺陷)注入到智能合约的所有潜在位置中以引入针对性的安全漏洞。然SolidiFI使用静态分析工具检查生成的合同,并识别工具无法检测到的错误(假阴性,False negatives,FN)和假阳性(FP,false positives)的错误。 SolidiFI评估了六个广泛使用的静态分析工具,即Oyente,Securify,Mythril,SmartCheck,Manticore和Slither,使用了9369个不同的漏洞注入的50个合同

智能合约背景

智能合约的理论一旦部署在区块链上实际上就无法更新,并且Etherscan上的交易是一成不变的,此外,智能合约的丰厚的利益回报还为攻击者提供了诱因,使攻击者可以针对智能合约寻找漏洞。
针对智能合约的攻击事件以及这些攻击导致价值数百万美元的损失,因此实际上需要对智能合约进行分析以发现错误并进行修复,然后再部署到区块链上,当然已经提出了这样的工具使开发人员能够发现智能合约中的安全漏洞,不幸的是,这些工具不影响安全智能合约的部署,没有这样的系统性方法或方法可以用来评估此功能的有效性。(以太坊安全漏洞,黑客及其修复的历史

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pragma solidity >=0.4.21 <0.6.0;
contract EGame {
address payable private winner ;
uint startTime ;

constructor () public {
winner = msg . sender ;
startTime = block . timestamp ; // timestamp dependency bug
}
function play ( bytes32 guess ) public {
if( keccak256 (abi.encode(guess)) ==keccak256 (abi.encode ('solution '))){
if ( startTime + (5 * 1 days ) == block . timestamp){ // timestamp dependency bug
winner = msg.sender ;}}}// transaction ordering dependence(TOD)

function getReward () payable public {
winner.transfer ( msg.value );}// TOD
}

二、设计实现

漏洞类型

Timestamp dependency

合同可以使用该块的当前时间戳来触发一些与时间有关的事件。考虑到以太坊的分散性,矿工可以(在某种程度上)更改时间戳。恶意矿工可以使用此功能并更改时间戳以使自己满意。
该漏洞已在GoverMental Ponzi计划攻击中被利用。因此,开发人员不应依赖该块时间戳的精度。图4显示了代表该错误的代码段示例

1
2
3
4
function bug_tmstmp () public returns ( bool )
{
return block.timestamp >= 1546300;// Timestamp dependency
}
Timestamp dependency examples.

Unhandled exceptions

在以太坊中,合约可以相互调用,并相互发送以太币(例如,send指令,call指令等)。如果被调合约抛出了异常(例如,执行所需的限制气体),则合同终止,其状态恢复,并且将false返回给调用合同。
因此,调用合同中未经检查的返回值可用于攻击合同,从而导致不良行为。此漏洞的严重版本出现在==“King of the Ether”== 中。下图显示了一个示例(send()指令要求检查其返回值是否存在异常以确保其安全性)。

1
2
3
function unhandledsend () public {
callee.send(5 ether);// Unhandled exceptions
}
Unhandled exceptions

Integer overflow/underflow

在Solidity中,将值存储在大于或小于其限制的整数变量中会导致整数上溢或下溢。
攻击者可以使用它来欺诈地窃取资金。例如,下图显示了一个示例代码片段,其中攻击者可以通过调用函数incrLockTime并传递256作为参数来为用户重置lockTime,这将导致溢出,并最终将lockTime设置为0。Batch Transfer Overflow是个真实例子。

1
2
3
function incrLockTime ( uint _sec ) public {
lockTime [msg.sender ] += _sec ;// Integer overflow
}
Integer overflow example

Use of tx.origin

在调用链中,当合同彼此调用函数时,使用tx.origin(返回最初发送调用的第一个调用者)进行身份验证而不是使用msg.sender(返回直接调用者)进行身份验证。导致phishing-like attacks类攻击。下图显示了一个示例片段,其中使用tx.origin提取资金

1
2
3
4
function bug_txorigin (address _recipient) public {
require (tx.origin == owner);
_recipient.transfer (this.balance );
}
Use of tx.origin example

Re-entrancy

合同在其接口中公开外部调用。攻击者可以劫持这些外部调用,以多次调用合约本身内的函数,从而在合约本身内执行意外操作。例如,攻击者可以使用图中所示的代码段的第3行中的外部调用来重复调用bug_reEntrancy()函数,从而有可能导致以太币的提取多于用户的余额。 ==DAO攻击[dao 2016]==是利用此bug的一个著名示例。

1
2
3
4
5
function bug_reEntrancy ( uint256 _Amt ) public {
require (balances [msg.sender ] >= _Amt );
require (msg.sender.call.value( _Amt )); // Re-entrancy
balances [msg.sender ] -= _Amt ;
}
Re-entrance example

Unchecked send

如果外部用户对公众可见,即使他们没有正确的凭据,也可以由外部用户调用未经授权的以太坊传输,例如非零发送。这意味着未经授权的用户可以调用此类功能,并从易受攻击的合同[sol [n.d.]]中转移以太币。下图显示了一个示例代码片段。

1
2
3
function bug_unchkSend () payable public {
msg.sender.transfer (1 ether );
}
Unchecked send example

Transaction Ordering Dependence (TOD)

TOD bug 是开发人员不应依赖智能合约的状态。

在具有多次调用合同的单个块中更改交易顺序会导致更改最终输出。
恶意矿工可以从中受益。易受此bug攻击的示例代码片段如下图所示。在此示例中,攻击者可以通过在bug_tod1()之前执行bug_tod2()来向自己而不是游戏的赢家发送解决难题的奖励。

1
2
3
4
5
address payable winner_tod ;
function setWinner_tod () public {
winner_tod = msg.sender ;}
function getReward_tod ()payable public {
winner_tod.transfer ( msg.value );}
TOD example

Bug injection challenges

将bug注入智能合约的最简单方法是将它们注入随机位置,但是,随机注入并不是一种经济有效的方法,因为我们必须遵循特定的准则才能使注入的bug可以被利用。确定了两个主要挑战

Bug injection locations

由于某些工具使用的基础技术(例如,符号执行)取决于所分析合约中的控制和数据流,因此将每个Bug的实例注入单个位置是不够的。因此,应将bug注入合同代码中的所有潜在位置。
另一方面,确定潜在位置的过程取决于原始合同的代码,还取决于每个错误的类型和性质。

Semantics dependency

为了使注入的漏洞成为攻击者可以利用的活动漏洞(active bug),必须使其与原始合同的语义保持一致。例如,假设我们要通过调用外部合同来注入拒绝服务(DoS,Denial of Service)Bug。可以将if语句与包含对另一个合约函数的调用的条件一起使用。但是,要执行此错误,我们还需要定义适当的外部合同。

SolidiFI通过将Solidity语言解析为抽象语法树(AST)并将bug注入所有语法有效的位置来解决第一个挑战。它通过为每种错误类型制定可利用的代码段来应对第二个挑战。

Bug model

SolidiFI执行步骤:

  1. 将安全漏洞的代码段插入所有可能位置的每个智能合约的源代码中,注入位置的选择取决于要注入的漏洞类型。
  2. 静态分析工具扫描注入的代码。
  3. 检查每个工具的结果,并测量假阴性和假阳性。

image-20201205190103555

SolidiFI Workflow

SolidiFI从作者准备的预定义可扩展漏洞库中读取要插入的代码段,对于每个工具,仅注入该工具声称可以检测的漏洞类型。

  • Code snippets which lead to vulnerabilities
  • Injecting bugs claimed to be detected
  • Playing the role of developers rather attackers
  • Injecting distinct bugs as possible

Bug injection

通过三种方式将安全漏洞注入源代码。

Full code snippet

为每个漏洞准备了几个代码段。

Code transformation

这种方法旨在在不更改其功能的情况下转换一段代码,但使其易于受到特定bug的影响。我们利用已知的易受攻击的代码模式来注入此bug。使用这种方法来注入与该方法兼容的两个错误类,即(1)整数上溢/下溢和(2)使用tx.origin。

表1显示了替换为引入错误的代码模式的示例,以及每种错误类型的易受攻击的模式。

Table1 Code transformation patterns

image-20201206112723654

图11显示了使用这种方法进行漏洞注入之前和之后的示例。在此示例中,transfer 指令用于在验证sendto()的直接调用方为所有者之后,将指定的以太金额转移到接收者的帐户中。
要注入tx.origin错误,应将授权条件 msg.sender == owner 替换为 tx.origin == owner,其中owner不是sendto()的直接调用者。但是,授权检查已成功通过,这使攻击者可以进行自我授权,并从合同发送以太币,即使他们不是所有者。

1
2
3
4
5
6
7
8
9
/*( Before )*/
function sendto ( address receiver , uint amount ) public
{
require ( msg.sender == owner );
receiver.transfer ( amount );}
/*( After injection )*/
function sendto ( address receiver , uint amount ) public {
require (tx.origin == owner);
receiver.transfer (amount);}
Code transformation example.

Security weakening

这种方法削弱了智能合约代码中的安全保护机制,可以保护外部调用。我们的目标是评估静态分析工具,而不是智能合约本身。我们使用这种方法来注入未处理的异常错误。
下图显示了一个示例,其中通过删除revert()语句(通过该语句在转移交易失败时还原合同状态)来注入未处理的异常bug即使交易失败,余额也会错误地变为0。

1
2
3
4
5
6
7
8
9
10
11
12
/*( Before )*/
function withdrawBal () public {
Balances [ msg . sender ] = 0;
if (! msg . sender . send ( Balances [ msg . sender ])){
revert (); }}

/*( After injection )*/
function withdrawBal () public {
Balances [ msg . sender ] = 0;
if (! msg . sender . send ( Balances [ msg . sender ]))
{ // revert ();
}}

SOLIDIFI ALGORITHM

注入漏洞的过程将智能合约的抽象语法树(AST)作为输入,并具有以下步骤:

1、确定潜在的漏洞注入位置,并生成带注释的AST(annotated AST),标记所有确定的位置。

2、将bug注入所有标记的位置以生成包含漏洞的合同

3、使用评估工具检查包含漏洞的合同,并检查结果是否正确检测到漏洞。

Bug locations identification

AST传递给漏洞位置标识符(Bug Locations Identifier),该漏洞标识符为给定的安全漏洞驱动目标合同中所有可能的注入位置的漏洞注入配置文件(BIP,bug injection profile)。

使用基于AST的分析通过算法1来识别智能合约代码中的潜在注入位置,从而得出BIP。

算法1将AST和要注入的漏洞类型作为输入,并输出BIP。

image-20201206114121170

bugs 采取两种形式:单独的语句和语句块。

语句块可以定义为独立功能,也可以定义为非功能块,例如"if"语句。因此,对于每种形式的错误,都使用一个规则,该规则定义了注入错误的位置。

为了识别这样的位置,对于定义要注入的错误类型的每种不同形式的代码片段,我们根据代码片段形式和相关规则(算法1中的2-10行)遍历AST.WalkAST(simpleStatement),例如,将解析AST,并找到可以在不使合同的编译状态无效的情况下插入简单语句的所有位置,对于其他形式的错误类型的代码段也是如此。在确定了注入错误代码段的位置之后,我们还将寻找削弱现有安全机制以引入相关错误的方法,并寻找要转换以引入缺陷的代码模式(第11和12行)。

After identifying the locations for injecting code snippets of bugs, we also look for existing security mechanisms to be weakened to introduce the related bug, and the code patterns to
be transformed for introducing the bug (lines 11 and 12).

Bug injection and code transformation

SolidiFI使用系统的方法将错误注入目标合同中的潜在位置。错误注入器模型会为BIP中指定的每个位置播种错误。它使用基于文本的代码转换来修改代码,其中从AST派生的信息用于修改代码以注入错误。使用了三种不同的方法来注入错误。除了在目标合同中注入错误之外,Bug Injector还会生成一个BugLog,该BugLog为每个注入的bug指定唯一的标识符,并在目标合同中将其注入的对应位置指定一个或多个。

Buggy code check and results inspection

生成的带漏洞合同将传递到工具评估器,后者使用评估中的工具检查越野车代码。
然后,在Bug Injector生成的BugLog的帮助下,扫描由工具生成的结果以查找已注入但未被检测到的bug。 SolidiFI仅考虑未检测到的注入错误。因此,如果经过评估的工具在注入错误的位置以外的其他位置报告了错误,则SolidiFI不会在其误报输出中考虑这些错误。这是为了避免SolidiFI报告原始合同中的潜在漏洞,从而使结果不正确。此外,SolidiFI会检查由工具生成的结果,以查找其他报告的错误,并检查它们是否为真正的错误或错误警报。

三、实验评估

在我们的实验中,我们使用了从50种智能合约中选择的数据集,这些智能合约从具有不同大小和不同连接能力的扫描中选择,实际上这些合约代表了兴趣,而在我们的研究中,我们回答了这三个研究问题,分别是假阴性和假阳性

Evaluation

  • 6 static analysis tools
    (Oyente, Securify,Mythril, Smartcheck,Manticore,Slither)
  • 50 Smart Contracts representative of Etherscan (39-741 loc) ~Most Etherscancontracts size <1000 loc
  • Different functionalities and syntactic elements

RQ1: False negatives of the evaluated tools?

RQ2: False positives of the evaluated tools?

RQ3: Injected bugs can be activated?

setup

  • 7 common bug classesconsidered by the tools
  • 9,369 distinct bugs
  • Timeout: 15 minutes persmart contract

image-20201205184313316

RQ1: False negatives of the evaluated tools?

image-20201205184258116

  • None of the tools detect all bugs

  • Many undetectedcorner cases

  • Misidentification is high as well

RQ2: False positives of the evaluated tools?

Challenges:

  • Lack of ground truth
  • Large number of bugs

Approach:

为了使确定假阳性的问题易于处理,我们提出了以下方法。主要思想是仅手动检查每个智能合约的大多数其他工具未报告的错误。保守地假设大多数工具报告的错误不能为假阳性
对于每种工具,我们随机选择了大多数方法未排除的每种错误类型类别的20个错误,并手动对其进行了检查。对于那些错误数量小于或等于20的情况,我们进行了全部检查。

假设一个工具报告的错误总数为100。在这100个错误中,我们假设大多数其他智能合约工具也报告了60个错误,因此我们将其排除在外。在剩下的40个经过过滤的错误中,我们手动检查了随机选择的20个错误。假设其中16个确实为假阳性。过滤掉的错误中有80%是假阳性,并且估计假阳性的数量为32。

  • Assuming a bug reported by the majority of the tools cannot be false positive

image-20201205185024364

在表中:

  • Threshold:表示多数阈值,即必须检测该错误才能将其排除在外的工具数量,此数量取决于能够检测到该错误类型的工具数量
  • Reported显示该工具报告的错误数量
  • 子列FIL显示已通过多数方法过滤(但不排除)的错误数量
  • 子列FP显示了基于上述人工检查的工具的误报。
  • 「miscellaneous」:某些工具检测到7类之外的错误

image-20201206160927503

RQ3: Injected bugs can be activated?

由于存在大量未检测到的错误,从不同的合同中为每种错误类型随机选择了5个未检测到的错误,以测试它们的激活。
表6显示了我们激活实验的结果。在表中“ –”表示我们无法对该错误类型进行实验,因为它要求攻击者充当矿工,这将消耗大量的计算资源。
结果表明,人们可以利用(激活)其相关带有漏洞的合同中的所有选定错误。因此,激活错误(the infeasibility of activation)是不可行的,并不是评估工具无法检测到注入的错误的原因。

image-20201206162235814

四、总结评价

1、本文主要贡献发现当前的静态分析工具依然仍然不成熟,并且假阴性(false negatives,FN)和假阳性(FP,false positives)的数量过高,这个跟之前的SmartBugs的工作类似,不过是采取的另一种方法。

2、提出了一种系统性注入漏洞的方法:SolidifI