暂停交易?ERC20合约整数溢出安全漏洞案例技术分析(一)

区块链兄弟社区,区块链技术专业问答先行者,中国区块链技术爱好者聚集地

作者:吴寿鹤,《区块链开发实战——以太坊关键技术与案例分析》的第一作者,《区块链开发实战——Hyperledger Fabric关键技术与案例分析》联合作者,IONChain 离子链 首席架构师,hyperLedger核心项目开发人员,区块链技术社区-区块链兄弟联合创始人。github: https://github.com/gcc2ge

来源:区块链兄弟,国内第一家专注区块链技术分享实战的公益性媒体社区

原文链接:http://www.blockchainbrother.com/article/1992

文章发布:请标题作者和来源,版权归区块链兄弟所有



事件回顾:

4月25日早间,火币Pro公告,SMT项目方反馈今日凌晨发现其交易存在异常问题,经初步排查,SMT的以太坊智能合约存在漏洞。火币Pro也同期检测到TXID为https://etherscan.io/tx/0x0775e55c402281e8ff24cf37d6f2079bf2a768cf7254593287b5f8a0f621fb83的异常。受此影响,火币Pro现决定暂停所有币种的充提币业务,随后,火币Pro又发布公告称暂停SMT/USDT、SMT/BTC和SMT/ETH的交易。此外,OKEx,gate.io等交易平台也已经暂停了SMT的充提和交易。截止暂停交易,SMT在火币Pro的价格下跌近20%。

该漏洞代理的直接经济损失高达上亿元人民币,间接产生的负面影响目前无法估量。这到底是怎样一个漏洞呢?下面将详细分析该漏洞的产生和解决方案。

漏洞分析:

SMT与前几天爆出的美图BEC代币都出现类似的安全漏洞—整数溢出,那么什么是整数溢出,整数溢出出现的原因是什么,怎样才能避免整数溢出呢?接下来我们带着这些问题来看下面的内容。

整数溢出

整数溢出分向上溢出和向下溢出,有关智能合约安全的其他关键点作者在《区块链开发实战——以太坊关键技术与案例分析》中有详细介绍,以下是截取本书中关于整数溢出的部分,通过下面文字的阅读大家就可以对:什么是整数溢出,整数溢出出现的原因是什么,怎样才能避免整数溢出呢?这三个问题有个答案了。

以下文字截取于《区块链开发实战——以太坊关键技术与案例分析》


pragma solidity ^0.4.10;
/**
这是一个测试整数类型上溢和下溢的例子
*/
contract Test{
 // 整数上溢
 //如果uint8 类型的变量达到了它的最大值(255),如果在加上一个大于0的值便会变成0
 function test() returns(uint8){
 uint8 a = 255;
 uint8 b = 1;
 return a+b;// return 0
 }
 //整数下溢
 //如果uint8 类型的变量达到了它的最小值(0),如果在减去一个小于0的值便会变成255(uin8 类型的最大值)
 function test_1() returns(uint8){
 uint8 a = 0;
 uint8 b = 1;
 return a-b;// return 255
 }
}

有了上面的理论基础,我们在看一个转账的例子,看在我们的合约中应该如何避免不安全的代码出现:


// 存储用户余额信息
mapping (address => uint256) public balanceOf;
// 不安全的代码
// 函数功能:转账,这里没有做整数溢出检查
function transfer(address _to, uint256 _value) {
 /* 检查发送者是否有足够的余额*/
 if (balanceOf[msg.sender] < _value)
 throw;
 /* 修改发送者和接受者的余额 */
 balanceOf[msg.sender] -= _value;
 balanceOf[_to] += _value;
}
// 安全代码
function transfer(address _to, uint256 _value) {
 /* 检查发送者是否有足够的余额,同时做溢出检查:balanceOf[_to] + _value < balanceOf[_to] */
 if (balanceOf[msg.sender] < _value || balanceOf[_to] + _value < balanceOf[_to])
 throw;
 /* 修改发送者和接受者的余额 */
 balanceOf[msg.sender] -= _value;
 balanceOf[_to] += _value;
}

我们在做整数运算的时候要时刻注意上溢,下溢检查,尤其对于较小数字的类型比如uint8、uint16、uint24更加要小心:它们更加容易达到最大值,最小值。

案例分析:

SMT合约中的整数安全问题简析

SMT的合约地址是:0x55F93985431Fc9304077687a35A1BA103dC1e081,合约代码可以访问etherscan的如下网址进行查看

https://etherscan.io/address/0x55f93985431fc9304077687a35a1ba103dc1e081#code

SMT合约有问题的代码存在于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;
 }

其中的问题分析如下:


