引子:至道问学之有知无行,分温故为存心,知新为致知,而敦厚为存心,崇礼为致知,此皆百密一疏。

—— 清·魏源《庸易通义》

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

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

事件回顾

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

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

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

想要分析黑客如何对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事件在当时区块链行业轰动一时,损失之重,令无数投资人捶胸顿足,我们总结下来,为了防止类似的情况发生,开发者应注意以下几点:

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

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

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

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

引用

[1]:  https://courses.csail.mit.edu/6.857/2017/project/23.pdf

[2]: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

以太坊漏洞分析————3、竞态条件漏洞相关推荐

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

    安全,区块链领域举足轻重的话题,为什么一行代码能瞬间蒸发几十亿市值?合约底层函数的使用不当会引起哪些漏洞?重入漏洞会导致什么风险? 「区块链大本营」携手「链安科技」团队重磅推出「合约安全漏洞解析连载」 ...

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

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

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

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

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

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

  5. golang data race 竞态条件

    golang race condition 竞态条件 data race race condition golang race detector golang的协程机制使得编写并发代码变得非常容易,但 ...

  6. 别混淆数据争用(data race) 和竞态条件(race condition)

    在有关多线程编程的话题中,数据争用(data race) 和竞态条件(race condition)是两个经常被提及的名词,它们两个有着相似的名字,也是我们在并行编程中极力避免出现的.但在处理实际问题 ...

  7. 竞态条件的赋值_信号-sunshine225-51CTO博客

    一.基础知识信号产生的条件 a. 终端按键产生.如:ctrl+c(SIGINT信号),ctrl+\(SIGQUIT信号),ctrl+z(SIGTSTP信号)...... b. 系统命令和函数.如:ki ...

  8. 计算机系统学习之(1):基础知识概要——进程、中断、线程、竞态条件、关键区域、死锁、进程调度

    文章目录 进程的创建 哪些事件导致进程的创建 fork 和 exec 命令创建和控制进程 fork() 命令 execve() 命令 进程的状态 中断 中断的种类 线程 线程共享内容 线程独有内容 进 ...

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

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

最新文章

  1. 属于python文件的操作有_Python的文件操作
  2. splay 1296 营业额统计
  3. Android App压力测试(Monkey和ADB)
  4. ORA-38301:can not perform DDL/DML over objects in Recycle Bin
  5. Android自定义Application的作用
  6. egg mysql 项目实战,egg.js创建项目,目录介绍,简单使用,sequelize mysql使用
  7. python运行结果图_[宜配屋]听图阁
  8. windows docker常用命令
  9. java_web用户的自动登录模块的实现
  10. 均分纸牌问题——(分治 + 贪心 + 前缀和 + 中位数 + 排序)
  11. C#服务启动以及服务指令
  12. 计算机导论第4版第五章答案,《计算机导论》习题答案.doc
  13. 嵌入式mysql数据库文件读取_使用嵌入式关系型SQLite数据库存储数据
  14. java 切图_分布式切图服务——切图篇
  15. HTML给表格写个标题居中,如何将表格中的各个标题居中?
  16. 机器学习算法——手动搭建决策树分类器(代码+作图)
  17. 数据分析师常见的十道面试题目
  18. [译] 2019 前端性能优化年度总结 — 第五部分
  19. 2021年湖南省高考体考成绩查询,2021年湖南体育专业考试成绩查询网址:http://jyt.hunan.gov.cn/...
  20. InnoDB的“无用”知识

热门文章

  1. 会PHP如何拥有一个自己的QQ机器人?(三)
  2. JAVA中类scanf的使用方法
  3. docker-comose搭建openldap
  4. 解决字符终端下fbterm打不开yong输入法的问题
  5. Xshell 5 解除强制更新方法汇总(文内含密钥与下载链接)
  6. win 10 添加账号
  7. 查找和排序算法的学生成绩分析实验
  8. Linux错安装libpng12的问题
  9. Java登录页面实时验证用户名密码和动态验证码
  10. READ-2204 FL-WBC Enhancing Robustness against Model Poisoning Attacks in Federated Learning