黑客利用 ERC777 标准的 transferFrom() 回调机制发起攻击,PeckShield 建议开发者采用 Checks-Effects-Interactions 方法进行防范。 原文标题:《Uniswap 和 Lendf.Me 遭攻击始末:DeFi 乐高组合下的「多米诺」式崩塌》 04 月 18 日上午 08:58 开始, 一 DeFi 平台 Uniswap 被黑客利用重入漏洞实施了攻击。PeckShield 安全团队迅速定位到问题,发现黑客利用了 Uniswap 和 ERC777 标准的兼容性问题缺陷实施了重入攻击。 糟糕的是,仅仅在 24 小时后,于 04 月 19 日上午 08:45,又一知名 DeFi 平台 Lendf.Me 也被黑客以类似的手段实施了攻击。
在 Uniswap 的攻击案例中,攻击者利用此漏洞消耗尽 Uniswap ETH-imBTC 池约 1,278 个 ETH。而在 Lendf.Me 中,攻击者则利用它来任意增加内部 imBTC 抵押金额,并通过从其他可用的 Lendf.Me 交易中借入 10 多种资产(总价值约 2,524 万美元)。 PeckShield 安全团队认为这是自年初 bZx 遭攻击之后,又两起黑客利用 DeFi 系统性风控漏洞实施的攻击。一个不容忽视的问题是,DeFi 市场的风险可能不仅仅局限于平台本身,单个平台的模式创新很可能在与其他平台业务接轨时产生漏洞风险。 详细漏洞攻击细节,我们将在文章后面做详细介绍。 Figure 1: ERC777 transferFrom() ERC777 标准的业务组合兼容性问题我们首先介绍下 ERC777 标准,ERC777 出现的目的是对 ERC20 标准进行改进。其不但实现了功能扩展,还有 ERC20 标准一样良好的兼容性,愿景是成为 ERC20 标准的有效继承者。 该标准扩展的功能之一是提供了「hook」机制,可以使普通地址或合约通过注册一个 tokensToSend() hook 函数来控制或拒绝发送 Token。这原本是在 ERC20 基础上加强了对 Token 的风险控制接口,是一次有益的改进。不过由于 DeFi 项目的可组合特性,一个合约在不同产品之间相互调用时,其业务逻辑复杂度也会大大增加,这就给注入代码攻击提供了可能性。 其中最关键的部分是,攻击者可以通过注册 from 的 tokensToSend() 来实行回调。我们从下面的代码片段可以看到,ERC777 标准中可以通过 getInterfaceImplementer()(1,054 行)获得攻击者的 tokensToSend() 接口,并在第 1,056 行调用此函数。而此处正是黑客劫持交易实施攻击的入口。 Figure 2: ERC777-Compatible tokensToSend() Hijacking 如 2019 年 4 月 OpenZeppelin 发布的帖子以及 2019 年 7 月发布的漏洞利用演示中所述,攻击者可以自己定义函数 tokensToSend(),并通过 setInterfaceImplementer() 来设置合约中的 hook 函数。 Figure 3: OpenZeppelin's Exploit Demo (Hook Setup) 之后攻击者就可以像传统 PC 上的 hook 函数一样,在 tokensToSend() 做任何事情。如下图所示,攻击者可以对同一笔交易进行多次交易。 Figure 4: OpenZeppelin's Exploit Demo (Hook Function) Uniswap 攻击分析Uniswap 被率先发现利用 ERC777 的兼容性问题实施了攻击。就如此恶意交易在 Bloxy 中的截图所示 (hash:0x9cb1d93d6859883361e8c2f9941f13d6156a1e8daa0ebe801b5d0b5a612723c1),函数内部进行了一次 tokenToEthSwapInput() 调用。这意味着攻击者可以先通过操纵交易汇率,然后再用另一笔 imBTC 以较低价格兑换更多的 ETH。 Figure 5: Uniswap Hack Lendf.Me 攻击分析在 Uniswap 遭攻击约 24 小时后,又一 DeFi 平台 Lendf.Me 也遭到了黑客攻击。下面是其中一个攻击交易的截图。如图所示,supply() 函数中调用真实转账函数 transferFrom() 时,被 hook 的攻击者合约里嵌入了盗用 Lendf.Me 的 withdraw() 的提币操作。 Figure 6: Lendf.Me Hack 在这个交易例子中,攻击者第一次 supply() 时确实向 Lendf.Me 存放了 289.99999999 个 imBTC,而在第二个 supply() 中,攻击者只存放 0.00000001 个 imBTC,但由于攻击者注册了 tokensToSend(),所以在执行 doTransferIn() -> IMBTC :: transferFrom()(第 1,583 行)时,调用了攻击者函数 tokensToSend(),攻击者函数通过调用 Lendf.Me 的 withdraw() 函数把 290 个 imBTC 直接全部提走。 需要注意的是,正常的业务逻辑应该是项目合约中的 Balance 会减去被攻击者提走的 290 个 imBTC,然而当 supply() 执行返回时,余额并未被重置,仍然为 290 imBTC (第 1,599 行)。攻击者就是通过控制修改 Lendf.Me 中攻击者的 imBTC 抵押金额,有了足够大的 imBTC 抵押,攻击就可以从各种流动交易对中借出所有可用的 10 多种资产(资产总值 25,236,849.44 美元)。 Figure 7: Lendf.Me Hack Details 资产流向攻击者 0x538359 共计从 Lendf.Me 获利 25,236,849.44 美元,其中各个 Token 分布如下: 如上图,攻击者在获利之后,马上将各个 Token 转移至其关联账号 0xa9bf70 之中,之后攻击者数十次 通过 1inch.exchange, ParaSwap 等平台将其中比较抢手的 WETH, PAX, BUSD 等 Token 换成 ETH, DAI, BAT 代币,另外将其中的 TUSD, USDT 代币存入 Aave 借贷平台。至此为止,攻击者及其关联账号的余额如上所示。 修复建议PeckShield 安全团队在此建议开发者,可以采用 「Checks-Effects-Interactions」方法来防止这类重入攻击。举个例子,Lendf.Me 的 supply() 里如果是先更新 token 余额,再调用 doTransferIn() 。这将会让攻击在 withdraw() 之后没有重置余额的可能性。 另一方面, ERC777 标准特性会不可避免地启用 hook 机制,因此我们需要检测并防止所有交易功能产生可以重入的风险。例如,如果 supply() 和 withdraw() 同时运行时加个互斥锁,那么攻击者就无法在 supply() 函数内部执行 withdraw() 操作。 最后并不能被忽视的一点是,我们需要认真思考下 DeFi 业务组合可能存在的系统性风险问题,平台方不仅要确保在产品上线前有过硬的代码审计和漏洞排查,还要在不同产品做业务组合时考虑因各自不同业务逻辑而潜在的系统性风控问题。 可能一个新创新,在原平台一点问题都没有,但组合接入另一个产品后就可能存在业务逻辑缺陷,进而成为黑客攻击整个 DeFi 市场的入口。 PS:此次黑客对 Lendf.Me 的攻击对 DeFi 社区来说无疑是一场灾难,在此建议广大 DeFi 开发者务必注意业务存在的系统性风控风险,应尽可能和第三方安全公司合作排查一切潜在的安全风险。 (责任编辑:admin1) |