一、漏洞
比如uint8
类型,总共有8位,能够表示00000000~11111111
的数值,换算成十进制,就是0~255
的数值范围。这时候一旦结果为256
时,由于总共只有8位,所以第9位1无法显示,只剩下00000000
,即想要的256,其实得到了0。
比如下面这个TimeLock合约:
pragma solidity ^0.4.18;
contract TimeLock {
mapping(address => uint) public balances;
mapping(address => uint) public lockTime;
function deposit() public payable {
balances[msg.sender] += msg.value;
lockTime[msg.sender] = now + 1 weeks;
}
function increaseLockTime(uint _secondsToIncrease) public {
lockTime[msg.sender] += _secondsToIncrease;
}
function withdraw() public {
require(balances[msg.sender] > 0);
require(now > lockTime[msg.sender]);
uint transferValue = balances[msg.sender];
balances[msg.sender] = 0;
msg.sender.transfer(transferValue);
}
}
其中的increaseLockTime
函数中,由于可以自己输入一个自由的时间戳增量,所以会带来整数溢出的危险。试想一下,如果输入的_secondsToIncrease
和原有的lockTime[msg.sender]
相加,由于溢出,最后使得lockTime[msg.sender]
的值成为一个很小的值,这样在withdraw
函数中,就可以顺利通过
require(now > lockTime[msg.sender]);
这一行,使得deposit进去的ETH可以提前被取出。
二、预防手段
首先,在0.8.0
版本,在语言层面已经解决了这个问题:一旦发生整数溢出,transaction会直接被revert。
在0.8.0
版本之前,需要用到一个open zeppelin的SafeMath
库。
三、真实案例
2018年4月22日,黑客对BEC智能合约发起攻击,凭空取出:
57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000.792003956564819968
个BEC代币并在市场上进行抛售,BEC随即急剧贬值,价值几乎为0,该市场瞬间土崩瓦解。
合约版本是^0.4.16
,小于 0.8 版本,也没有使用 SafeMath 库,因此存在整数溢出问题。
function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
uint cnt = _receivers.length;
uint256 amount = uint256(cnt) * _value; //溢出点,这里存在整数溢出
require(cnt > 0 && cnt <= 20);
require(_value > 0 && balances[msg.sender] >= amount);
balances[msg.sender] = balances[msg.sender].sub(amount);
for (uint i = 0; i < cnt; i++) {
balances[_receivers[i]] = balances[_receivers[i]].add(_value);
Transfer(msg.sender, _receivers[i], _value);
}
return true;
}
黑客传入了一个极大的值(这里为2**255),通过乘法向上溢出,使得 amount(要转的总币数)溢出后变为一个很小的数字或者0(这里变成0),从而绕过 balances[msg.sender] >= amount 的检查代码,使得巨大 _value 数额的恶意转账得以成功。
实际攻击的恶意转账记录:
etherscan截图 https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f
网友评论