欢迎光临散文网 会员登陆 & 注册

智能合约漏洞检测

2022-05-23 19:32 作者:安静的秃头怪  | 我要投稿


漏洞介绍

智能合约漏洞分类图


1. 可重入漏洞(Reentrancy attack)

案例:The DAO 事件,最终损失360万枚ETH并导致以太坊硬分叉

本质:在调用取钱函数的时候会执行fallback回调函数(一般用于输入日志),攻击者利用该性质在fallback函数中修改逻辑,导致函数递归调用取钱函数,因此造成账户的所有ETH被转走

利用两点:程序的健壮性fallback函数

注:合约在以下两种情况下会执行fallback函数

  • 当外部账户或其他合约向该合约地址发送 ether 时;

  • 当外部账户或其他合约调用了该合约一个不存在的函数时;

up主RuiChao_web3视频中的代码截图1


4种解决方法

  • 声明一个全局的控制变量,使程序的运行受到控制(互斥锁)


up主RuiChao_web3视频中的代码截图2
  • 完备程序的健壮性,通过调整代码的顺序防范攻击


up主RuiChao_web3视频中的代码截图3
  • 更换calltransfer,因为后者的gas费有限制为2300,这点gas只能用来打印日志,而不支持多次回调;call的gas费无限制,所以应该根据具体需求来选择调用call还是transfer实现转币


up主RuiChao_web3视频中的代码截图4
三个底层调用函数的区别
  • 使用 OpenZeppelin 官方提供的 ReentrancyGuard 修饰器来防止重入攻击的发生


2. 数值溢出漏洞(Over and under flows)

案例:美链BEC代币

美链BEC代币案例


在 Solidity 中 uint 默认为 256 位无符整型,可表示范围 [0, 2^256-1],在下面的示例代码中通过做差的方式来判断余额,如果传入的 _amount 大于账户余额,则 balances[msg.sender] - _amount 会由于整数下溢而大于 0 绕过了条件判断,最终提取大于用户余额的 Ether,且更新后的余额可能会是一个极其大的数。

解决方案:将条件判断改为 require(balances[msg.sender] > _amount),这样就不会执行算术操作进行逻辑判断,一定程度上避免了整数溢出的发生。

总之,为了防止整数溢出的发生,一方面可以在算术逻辑前后进行验证,另一方面可以直接使用 OpenZeppelin 维护的一套智能合约函数库中的 SafeMath 来处理算术逻辑。


3. 重放攻击(Replay attack)

重放攻击是指“一条链上的交易在另一条链上也往往是合法的”,所以重放攻击通常出现在区块链硬分叉的时候。由于硬分叉的两条链,它们的地址和私钥生产的算法相同,交易格式也完全相同,因此导致在其中一条链上的交易在另一条链上很可能是完全合法的。所以你在其中一条链上发起的交易,就可以到另一条链上去重新广播,可能也会得到确认。

解决方案

  1. 最常用的方式便是完善交易格式,让分叉点之前即存在的资金所关联的交易只能在一条链上生效,这就要让分叉后的链条在交易结构上做出区别。对于以升级为目的的分叉而言很容易就能达到,因为硬分叉升级将采用不同的客户端版本,交易的前缀中通常包含有发起交易客户端的版本信息。分叉后矿工为了避免打包旧客户端的“非法交易”(并非恶意交易,仅仅只是版本号过低不被其他节点所承认),通常会拒绝一定版本号以前的交易,保证恶意攻击者很难在硬分叉升级时通过重放攻击窃取资金。

  2. 对于非升级目的发起的分叉,不论是算力战争导致的项目分裂,还是恶意攻击者发起的分叉攻击,由于交易发起版本大概率是不会变化的,分叉后的链条无法通过版本验证来防止重放攻击,需要在其他方向做出努力。常见的方式之一是为每一个交易附加上随机数Nonce,尽管Nonce一般被认为是用于防止重复交易与双花交易的,但防止重放攻击方面,Nonce也功不可没。

    重放攻击需要监听一条链上的交易情况并转发至另一分叉链上以窃取资金,如果在一条分叉链上发起的交易,其使用的随机数已被另一条分叉链中的相同用户发起的交易所使用,那么节点将不会广播该交易,矿工也不会打包此可疑交易。对于普通用户而言只需要重新设立随机数并用私钥签名即可恢复交易。


4. 重排攻击(Reordering attack)

