智能合约整数溢出
在编程语言里面由算数问题导致的整数溢出是很常见的,智能合约中的 Solidity 语言中也存在整数溢出问题,类型包括三种:
乘法溢出
加法溢出
减法溢出
简单示例
参考: https://zhuanlan.zhihu.com/p/105471579
在 Solidity 语言中,变量支持的整数类型步长是以8递增的,从 uint8 到 uint256, uint 默认是 uint256,以 uin8 为例
我们知道 uint8 是8位,我们最多可以 2**8-1,也就是 255,若是256则会造成溢出,这是上溢

下溢也是一样的, uint(0)-1 就是255
Solidity简单示例
pragma solidity ^0.4.25;
contract POC{
//加法溢出
function add_overflow() returns (uint256 _overflow){
uint256 max = 2**256-1;
return max+1;
}
//减法溢出
function sub_overflow() returns (uint256 _underflow){
uint256 min =0;
return min -1 ;
}
//乘法溢出
function mul_overflow() returns (uint256 _underflow){
uint256 mul = 2**255;
return mul * 2;
}
}
加法溢出

减法溢出

乘法溢出

历史漏洞
参考: https://www.jianshu.com/p/1620779ee75e
BEC合约
https://etherscan.io/address/0xc5d105e63711398af9bbff092d4b6769c82f793d#code
漏洞位于 batchTransfer 函数
相关代码如下
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;
}
其中我们可控的点是 _receivers 和 _value

_receivers 是需要转账的地址, _value 是单个账户转账的数量
amount 是一共需要转账的数量,直接采用乘法运算符,后面 require(_value > 0 && balances[msg.sender] >= amount); 是判断余额是否大于 amount,符合条件的话后面就进行转账
根据上面我们所学习的知识,我们让 cnt 为2,value 为 2**255,就可以让 amount 为0,因此就能进行恶意转账
这次攻击的恶意转账记录为
https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f
可以看到向两个账号转了很多代币

本地测试一下合约是可以成功的

然后该代币市场价额瞬间归零....
修复方式
后面 balances[msg.sender] = balances[msg.sender].sub(amount); 使用了 Safemath 中的函数进行运算,因此前面的也用该库函数即可
uint256 amount = uint256(cnt).mul(_value);

SMT
合约地址: https://etherscan.io/address/0x55f93985431fc9304077687a35a1ba103dc1e081#code
存在溢出漏洞的合约代码是 transferProxy 函数
function transferProxy(address _from, address _to, uint256 _value, uint256 _feeSmt,
uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){
if(balances[_from] < _feeSmt + _value) revert();
uint256 nonce = nonces[_from];
bytes32 h = keccak256(_from,_to,_value,_feeSmt,nonce);
if(_from != ecrecover(h,_v,_r,_s)) revert();
if(balances[_to] + _value < balances[_to]
|| balances[msg.sender] + _feeSmt < balances[msg.sender]) revert();
balances[_to] += _value;
Transfer(_from, _to, _value);
balances[msg.sender] += _feeSmt;
Transfer(_from, msg.sender, _feeSmt);
balances[_from] -= _value + _feeSmt;
nonces[_from] = nonce + 1;
return true;
}
可以看到这里面的 加减乘除 都没做安全处理,且参数都可控
if(balances[_from] < _feeSmt + _value) revert(); 我们可以让 feeSmt 和 _value 相加刚好溢出为 0,从而绕过判断,因此可以让从而给to账户和msg.sender账户分别转入value和feeSmt的代币
这是攻击者的交易信息

FNT
地址为
https://etherscan.io/address/0x82cf44be0768a3600c4bdea58607783a3a7c51ae#code
漏洞位于 batchTransfers 函数
大致逻辑是向多地址转账,先计算总共转账的数目,然后判断是否大于转账地址的余额,之后再一个个转账

显然转账额度相加的部分存在溢出
我们溢出个很小的数目就可以了
看下攻击者的操作

所以通过上面的构造,我们成功绕过了判断,最后给两个账户分别转了2个代币和115792089237316195423570985008687907853269984665640564039457584007913129639935个(2*256-1)的代币(加起来溢出为1)
最后
Solidity 0.8.0 之后编译器内部默认集成了 SafeMath Library ,因此,在后续的版本直接使用 Solidity 默认的数学运算符 即可避免整型溢出漏洞的产生.
总结
感觉在智能合约中的整数溢出危害更大,很多币因此直接归零,所以为了防止整数溢出的发生,一方面可以在算术逻辑前后进行验证,另一方面可以直接使用 OpenZeppelin 维护的一套智能合约函数库中的 SafeMath 来处理算术逻辑.
同样看来,智能合约的漏洞跟传统的Web安全漏洞是大差不差的,都特别要注意来用户的输入

浙公网安备 33010602011771号