公开数据表明,大量已经部署的智能合约或多或少都存在着一定的安全风险和漏洞,BEC事件也只是冰山一角。那么就ERC20合约而言,除了整数溢出漏洞以外,还有可能面临哪些风险呢?
可重入
若一个程序或子程序可以「在任意时刻被中断然后操作系统调度执行另外一段代码,这段代码又调用了该子程序不会出错」,则称其为可重入(reentrant或re-entrancy)的。
在智能合约代码中,黑客可以利用fallback函数,来递归调用包含 call.value() 的函数,从合约中重复提取以太币。通常造成该类事件的原因是余额校验不到位或余额更新不及时。
当ERC20代币合约中涉及以太币的转出,应当尤为注意。以一个 withdraw 函数为例,这也是ERC20合约中经常出现的函数。以下代码中首先进行余额校验,并向msg.sender地址转出以太币,再修改 balance 数组中余额值。
当函数执行到 msg.sender.call.value(amount)() 时,黑客就可以通过msg.sender的 fallback函数来重复调用 withdraw,进而重复执行 msg.sender.call.value(_amount)(),直到gas全部消耗完毕或者合约中的以太余额全部被取完。于是就给黑客实现 reentrancy 攻击创造了条件。
因此,SECBIT实验室的工程师强烈建议智能合约开发者在转账之前做好余额校验工作,并且将余额计算放在转账之前处理。
另外,值得重视的是,除了 call.value() 以外,任何直接或者间接调用call方法的步骤,都有可能引起回调,从而引发重入的安全事件。
转账方式风险
当然,杜绝上述问题的一个更好的方式就是不使用 call.value() 进行转账。发起以太币转账的方式有三种transfer() ,send() 和 call.value()。我们对这三种方式进行比较。
如上图所示,其中最安全的方式当属 transfer(),一旦转账失败,transfer() 会抛出异常直接触发 revert() 事件,而另外两者不会,需要开发人员手动处理返回值。send() 与 transfer() 唯一的区别也在于返回值,通常我们可以认为addr.transfer(v) 就相当于require(addr.send(v))。
而call.value() 与另外两者一个最明显的区别在于gas的限制上面,call.value()允许消耗掉所有的gas。但另外两种方式由于gas消耗限制到2300,不足以完成递归调用,这也是能够避免 reentrancy 攻击的原因,上述 withdraw 的代码可以按照以下的写法来实现。
所以,SECBIT实验室的工程师建议开发人员,在ERC20合约开发过程中,
如果遇到以太币转出的情况,如非必要,尽量选择使用transfer()函数来完成。
避免混淆 tx.origin 和 msg.sender
solidity提供了两种标准的方式来获取合约调用方的地址,tx.origin 和 msg.sender,但是这两者的含义是不同的,其中 msg.sender 是指直接调用当前合约的调用方地址,tx.origin 是指发起本次调用的起始调用方地址。
比如合约(或外账户地址)A去调用合约B,合约B调用合约C。此时在合约C中读取到的 msg.sender 即为合约B的地址,tx.origin 即为A的地址。
若从一个地址直接对合约发起调用,那么 msg.sender 和 tx.origin 是一样的,否则这两个地址就不一样。由于合约无法决定外部调用的关系,开发人员又往往容易混淆两者的含义,进而留下隐患。
以两段真实的ERC20合约为例,第一段将 tx.origin 地址设为合约的owner地址,第二段将 msg.sender 设为合约的owner地址。在智能合约开发过程中,一定先搞明白代码的实现意图,再选择使用 tx.origin 还是 msg.sender,使用 tx.origin 的时候要尤为慎重。
依赖时间戳或者块高度
在 solidity 中,允许获取当前时间戳(或者说交易所在区块的区块高度)。但是,这并不是安全的。一方面,时间戳是打包交易时候由矿工设置的,存在一定的人为操作因素在里面,矿工完全可以对时间戳做轻微的改动;另一方面,我们不能完全排除以太坊未来会在出块时间上做出调整的可能性,因此通过块高度来预估时间是存在一定隐患的。
上述例子中,通过块高度来限制ERC20代币购买的时间段,假如在合约发布后,购买结束前的时间段内,以太坊平台的出块时间做出了调整,那么购买时效也会发生变动。
另外,千万不要使用这两个值来产生随机数。因为在合约外部一定范围内(即同一个区块内)是可以获取到时间戳和块高度的,所以对于同一个区块中的合约来说,这两个值就变得不随机了,这便给黑客留下了可乘之机。
整数相除
在solidity中,整数相除遵循向下取整的原则。因此,当遇到不能整除的情况时,一定要谨慎处理。
如上述代码所示,计算结果的关系是`a == b * c + a % b`,而并非`a == b*c`。在ERC20合约开发过程中,经常会遇到使用除法的场景,要当心除法取整的问题,避免引起数据前后不一致的麻烦。
关键字过时
随着solidity不断的升级更新,老版本上的一些用法也逐渐被标记为过时然后废弃掉。因此在合约开发和升级过程中,一定要当心过时的用法,避免造成不必要的损失。下表展示了部分过时用法和其替代用法可供参考。
智能合约掌握着巨额的经济价值,其影响力之大,波及范围之广可见一斑。哪怕一个很不起眼的小问题,都有可能造成不可挽回的经济损失,这对大多数的项目来说无疑是灭顶之灾。因此,智能合约的开发一定要慎之又慎,不要忽略任何细枝末节。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 智能合约编写之Solidity的编程攻略
- 如何用 C# 编写 NEO 智能合约
- 智能合约攻击分析之庞氏代币合约漏洞
- 检测了3万多份智能合约,这份白皮书找到了9大智能合约安全漏洞(附下载链接)
- 智能合约工程
- 智能合约微服务
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java JDK6学习笔记
林信良 / 清华大学出版社 / 2007-4 / 59.90元
《Java JDK6学习笔记》是作者良葛格本人近几年来学习Java的心得笔记,结构按照作者的学习脉络依次展开,从什么是Java、如何配置Java开发环境、基本的Java语法到程序流程控制、管理类文件、异常处理、枚举类型、泛型、J2SE中标准的API等均进行了详细介绍。本书还安排了一个“文字编辑器”的专题制作。此外,Java SE6的新功能,对Java lang等套件的功能加强,以及JDBC4.0、......一起来看看 《Java JDK6学习笔记》 这本书的介绍吧!