查看原文
其他

CVE-2018-17405漏洞预警及OpenZepplin详解预告

roysue 看雪学院 2019-05-26

漏洞预警:CVE-2018-17405


近日看雪区块链小组成员roysue@kanxue在对Bitron Coin智能合约的代码进行审计的过程中,发现其存在着整数溢出型漏洞,看雪学院第一时间上报漏洞并发布预警威胁情报。

 

漏洞详情如下:


CVE-2018-17405

A smart contract implementation for Bitron Coin (BTO),
an Ethereum token, uses a raw form of math operations. The minus operation could trigger a integer overflow on the contract owner's balance, leading to a very large balance, as exploited in the wild in September 2018.



漏洞的原理


我们先举两个个非常简单的生活例子:


  • 例子1:一个小朋友,可以数着手指,计算10以内的加法。如果问小朋友,1+1等于几,他会扳手指算出来2;如果问他6+5,他扳完10个手指头后发现不够了,又扳回来说是1。因为对小朋友来说,问题已经超纲“溢出”了。


  • 例子2:假设银行系统的转账,每次限制在1亿元内。如果一个用户输入的数字,超过了1亿,系统要如何处理?在技术层面,其实有很多方式来规避这个问题,但这确实是一个必须考虑的问题。


在solidity语言中,当一个整型变量高于或者低于它所能承受的范围时就会发生溢出,导致发生一些不可预期的情况。比如,忽略了整型溢出漏洞,那么当用户输入转账数值,超过设定的最大值时,只要账户余额大于0,就可以直接将巨额的币“转走”,这就让黑客有了攻击的机会,最终可能导致一些严重的经济损失。所以在以太坊智能合约中做数学运算之前必须对输入参数以及输出结果进行有效性验证。



漏洞的利用方式


针对整数溢出漏洞,有一个非常经典的影响巨大的案例,那就是蔡文胜的美链,因为在转账的过程中,没有对数学运算的结果进行校验,导致发生溢出,形成了两个很大的数字相加等于一个很小的数字的结果

 

我们来看下代码:


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个地址,用于接收溢出转账。


  • 第二步,调用batchTransfer函数,_receivers设为刚创建的2个地址,_value变量设置为uint256的最大值/2+1,如此一来,计算amount时,就可以得到amount=unit256最大值+1,超出unit256最大范围成功溢出为0。


    这里需要注意:
    unit256的最大值为奇数115792089237316195423570985008687907853269984665640564039457584007913129639935,故将其"/2"刚好向下取整,然后再"+1"得到_value,这样_value*2所得到的amount刚好溢出。


  • 第三步,执行完之后,2个地址每个都会得到_value个代币,也就是说每个账户都会凭空增加57896044618658097711785492504343953926634992332820282019728792003956564819968个币(也就是_value的值)。


  • 第四步,将代币在交易市场上大量抛售获利。


溢出补充说明:Solidity最大可以处理256位数字, 最大值为2**256 - 1, 对(2**256 - 1)加1的结果会溢出归0。2**255 乘2也同样会溢出归0。对无符号类型最小值是零,对零做减1会得到(2**256 - 1)

 

同理,对该CVE的利用也是构造一个溢出即可,下面是产生漏洞的代码,可以看出其实比美链的利用要简单的多。


function() payable public {

 if( msg.sender != owner && msg.value >= 0.02 ether && now <= icoEndDate && stopped == false ){

   tokens                 = ( msg.value / 10 ** decimals ) * oneEth;
   balance[msg.sender] += tokens;
   balance[owner]        -= tokens;

   emit Transfer(owner, msg.sender, tokens);



漏洞的修补方式:规避整型溢出的神器——SafeMath库


由于目前Solidity还未解决此问题,所以只能由各个合约自行完成整型溢出的判断,这里给出一个规避的建议:无论在任何时候都不要直接使用"+"、"-"、"*"、"/"数学运算符,而改用使用SafeMath库来进行数学运算。其实SafeMath在第三章就有出现,它是openzeppelin实现的。下面我们来看看SafeMath库是什么样的。


pragma solidity ^0.4.24;

/**
* @title SafeMath
* @dev Math operations with safety checks that revert on error
*/

library SafeMath {

 /**
 * @dev Multiplies two numbers, reverts on overflow.
 */

 function mul(uint256 _a, uint256 _b) internal pure returns (uint256) {
   // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
   // benefit is lost if 'b' is also tested.
   // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
   if (_a == 0) {
     return 0;
   }

   uint256 c = _a * _b;
   require(c / _a == _b);

   return c;
 }

 /**
 * @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
 */

 function div(uint256 _a, uint256 _b) internal pure returns (uint256) {
   require(_b > 0); // Solidity only automatically asserts 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;
 }

 /**
 * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
 */

 function sub(uint256 _a, uint256 _b) internal pure returns (uint256) {
   require(_b <= _a);
   uint256 c = _a - _b;

   return c;
 }

 /**
 * @dev Adds two numbers, reverts on overflow.
 */

 function add(uint256 _a, uint256 _b) internal pure returns (uint256) {
   uint256 c = _a + _b;
   require(c >= _a);

   return c;
 }

 /**
 * @dev Divides two numbers and returns the remainder (unsigned integer modulo),
 * reverts when dividing by zero.
 */

 function mod(uint256 a, uint256 b) internal pure returns (uint256) {
   require(b != 0);
   return a % b;
 }
}


以上就是SafeMath库的全部代码,此库对所有数学运算都做了防溢出判断,可有效杜绝整型溢出的问题。

 

我们可以看到 SafeMath 在各个运算的函数开头使用来 require 来进行判断,从而有效地规避了整型溢出问题。实际上,正常编译合约的时候,使用加减乘除都应该进行溢出判断,而openzeppelin的这个SafeMath帮我们实现了这个功能,从而我们直接使用此库的所有数学运算即可,省去了繁琐的溢出判断。


using SafeMath for uint256;
uint256 a = 1;
a = a.div(1);


以上便是使用SafeMath库的方法。


OpenZepplin详解预告


SafeMath库是OpenZepplin公司开源的众多库的其中一个,除此之外他们还有代币众筹等标准库,直接继承和使用这些标准库,可以大大缩减开发时间并且全面提高安全性。这些库的代码也是开源的,地址在这里:


https://github.com/OpenZeppelin/openzeppelin-solidity

 





市面上讲整数漏洞的资料已经非常多,再讲也只是重复,因此我们决定讲讲OpenZepplin库,OpenZepplin提供的库及其各种功能非常丰富。



  • 时间:2018年10月9日 19:00~20:00


  • 地点:微信群&QQ群 加群方式:微信添加:roy5ue


  • 人物:roysue@kanxue


  • 内容:

    1、区块链实验环境搭建指南;


    2、OpenZepplin概论及入门;


    3、OpenZepplin提供的访问控制、代币、众筹库;


    4、OpenZepplin提供的其他实用工具库;


    5、智能合约与传统BS/CS架构的交互。





- End -


看雪ID:roysue                 

https://bbs.pediy.com/user-581423.htm



本文由看雪论  roysue 原创

转载请注明来自看雪社区




热门技术文章推荐:






戳原文,看看大家都是怎么说的?

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存