前言

在10月8日,区块链项目方SpankChain在medium上发表了一篇文章, 并表明其受到了攻击,导致损失了160多个ETH和一些Token,这次攻击事件,相对来说损失金额是较小的,约4万美元,不过值得一提的是:这次攻击事件的起因与2016年闹得沸沸扬扬的TheDAO事件如出一辙!一共被盗走300多万ETH,更为严重的是还间接导致了以太坊硬分叉...

自那次事件起,以太坊的智能合约开发者大部分都认识到了重入漏洞这类严重问题,而且自那以后也很少有重入漏洞导致资产被窃取的事件,然而相隔两年,悲剧在SpankChain上重演。

什么是重入漏洞?

了解重入漏洞是理解这次攻击事件必要的知识储备,所以接下来我们会将该类漏洞进行一个详细解释,让读者深刻理解,已经了解的大佬可直接略过。

在以太坊智能合约中,合约与合约之间是可以互相调用的,在gas足够的情况下,合约与合约之间甚至可以互相循环调用直至达到gas上限,这本身是合理的,但是若循环中会产生敏感操作例如转账,则有可能会导致产生很严重的问题。

还是挺抽象对不对,我们直接来用代码进行解释,引用大佬的一句话。

「Talk is cheap. Show me the code」

漏洞代码片段:

function withdraw(){require(msg.sender.call.value(balances[msg.sender])());balances[msg.sender]=0;
}

以上是最简化版的withdraw函数,此函数多数在钱包合约、去中心化交易所等合约中实现,目的是为了让用户能进行“提款”,这里的提款是指将智能合约体系内的代币换成通用的以太币。

漏洞分析:
前面提到过,合约与合约之间是可以互相循环调用的,只要循环所需的gas不超过gas上限即可,使用call来进行转账可以使用更多的gas,这是以太坊的机制。
但是如上代码片段中犯了一个致命的问题:没有在使用call转账之前将用户的代币余额归零,在循环的过程中,攻击者的账户一直是处于有余额的状态。

这会导致什么问题呢?
在前面的章节中我们提到过,在给智能合约转账的时候会触发智能合约的fallback函数,若收款的智能合约在fallback函数中再次调用对方的withdraw函数的话,那将会产生一个循环调用。

如图,漏洞合约会不断向攻击者合约转账,直至循环结束(有限循环,以太坊的gas上限不允许出现无限循环)后才将用户代币余额归零。

用DEMO调试复现:

contract Bank{mapping (address => uint256) public balances;function wallet() constant returns(uint256 result){return this.balance;}function recharge() payable{balances[msg.sender]+=msg.value;}function withdraw(){require(msg.sender.call.value(balances[msg.sender])());balances[msg.sender]=0;}
}
contract Attacker{address public bankAddr;uint attackCount = 0;constructor(address _bank){bankAddr = _bank;}function attack() payable{attackCount = 0;Bank bank = Bank(bankAddr);bank.recharge.value(msg.value)();bank.withdraw();}function () payable{if(msg.sender==bankAddr&&attackCount<5){attackCount+=1;Bank bank = Bank(bankAddr);bank.withdraw();}}function wallet() constant returns(uint256 result){return this.balance;}
}

本文提供了一份复现DEMO,将上面代码复制至remix IDE中即可对重入漏洞复现并有一个充分的了解。
第一步,使用remix账户列表中的第一个账户扮演受害者来部署漏洞合约以及在漏洞合约中存入一些以太币

如图,笔者在Bank合约也就是漏洞合约中充值了500wei个以太币。
第二步,使用remix账户列表中的第2个账户来扮演攻击者部署一份攻击者合约。
部署攻击者合约需要填入Bank合约的地址,按如下按钮即可复制地址,然后点击Deploy按钮来部署

第三步,部署完之后,在value中填入要向Bank合约充值的数量,然后点击attack函数,即可窃取相当于充值数额5倍的以太币,这是由于为了防止超出gas上限,攻击者合约的fallback函数中限制了循环调用次数为5次。