function transferProxy(address _from, address _to, uint256 _value, uint256 _feeSmt,
 uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){
 //错误1:这里没有做整数上溢出检查
 //_feeSmt,value都是由外部传入的参数,通过我们之前的理论这里可能会出现整数上溢出的情况
 // 例如:_feeSmt = 8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ,
 // value = 7000000000000000000000000000000000000000000000000000000000000001
 // _feeSmt和value均是uint256无符号整数,相加后最高位舍掉,结果为0。
 // 那么_feeSmt + _value = 0 直接溢出,绕过代码检查,导致可以构造巨大数量的smt代币并进行转账
 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;
 }

作者修改后的代码如下:


function transferProxy(address _from, address _to, uint256 _value, uint256 _feeSmt,
 uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){
 //错误1:这里没有做整数上溢出检查
 //_feeSmt,value都是由外部传入的参数,通过我们之前的理论这里可能会出现整数上溢出的情况
 // 例如:_feeSmt = 8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ,
 // value = 7000000000000000000000000000000000000000000000000000000000000001
 // _feeSmt和value均是uint256无符号整数,相加后最高位舍掉,结果为0。
 // 那么_feeSmt + _value = 0 直接溢出,绕过代码检查,导致可以构造巨大数量的smt代币并进行转账
 // 在这里做整数上溢出检查
 if(balances[_to] + _value < balances[_to]
 || balances[msg.sender] + _feeSmt < balances[msg.sender]) revert();
 // 在这里做整数上溢出检查 ,防止交易费用 过大
 if(_feeSmt + _value < _value ) revert();
 // 在这里做整数上溢出检查 ,防止交易费用 过大
 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;
 }

攻击者发送的交易:

以下是攻击者恶意发送的转账交易地地址:https://etherscan.io/tx/0x1abab4c8db9a30e703114528e31dee129a3a758f7f8abc3b6494aad3d304e43f

(黑客攻击交易日志截图)

BEC合约中的整数安全问题简析

BEC的合约地址是0xC5d105E63711398aF9bbff092d4B6769C82F793D,合约代码可以访问etherscan的如下网址进行查看https://etherscan.io/address/0xc5d105e63711398af9bbff092d4b6769c82f793d#code

BEC合约有问题的代码存在于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;
 }
}

其中问题代码分析如下:


 function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
 uint cnt = _receivers.length;
 // 这里直接使用乘法运算符,可能会导致溢出
 // 变量cnt为转账的地址数量,可以通过外界的用户输入_receivers进行控制,_value为单地址转账数额,也可以直接进行控制。
 // 外界可以通过调整_receivers和_value的数值,产生乘法运算溢出,得出非预期amount数值,amount溢出后可以为一个很小的数字或者0,
 uint256 amount = uint256(cnt) * _value;
 require(cnt > 0 && cnt <= 20);
 // 这里判断当前用户拥有的代币余额是否大于或等于要转移的amount数量
 // 由于之前恶意用户通过调大单地址转账数额_value的数值,使amount溢出后可以为一个很小的数字或者0,
 // 所以很容易绕过balances[msg.sender] >= amount的检查代码。从而产生巨大_value数额的恶意转账。
 require(_value > 0 && balances[msg.sender] >= amount);
 //调用Safemath库中的安全函数来完成加减操作
 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;
 }
}

攻击者发送的交易:

以下是攻击者恶意发送的转账交易:

https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f。

(黑客攻击交易日志截图)

合约整数漏洞事件总结

从上面的分析中,大家可以看出针对SMT和BEC的合约恶意攻击都是通过恶意的整数溢出来绕过条件检查。目前以太坊上运行着上千种合约,这上千种合约中可能也存在类似的安全隐患,所以作为合约的开发人员需要投入更多的精力来确保合约的安全性。

下篇我们将详细的介绍如何正确保合约的安全,敬请期待。

文章发布只为分享区块链技术内容,版权归原作者所有,观点仅代表作者本人,绝不代表区块链兄弟赞同其观点或证实其描述

原文地址:https://www.cnblogs.com/blockchainbrother/p/8961756.html

时间: 2024-10-02 01:20:22

暂停交易?ERC20合约整数溢出安全漏洞案例技术分析(一)的相关文章

BEC合约整数溢出漏洞还原与分析

一.币圈一秒,人间一年 有道是币圈一日,人间一年.这个说法又得升级了,叫币圈一秒,人间一年. 前不久,币圈又出大事啦.BEC智能合约被爆出整数溢出漏洞,导致黑客能无限印币,在一次交易中,也就那么几秒钟的事情,黑客就“无中生有”地给两个账户转了天文数字般的BEC币,而原账户一分BEC币都没损失.大家来围观下这笔交易: https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f

缓冲区溢出分析第11课:整数溢出的原理