这种攻击是指矿工或其他方试图通过将自己的信息插入列表(list)或映射(mapping)中来与智能合约参与者进行“竞争”,从而使攻击者有机会将自己的信息存储到合约中。


5. 短地址漏洞(Short address attack)

将参数传递给智能合约时,参数根据 ABI 规范进行编码,可以发送比预期参数长度短的编码参数(例如,发送一个只有 38 个十六进制字符(19 个字节)而不是标准的 40 个十六进制字符(20 个字节)的地址)。在这种情况下,EVM 将在编码参数的末尾添加零以构成预期长度。

例子:

现在考虑一个持有大量代币的交易所和一个希望提取其 100 个代币的用户。该用户提交自己的地址0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddead和需要取的代币数量100,交易所将按照transfer函数指定的顺序对这些参数进行编码;也就是 address+tokens。则编码结果为:

总字节数为4(transfer函数签名)+32(合约地址)+32(代币数量)。

如果我们发送的地址缺少1个字节(2个十六进制数字),假设攻击者发送地址为0xdeaddeaddeaddeaddeaddeaddeaddeaddeadde(缺少后两位数字),代币数量不变为100,则它将被编码为:

此时整串编码末尾会补上两个0以达到预期长度4+32+32,但这时tokens将被读取为56bc75e2d6310000000,该值现在是25600 tokens,而交易所认为用户只是在提取100 tokens。

解决方案:在将外部应用程序中的所有输入参数发送到区块链之前,应对其长度进行验证

注:短地址攻击通常发生在接受畸形地址的地方,如交易所提币、钱包转账,所以除了在编写合约的时候需要严格验证输入数据的正确性,而且在 Off-Chain 的业务功能上也要对用户所输入的地址格式进行验证,防止短地址攻击的发生。


6. 拒绝服务漏洞(Denial of Service)

此类别非常广泛,攻击者通过破坏合约中原有的逻辑,消耗以太坊网络中的资源(如以太币和 Gas),从而使合约在一段时间内无法正常执行或提供正常服务。

针对智能合约的 DoS 攻击方式通常有 3 种:

  1. 通过Revert (可触发回滚)发动 DoS 攻击。当智能合约状态的改变由外部函数的执行结果决定并且这个执行一直失败时,若未对函数执行失败的情况进行处理,将会使智能合约处于容易遭到 DoS 攻击的状态;

  2. 通过以太坊区块的 Gas 限制发动 DoS 攻击。以太坊网络中每个区块都设定了 Gas 上限,如果交易花费的 Gas 超过上限会导致交易失败。因此,即使没有受到恶意攻击,智能合约的运行也可能因为超过 Gas 限制而出现问题;更严重的情况是,若攻击者恶意操纵 Gas 消耗而导致其达到区块上限,则会使合约的交易过程以失败告终;

  3. 合约 Owner 账户发动 DoS 攻击。很多智能合约都有 Owner 账户,其拥有开启或停止合约交易的权限,若没有保护好 Owner 账户,导致其被攻击者操控,很可能会使合约交易被永久冻结。


7. 条件竞争(Front Running)

条件竞争也叫抢跑提前交易,就是攻击者提前获取到交易者的具体交易信息(或者相关信息),抢在交易者完成操作之前,通过一系列手段(通常是提高报价)来抢在交易者前面完成交易。

在以太坊中所有的 TX 都需要经过确认才能完全记录到链上,而每一笔 TX 都需要带有相关手续费gasPrice,而手续费的多少也决定了该笔 TX 被矿工确认的优先级,手续费高的 TX 会被优先得到确认,而每一笔待确认的 TX 在广播到网络之后就可以查看具体的交易详情,一些涉及到合约调用的详细方法和参数可以被直接获取到。那么这里显然就有 Front-Running 的隐患存在了。

解决方案:对链上数据进行加密


8. 访问控制漏洞(Access Control)

根本原因:未能明确或未仔细检查合约中函数的访问权限,从而允许恶意攻击者能进入本不该被其访问的函数或变量。

主要体现在 2 个层面:

  • 合约代码层面。Solidity 智能合约函数和变量的访问限制有 4 种,即 public、private、external、internal。如果函数未使用这些标识符,那么默认情况下,智能合约函数的访问权限为 public,亦即该函数允许被本合约或其他合约的任何函数调用,这种情况可能导致该函数被攻击者恶意调用;

  • 合约逻辑层面。通常使用函数修饰器对函数或变量进行约束。例如,某些关键函数需要使用修饰器 onlyOwneronlyAdmin 来约束,若未给这些函数添加修饰器,任何人都有权利访问并操纵这些关键函数,则很有可能导致关键函数被恶意攻击者操纵,从而进一步地破坏智能合约逻辑。

