专栏首页信安之路浅谈以太坊智能合约的安全漏洞

浅谈以太坊智能合约的安全漏洞

本文作者:Evi1ran

智能合约的安全是区块链安全中的热议话题,但其实 89% 的智能合约都存在漏洞,本文将浅谈以太坊智能合约出现过的一些安全漏洞。

以太坊智能合约

智能合约(Smart contract)是在 1994 年由 Nick Szabo 首次提出的,目的是提供优于传统合约的安全方法,并减少与合约相关的其他交易成本。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。

简单的来说,智能合约就是一个“执行合约条款的计算机交易协议”。 我们可以把它想象成一个绝对可信的人,我们让它临时保管我们的资产,并且严格按照事先交易双方商定好的规则执行操作。

以太坊是一个开源的区块链底层系统,就像安卓一样,提供了非常丰富的 API 和接口,让许多人在上面能够快速开发出各种区块链应用。目前已经有超过 200 多个应用在以太坊上开发。

以太坊主要使用 Solidity (本文所引用代码)编写智能合约,并在微软云服务上提供了智能合约工具箱,运行在以太坊区块链上,保证交易公平进行。

举个简单的例子:

在传统的合约中,A 和 B 打赌 10 元今晚会不会下雨,结果 A 赢了,但是 B 耍赖,不愿意给 A 10 元,A 没有办法,只好做罢。

第二天,B 找到 A 继续打赌今晚会不会下雨,A 学聪明了,他找了互相都认识的 C 做见证人,自己和 B 分别押了 10 元给 C。晚上 A 抬头看见天下雨了,心想这次肯定赢了。结果 B 和 A 不在同一个区,A 居住的区下雨了,B 住的却没下,C 也不知道该判谁赢。最后一番争吵后,尽管 B 不满意,C 觉得在这个城市里任何地方下雨都算下雨,正准备给 A 钱,却发现走得太匆忙,忘了带钱。

所以,传统的合约会受到各种维度的影响,自动化维度,主客观维度,成本维度,执行时间维度,违约惩罚维度,适用范围维度等。

而智能合约便是在打赌之前便考虑并规定好了所有可能出现的情况,事件发生后程序就会按照预先设定好的合约内容自动严格执行。因此可以解决传统的合约出现的问题。

fallback 函数漏洞原理

我相信关注区块链的都知道 The DAO 攻击事件,

事件回顾:

2016 年 5 月,The DAO 正式发布。该项目使用了由德国以太坊创业公司 Slock.it 编写的开源代码。The DAO 的设计职能类似于一项风险投资基金,可以授权为以太坊项目提供资金。这个众筹超过 1.5 亿美元的分布式自治组织,遭受到了黑客利用递归调用以太坊发送漏洞的攻击,引发了广泛的市场抛售。

那么黑客到底是怎么利用这个漏洞的呢?我们先了解下智能合约 fallback 函数漏洞利用的原理。

当智能合约执行时,如果没有找到指定的函数,或者根本就没有指定哪个函数(如发送 ether)时,就会调用 fallback 函数。

我们先来看一下 addr.call.value()()addr.send() 的区别。两者都是向某个地址发送以太币,不同的是这两个调用的 gaslimit 不一样。send() 基于 0 gas(相当于 call.gas(0).value()()),而 call.value()() 基于当前剩余的所有 gas

所以当你通过 addr.call.value()() 发送 ether 时,没有指定哪个函数,fallback 函数就会被调用,并附带上当前剩余的所有 gas。

根据这一点,黑客可以通过 fallback 函数构造出一个递归调用直到 trasnfer 把所有币转完。

 function () { address addr = 0x6c8f2a135f6ed072de4503bd7c4999a1a17f824b;  if(COUNT<100){  addr.call(“withdrawBalance”);  COUNT++;  } }

在这段 fallback 代码中,当计数器小于 100 时,递归调用 withdrawBalance 函数。在这种情况下:

msg.sender.call.value(amountToWithdraw)() 将被调用 100 次,从而取走 100 * 10 ether。

简单地来说,Call.value() 存在漏洞,可以被黑客构造 while 循环漏洞直到 trasnfer 把所有币转完。

因为智能合约的开放性,也存在着 token 信息泄露漏洞。

The DAO 攻击

在 The DAO 攻击事件中,黑客就是通过上文所说的原理利用 fallback 函数存在的漏洞进行递归调用攻击。黑客分析了 DAO.sol,并且注意到了 splitDAO 功能,这个功能最后会更新用户的余额和总额,所以如果我们能在它访问 splitDAO 之前再调用这项功能,我们就可以无限递归调用来 transfer 我们想要数量的代币。