<缓冲区溢出分析>这一系列的内容是我为"i春秋"(www.ichunqiu.com)所录制的同名视频课程的讲稿汇总.每次我都是在写完课程的文档后,再依据文档内容进行课程的讲解.而本系列的内容也是从零开始,来给大家由浅入深地进行缓冲区溢出漏洞的讲解.整个课程是理论与实践相结合,每讲完几个基础理论后,都会配以实际的软件中的漏洞进行分析,以帮助大家更好地理解漏洞的原理.有兴趣的朋友可以结合本文与配套视频进行学习. 前言 我们之前所研究的漏洞,都是非常经典的栈溢出漏洞,也是最为常见

PHP “shmop_read()”远程整数溢出漏洞

PHP是广泛使用的通用目的脚本语言,特别适合于Web开发,可嵌入到HTML中. PHP的"shmop_read()"函数在实现上存在远程整数溢出漏洞,远程攻击者可利用此漏洞在受影响网络服务器中执行任意代码,造成拒绝服务. 解决方法 前厂商已经发布了升级补丁以修复这个安全问题,请到厂商的主页下载: http://www.php.net/downloads.php 原文地址:https://www.cnblogs.com/mrhonest/p/10892552.html

Linux kernel 4.20 BPF 整数溢出漏洞分析

分析的代码为linux-4.20-rc3版本:https://elixir.bootlin.com/linux/v4.20-rc3/source.因为该漏洞影响`Linux Kernel 4.20rc1-4.20rc4`,主要Linux发行版并不受其影响. 一.简介 BPF的全称是Berkeley Packet Filter,字面意思意味着它是从包过滤而来,该模块主要就是用于用户态定义数据包过滤方法:从本质上我们可以把它看作是一种内核代码注入的技术,BPF最大的好处是它提供了一种在不修改内核代码

整数溢出漏洞小结

有无符号数是CPU架构决定的,是硬件特性直接反映到汇编指令中.C语言忠实的展现了汇编的特性. 无符号数比较: ja.jae.jb.jbe.je或jne 小于.小于等于.等于.不等于.大于或大于等于: 有符号数比较: 则使用jl.jle.je.jne.jg.jge指令 小于.小于等于.等于.不等于.大于或大于等于: 无符号:十六进制表示 有符号:补码表示 无符号与有符号转换: 基本原则:保证底层的位模式保持不变 导致的问题:有符号数赋给无符号数之后,会从-1变成4294967295(导致溢出) (

Java运行时环境JPEGImageWriter.writeImage函数整数溢出漏洞_

在使用PDFBOX的接口,代码如下: PDFImageWriter imageWriter = new PDFImageWriter(); imageWriter.writeImage(pdDoc, imageType, null, startPage, endPage, imageFilePath, 1, Constants.NUM_TWO_HUNDRED),发现图片生成了,但是报内存溢出错误.后面看了下源代码搜寻相关资料发现存在这样一个问题,所以更换JDK就OK了. Java运行时环境的JP

利用漏洞溢出掉360安全卫士逆向分析

注:本文测试环境为360安全卫士9.0,最新版的安全卫士已修复此漏洞 现象 某个木马运行后可以关闭360安全卫士,经过逆向分析发现该木马只是简单运行了以下代码: /* HMODULE h360 =GetModuleHandle(TEXT("safemon.dll")); int i = 0; for (i = 0; i<0x30000; i++) { if (memcmp((BYTE *)(h360+i), "\x83\xEC\x10\x56\x8D\x44\x24\x

CTF 两道web整数溢出题目(猫咪银行和ltshop)

①猫咪银行: (2018中科大hackgame) 一开始给十个CTB,而flag需要20个CTB,我们需要理财赚够20个. 理财是只能买入TDSU才可以获得收益.我们先上来直接把CTB全部换成TDSU. 上边是我们花了所有TDSU:66060买了19分钟后的收益.(因为一个账号最多存在20分钟,计算你用脚本极限也不能买20分钟,必须留一分钟用来换TDSU和买入,取出等操作) 还是不行,算上收益的钱1200231也不够买20个CTB. 考虑买RMX是否存在汇率差. 发现一个CTB57个RMX,一个

整数溢出实验

视频链接:  课程编写 类别 内容 实验课题名称 整数溢出实验 实验目的与要求 了解整数及整数溢出的基本概念 了解整数溢出的常见类型 掌握整数溢出的基本原理 通过编写代码,体验整数溢出 实验环境 VPC1(虚拟PC) Windows XP操作系统 软件描述 命令行窗口 实验代码 预备知识 1.整数及整数溢出 关于整数的概念,应该说我们在上中学的时候就学过了.这里我们需要了解的是:整数分为无符号和有符号两类,其中有负符号整数最高位为 1,正整数最高位为 0,无符号整数无此限制:此外,常见的整数类型