以太坊君士坦丁堡漏洞分析

栏目: 编程工具 · 发布时间: 5年前

内容简介:这两天关于以太坊延迟君士坦丁堡升级的报导铺天盖地,可惜到现在都没看到一篇能把这个漏洞讲透彻的,就由我来给大家解密吧。上一篇文章给大家介绍过EIP 1283,是为了优化SSTORE指令的gas计算方式的,这次的漏洞就出在这个EIP上,可能会导致“重入攻击”。所谓“重入攻击”,指的是在

这两天关于以太坊延迟君士坦丁堡升级的报导铺天盖地,可惜到现在都没看到一篇能把这个漏洞讲透彻的,就由我来给大家解密吧。

上一篇文章给大家介绍过EIP 1283,是为了优化SSTORE指令的gas计算方式的,这次的漏洞就出在这个EIP上,可能会导致“重入攻击”。

1.什么是“重入攻击”

所谓“重入攻击”,指的是在 同一笔交易 中,合约A调用合约B,而合约B又反过来调用合约A的现象。

这种情况是必须被禁止的,因为合约A可能需要依赖自身的一些状态来给其他账户转账,而如果在这个过程中间,合约B又反过来调用合约A并修改了状态,有可能导致状态紊乱,黑客可以通过这个漏洞“偷”走别的账户的钱。

2.漏洞原理分析

ChainSecurity组织最先向以太坊团队提交了这个漏洞,他们设计了下面这个场景:

这是一个“共享支付合约”,其实就跟我们平时去食堂刷饭卡是类似的。我去管理处办了张饭卡,往里面充了100块钱,现在这些钱100%都是属于我自己的。然后我去吃了顿饭花了20,这时候我就更新一下卡里的参数:这张卡里的钱80%归我,剩下归食堂。然后我突然接到通知,公司要搬家了,这张卡用不上了,于是我就去管理处退卡,管理处的会计就根据这个80%的比例, 退我100*80%=80块钱,还有100*(1-80%)=20块钱打到食堂帐上 。请注意,这个操作必须是 原子 的,假如他先退了80块给我,然后我在他给食堂打钱之前,把参数改成了0%,他就会给食堂帐上打100*(1-0%)=100块钱!也就是说,虽然我只充了100块,但是我跟食堂加起来却得到了180块钱,这多出来的80块钱是哪里来的呢?当时就是从其他充饭卡的人那里“偷”来的啦~

具体到代码层面,攻击的流程参见下图:

以太坊君士坦丁堡漏洞分析

黑客首先给“攻击合约账户A”和一个“普通账户B”之间建立一条共享支付通道(办张卡),请注意,这两个账号都是黑客自己控制的。

然后黑客操纵账户A调用deposit()方法往“共享支付合约”里充了一些钱(比如100 ETH)。

接着,黑客调用攻击合约的attack()方法,这个方法会接连执行下面两个调用:

  • 调用“共享支付合约”的updateSplit()方法,把分配参数更新成100%(没毛病,这些钱都是账户A的)
  • 调用"共享支付合约"的splitFunds()方法销卡退款(理论上应该给账户A转100 ETH,账户B转0 ETH)

"共享支付合约"先给账户A转100 ETH,调用账户A的transfer()方法。但是账户A是个合约,并且没有transfer()方法,因此会调用到它的fallback方法。

在合约A的fallback方法里,它再次调用了“共享支付合约”的updateSplit()方法,把分配参数更新成了0%(这一步是通过内联汇编完成的,比较省gas,具体原因后面会说)。

接着,“共享支付合约”会继续给账户B转账,但是由于分配参数变了,现在账户B占100%了,所以它又给账户B转了100 ETH。

可以看到, 黑客每发起一次攻击,都可以赚100 ETH(因为两个账号都是他自己的),而且可以无限次数攻击,直到把“共享支付合约”里的钱偷光 ,太可怕了。。。

3.为什么升级前没有这个漏洞

实际上在此之前,EVM是考虑过重入攻击问题的,在合约A调用合约B时,合约B的代码只能执行一些非常简单的操作(比如发送一个event,对应LOG指令),消耗的总gas不能超过2300,这被称为“ 调用津贴(CallStipend) ”。由于CALL指令本身需要消耗700 gas,所以 实际上可用的gas只有1600 ,这对于普通指令足够用了,比如LOG指令每个字节只需要消耗8 gas,因此最多可以写200个字节来记录这次调用事件。但是,SSTORE指令需要消耗5000 gas,因此如果合约B中使用了SSTORE指令,会导致Out of Gas从而中止交易的执行。因此,EVM是依靠SSTORE指令的高额油费消耗来避免重入攻击的。

以太坊君士坦丁堡漏洞分析

但是,这一保证被EIP 1283打破了。我们先来回顾一下EIP 1283对SSTORE的gas计算方法(不熟悉的朋友请参考前一篇文章):

  • No-op状态:收取200 gas
  • Fresh状态:
    • 如果原始值是0,收取20000 gas
    • 否则,收取5000 gas。如果新值是0,退还15000 gas
  • Dirty状态:收取200 gas,并检查下面2个条件:
    • 如果原始值不是0
      • 如果当前值是0(说明新值不是0),收回退还的15000 gas
      • 如果新值是0(说明当前值不是0),退还15000 gas
    • 如果原始值等于新值(被reset回原始值了)
      • 如果原始值是0,退还19800 gas
      • 否则,退还4800 gas

黑客发起攻击时,先调用一次SSTORE把分配参数从0更改为100,进入Fresh状态,收取20000 gas。然后在fallback函数中再次把分配参数从100更改为0,此时会进入Dirty状态, 只会收取200 gas ,并退还19800 gas。这一数值远远低于1600 gas,因此黑客就可以成功地发起重入攻击。

4.如何重现这个漏洞

ChainSecurity在github上公开了攻击示例代码: https://github.com/ChainSecurity/constantinople-reentrancy

可以在Ganache上模拟测试攻击过程:

ganache-cli --hardfork=constantinople
truffle test

可以看到正常调用后余额几乎没有什么变化,而发起重入攻击后账户增加了1 ETH:

以太坊君士坦丁堡漏洞分析

据称,目前为止还没有发现合约因为该漏洞而造成损失,但是这显然是一个极大的隐患。幸好,该漏洞在君士坦丁堡升级之前被发现,以太坊团队决定推迟升级时间,从这一点也可以看出项目社区化运营的巨大力量~

更多文章欢迎关注“鑫鑫点灯”专栏: https://blog.csdn.net/turkeycock

或关注飞久微信公众号: 以太坊君士坦丁堡漏洞分析

以上所述就是小编给大家介绍的《以太坊君士坦丁堡漏洞分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Docker开发指南

Docker开发指南

[英] Adrian Mouat / 黄彦邦 / 人民邮电出版社 / 2017-4 / 79.00元

Docker容器轻量和可移植的特性尤其适用于动态和分布式的环境,它的兴起给软件开发流程带来了一场革命。本书对Docker进行了全面讲解,包括开发、生产以至维护的整个软件生命周期,并对其中可能出现的一些问题进行了探讨,如软件版本差异、开发环境与生产环境的差异、系统安全问题,等等。一起来看看 《Docker开发指南》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换