这里我们充值了10wei的以太币用于攻击Bank合约,执行attack函数后我们在看看Attacker合约的余额,以此推断是否利用成功。

查看wallet,可以看到合约的钱包余额为60,正好多出了充值数的5倍。

SpankChain重入漏洞分析

有了上面的基础知识接下来就好理解多了,我们先进行一个总结:重入漏洞产生的原因是未在进行转账操作之前进行状态变更操作而在之后进行状态变更操作,这是最根本的原因,换句话说,在进行一个类似于"提现"的操作中,转账只能为最后一个要进行的步骤。

接下来我们要列出几个重要的线索:

SpankChain支付通道合约: https://etherscan.io/address/0xf91546835f756da0c10cfa0cda95b15577b84aa7#code
攻击者地址: https://etherscan.io/address/0xcf267ea3f1ebae3c29fea0a3253f94f3122c2199
攻击者恶意合约地址: https://etherscan.io/address/0xc5918a927c4fb83fe99e30d6f66707f4b396900e
攻击者恶意合约发起的攻击交易: https://bloxy.info/zh/tx/0x21e9d20b57f6ae60dac23466c8395d47f42dc24628e5a31f224567a2b4effa88#

有上面的线索就很好分析了,我们先来看发起攻击的那笔交易:

可以看到攻击者先转了5个ETH到他自己部署的恶意合约,然后再通过恶意合约将5ETH转入SpankChain的支付通道合约,最后支付通道合约转出了32次5个ETH到其恶意合约,恶意合约再将总金额32*5=160 ETH转到了攻击者账户中。

我们再来看看攻击者具体的操作:

可以看到攻击者先调用了支付通道合约的createChannel函数并转入了5个ETH,然后循环调用了支付通道合约的LCOpenTimeou函数,并一直获取ETH,每调用一次获取5 ETH,一共调用了32次。

我们再来看看这两个函数的具体代码,先来看createChannel函数:

为方便读者理解,我们将该函数的每一行都进行了注释,简单来说该函数是用于创建一个“安全支付通道”,其原理是先将要转的资金存到支付通道合约中,在规定的时间内收款方才可以收款,超出规定时间发起方可以将转账撤回,支付通道合约相当于一个中转担保的角色。

再来看看LCOpenTimeou函数:

该函数相当于提款函数,不过在这个支付通道内的意义为转账超时撤回,就是说通道已经超出其开放时间了,发起方有权将转账撤回,具体漏洞点看红框中的代码以及注释,简单来说就是在发起转账之后才进行状态变更操作,从而引发了重入漏洞。

于是攻击者可以通过自己部署的使用createChannel函数创建一个支付通道,在确认时间超出之后,使用自己部署的合约去调用支付通道的LCOpenTimeou函数,然后支付通道合约向恶意合约转账,触发恶意合约的fallback函数,接着又触发攻击者在恶意合约fallbck函数中调用的LCOpenTimeou函数,形成重入循环...

更新,经过PeckShield团队友情提醒,上述描述存在一处错误,向大家道歉!正确的结论如下:

尽管是在进行转账之后更新的状态,但是上面的代码要形成重入也又一定难度,看第一个红框中的代码,因为该函数里进行ETH转账不是使用的call.value,而是使用的transfer,使用transfer只能消耗2300 GAS,无法构成重入,这也是SpankChain与TheDAO不同的点。

再看第二个红框,其中调用了token的transfer函数,而token是攻击者可控的,调用token合约的transfer函数不会有2300 GAS限制!于是攻击者可以在自己部署的恶意token合约的transfer函数中调用支付通道合约的LCOpenTimeou函数,形成重入循环...

解决方案

最根本的解决方案还是在转账之前就把所有应该变更的状态提前更新,而不是在转账之后再进行更新,希望这次事件能让TheDAO惨案不再重演。

