安全,一直是区块链领域举足轻重的话题。今天咱们聊聊由RAM 消耗漏洞及回滚交易漏洞引起的安全隐患,以及对应的解决方案。
文章趣味十足且不生涩,干货满满,希望对你有所帮助!
千淘万漉虽辛苦,吹尽狂沙始到金 —— 《浪淘沙》刘禹锡
上回书说到
DApp 假币鱼目混珠
转账函数检测疏漏马虎
对于制造伪 EOS 以次充好,其实只需要检查发行方是否为 eosio,或者调用相关合约查看代币信息,但是检查代币操作依然不够严谨的情况下又会产生更多的变体漏洞,导致遭受变体攻击的风险,所以 EOS 智能合约中的代码非黑即白,切不可模棱两可。
要做到完备的漏洞防御,必须先透彻理解 EOS 业务逻辑特性以及规则限制。本期我们就由 EOS 业务逻辑特性为切入点,解析 RAM 消耗及回滚交易漏洞。
本期话题
陌生转账 RAM 被吞,交易回滚大奖送人
每一位游戏玩家都曾在游戏中体验过一马当先,万夫莫开的畅快淋漓,也体验过心惊胆战,如临大敌的彻骨寒意。但游戏终究是游戏,大多数情况下,都有许多尝试的机会,尤其是单机游戏,可以适时存档后,重新读档不断尝试“从跌倒的地方爬起来”。
在 EOS 游戏和 DApp 不断涌现的今天,还没有出现与传统单机游戏结合的机制。倒是有很多竞猜类的游戏大受欢迎,但是竞猜类游戏以公平竞猜为游戏理念,不能允许玩家以不断尝试的方式获取奖励。
不断尝试确实是增加获取奖励几率很有效的方式,于是黑客们找到了一种零成本不断尝试竞猜的方式,试图在 EOS DApp 中轻而易举拔得头筹。介绍这种攻击手段之前,让我们来了解一下 EOS 特殊的业务逻辑。
基础知识
EOS 智能合约业务逻辑特性
在 EOS 智能合约交易情景中,合约常常需要根据收到 EOS token 的情况来执行相关业务逻辑,这主要是通过 eosio.token 合约中 transfer 函数的通知回执来实现的:
当用户 A 向用户 B 转账时,用户 B 会接收到这个通知,并可以进行相应的函数处理,这是由require_recipient 的特殊机制产生的结果。
require_recipient 在这里的实现逻辑是: 将原本的 action receiver 修改为传入的账户,再发起一次 action 调用,相当于带着同样的参数又调用了一次 A 和 B 账户的 transfer 函数。
关于这个特殊的机制,EOS 官方给出了相关的解释:
那么,如果在 B 账户部署一个智能合约,定义一个 transfer 函数就可以进行相应的业务逻辑处理,示例如下:
注意,这里的 transfer 函数使用了上一期提到的 if (from == _self || to != _self)防御手段,验证收到转账的是自己来预防变体转账攻击。
当然,除了 transfer,require_recipient(account_name 合约账户 XXX)也可以调用 XXX 合约中的其他同名函数。
漏洞如影随形
RAM 消耗漏洞
按照上面 B 账户里面的智能合约实现业务逻辑,是没有什么问题的,但是如果按照如下合约实现的话,问题就会出现了:
这个智能合约中,komo::transfer 中的 for 循环用账户 from 的授权写了很多无用的记录到state.db,而这个操作用户在 eosio::transfer 时是不知情的。
有关方面给官方写出了这个情况的修改建议:
在 require_recipient 触发 action handler 执行时, 禁止被触发的 handler 使用当前 action 的授权。
如果被触发的 action handler 有存储要求,可以使用 inline actions 来解决, inlineaction 被执行时就不会用到原来 action 的授权了。
但是,使用 inline action 如果不加注意,又会产生另一个漏洞。
回滚交易漏洞
历史事件
eos.win 在 2 月 9 日被攻击者利用 inline action 漏洞的回滚交易攻击通关过,交易细节如下:
EOS 合约内部的 action 调用分为 inline action 和 deferred action,用于合约对其他 action的调用,这两种调用方式是有一定区别的:
1. inline action 与原来的 action 是同一个事务,如果 inline action 失败了,整个事务会回滚;
2. deferred action 与原来的 action 不属于同一个事务,并且不保证 deferred action 能够成功执行,如果失败了,也不会引起原有 action 的事务回滚。
在早期,很多 EOS 游戏合约都是使用 inline action 这种方式来实现的,从下注、开奖、发奖、通知,一系列操作虽然写在不同的 action 中,但是执行的时候都是通过 inline action的方式来联动,达到用户下注后自动开奖发奖的效果。
在这种情况下,如果合约通过 require_recipient 来向用户账户发送开奖通知,那么用户账户就可以通过接收 require_recipient 的通知来判断是否成功赢取奖励,如果失败,那么调用 eos_assert(0)来使 action 执行失败,由于所有 action 都是 inline action,这将会导致整个 transaction 失败,用户下注的 EOS 会退回。
如果合约没有发送 require_recipient 通知,攻击者也能通过自己组装 inline action,然后通过对自身余额或者合约数据库数据的判断来实现回滚攻击。
这样,攻击者制造 action 执行失败后,就能将每次投注的 EOS 退回,从而无成本重新加入游戏,直到获奖为止,模拟了单机游戏中“无限读档”,直到通关的游戏方式。
漏洞修复
在实现自动开奖发奖的一系列动作中,不要惯性思维的使用 inline action,适时使用 deferred action 可以预防整个一套交易流程回滚到下注之前,规避攻击者零成本再次参与游戏的风险。
黑客尚可锲而不舍,安全怎能松懈怠惰
EOS 与以太坊相比,以其吞吐量大,处理速度快,以及交易成本低等优势占据了开发 DApp 的有利地位,与初期的以太坊相比,低级错误导致得安全漏洞发生率似乎有所降低,但是由于游戏以及 DApp 的业务逻辑性比单纯的数字货币智能合约要复杂许多,黑客依然能够在业务逻辑中找到开发者不曾设想过的“套路”,作为迅速获利的垫脚石。
本期的回滚交易攻击说明,即使是攻击者,也想到用不断尝试,不断努力的方式达到自己赢得游戏的目标。作为区块链安全生态的支持者和建设者,我们每一个人更应该以锲而不舍的精神,从底层代码做起,维护区块链相关应用的安全,稳步发展。
参考资料:
[1] 警惕!EOS 恶意合约可吞噬用户 RAM 漏洞分析
http://www.cocoachina.com/cms/wap.php?action=article&id=24425
[2] eosflare.io
https://eosflare.io/tx/e28fa9fbd6008edc1a66e01ea4e443265eb9bacce0b423351d178dcb6928de3b
[3] Inline action to external contract
https://developers.eos.io/eosio-home/docs/sending-an-inline-transaction-to-external-contract
领取专属 10元无门槛券
私享最新 技术干货