4种常用访问控制修饰符


9. 异常处理漏洞

以太坊智能合约中,有 3 种情况会抛出异常:

  1. 执行过程中的 Gas(即部署或执行智能合约的费用)消耗殆尽;

  2. 调用栈溢出(最大层数1024);

  3. 执行语句中有 throw 命令。

然而,智能合约中的一些底层调用函数(如 send,call,delegatecall)发生异常时只返回 False,而不抛出异常。因此仅仅根据有无异常抛出就判断合约执行是否成功是不安全的,在调用底层函数时必须严格检查返回值,并且对异常采用一致性的处理方式。


10. 区块参数依赖漏洞

以太坊智能合约中无法直接创建随机数,合约开发者往往会编写随机函数来产生随机数,一般通过区块号(block.number)、区块时间戳(block.timestamp)或者区块哈希(block.blockhash)等相关的区块参数或信息作为产生随机数的种子。然而由于随机数生成依赖的这些区块参数可以被矿工提前获取,这将导致生成的随机数是可预测的,从而可能会被恶意攻击者利用并产生对他们有利的随机数。


漏洞检测方法

1. 形式化验证法

在智能合约部署之前,对其代码和文档进行形式化建模,然后通过数学的手段对代码的安全性和功能正确性进行严格的证明,可有效检测出智能合约是否存在安全漏洞和逻辑漏洞。该方法可以有效弥补传统的靠人工经验查找代码逻辑漏洞的缺陷。

  1. 模型检测列举出所有可能的状态并逐一检验,基本思想是通过状态空间搜索来确认合约是否具有相应的性质。

  2. 演绎验证该方法基于定理证明的思想,采用逻辑公式描述系统及其性质,通过一些推理规则证明系统具有某些性质。


缺点:需要交互式的验证与判断,因此自动化程度较低,并且依赖人工二次核验,导致其无法较好地兼容 EVM 执行层漏洞。同时,由于形式化验证手段依赖于严谨的数学推导与验证,它无法执行动态分析,并且缺少对合约中可执行路径的检测与判断,从而导致了较高的误报率和漏报率。

方案:

(1) F* framework. F是一种形式化验证方法,通过将 EVM 字节码的语义形式化并把 EVM 字节码编译成 Ocaml 形式,最终将智能合约源码和字节码转化成函数编程语言 F,以便分析和验证合约的安全性和功能正确性,从而成功检测以太坊智能合约漏洞.

(2) KEVM framework. KEVM 是一种形式化分析的框架,它利用 K 框架构建了基于 EVM 字节码栈的可执行形式化规范,提供了 EVM 的规范、参考解释器以及用于程序分析和验证的工具.

(3) Isabelle/HOL. Isabelle/HOL 是一种 EVM 字节码级别的形式化验证方法,其通过将字节码序列构造成直线代码块或将合约拆分成基本块,并在此基础上创建逻辑程序进行推理验证.

(4) ZEUS. ZEUS 是一种静态分析工具, 其基于形式化验证的方法能够正确且公平地分析智能合约.ZEUS 利用抽象解释和符号模型检查以及约束语句来快速验证合约的安全性,支持多种智能合约漏洞的检测,例如可重入漏洞、整数溢出漏洞、异常处理漏洞等.

(5) VaaS. VaaS 是基于形式化验证方法的“一键式”智能合约安全检测平台,它可以自动检测智能合约中 10 大项 32 小项的常规安全漏洞,并且能够精准定位风险代码位置以及给出修改建议.

2. 符号执行法

主要思想是将代码中的变量符号化。通过符号化程序输入、符号执行能够为所有的执行路径维护一组约束,执行之后,约束求解器将用于求解约束并确定导致该执行的输入,最后利用约束求解器得到新的测试输入,检测符号值是否可以产生漏洞。

符号执行法应用于智能合约漏洞检测的执行过程为:首先,将合约中的变量值符号化,然后逐条解释执行程序中的指令,在解释执行过程中更新执行状态、搜集路径约束,以完成程序中所有可执行路径的探索并发现相应的安全问题。

缺点:显著地提高了漏洞分析过程中的计算资源和时间开销,并且无法彻底解决状态空间爆炸与执行路径指数级增长等问题。另外,很多符号执行法其实并不能做到完全自动化,同样需要人工协助与反馈。

方案:

(1) Oyente. Oyente 是最早的智能合约漏洞静态检测工具之一,其在合约控制流图的基础上利用符号执行的方法检测智能合约漏洞.Oyente 以智能合约字节码和以太坊状态作为输入,模拟 EVM 并且遍历合约的不同执行路径,其支持检测的漏洞类型包括可重入漏洞、异常处理漏洞、交易顺序依赖漏洞等.

(2) Maian. Maian 是一种基于符号分析的智能合约分析工具,它通过长序列的合约调用过程来发现安全漏洞.区别于一般的合约分析工具,Maian 只专注于 3 种类型的合约漏洞,即资产无限期冻结的合约漏洞 (Greedy),易泄露资产给陌生账户的合约漏洞(Prodigal),合约可以被任何人随意销毁的漏洞(Suicidal).

(3) Securify. Securify 是一种用于以太坊智能合约的静态安全分析器,具有可伸缩、完全自动化、准确率高等特性,其通过分析合约的依赖图以及从代码中提取精确的语义信息来检查合约的合规性与安全漏洞.

(4) Mythril. Mythril 是一种智能合约静态分析工具,其使用概念分析、污点分析以及控制流验证来检测以太坊智能合约中常见的漏洞类型,包括可重入漏洞、整数溢出漏洞、异常处理漏洞等.

(5) TeEther. TeEther 是一种智能合约静态分析工具,区别于一般的漏洞检测工具,它考虑了智能合约漏洞自动识别以及合约生成方法,并通过分析合约字节码查找关键的执行路径以检测合约的安全问题.

(6) Sereum. Sereum 是一种专注于智能合约可重入漏洞的新颖检测方案,该解决方案利用动态污点跟踪监视智能合约执行过程中的数据流,从而自动检测并防止状态不一致的情况,有效地检测了可重入攻击.

3. 模糊测试法

从概念上讲,模糊测试从目标应用程序中生成大量正常和异常的测试用例,尝试将生成的用例提供给目标应用程序并监视执行状态中的异常结果以发现安全问题。与其他技术相比,模糊测试具有良好的可扩展性和适用性,可以在没有源代码的情况下执行。

缺点:依赖于精心设计的测试用例,对导致漏洞的具体语义代码洞察有限,很难追踪到存在漏洞的确切代码位置,同时由于测试用例的随机性,其无法达到理想的测试路径覆盖率,很难找出所有的潜在威胁。

方案:

(1) ContractFuzzer. ContractFuzzer 是第一个基于模糊测试的以太坊智能合约安全漏洞的动态分析方法,其基于智能合约 ABI 规范生成模糊测试用例并定义测试方案来检测安全漏洞.首先,ContractFuzzer 对 EVM 进 行 配 置 并 记 录 智 能 合 约 运 行 时 的 行 为 , 然 后 通 过 分 析 这 些 日 志 并 检 测 漏 洞 .

(2) Regurad. Regurad 是一种专注于智能合约可重入漏洞的模糊测试分析器,其通过迭代生成随机且多样化的测试用例对智能合约执行模糊测试,从而在合约的执行过程中进行跟踪,进一步地动态识别可重入漏洞.

(3) ILF. ILF 是基于神经网络的智能合约模糊测试器,它利用符号执行引擎生成有效的测试和调用序列,以指导神经网络模型的特征学习,从而实现有效的漏洞检测.

4. 中间表示法

研究者们通过将智能合约源码或字节码转换成具有高语义表达的中间表示(intermediary representation,简称 IR),然后利用控制流、数据流以及污点分析等手段对合约的中间表示进行分析以发现安全问题。

缺点:依赖于预定义的语义规则或分析列表,从而无法检测出智能合约复杂的业务逻辑问题且极易产生误报。另外,它们无法对合约中可能存在的执行路径进行遍历。

方案:

(1) Slither. Slither 是一种以太坊智能合约的静态分析框架,它将智能合约 Solidity 源代码转换为 SlithIR 的中间表示,SlithIR 使用静态单一分配(SSA)形式和精简指令集来简化合约分析过程,同时保留了 Solidity 源代码转换为 EVM 字节码时丢失的语义信息.

(2) Vandal. Vandal 是一种 EVM 字节码层面的智能合约静态分析工具,它由一个分析管道和一个反编译器组成.该反编译器执行抽象解释,以逻辑关系的形式将字节码转换为更高级别的中间表示(IR),然后使用新颖的逻辑驱动方法检测合约漏洞.

