区块链整数溢出漏洞
字数 1638 2025-08-22 12:22:54
区块链智能合约整数溢出漏洞详解与防御
1. 整数溢出漏洞概述
整数溢出是智能合约开发中常见的安全漏洞,当算术运算结果超出变量类型所能表示的范围时,就会发生溢出。Solidity作为智能合约开发语言,其整型变量只能存储固定大小数值范围内的数,从uint8到uint256(int8到int256)。
1.1 溢出类型
- 上溢(Overflow): 当数值超过类型最大值时,会回绕到0
- 下溢(Underflow): 当数值低于类型最小值时,会回绕到最大值(2^n-1)
1.2 溢出形式分类
- 加法溢出
- 乘法溢出
- 减法溢出
2. 历史案例分析
2.1 BEC攻击事件(2018年4月22日)
黑客利用整数溢出漏洞,凭空取出57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000.792003956564819968个BEC代币并抛售,导致BEC价值归零。
2.2 SMT攻击事件(2018年4月25日)
黑客利用函数漏洞创造了65,133,050,195,990,400,000,000,000,000,000,000,000,000,000,000,000,000,000,000 + 50,659,039,041,325,800,000,000,000,000,000,000,000,000,000,000,000,000,000,000的SMT币。
2.3 FNT攻击事件(2018年12月27日)
以太坊智能合约Fountain(FNT)出现整数溢出漏洞,黑客创造了2 + 115792089237316195423570985008687907853269984665640564039457584007913129639935的SMT币。
3. 漏洞原理与演示
3.1 加法溢出示例
uint8 max = 255; // 2^8 -1
uint8 _overflow = max + 1; // 结果为0,发生加法上溢
3.2 乘法溢出示例
uint8 max = 255; // 2^8 -1
uint8 _overflow = max * 2; // 结果为254,发生乘法上溢
3.3 减法溢出示例
uint8 min = 0;
uint8 _underflow = min - 1; // 结果为255,发生减法下溢
4. 典型漏洞代码分析
4.1 SMT合约漏洞(加法溢出)
漏洞代码片段:
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(); // 溢出点
// ...其他代码...
}
攻击方式:
- 构造极大的
_value和_feeSmt值,使两者相加后溢出为0 - 绕过余额检查
balances[_from] < _feeSmt + _value - 实现恶意转账
4.2 EBC合约漏洞(乘法溢出)
漏洞代码片段:
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);
// ...其他代码...
}
攻击方式:
- 设置
_value为极大值(如0x8000000000000000000000000000000000000000000000000000000000000000) - 当与接收者数量(cnt)相乘时,结果超出uint256范围,amount变为0
- 绕过余额检查
balances[msg.sender] >= amount
4.3 BTCR合约漏洞(减法溢出)
漏洞代码片段:
function distributeBTR(address[] addresses) onlyOwner {
for(uint i = 0; i < addresses.length; i++) {
balances[owner] -= 2000 * 10**8; // 潜在下溢点
balances[addresses[i]] += 2000 * 10**8;
Transfer(owner, addresses[i], 2000 * 10**8);
}
}
漏洞特点:
- 合约部署时
balances[owner] = 21000000 * 10^8 - 最多执行10500次Transfer()就会产生下溢
- 没有检查owner账户余额是否足够
5. 防御措施
5.1 算术运算前后验证
- 加法验证: 确保和大于加数和被加数
- 乘法验证: 确保积大于乘数和被乘数
- 减法验证: 确保差小于被减数
5.2 使用SafeMath库
OpenZeppelin提供的SafeMath库是防御整数溢出的最佳实践:
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
assert(c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a / b;
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
使用示例:
using SafeMath for uint256;
function safeAdd(uint256 a, uint256 b) public pure returns (uint256) {
return a.add(b); // 使用SafeMath的加法
}
5.3 其他防御建议
- 对所有用户输入进行严格验证
- 使用最新版本的Solidity编译器,它内置了部分算术检查
- 进行充分的测试,包括边界值测试
- 考虑使用形式化验证工具验证合约安全性
6. 总结
整数溢出漏洞在智能合约开发中危害严重且常见,开发者必须:
- 充分理解Solidity整数类型的范围和限制
- 对所有算术运算进行安全检查
- 优先使用经过验证的安全库如SafeMath
- 进行全面的测试和审计
由于区块链的不可篡改性,合约部署后无法修复漏洞,因此必须在部署前消除所有潜在的整数溢出风险。