以太坊“君士坦丁堡”升级漏洞详解

 EOS随机数问题形势严峻,安全和安全圈都乱,而难兄难弟以太坊也好不到哪去,不升级会死,升级却总延期,这次延期就是因为“君士坦丁堡”升级漏洞,今天就来分享一下该漏洞的详细信息,也有利于EOS设计和架构的思考。

    “君士坦丁堡”升级漏洞是一个重入攻击,要理解以太坊的重入攻击,就不得不提以太坊著名的The Dao攻击,这个就是利用的重入攻击攻击了The DAO合约,盗取了大把大把的以太,最后以太坊不得不通过硬分叉来回滚这次攻击。

重入攻击原理

    重入攻击的核心是智能合约的fallback函数,该函数和EOS的require_recipient类似,就是当其他账号给智能合约账号转ETH的时候,如果目标智能合约存在fallback函数,就会执行该fallback函数,进而控制权就到了恶意合约的代码里了,攻击者就可以在fallback函数里再次回调发起攻击。比如如下代码:

    

      该攻击预防方法有如下三种:

        1)确保所有改变状态变量的逻辑,都发生在以ETH转账给外部智能合约账号之前。比如上面的娥实例就是balance=0可以放在call.value之前,这样fallback函数回调payOut时就会因为balance不够没法再次转ETH

        2) 引入互斥锁。也就是说,添加一个状态变量,在代码执行期间锁定合约,防止重入调用

        3)转账ETH给外部合约账号时使用内置的 transfer() 函数

新重入漏洞原理

        我们知道链上的币等资产信息及合约变量都是保存在状态数据里,所以智能合约要攻击必须执行sstore来修改状态数据,否则就是无用代码。

        上面有提到使用transfer函数可以避免重入攻击,具体机制如下:

        send、transfer、call 消息时候都会调用 fallback 函数,不同的是 send 和 transfer 有 2300 gas 的限制,也就是传递给 fallback 的只有 2300 gas,这个 gas 只能用于记录日志等不耗gas的操作,因为其他操作都将超过 2300 gas,比如sstore。但 call 则会把剩余的所有 gas 都给 fallback 函数,这有可能导致循环调用。transfer() 函数将该操作限制在 2300 Gas ,但是sstore指令的消耗最少5000,远远超过2300 gas, 所以这不足以使上面的fallback函数再调回payOut,进而可以规避这个问题。因而,目前以太坊合约就有部分合约采用了该方法。但是最新的“君士坦丁堡”升级补丁中,某些场景下sstore指令的gas消耗值调低了。

     我们知道,以太坊的数据都是<k, v>的组织结构,一个sstore指令就是为了更改其中一个<k, v>。

 原来的收费标准(只看sstore指令):

        上面的sstore设计还是蛮有意思的,对某个具体key的sstore操作的几个规则:

         1)0->非0属于新增key值,sstore调用gas消耗是20000

           2)非0->非0属于修改key值,sstore调用gas消耗是5000

                    这个设计其实是合理的,因为非0到非0的修改,本身不增加状态数据的存储量

           3)非0->0属于删除key值,会退款

                 相当于删除了存储数据会退款也很合理,和EOS删除数据库数据退回RAM一样的道理

修正后的收费标准(EIP 1283):

    有一个关键改动点:对交易中同一个key的多次sstore调用进行细化。比如对某个账号币的balance余额多次修改,那么每次sstore的gas消耗可能是不一样的。

       1)如果是属于对某个具体key的非0到非0的第一次sstore调用gas消耗还是5000,和老方案一样

        2)如果是属于对某个具体key的非0到非0的非第一次sstore调用gas消耗从5000降到了200

         这个调整其实是合理的,交易执行完成后才会执行写入到db的操作,也就是只执行一次高CPU消耗的操作,中间操作都是对MPT内存数据的修改,CPU消耗相比少很多,因而全程只需收取一次数据库操作高gas消耗费用。

     再回到fallback重入攻击,由于存在200 gas这种极低的sstore指令消耗场景,导致上面的2300 gas限制下,fallback函数仍旧有机会执行外部调用并执行sstore指令来修改状态数据, 进而在使用transfer情况下,升级后,重入攻击可以再次发起了。

    因而该漏洞会影响transfer等方式来转账的场景。

附录

    transfer指令限制为2300的原理:

     我们来看下call.value和transfer生成的EVM汇编代码就知道了:

核心就是transfer不会调gas指令,call会调用gas指令

猜你喜欢

转载自blog.csdn.net/ITleaks/article/details/86520945