TheDAO悲剧重演,SpankChain重入漏洞分析相关推荐

  1. Cream Finance 重入漏洞事件分析

    前言 8 月 30 日,知道创宇区块链安全实验室 监测到以太坊上的 DeFi 协议 Cream Finance 遭遇重入漏洞袭击,损失超 1800 万美元.实验室第一时间跟踪本次事件并分析. 涉及对象 ...

  2. 创宇区块链 | 黑客利用重入漏洞盗币,Ola_finance 攻击事件分析

    1. 前言 北京时间 2022 年 3 月 31 日,知道创宇区块链安全实验室 监测到借贷平台 Ola_finance 遭到重入攻击,黑客窃取 216964.18 USDC.507216.68 BUS ...

  3. 区块链的安全问题(乱序时间戳,重入攻击)

    原文链接:郭老师的备课资料 在引入比特币等加密货币时,一个经常提及的概念是支撑着这些加密货币的底层框架--区块链协议非常地安全可靠.各种加密算法保证了区块链的正常运行,区块链中的信息不可篡改.不能删除 ...

  4. Solidity重入攻击深入理解

    始于一份样例代码 下面是样例合约的完整代码: pragma solidity ^0.4.23;contract babybank {mapping(address => uint) public ...

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

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

  6. 创宇区块链|Rari Capital 遭受重入攻击,损失超 8000 万美元

    前言 北京时间 2022 年 4 月 30 日,知道创宇区块链安全实验室 监测到以太坊上 feiprotocol 和 RariCapital 协议中的多个池子遭到重入攻击,导致损失超 8000 万美元 ...

  7. 【Linux系统编程】可重入和不可重入函数

    00. 目录 文章目录 00. 目录 01. 不可重入函数 02. 可重入函数 03. 判断条件 04. Linux常用可重入函数 05. 附录 01. 不可重入函数 在实时系统的设计中,经常会出现多 ...

  8. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  9. Java 可重入锁内存可见性分析

    转载自 深度好文 | Java 可重入锁内存可见性分析 一个习以为常的细节 之前在做 ReentrantLock 相关的试验,试验本身很简单,和本文相关的简化版如下:(提示:以下代码均可左右滑动) p ...

最新文章

  1. 对Java Inputstream的一次采访
  2. win10任务栏透明_任务栏1秒变透明,桌面瞬间高大上,美化必备!
  3. 2018年第九届蓝桥杯 - 省赛 - C/C++大学A组 - F.航班时间
  4. Oracle10g数据库归档与非归档模式下的备份与恢复
  5. 操作系统课设之Windows 的互斥与同步
  6. python作用域——LEGB规则
  7. Android 8.0学习(18)--- Android8.0运行时权限策略变化和适配方案
  8. pr计算机相关知识,影视制作技术第一讲认识premiere(pr)与视频基础知识.ppt
  9. netty权威指南第二版源码
  10. uniapp app端拉起高德网页地图
  11. 打猎游戏——HTML版(JavaScript的应用)
  12. 如何配置java环境_vscode配置java环境
  13. 最新企业管理软件发展趋势分析
  14. html每个页面添加尾部
  15. JSP-简单的练习省略显示长字符串
  16. node.js下安装 webpack 的时候,出现:TypeError:this is not a typed array;
  17. 实现用户登录权限验证
  18. 手机GPU性能评估指标
  19. 汽车无钥匙进入系统工作原理、汽车无钥匙进入系统简介
  20. 微信小程序开发者工具初体验及实现技术初探

热门文章

  1. new Date()得到时间是东八区时间 存储到mysql里面少了八个小时 原来以为是 java new date 与系统时间相差8小时
  2. Python采集网易云音乐
  3. 《72个促成交易的经典技巧:…
  4. 《奋斗》中徐志森的生意经
  5. Linux从头学09:x86 处理器如何进行-层层的内存保护?
  6. 【vue】Element Calendar 组件显示农历及节日
  7. 神经网络传递函数的选择,神经网络的函数表达式
  8. 爱的十个秘密--11.信任的力量
  9. SOLIDWORKS如何实现放样折弯
  10. 基于稀疏表示的分类方法 Sparse Representation based Classification Method