智能合约安全:整数溢出攻击
整数溢出就是向存储整数的内存单位中存放的数据,超过了该内存单位所能存储的最大值,从而导致了溢出。
比如,我们要把整数1024赋值给一个uint8的变量,那么就会导致整数溢出,因为uint8变量能存放的最大值才是255。
由整数溢出引发的程序漏洞就称为“整数溢出漏洞”,它常常被攻击者利用。
其实,整数溢出漏洞不止存在于智能合约,而是在计算机系统中普遍存在,危害极大。
在智能合约领域,发生过多起严重事件,以下都是真实案例。
2018年4月23日,一款名为BEC的代币遭到攻击。黑客利用智能合约的漏洞,向外部账户转出了天价的合约代币,数量为2的256方减1,远超它的发行量。
最终,使得BEC的价格直接归零。
2018年4月25日, 代币SMT再次爆出整数溢出漏洞,又被转走天量SMT。
随后,著名的安全公司派盾PeckShield,利用自动化系统扫遍了以太坊智能合约,并对它们进行了分析。结果发现,有12个ERC-20智能合约都存在着整数溢出漏洞,很多是在交易所上正在进行交易的币种。
以下是漏洞代码:
// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; contract BECOverflow { // 合约的内部账本,用来记录每一个账户地址的余额 mapping(address => uint256) balances; // 批量转账,_receivers是接收者的地址,_value是转给每个人的代币数量 function batchTransfer(address[] calldata _receivers, uint256 _value) payable public returns (bool) { uint cnt = _receivers.length; // 计算转账人应该扣除的代币总数量 uint256 amount = uint256(cnt) * _value; //整数溢出漏洞点 // 如果接收者为空,或者接收者多于20个,终止回退 require(cnt > 0 && cnt <= 20); // 保证转账人在合约中的余额,要大于等于转账总额 require(_value > 0 && balances[msg.sender] >= amount); // 转账人在合约中的余额减掉本次的转账总额 balances[msg.sender] = balances[msg.sender] - amount; for (uint i = 0; i < cnt; i++) { // 给每一个接收人的账户,增加代币value个代币 balances[_receivers[i]] = balances[_receivers[i]] + _value; // 执行实际的转账操作 //Transfer(msg.sender, _receivers[i], _value); payable(_receivers[i]).transfer(1 ether); } return true; } constructor() payable { } }
解决整型溢出漏洞的方法很简单,几行代码就可以搞定。 如果智能合约使用Solidity 0.8.0之前的版本开发,可以引入OpenZeppelin的Safe Math库,进行数学计算。SafeMath库的代码量不大,也可以复制到自己的智能合约中,直接使用。
Solidity 0.8.0之后的版本,编译器内部默认集成了SafeMath库。因此,直接使用Solidity默认的数学运算符,就可以避免整型溢出漏洞,无需特殊处理了。
访问控制漏洞是指未能仔细检查合约中函数或变量的访问权限,从而允许恶意攻击者能够进入本不该被其访问的函数或变量。访问控制漏洞不是智能合约特有的问题,而是所有计算机系统普遍存在的问题。说到访问控制漏洞,我们就不得 ...