Solidity十大安全问题【2020】

本文介绍CheckMarx安全研究小组通过扫描公开的以太坊智能合约 所发现的Solidity智能合约开发中常见的十大安全问题,其中 未检查的外部调用高成本循环 分列排行榜前两名。 该安全问题排行榜于2020年5月发布。

在2018年,CheckMarx安全研究小组首次发布了Solidity智能合约 开发中常见的十大安全问题排行榜,其中位居榜首的两个问题分别是 外部合约拒绝服务重入 。2020年的情况已经有所变化,

下表比较了2018年和2020年Solidity十大常见安全问题之间的变化。 这些问题按严重程度和流行程度排序:

用自己熟悉的语言学习 以太坊DApp开发Java | Php | Python | .Net / C# | Golang | Node.JS | Flutter / Dart

S1 – 未检查的外部调用

这是我们之前的Solidity十大安全问题榜单上第三常见的问题。由于现在解决了前两个问题, 因此 未检查的外部调用 已升至2020年更新列表中最常见的问题。

Solidity低级调用方法,例如address.call(),不会引发异常。相反,如果调用 遇到异常,则它们返回false。另一方面,如果 doSomething()抛出异常,则合约 调用(例如ExternalContract.doSomething())会自动传播异常。

使用addr.send()发送以太币是一个很好的例子,应该通过检查返回值来 显式处理不成功的发送,但这对于其他外部调用也有效:

S2 – 高成本循环

代价高昂的循环从Solidity安全榜单的第四名上升至第二名。尽管我们以前的清单中的前2个问题都已解决, 但受该问题影响的智能合约数量却增长了近30%。

以太坊环境的算力是需要付费的(使用以太币)。因此,减少完成操作所需的计算步骤不仅是 优化的问题,而且还涉及成本效率。

循环是昂贵操作的一个很好的例子:由于数组中包含许多元素,因此需要更多迭代才能完成循环。 如你所料,无限循环会耗尽所有可用GAS。

如果攻击者能够影响元素数组的长度,则上述代码将导致拒绝服务,从而阻止执行跳出循环。 尽管它与十大常见问题相去甚远,但在扫描的智能合约中发现有8%的合约存在数组长度操纵 问题。

S3 – 权力过大的所有者

这是Soldiity十大安全问题榜单中的一个新条目,该问题影响了约16%的已扫描智能合约。

某些合约与其所有者(Owner)紧密相关,使得某些功能只能由所有者地址调用, 如下例所示:

只有合约所有者能够调用doSomething()doSomethingElse()函数:前者使用onlyOwner修饰符, 而后者则显式执行该修饰符。这带来了严重的风险:如果所有者的私钥遭到泄露, 则攻击者可以控制该合约。

S4 – 算术精度问题

由于使用256位虚拟机(EVM),因此Solidity的数据类型有些复杂。该语言不提供浮点表示形式, 并且少于32个字节的数据类型将被打包到同一个32字节的槽位中。考虑到这一点,你应该会想到 可能存在的精度问题:

如上例所示,在乘法之前执行除法时,可以预料存在很大的舍入误差。

S5 – 过于依赖tx.origin

智能合约不应依赖于tx.origin进行身份验证,因为恶意合约可能会进行中间人攻击,耗尽所有资金。 建议改用msg.sender

可以在Solidity的文档中查看Tx Origin攻击 的详细说明。简单的说,tx.origin始终是合约调用链中的第一个帐户,而msg.sender则表示直接调用者。如果链中的最后一个 合约依赖于tx.origin进行身份验证,那么调用链中间环节的合约将能够榨干被调用合约的资金,因为 身份验证没有检查究竟是谁(msg.sender)进行了调用。

S6 – 上溢出/下溢出

Solidity的256位虚拟机(EVM)存在上溢出和下溢出问题,具体演示可以查看 这里。 在for循环条件中使用uint数据类型时,开发人员要格外小心,因为它可能 导致无限循环:

在上面的示例中,当i的值为0时,下一个值为2^256 -1,这使条件始终为true。 开发人员应当尽量使用<>!===进行比较。

S7 –不安全的类型推断

该问题在Solidity十大安全问题排行榜中上升了两个位置,现在影响到的智能合约比之前多出17%以上。

Solidity支持类型推断,但有一些奇怪的表现。例如,字面量0会被推断为byte类型, 而不是我们通常期望的整数类型。

在下面的示例中,i的类型被推断为uint8,因为这时能够存储i的值的 最小长度的整数类型。但如果elements数组包含256个以上的元素,则下面的 代码就会发生溢出:

因此,我们建议显式声明数据类型,以避免代码意外的行为或错误。

S8 – 不正确的转账

此问题在Solidity十大安全问题榜单中从第六位下降到第八位,目前影响不到1%的智能合约。

在合约之间进行以太币转账有多种方法。虽然官方推荐使用addr.transfer(x)函数,但我们 仍然找到了还在使用send()函数的智能合约:

请注意,如果转账不成功,则addr.transfer(x)会自动引发异常,这样可以减轻了 先前讨论的未检查外部调用的问题S1。

S9 –循环内转帐

当在循环中进行以太币转账时,如果其中一个合约不能收到,那么整个交易将被回滚。

攻击者可能利用此行为来导致拒绝服务,从而阻止其他合约接收以太币。

S10 – 时间戳依赖性

在Soldity十大安全问题榜单中,该问题在2018年排名第五。

重要的是要记住,智能合约在不同时刻在多个节点上运行。以太坊虚拟机(EVM) 不提供时钟时间,并且通常用于获取时间戳的now变量实际上是矿工可以操纵的 环境变量(block.timestamp的别名)。

由于矿工当前可以操纵环境变量,因此只能在不等式><>=<=中使用其值。

如果你的应用需要随机性,可以参考RANDAO合约, 该合约基于任何人都可以参与的去中心化自治组织(DAO),是所有参与者共同生成 的随机数。

总结

在比较2018年和2020年的Solidity十大常见安全问题列表时,我们可以观察到有关开发 最佳实践的一些进展,尤其是那些影响安全性的实践。看到2018年排名前2的问题, 外部合约拒绝服务重入 离开前十名榜单是一个积极信号,但开发者仍然 需要采取一些重要步骤来避免常见错误。


原文链接:Solidity Top 10 Common Issues

汇智网翻译整理,转载请标明出处