安全,区块链领域举足轻重的话题,为什么一行代码能瞬间蒸发几十亿市值?合约底层函数的使用不当会引起哪些漏洞?重入漏洞会导致什么风险?

「区块链大本营」携手「链安科技」团队重磅推出「合约安全漏洞解析连载」,以讲故事的方式,带你回顾区块链安全走过的历程;分析漏洞背后的玄机。让开发者在趣味中学习,写出更加牢固的合约,且防患于未然。

当然,这些文章并不是专为开发者而作的,即使你不是开发者,当你读完本连载,相信再有安全问题爆出时,你会有全新的理解。

引子:至道问学之有知无行,分温故为存心,知新为致知,而敦厚为存心,崇礼为致知,此皆百密一疏。—— 清·魏源《庸易通义》

却说“DoS攻击重现区块链江湖,缜密防范助阵安全阵营”,例外判定合力数据结构的加固,亦使老牌劲敌DoS节节败退。

没看过的请戳:合约安全漏洞连载之二

本回咱们聊聊:

“重入”“竞态”里应外合币穷财尽

“交互”“限制”强强联手链泰民安

区块链的“高速公路”在川流不息的同时,却也事故频发。究其缘由,大批投资者涌入这个似乎畅通无阻,通向明日辉煌的康庄大道,都跃跃欲试一场“速度与激情”,展开对成功的追逐赛。未曾想,挑战者中并非只有彼此,一袭黑衣,手段了得的选手大有人在,这些处心积虑的黑客总有办法让智能合约看似神通广大,实则百密一疏。

这一回,我们将重点剖析竞态条件漏洞的两种形式:重入漏洞以及交易顺序依赖漏洞。

事件回顾

2016年4月,完全自治,去中心化的项目DAO启动,立刻成为最受欢迎的以太坊项目,然而在其发布之后,有开发者警告DAO的发起者,在splitDAO函数中潜伏着递归调用漏洞[1]。 2016年6月14日,DAO的项目方声称漏洞已被定位,资金和合约安全已受到保障。

然而就在3天之后,6月17日,黑客却利用上述漏洞向DAO发起攻击,360万以太币岌岌可危,超过6百万美元的资金被源源不断地被黑客暗度陈仓,着实来了一场“无间DAO”。

事件发生后,DAO负责人采取措施减缓了资金流失的速度,以太坊也在7月修改源码帮助DAO转移资金,尝试夺回失窃资金,却导致了以太坊的硬分叉[2]

想要分析黑客如何对DAO的资金探囊取物,就不得不提到竞态条件这个术语。

什么是竞态条件

竞态条件的官方定义是如果程序的执行顺序改变会影响结果,它就属于一个竞态条件 [3]

在智能合约中,竞态条件漏洞被攻击者利用后,攻击者利用一个与存在漏洞合约平起平坐的外部合约竞争夺取控制权,改变该智能合约的行为。

用一个形象的比喻来说明,将智能合约理解成一条高速公路,所有函数和功能理解为车辆,原本的执行顺序规定了车辆经过的顺序,此时一名熟练的老司机,驾驶着GTR在弯道超车加塞,扰乱了整个道路的秩序,抢占了在道路中的领先地位,进而为所欲为,戏耍合约规则。

以太坊智能合约的特点之一是能够调用和利用其它外部合约的代码,调用外部合约主要存在的危险就是外部合约可以接管控制流,并对调用函数不期望的数据进行更改。这类漏洞有多种形式,我们在这里深度解析重入和交易顺序依赖两种。

竞态条件漏洞分析及详细修复建议

1.重入漏洞(Reentrancy)

问题描述

合约通常用来处理 Ether,因此通常会将 Ether 发送给各种外部用户地址。调用外部合约或将以太网发送到地址的操作需要合约提交外部调用。这些外部调用可能被攻击者劫持,迫使合约执行进一步的代码(即通过回退函数),包括回调自身。因此代码执行"重新进入"合约。这种攻击被用于上述臭名昭著的DAO 攻击。

我们把存在漏洞的合约简化成如下案例合约:

该合约有两个函数:depositFunds()和withdrawFunds(),depositFunds()的功能是增加msg.sender的余额,withdrawFunds()的功能是取出msg.sender指定的数值为_weiToWithdraw的Ether。