(3) Madmax. Madmax 是一种专注于以太坊智能合约 Gas 相关的漏洞分析工具,它基于 Vandal 实现了 控制流分析和反编译器的程序结构性检测方法,该工具同样将 EVM 字节码反编译成具有高语义信息的中间表 示,能够高精度地检测 Gas 相关的漏洞,例如以太冻结漏洞等.

(4) Ethir. Ethir 是一种基于 EVM 字节码层面的分析工具,它基于 Oyente 生成控制流图(CFG),然后将 CFG 转换为基于规则的中间表示(RBP),从而分析和推断 EVM 字节码的安全属性.

(5) Smartcheck. SmartCheck 是一种可扩展的智能合约静态分析工具,它将智能合约 Solidity 源代码转换为基于 XML 的中间表示,然后利用 XPath 的模式来检测智能合约漏洞.

(6) ContractGuard. ContractGuard 是一种面向以太坊智能合约的入侵检测工具,它基于入侵检测系统 (IDS)检测潜在攻击引发的异常控制流,通过有效的上下文标记(context-tagged)无环路径实现入侵检测.

5. 深度学习法

缺点:其大多数情况下可解释性较差,即无法像传统的检测工具一样给出可能存在漏洞的确切位置或代码行。

方案:

(1) SaferSC. SaferSC 是第一个基于深度学习的智能合约漏洞检测模型,其基于 Maian 划分的 3 类合约漏洞,实现了比 Maian 更高的检测准确率.此外,SaferSC 在智能合约操作码(operation code,简称 opcode)层面进行分析,利用 LSTM 网络构建了以太坊操作码序列模型,实现了精准地智能合约漏洞检测.

(2) ReChecker. ReChecker 是第一个基于深度学习的智能合约可重入漏洞检测方法,其通过将智能合约 Solidity 源 代 码 转 换 为 合 约 块 (contract snippet) 的 形 式 , 捕 获 了 合 约 中 基 本 的 语 义 信 息 和 控 制 流 依 赖 信 息.ReChecker 利用双向长短期记忆模型(Bidirectional Long Short-Term Memory,简称 BLSTM)和注意力机制 (Attention)实现了以太坊智能合约可重入漏洞的自动化检测.

(3) DR-GCN. DR-GCN 是第一个利用合约图(contract graph)的方式来检测智能合约漏洞,其将智能合约源代码转换为具有高语义表示的合约图结构,并利用图卷积神经网络构建了安全漏洞检测模型.DR-GCN 支 持 2 个平台(即以太坊和维特链)的智能合约漏洞分析,能够检测可重入漏洞、时间戳依赖漏洞以及死循环漏洞.

(4) TMP. TMP 通过将智能合约中的关键函数和关键变量转换成具有高语义信息的核心结点来构建合约图,关键的执行方式转换成控制流和数据流依赖的有向时序边.TMP 在 DR-GCN 的基础上考虑了合约图中边的时序信息,并利用时序图神经网络实现了相应的智能合约漏洞检测.

(5) ContractWard. ContractWard 从智能合约操作码中提取 bigram 特征,利用多种机器学习算法和采样算法进行智能合约漏洞检测,其总共支持 6 种漏洞类型,包括可重入漏洞、整数溢出漏洞以及时间戳依赖漏洞.

目前检测工具存在的问题

  1. 漏洞检测准确率低

  2. 漏洞类型覆盖率低

  3. 漏洞审计时间较长

  4. 漏洞检测完全自动化

  5. 智能合约语言多样性

改进思路

  1. 提高形式化验证自动化程度,扩展应用范围

    重点:自动化程度不高,需突破其不适应大规模合约及多漏洞类型等技术限制

  2. 提取符号执行重点路径,缩减路径空间

    重点:状态空间爆炸和执行路径指数级增长,可定义易产生漏洞的高危指令重点路径

  3. 完善测试用例,改进模糊测试工具

    重点:改进现有的测试用例生成算法,考虑结合其他检测方法来提高检测效率

  4. 优化中间表现形式,结合动态执行

    重点:兼顾不同智能合约的统一表示形式

  5. 加强深度学习可解释性,融合专家规则

    重点:改善对漏洞检测结果的合理解释(如标注可能存在漏洞的确切代码位置或代码行)


ps:钱鹏,刘振广,何钦铭,黄步添,田端正,王勋.智能合约安全漏洞检测技术研究综述.软件学报,2021.


智能合约漏洞检测的评论 (共 条)

分享到微博请遵守国家法律