内容简介:这两天关于以太坊延迟君士坦丁堡升级的报导铺天盖地,可惜到现在都没看到一篇能把这个漏洞讲透彻的,就由我来给大家解密吧。上一篇文章给大家介绍过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
- 如果原始值不是0
黑客发起攻击时,先调用一次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
或关注飞久微信公众号:以上所述就是小编给大家介绍的《以太坊君士坦丁堡漏洞分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 漏洞分析:OpenSSH用户枚举漏洞(CVE-2018-15473)分析
- 【漏洞分析】CouchDB漏洞(CVE–2017–12635, CVE–2017–12636)分析
- 【漏洞分析】lighttpd域处理拒绝服务漏洞环境从复现到分析
- 漏洞分析:对CVE-2018-8587(Microsoft Outlook)漏洞的深入分析
- 路由器漏洞挖掘之 DIR-815 栈溢出漏洞分析
- Weblogic IIOP反序列化漏洞(CVE-2020-2551) 漏洞分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。