现在,一个攻击者创建了下列合约

PS:注意此处由于重入攻击造成了balances[msg.sender]溢出,强烈推荐所有数学运算都使用SafeMath进行,这个要点我们在第一期溢出漏洞中已经提到(敲黑板)。

我们来分析下该合约是如何进行重入攻击的:

(1)假设普通用户向原合约(Reentrancy.sol)存入15 ether;

(2)攻击者部署攻击合约(POC.sol),并调用setInstance()指向原合约部署地址;

(3)攻击者调用攻击合约的depositEther()函数,预先向原合约预存1 ether,此时, 在原合约中,攻击合约的地址有1 ether余额;

(4)攻击者调用攻击合约的withdrawFunds()函数,该函数再调用原合约的withdrawFunds()函数,并传参1 ether;

(5)进入原合约,withdrawFunds()函数的第一行:

require(balances[msg.sender] >= _weiToWithdraw);,攻击合约地址下余额为1 ether,等于_weiToWithdraw,条件满足,进入下一行;

(6)withdrawFunds()函数的第二行:

require(msg.sender.call.value(_weiToWithdraw)());,向msg.sender转入_weiToWithdraw(此时是1 ether),由于msg.sender是合约地址,solidity规定向合约地址接收到ether时如果未指定其他有效函数,那么默认会调用合约的fallback函数。执行流进入攻击合约,并调用攻击合约的fallback函数,并且,因为是通过call.value()()方式发送以太币,该方法会发送所有剩余gas;

(7)进入攻击合约的fallback函数,if判断原合约余额,此时为16 ether,条件满足,再次"重入"原合约的withdrawFunds()函数;

(8)再次进入原合约的withdrawFunds()函数,因为balances[msg.sender] -= _weiToWithdraw;并未执行,所以此时攻击合约地址仍有1 ether,第一个require条件满足,执行到第二个require;

(9)此后步骤6-8将一直重复,直到原合约余额少于1 ether或者gas耗尽;

(10)最后进入原合约,执行balances[msg.sender] -= _weiToWithdraw;,注意,此处会从balances[msg.sender]中减去所有提取的ether,导致balances[msg.sender]溢出,如果此处使用SafeMath,可以通过抛出异常的方式避免重入攻击;

最终的结果是攻击者只使用了1 ether,就从原合约中取出了所有的ether。

漏洞修复

(1)在可能的情况下,将ether发送给外部地址时使用solidity内置的transfer()函数[4],transfer()转账时只发送2300 gas,不足以调用另一份合约(即重入发送合约),使用transfer()重写原合约的withdrawFunds()如下;

(2)确保状态变量改变发生在ether被发送(或者任何外部调用)之前,即Solidity官方推荐的检查-生效-交互模式(checks-effects-interactions);

(3)使用互斥锁:添加一个在代码执行过程中锁定合约的状态变量,防止重入调用

接述事件回顾,重入在DAO攻击中发挥了重要作用,最终导致了 Ethereum Classic(ETC)的分叉。有关The DAO原始漏洞的详细分析,请参阅 Phil Daian的文章。

2.交易顺序依赖攻击

问题描述

与大多数区块链一样,以太坊节点汇集交易并将其形成块。一旦矿工解决了共识机制(目前Ethereum的 ETHASH PoW),这些交易就被认为是有效的。解决该区块的矿工也会选择来自该矿池的哪些交易将包含在该区块中,这通常是由gasPrice交易决定的。在这里有一个潜在的攻击媒介。攻击者可以观察事务池中是否存在可能包含问题解决方案的事务,修改或撤销攻击者的权限或更改合约中的对攻击者不利的状态。然后,攻击者可以从这个事务中获取数据,并创建一个更高级别的事务gasPrice 并在原始之前将其交易包含在一个区块中。

我们来看如下案例漏洞合约:

这个合约包含1000个ether,找到并提交正确答案的用户将得到这笔奖励。当一个用户找出答案Ethereum!。他调用solve函数,并把答案Ethereum!作为参数。不幸的是,攻击者可以观察交易池中任何人提交的答案,他们看到这个解决方案,检查它的有效性,然后提交一个远高于原始交易的gasPrice的新交易。解决该问题的矿工可能会因攻击者的gasPrice更高而先打包攻击者的交易。攻击者将获得1000ether,最初解决问题的用户将不会得到任何奖励(合约中没有剩余ether)。

