智能合约安全:整数溢出攻击
整数溢出就是向存储整数的内存单位中存放的数据,超过了该内存单位所能存储的最大值,从而导致了溢出。
比如,我们要把整数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默认的数学运算符,就可以避免整型溢出漏洞,无需特殊处理了。
下一章:智能合约安全:访问控制漏洞攻击
访问控制漏洞是指未能仔细检查合约中函数或变量的访问权限,从而允许恶意攻击者能够进入本不该被其访问的函数或变量。访问控制漏洞不是智能合约特有的问题,而是所有计算机系统普遍存在的问题。说到访问控制漏洞,我们就不得 ...
AI 中文社