tx.origin 进行身份验证,那么调用链中间环节的合约将能够榨干被调用合约的资金,因为身份验证没有检查究竟是谁(msg.sender)进行了调用。
6. 溢出(Overflow / Underflow) Solidity 的 256 位虚拟机存在上溢出和下溢出问题(译者注:由于结果超出取值范围称为溢出), 这里 [9] 有具体的分析。在 for 循环条件中使用 uint 数据类型时,开发人员要格外小心,因为它可能导致无限循环: for (uint i = border; i >= 0; i--) { ans += i; } 在上面的示例中,当 i 的值为 0 时,下一个值为 2^256 -1,这使条件始终为 true。开发人员应当尽量使用 <、>、!= 和 == 进行比较。 7. 不安全的类型推导 该问题在 Solidity 十大安全问题排行榜中上升了两位,现在影响到的智能合约比之前多了 17%以上。 Solidity 支持类型推导,但有一些奇怪的表现。例如,字面量 0 会被推断为 byte 类型, 而不是通常期望的整型。 在下面的示例中,i 的类型被推断为 uint8,因为这时能够存储 i 的值 uint8 就足够。但如果 elements 数组包含 256 个以上的元素,则下面的代码就会发生溢出: for (var i = 0; i < elements.length; i++) { // to something } 建议明确声明数据类型,以避免意外的行为和 / 或错误。 译者注:在 Solidity 0.6 已经移除了 var 定义变量( Solidity 0.6 之后不再有类型推导了),如果使用新的编译器,将不是问题。 8. 不正确的转账 此问题在 Solidity 十大安全问题榜单中从第六位下降到第八位,目前影响不到 1%的智能合约。 在合约之间进行以太币转账有多种方法。虽然官方推荐使用 addr.transfer(x) 函数,但我们仍然找到了还在使用 send() 函数的智能合约: if(!addr.send(1)) { revert() } 请注意,如果转账不成功,则 addr.transfer(x) 会自动引发异常,同样减轻第一个未检查外部调用的问题 9. 循环内转帐 当在循环体中进行以太币转账时,如果其中一个转账失败(例如,一个合约不能接收),那么整个交易将被回滚。 for (uint i = 0; i < users.lenghth; i++) { users[i].transfer(amount); } 在这个例子中,攻击者可能利用此行为来进行拒绝服务攻击,从而阻止其他用户接收以太币. 10. 时间戳依赖 在 2018 年,时间戳依赖问题排名第五,重要的是要记住,智能合约在不同时刻多个节点上运行的。以太坊虚拟机(EVM)不提供时钟时间,并且通常用于获取时间戳的 now 变量(block.timestamp 的别名)实际上是矿工可以操纵的环境变量。 if (timeHasCome == block.timestamp) { winner.transfer(amount); } 由于矿工可以操纵当前的环境变量,因此只能在不等式 >、<、>= 和 <= 中使用其值。 如果你的应用需要随机性,可以参考 RANDAO 合约 [10], 该合约基于任何人都可以参与的去中心化自治组织(DAO),是所有参与者共同生成的随机数。 总结 比较 2018 年和 2020 年十大常见问题时,我们可以观察到开发最佳实践的一些进展,尤其是那些影响安全性的实践。看到 2018 年排名前 2 位的问题:外部合约拒绝服务和重入,已经不再榜单了,这是一个积极的信号,但仍然需要采取措施来避免这类常见错误。 请记住,智能合约在设计上是不可变的,这意味着一旦创建,就无法修补源代码。这对安全性构成了巨大挑战,开发人员应利用可用的安全测试工具来确保在部署之前对源代码进行了充分的测试和审核。 Solidity 是一种非常新且仍在成熟的编程语言, Solidity v0.6.0 引入了一些重大更改 [11],并且预计在以后的版本中还会有更多更改。 (责任编辑:admin1) |