漏洞修复

有两类用户可以进行这种的提前交易攻击:用户(修改他们的交易的gasPrice)和矿工自己(他们可以按照他们认为合适的方式重新排序交易)。一个易受第一类(用户)攻击的合约比一个易受第二类(矿工)攻击的合约明显更糟糕,因为矿工只能在解决一个区块时执行攻击,这对于任何针对特定区块的单个矿工来说都是不可能的。在这里,我将列出一些与他们可能阻止的攻击类别相关的缓解措施。

可以采用的一种方法是在合约中创建限制条件,即gasPrice上限。这可以防止用户增加gasPrice并获得超出上限的优先事务排序。这种预防措施只能缓解第一类攻击者(任意用户)的攻击。在这种情况下,矿工仍然可以攻击合约,因为无论gasPrice如何,他们都可以根据需要排序交易。

更可靠的方法是尽可能使用提交—披露方案(commit-reveal)。这种方案规定用户使用隐藏信息(通常是散列)发送交易。在交易已包含在块中之后,用户发送一个交易解密已经发送的数据(披露阶段)。此方法可防止矿工和用户进行前瞻性交易,因为他们无法确定交易内容。然而,这种方法无法隐藏交易价值(在某些情况下,这是需要隐藏的有价值信息)。 ENS智能合约允许用户发送交易,其承诺数据包括他们愿意花费的以太数量。然后,用户可以发送任意值的交易。在披露阶段,用户退还了交易中发送的金额与他们愿意花费的金额之间的差额。

前事不忘,后事之师

DAO事件在当时区块链行业轰动一时,损失之重,令无数投资人捶胸顿足,我们总结下来,为了防止类似的情况发生,开发者应注意以下几点:

开发过程中注意查阅Solidity或者其他官方语言中是否已给出相关内置函数或者严谨的交互模式,如有应严格遵守,切不可异想天开

勤于思考状态变量有可能发生的意外,对有潜在问题的状态变量应予以锁定。

综合运用gas限制以及披露方案,保障交易信息在合理的环节以合理的形式呈现。

区块链时代的安全问题都带有互联网发展早期的影子,安全知识的迁移以及防范意识的提升将会是斩除隐患的利刃。

次回予告:

底层函数调用险象环生

外部功能慎用防患未然

杨霞

成都链安科技CEO,创始人。电子科技大学副教授,最早研究区块链形式化验证的专家。一直为航空航天、军事领域提供形式化验证服务。主持国家核高基、装发重大软件课题等近10项国家课题。CC国际安全标准成员、CCF区块链专委会委员。发表学术论文30多篇,申请20多项专利。

DappBrowser,是一款安全、易用、优质的Dapp导航产品,志在为用户提供深度优化的Dapp交易和管理体验。目前已经支持云斗龙等优质的Dapp,可以直接在手机端运行,不需要下载或在PC端安装插件。新版本增加交易所搬砖神器,合约安全检测功能 。下载链接

也可以扫码下载:

本文引用:

[1] 智能合约的积极监控和防御:https://courses.csail.mit.edu/6.857/2017/project/23.pdf

[2] 八卦以太坊,说一说TheDAO的“分家”事件:

http://baijiahao.baidu.com/s?id=1587206953375229861&wfr=spider&for=pc

[3] 什么是竞态条件:

https://blog.csdn.net/Clifnich/article/details/78447524

[4] https://blog.sigmaprime.io/solidity-security.html#race-conditions