漏洞代码(DAO.sol):

  function splitDAO(  uint _proposalID,  address _newCurator)noEther onlyTokenholders returns (bool _success){  …  uint fundsToBeMoved =  (balances[msg.sender] * p.splitData[0].splitBalance) /  p.splitData[0].totalSupply;  if(p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender)  == false) throw;  …  withdrawRewardFor(msg.sender);  totalSupply -= balances[msg.sender];  balances[msg.sender] = 0;  paidOut[msg.sender] = 0;  return true;  }

当合约执行到:

withdrawRewardFor(msg.sender);

会进入相应的函数:

  function withdrawRewardFor(address _account)  noEther internal returns (bool _success){  …  if(!rewardAccount.payOut(_account,reward)) //漏洞代码  throw;  …  }

payOut 函数定义如下:

  function payOut(address _recipient, uint _amount) returns (bool){  …  if(_recipient.call.value(_amount)) //漏洞代码  PayOut(_recipient, _amount);  return true;  }else{  return false;  }  }

黑客通过将下面的代码调用多次,以转移多份以太币:

p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender)

智能合约一旦发布便不能修改,只能通过硬分叉解决。

The DAO 已经丢失了 360 万以太币,这些以太币在被分离到一个独立的团组 child DAO 后,目前正被保存在一个独立的钱包内。 在攻击事件发生之后,Slock.it 制定了一个解决方案并把它上传到了 GitHub:

https://github.com/slockit/smart-contract/blob/master/DAOSecurity.sol

parity 多重签名钱包安全漏洞

事件回顾:

2017 年 7 月 19 日,用作 Parity Wallet 的多重签名钱包(“multi-sig”)代码中存在的漏洞被黑客所利用。持有 ETH 大额余额的三个钱包账户已被入侵,余额转入攻击者持有的账户。攻击者从三个高安全的多重签名合约中窃取到超过 15 万以太坊(约 3000 万美元)。原始报告:

https://paritytech.io/the-multi-sig-hack-a-postmortem/

我们可以看到黑客的资金账户:

https://etherscan.io/address/0xb3764761e297d6f121e79c32a65829cd1ddb4d32#internaltx

一共盗取了 153,037 个 ETH。

漏洞代码:

  // constructor - just pass on the owner array to the multiowned and   // the limit to daylimit   function initWallet(address[] _owners, uint _required, uint _daylimit) {   initDaylimit(_daylimit);   initMultiowned(_owners, _required);   }

源码地址:

https://github.com/paritytech/parity/blob/4d08e7b0aec46443bf26547b17d10cb302672835/js/src/contracts/snippets/enhanced-wallet.sol

当转账交易执行到 _walletLibrary.delegatecall 的分支时,因为该函数能无条件地调用合约内的任何一个函数,黑客通过调用 initWallet() 函数,初始化钱包,重新调用了 owner 函数。将 owner 直接分配给 library 后,可以把这个 library 转换为一个常规的多重签名钱包。再取得 owner 权限后,黑客还可以调用 kill() 指令,导致所有依赖于第三方 party 库的钱包瘫痪。

以太坊节点 Geth/Parity RPC API 鉴权缺陷

近日,慢雾安全团队观测到一起自动化盗币的攻击行为,攻击者利用以太坊节点 Geth/Parity RPC API 鉴权缺陷,恶意调用 eth_sendTransaction 盗取代币,持续时间长达两年,单被盗的且还未转出的以太币价值就高达现价 2 千万美金,还有代币种类 164 种,总价值难以估计(很多代币还未上交易所正式发行)。 原始报告

https://mp.weixin.qq.com/s/Kk2lsoQ1679Gda56Ec-zJg

黑客通过全球扫描 8545 端口(HTTP JSON RPC API)、8546 端口(WebSocket JSON RPC API)等开放的以太坊节点,发送 eth_getBlockByNumbereth_accountseth_getBalance 遍历区块高度、钱包地址及余额并不断重复调用 eth_sendTransaction 尝试将余额转账到攻击者的钱包。当正好碰上节点用户对自己的钱包执行 unlockAccount 时,在 duration 期间内无需再次输入密码为交易签名,此时攻击者的 eth_sendTransaction 调用将被正确执行,余额就进入攻击者的钱包里了。

unlockAccount 函数将使用密码从本地的 keystore 里提取 private key 并存储在内存中,函数第三个参数 duration 表示解密后 private key 在内存中保存的时间,默认是 300 秒;如果设置为 0,则表示永久存留在内存,直至 Geth/Parity 退出。详见:

https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_unlockaccount

溢出漏洞

事件回顾:

BEC 智能合约批量转账函数中有一行代码存在 bug,导致了溢出漏洞。被黑客所利用,出现一笔高达 57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000 的 BEC 代币转账。瞬间套现抛售大额 BEC,6 亿在瞬间归零。

交易地址:

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

漏洞代码:

 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; } 

