🐧17 合约安全

[TOC]

合约安全

智能合约的可信度源自其不可篡改性,一旦被部署上链就无法修改。如果合约存在漏洞也会因为无法更新修复漏洞而成为一个天坑的存在。智能合约的发展时间并不长,还很缺少成熟的安全工具进行安全审计,增加了合约出现漏洞的可能。

合约中出现频率最高的的安全问题有代码重入、访问控制、整数溢出、未严格判断不安全函数调用返回值、拒绝服务 DoS、可预测的随机处理、竞争条件/非法预先交易、时间戳依赖、短地址攻击等,还有一些其它未知的漏洞。针对智能合约安全问题,应该从使用安全库进行开发、安全团队开展合约测试和合约审计这三个角度采取措施。

合约审计

形式化验证

形式化验证用逻辑语言对智能合约文档和代码进行形式化建模,通过严谨的数学推理和证明,检查智能合约的功能正确性和安全性。严谨的数学推理和证明克服了传统测试手段无法穷举所有可能输入的缺陷,能完全覆盖代码的运行期行为,确保代码在一定范围内的绝对正确。形式化验证弥补了合约测试和合约审计工作的局限性。

Coq\Isabelle/HOL、Why3 等工具实现了 EVM 的语言表示,并且进行了一些形式化验证智能合约的工作。还有一些智能合约分析和验证框架通过 Solidity 和 EVM 工具将智能合约源代码和字节码转化成函数编程语言 F*,以便分析和验证智能合约运行时的安全性和功能正确性。

模糊测试

模糊测试是一种通过构造非预期的输入数据并监视目标软件在运行过程中的异常结果来发现软件故障的方法。对智能合约进行模糊测试时,会利用随机引擎生成大量的随机数据,构成可执行交易。参考测试结果的反馈,随机引擎动态调整生成的数据,从而探索尽可能多的智能合约状态空间。基于有限状态机分析每一笔交易的状态,检测是否存在攻击威胁。自动化工具 Echidna 采用了模糊测试技术来对 EVM 字节码进行检测,但是不能保证 API 功能的稳定性。

符号执行

符号执行的核心思想是使用符号值代替具体值执行程序。对于程序分析过程中任意不确定值的变量,包括环境变量和输入等,都可以用符号值代替。符号执行中的是指解析程序可执行路径上的指令,根据其语义更新程序执行状态,等同于解释执行。

借助符号执行检测智能合约漏洞的一般过程是先将智能合约中不确定值的变量符号化,然后逐条解释执行程序中的指令。在解释执行的过程中更新执行状态、搜集路径约束,并在分支节点进行 fork 执行,以完成程序中所有可执行路径的搜索,发现安全问题。约束求解技术能够对符号执行中搜集的路径约束进行求解,判断路径是否可达,并且在特点的程序点上检测变量的取值是否符合程序安全的规定,或者是否满足漏洞存在的条件。

污点分析

污点分析是针对污点信息的数据流分析技术。污点分析的一般流程是先识别污点信息在智能合约中的产生点并对其进行标记。其次按照实际需求和污点传播规则进行前向或后向数据依赖分析,得到污点的数据依赖被依赖关系的指令集合,最后在一些关键的程序点检查关键的操作是否会受到污点信息的影响。

漏洞分析

针对智能合约中出现频率较高的问题进行漏洞分析。

短地址攻击

满足 ERC-20 标准的代币实现的 transfer 方法,在 ERC-20 标准定义了函数的名称、参数类型、返回值和具体的行为。方法的第一个参数是接收代币的地址,第二个参数是发送的代币的数量。

当调用 transfer 函数向某个地址发送 N 个 ERC-20 代币的时候,交易的 input 数据分为 3 个部分,第一部分是 4 个字节的函数签名。第二部分是 32 个字节的存储文职,存储以太坊地址,目前以太坊地址是 20 字节,高位都是补 0。第三部分为 32 个字节的存储位置,存储需要传输的代币数量。

在以太坊中调用 transfer 方法转移代币时,如果用户输入一个短地址,没有校验用户输入的地址长度的合法性就会出现短地址攻击问题。如一个以太坊地址为 0x123456789...0,,结尾为 0,攻击者将后面的 00 省略时,这个地址的长度就会比正常地址短两位,EVM 会从下一个参数的高位拿来 00 来补充,这样代币数量参数就会少 1 个字节,造成代币数量参数向左移动一个字节会使合约多发送很多代币给这个地址。

代码重入

重入是编程中的一种现象,是指函数或程序中断,然后在先前调用完成之前再次被调用。在智能合约逻辑运行时,合约 A 调用合约 B 的一个函数时就可能会发生重入,即合约 B 又调用合约 A 中的相同函数,导致递归执行。在合约状态在关键性调用结束之后才更新的情况下就会引发安全问题。当以太坊智能合约将 ETH 发送给未知地址时就有可能受到代码重入攻击。

攻击者可以在地址对应的智能合约的 fallback 函数中构建一段恶意代码。当调用被攻击的智能合约完成代币发送给恶意合约地址的操作时,就会执行攻击者所构建 fallback 函数中的恶意代码。恶意代码可以是重新进入易受攻击的智能合约的相关代码,通过这样的方式,攻击者可以重新进入易受攻击的智能合约,执行一些开发者不希望执行的合约逻辑。

EtherStore 合约调用 deposit 函数用于存 eth,withdraw 函数用于取 eth,而 withdraw 函数中对调用者进行了余额判断,并且取钱后会将调用者余额归 0,按照逻辑调用者只能取走自己的存款。

攻击者创建 Attack 合约,使用 Attack 合约调用 EtherStore 合约,先在 EtherStore 存 0.1 ether,然后就开始取钱,这看似正常。但是 Attack 合约在收到 eth 后会触发 fallback 函数,fallback 函数中会继续调用 etherStore.withdraw(); 继续取钱,直到 etherStore 没有钱为止。在 fallback 中调用 etherStore.withdraw(); 时造成递归,导致 withdraw 中还没有将调用者的余额重置,未执行 balances[msg.sender] = 0; 代码。etherStore 会被掏空钱包。

防止重入有几个方面,首先转账可以使用 transfer 函数,transfer 函数只会发送 2300 个 Gas,这个 Gas 不足以递归调用。确保内部状态变量改变逻辑放在 eth 发送之前,如果发送失败可以以错误结束本次调用,这样状态变量的更改也不会失败。还有是加入防重入锁,如 bool internal locked;,通过重入锁避免出现重入攻击。

Last updated