弯道超车老司机戏耍智能合约——竞态条件漏洞 | 漏洞解析连载之三相关推荐

  1. python 很高兴问题_Python 3.7曾有一个很老的GIL竞态条件(race condition),我是这么解决的...

    Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 作者:Victor Stinner 作为Python最关键的组成部分之一:GIL(全局解释器锁),我花了4 ...

  2. 以太坊漏洞分析————3、竞态条件漏洞

    引子:至道问学之有知无行,分温故为存心,知新为致知,而敦厚为存心,崇礼为致知,此皆百密一疏. -- 清·魏源<庸易通义> 区块链的"高速公路"在川流不息的同时,却也事故 ...

  3. 竞态条件与sigsuspend函数

    前言: 实现自己的mysleep函数 中,存在一个问题: 当alarm(sec)执行完,CPU非常忙,还没有执行pause函数,就去执行其他代码段了.等到alarm时间到,调用了信号处理函数后,此时C ...

  4. 竞态条件的赋值_《Java并发编程实战》读书笔记一:基础知识

    一.线程安全性 一个对象是否是需要是线性安全的,取决于它是否需要被多个线程访问 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额外的同步,这个 ...

  5. 线程学习5——竞态条件

    竞态条件 概述:如果两个或两个以上的线程同时访问相同的对象,或者访问不同步的共享状态.就会出现竞态条件. 举例:如果多个线程同时访问类StateThread中的方法,最后结果会如何呢? 定义一个类St ...

  6. linux操作系统之竞态条件(时序竞态)

    (1)时序竞态:前后两次运行同一个程序,出现的结果不同. (2)pause函数:使用该函数会造成进程主动挂起,并等待信号唤醒,调用该系统调用的进程会处于阻塞状态(主动放弃CPU) 函数原型:int p ...

  7. [Linux]继续探究mysleep函数(竞态条件)

    之前我们探究过mysleep的简单用法,我们实现的代码是这样的: #include<stdio.h> #include<signal.h>void myhandler(int ...

  8. Linux系统编程----8(竞态条件,时序竞态,pause函数,如何解决时序竞态)

    竞态条件(时序竞态): pause 函数 调用该函数可以造成进程主动挂起,等待信号唤醒.调用该系统调用的进程将处于阻塞状态(主动放弃 cpu) 直 到有信号递达将其唤醒,等不到一直等 int paus ...

  9. 雪城大学信息安全讲义 五、竞态条件

    五.竞态条件 原文:Race Condition Vulnerability 译者:飞龙 1 竞态条件漏洞 下面的代码段属于某个特权程序(即 Set-UID 程序),它使用 Root 权限运行. 1: ...

最新文章

  1. Windows上打开大文件的工具
  2. 学python用什么系统好-Python用什么系统环境好?老男孩Python
  3. 031_自己对ArrayList中Iterator的实现
  4. git安装,windows下git bash默认目录更改
  5. eclipse @ 注释为何一写就报错
  6. c++ primer 习题13.39自己做的答案
  7. html离线地图,离线地图三维开发-添加HTML
  8. Practical Tactics for Social Intercourse One
  9. Jmeter——将Fiddeler作为代理服务器
  10. C#实现超长位整数运算
  11. wps 甘特图_【WPS神技能】在Excel表格中用图表阶梯式的展示任务进程?找甘特图呀...
  12. 【实战】Pytorch BiLSTM + CRF做NER
  13. FPGA BCD计数器(一位)
  14. Linux服务器安全防护
  15. pow是什么意思python,python中pow什么意思
  16. scout_mini使用步骤
  17. 老电脑也要玩tensorflow,解决AVX、SEE、SEE2等报错之路
  18. Unity零基础到入门 ☀️| 近万字教程 对 Unity 中的 动画系统基础 全面解析+实战演练,你确定要错过吗?
  19. 南京大学计算机 曹云浩,【2015.青春曲园】第二十六届“迎校庆”校园十佳歌手大赛独家放送~...
  20. win10c语言关机,win10如何设置定时关机?

热门文章

  1. vue axios 下载zip 解压报错 responseType blob arraybuffer
  2. 4GCPE工业路由器使用方法 4G转WiFi 网口 有线 串口 以太网
  3. Flutter Hero 实现共享元素转场动画
  4. WinForm 显示图片PictureBox控件
  5. python对财务的作用知乎_Python与财务「上」——数据采集篇
  6. 北京大学计算机硕博连读5年,关于2018年北京大学硕博连读研究生选拔工作的通知-更新...
  7. 上海2021高考成绩什么时候可以查询,关于2021年上海高考成绩什么时候出来
  8. Windows 8应用商店应用如何与Android和iPad对抗?
  9. 从源头解决问题,而不是曲线救国
  10. 基础知识----Symbian UIQ