我们看到这一行代码:

uint256 amount = uint256(cnt) * _value;

这行代码没有对 amount 做溢出的检测,uint256(cnt) 把 cnt 转成了 uint256 类型,它的取值范围是 0 到 2 的 256 次方 -1 , 传入的 _value 值在第一张图中是 8000000000000000000000000000000000000000000000000000000000000000,而转账地址有两个,所以 cnt 为 2, amout 已经超过了最大值,从而导致了溢出。

合约地址:

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

其实开发者已经考虑了溢出问题,除了 amount 的计算外, 其他转账都用了 safeMath 的方法(sub,add) 。唯独 amount 的计算用了 require 而不是 assert 来验证。两者都是干同一件事,他们有什么区别呢?原来 assert 会让程序的 gas limit 消耗完毕,而 require 只会消耗掉当前执行的gas。正式这一行代码,蒸发了 ¥6,447,277,680 人民币!

结语

如上所说,本文只列举了一部分以太坊智能合约中出现过的安全漏洞。

在伦敦大学学院 (University College London,UCL) 计算机科学系副教授伊利亚·谢尔盖最新的研究论文 《Finding The Greedy , Prodigal , and Suicidal Contractsat Scale》 中,通过对将近 100 万份智能合约进行每份合约 10 秒分析时间的分析后发现,这其中有 34200 份智能合约很容易受到黑客攻击。同时他们又对 3759 份智能合约抽样调查,在这之中,3686 份智能合约有 89% 的概率含有漏洞。

除此之外,许多交易所也没有设置过滤器,可以通过简单的字符串拼接构造 POC 取走交易所虚拟账户中的所有代币。

参考链接

谈谈区块链:以太坊智能合约的安全漏洞

http://geek.csdn.net/news/detail/139516

关于昨天蔡文胜的 BEC 智能合约出现漏洞,又一个要归零的币

https://www.v2ex.com/t/448992

以太坊生态缺陷导致的一起亿级代币盗窃大案

https://mp.weixin.qq.com/s/Kk2lsoQ1679Gda56Ec-zJg

本文分享自微信公众号 - 信安之路(xazlsec),作者:Evi1ran

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-05-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 常见端口及安全测试

    在渗透测试中,端口扫描是一个非常重要的环节,端口扫描的目的是了解服务器上运行的服务信息,针对不同的端口进行不同的安全测试,本文的主要内容是关于常见端口安全隐患以...

    信安之路
  • 一步一步带你体验 openvas

    openvas 是 nessus 项目的一个开源分支,用于对目标系统进行漏洞评估和管理,openvas 的配置使用相较于 nessus 更加复杂,扫描速度也不如...

    信安之路
  • 漏洞验证和利用代码编写指南

    有朋友问我近段日子做了些什么工作,作为安全研究员或者漏洞分析者最基础的工作之一,最近写了不少漏洞验证和利用的POC&EXP。所以就想结合下自己的经验和体会,分享...

    信安之路
  • 使用CNN预测电池寿命

    https://github.com/dsr-18/long-live-the-battery?source=post_page-----c5e1faeecc8...

    代码医生工作室
  • js获取页面宽高

    网页可见区域宽:document.body.clientWidth 网页可见区域高:document.body.clientHeight 网页可见区域宽:doc...

    SpiritLing
  • 【数据为证】警察!你不能凌驾法律之上!

    2011年10月,美国佛罗里达州劳德代尔堡市(Fort Lauderdale) 发生了一起恶性交通事故,事故原因是一名退休警察超速行驶。佛罗里达州...

    小莹莹
  • MySQL集群(一)之主从复制

    前面学完了JDBC,接下来带大家感受一下MySQL集群!其实什么是MySQL集群?简单的说就是一群机器(服务器)的集合,它们连在一起来工作。 其实各种数据库都有...

    用户1195962
  • ExtJs学习笔记(22)-XTemplate + WCF 打造无刷新数据分页

    ExtJs的Grid组件虽然不管从哪一方面来讲,都称得上是很好很强大,但是总会有一些应用场景并不需要这么多功能,比如网站的留言列表,开发者只想要一个简单的<li...

    菩提树下的杨过
  • Basic Concept

    使用偏移(bias)和 变化幅度(variance )作为估量model好坏的参数。

    李小白是一只喵
  • 前沿 | 循环神经网络不需要训练?复现「世界模型」的新发现

    作者:Corentin Tallec、Léonard Blier、Diviyan Kalainathan

    机器之心

扫码关注云+社区

领取腾讯云代金券