智能合约安全:整数溢出攻击

整数溢出就是向存储整数的内存单位中存放的数据,超过了该内存单位所能存储的最大值,从而导致了溢出。

比如,我们要把整数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默认的数学运算符,就可以避免整型溢出漏洞,无需特殊处理了。

访问控制漏洞是指未能仔细检查合约中函数或变量的访问权限,从而允许恶意攻击者能够进入本不该被其访问的函数或变量。访问控制漏洞不是智能合约特有的问题,而是所有计算机系统普遍存在的问题。说到访问控制漏洞,我们就不得 ...