Solidity合约开发安全问题【TOP 10】

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

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

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

Solidity合约开发安全问题【TOP 10】

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

S1 – 未检查的外部调用

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

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

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

Solidity合约开发安全问题【TOP 10】

S2 – 高成本循环

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

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

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

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

S3 – 权力过大的所有者

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

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

Solidity合约开发安全问题【TOP 10】

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

S4 – 算术精度问题

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

Solidity合约开发安全问题【TOP 10】

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

S5 – 过于依赖tx.origin

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

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

S6 – 上溢出/下溢出

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

Solidity合约开发安全问题【TOP 10】

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

S7 –不安全的类型推断

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

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

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

Solidity合约开发安全问题【TOP 10】
因此,我们建议显式声明数据类型,以避免代码意外的行为或错误。

S8 – 不正确的转账

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

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

Solidity合约开发安全问题【TOP 10】

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

S9 –循环内转帐

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

Solidity合约开发安全问题【TOP 10】

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

S10 – 时间戳依赖性

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

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

Solidity合约开发安全问题【TOP 10】

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

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

总结

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


原文链接:Solidity十大安全问题 — 汇智网

上一篇:《 嵌入式系统设计与实践》一一2.4 延伸阅读


下一篇:理解Rust的Result/Option/unwrap/?