智能合约安全漏洞(2)——溢出漏洞

首先要说明,整型溢出漏洞在Solidity的0.8.0版本以前是一直存在的,并且有不少因此漏洞被攻击的案例。但在0.8.0版本之后,官方团队修复了此漏洞,再出现溢出情况时会直接revert掉。此篇讲述的是0.8.0版本之前的整型溢出漏洞案例。

整型溢出漏洞描述

在以太坊虚拟机(EVM)中为整数指定固定大小的数据类型,而且是无符号的。这意味着一个整型变量只能有一定范围的数字表示。例如,一个 uint8 ,只能存储范围 [0,255] 的数字,试图在这个uint8变量中存储 256,将会导致这个变量变为0。不加注意的话,只要没有检查用户输入又执行计算,导致数字超出存储它们的数据类型允许的范围,Solidity 中的变量就可以被用来组织攻击。

整型溢出攻击案例

2018年4月22日,黑客对BEC智能合约发起攻击,凭空取出巨量BEC代币并在市场上进行抛售,BEC随即急剧贬值,价值几乎为0,该市场瞬间土崩瓦解。

漏洞代码:

js 复制代码
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;
  } 

在batchTransfer函数中,出问题的地方在下面这行:

uint256 amount = uint256(cnt) * _value

其中变量cnt为转账的地址数量,可以通过外界的用户输入_receivers进行控制,_value为单地址转账金额,也可以直接进行控制。外界可以控制_receivers和_value的数值,那么我们就可以控制amount变量的值,让其产生非预期的值,导致向上溢出。如cnt = _receivers.length = 2,_value = 2255,这样amount = uint256(cnt) * _value = 2255*2超过uint256表示的最大值,导致溢出,最终amount = 0。 紧接着下面有一句对amount进行条件检查的代码require(_value > 0 && balances[msg.sender] >= amount);其中balances[msg.sender]代表当前用户的余额,amount代表要转的总币数。代码意思为确保单用户转账金额大于0,并且当前用户拥有的代币余额大于等于本次转账的总币数才进行后续转账操作。因为amount溢出后可以为一个很小的数字或者0(这里变成0),很容易绕过balances[msg.sender] >= amount的检查代码。从而产生巨大_value数额(这里为2**255)的恶意转账。

规避方法

  1. 现在可以直接升级Solidity到0.8.0版本以上规避此问题
  2. 在0.8.0版本以前,有一种选择是通过使用Open Zepplin 的 SafeMath库中定义的算术运算防止溢出。

在上述例子中,把问题行:

uint256 amount = uint256(cnt) * _value

改为:

uint256 amount = uint256(cnt).mul(_value)

SafeMath库中基本运算的方法如下所示:

js 复制代码
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) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    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;
  }
} 
相关推荐
Biteagle1 天前
ZK + 比特币:B² Network 如何重塑 Layer2 的信任边界
web3·区块链
OpenBuild.xyz1 天前
对话 Nexus:从 DEX Alpha 到 APAC 生态的社区共建之路|AMA 回顾文章
web3·去中心化·区块链
fyihdg1 天前
在Remix和hardhat中集成UUPS升级合约
区块链
小明的小名叫小明2 天前
区块链核心知识点梳理(12)- 快速交易方案
区块链
Biteagle2 天前
BTC邮票:比特币链上艺术的「永恒封印」
区块链·智能合约
闲谈共视2 天前
基于去中心化社交与AI智能服务的Web钱包商业开发的可行性
前端·人工智能·去中心化·区块链
Jerry.张蒙2 天前
SAP业财一体化实现的“隐形桥梁”-价值串
大数据·数据库·人工智能·学习·区块链·aigc·运维开发
Biteagle2 天前
SNARK的信任魔法:从「毒性仪式」到以太坊隐私圣杯
web3·区块链·智能合约
小明的小名叫小明2 天前
区块链核心知识点梳理(11)- EVM 原理深度解析
区块链
古城小栈2 天前
Spring Boot 集成区块链:智能合约调用接口开发全解析
spring boot·区块链·智能合约