美文网首页Nervos Fans
智能合约溢出攻击、下溢攻击

智能合约溢出攻击、下溢攻击

作者: 526ba0512193 | 来源:发表于2018-07-13 19:15 被阅读11次

    看个图:

    这是个计算汽车行驶距离的里程表。 范围是000000 到999999。说明啥,车开了100万公里后,里程表直接回到000000。

    细思极恐。

    回看下2000年的千年虫(Y2K)。 Y2K是一类计算机漏洞,堪称假想中的大浩劫。

    千禧危机、千年虫、千年问题 。千年问题可以追溯到二十世纪六十年代。当时计算器内存非常宝贵,故而编程人员一直借助使用 MM/DD/YY 或 DD/MM/YY 即月月/日日/年年或日日/月月/年年的方式来显示年份,但是当年序来到公元2000年的1月1日,系统却无法自动辨识00/01/01究竟代表1900年的1月1日,还是2000年的1月1日,所有的软硬件都可能因为日期的混淆而产生资料流失、系统死机、程序紊乱、控制失灵等问题,如此所造成的损失以及灾难是无法估计想像的。

    上面的两个例子叫‘整数溢出’,属于一类错误。今天,我们就来探讨下溢出和下溢攻击对智能合约造成的影响。

    溢出错误

    数字增长超过其最大值时发生溢出。 好比声明一个uint8变量(8位的无符号变量)。 意思是,变量的数值范围0到28-1(255)。

    看下边:

    uint 8 a = 255;

    a++;

    发生溢出,因为a的最大值是255。

    Solidity最大能处理256位数字,最大值为2256-1,加1会导致归 0,发生溢出:

    0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

    + 0x000000000000000000000000000000000001

    —————————————-

    = 0x000000000000000000000000000000000000

    用简单的token转账合约看看溢出错误(代码取自GitHub):

    mapping (address => uint256) public balanceOf;

    // INSECURE

    function transfer(address _to, uint256_value) {

        /* Check if sender has balance */

        require(balanceOf[msg.sender] >= _value);

        /* Add and subtract new balances */

        balanceOf[msg.sender] -= _value;

        balanceOf[_to] += _value;

    }

    // SECURE

    function transfer(address _to, uint256_value) {

        /* Check if sender has balance and for overflows */

        require(balanceOf[msg.sender] >= _value && balanceOf[_to] +_value >= balanceOf[_to]);

        /* Add and subtract new balances */

        balanceOf[msg.sender] -= _value;

        balanceOf[_to] += _value;

    }

    什么意思?

    上面程序中有两个“转账”函数(安全、不安全)。 一个有检查溢出,另一个没有。 安全转账函数有检查余额是否到达最大值。

    注意一点,特别在上面的例子中,安全函数不一定必须被包含。 就是说作为开发者,对余额达到上限的可能性自己得有个判断,免得花无谓的gas。

    下溢错误

    好了,看看另个极端,也就是下溢错误。Over跟under是反义词。这俩错误也是反着来的。

    uint8只能取0到255之间的值,还记得吧? 那么,考虑以下代码。

    unint8 a = 0;

    a–;

    这就是下溢,后果是a的最大可能值是255。

    出现在Solidity智能合约中,就是这:

    0x000000000000000000000000000000000000

    – 0x000000000000000000000000000000000001

    —————————————-

    = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

    意思是,造成严重的数据错误表示。

    还是用合约代码解释下,下溢的严重后果(代码取自GitHub):

    mapping (address => uint256) public balanceOf;

    // INSECURE

    function transfer(address _to, uint256_value) {

        /* Check if sender has balance */

        require(balanceOf[msg.sender] >= _value);

        /* Add and subtract new balances */

        balanceOf[msg.sender] -= _value;

        balanceOf[_to] += _value;

    }

    // SECURE

    function transfer(address _to, uint256_value) {

        /* Check if sender has balance and for overflows */

        require(balanceOf[msg.sender] >= _value && balanceOf[_to] +_value >= balanceOf[_to]);

        /* Add and subtract new balances */

        balanceOf[msg.sender] -= _value;

        balanceOf[_to] += _value;

    }

    这个例子中,因为动态数组是以顺序方式存储的,黑客可以利用manipulateMe,然后这么做:

    调用popBonusCode产生下溢

    计算manipulateMe的存储位置

    使用modifyBonusCode修改并更新manipulateMe的值

    当然了,单独函数中能一眼找出错误。 试想,有个超复杂的智能合约,几千行代码,代码检查也能一眼找出错误么?

    溢出攻击、下溢攻击的危害

    下溢错误发生的几率高于溢出错误,因为某人为引发溢出获取所需数量token(最大值就是)的可能性相当低。

    假设Bob手里有X个token,但是不妨碍他想花掉X+1个。

    若程序不检查一下Bob的余额,就有可能什么呢,Bob最后还真花掉了X+1个token。

    看看下面的例子(取自nethemba):

    pragma solidity ^0.4.22;

    contract Token {

    mapping(address => uint) balances;

    function transfer(address _to, uint _value)public {

      require(balances[msg.sender] – _value >=0);

      balances[msg.sender] -= _value;

      balances[_to] += _value;

    }

    }

    代码中,“转账”函数中的require条件乍一看好像没问题,注意交易双方之间的操作产生的是单位值。

    意思是,不管条件为何,balances[msg.sender]– _value >= 0的值永远为真。

    这种情形下,黑客可以最大化自己的余额。

    比方说, 黑客有100个token,但是他想要101个。最后会得到100 - 101个token,因为下溢,一共得来2 256-1个token。

    注释:

    关于无符号数减去无符号数的用法错误:

    if ( i - j >=0) 假如i,j为无符号数,这样写可能会引发错误,即当i小于j的时候,这个式子仍然成立,因为无符号数始终是大于等于零的。例: if ( strlen( a ) >= 10) 与 if (strlen ( b ) -10 >= 0) 这两条语句是不相等的 ,因为strlen函数返回的是无符号数类型。

    意思是:

    无符号数相减,如果被减数小于减数,那么结果会是一个非常大的无符号数,而不是一个想象中的有符号数。

    所以对于无符号数相减之前需要进行判断,最好做比较的时候使用 if ( strlen( a ) >=

    10) 这种方式,而不要使用if (strlen ( b ) -10 >= 0) 这种方式。因为无符号数进行计算的结果还是无符号数;另外无符号数和有符号数计算时,有符号数会被强制转提升无符号数。

    再然后,系统可能就瘫了。

    解决方法是总是检查代码中的下溢或上溢。这里有安全库协助检查,例如SafeMath或者OpenZeepelin。

    现实生活中的下溢问题

    有个准庞氏币叫POWH(Proof of Weak Hands Coin)。然后买的人还不少,筹了100多万刀。

    但是,POWH开发者对操作的安全性不太上心,无法妥善应溢出或下溢攻击。然后,有个黑客瞄准这个弱点,直接卷了2000个ETH,差不多230万刀的样子。

    这里想说的是,作为开发者,真是要加强对这种攻击是防御;作为赏金猎人的话,也是不应该对这种攻击放松警惕的。

    Overflow and Underflow Attacks on Smart Contracts (Blockgeeks Guide)​blockgeeks.com

    相关文章

      网友评论

        本文标题:智能合约溢出攻击、下溢攻击

        本文链接:https://www.haomeiwen.com/subject/gepspftx.html