查看原文
其他

Checks-Effects-Interactions 魔咒:以太坊DeFi平台2500万美金又双叒叕被盗了

天宸安全实验室 ATEC
2024-08-22


引言


说起 The DAO 重入漏洞,很多小伙伴应该不陌生。The DAO 重入漏洞是以太坊智能合约史上第一个重大漏洞,发生在 2016 年 6 月,直接导致项目方损失了 360 万 ETH,当时价值超过 6000 万美元,更为严重的是间接导致了以太坊硬分叉。这时,重入漏洞第一次进入人们的视野。


自 The DAO 事件起,大部分合约开发者都认识到了重入漏洞的严重性,且项目方的安全意识也不断提升。然而,两年后,2018 年 10 月,悲剧在 SpankChain 上重演。攻击者利用重入漏洞窃取了 165.38  ETH,当时价值约 3.8 万美元。此外,还导致 SpankChain 公司内部名为 BOOTY 的 token 被冻结,价值 4,000 美元。


不到两年,重入攻击又发生了。2020 年 4 月 18 日,DeFi 平台 Uniswap 遭受重入漏洞攻击,攻击者利用 Uniswap 和 ERC777 标准的兼容性问题缺陷,对 Uniswap 实施了重入攻击,总获利 34 万美元。由于此次攻击数额不大,未曾引起重视。谁知,短短 24 小时内,另一 DeFi 平台 Lendf.me 遭受重入攻击,累计损失约 25,236,849.44 美元。


重入漏洞一而再,再而三,三而四的发生,其根因并不神秘。都是因为合约开发者未遵循 “Checks-Effects-Interactions” 编码原则,使得攻击者可以在合约状态更改之前,不断的从合约转出资金。


由于 Lendf.me 被盗数额巨大,很快引起了广泛的讨论,一些文章对此次攻击的过程进行了分析,但遗憾的是并未给出攻击代码。本文经过更深入的研究,完整的分析了 Lendf.me 重入攻击的原理,梳理了攻击思路,给出了攻击代码的 demo,并搭建了一个 DeFi 的测试环境,在测试环境下完全复现了这一价值 2500 万美元的攻击。


分析原理


漏洞根因简单来讲是没有遵循 “Checks-Effects-Interactions” ("检查-生效-交互")的编码原则。如图所示,漏洞合约在 supply() 函数里先通过外部调用进行了交互,后做的状态变更,使得合约存在重入漏洞。



接下来我们分析一下具体如何重入。如上图所示,漏洞合约调用了 doTransferIn() 函数。函数的具体实现截取代码片段如下:



由以上代码片段可知,函数调用了 token.transferFrom() 函数。注意这里的 token 是符合 ERC777 标准的 token。这里我们选取了 imBTC 作为实例来分析和复现整个攻击。


来看 imBTC 关于 transferFrom() 的实现。imBTC 的源码可以参考

https://etherscan.io/address/0x3212b29e33587a00fb1c83346f5dbfa69a458923#code


其中关键的函数调用是 _callTokensToSend()  函数,实现代码如下:



分析到这里就触及了可重入的核心位置。getInterfaceImplementer() 是 ERC1820 提供的接口函数,用于获取注册的 implementer,implementer 是一个地址,由攻击者控制,通过这个地址就可以调用到攻击者的函数。当然函数名是固定的 tokensToSend()。攻击者可以在 tokensToSend() 函数里,实现提币的逻辑。


至此,攻击原理就清晰了:



编写代码


根据攻击原理,攻击需要满足如下条件:
1、攻击者注册 implementer ,以引导执行逻辑
2、攻击者实现 tokensToSend() 函数,在此函数里实现提币操作
3、攻击者需要往 Lendf.me 账户里充值,让账户余额不为 0 才可以成功提币,这需要先成功的调用一次 supply()


我们编写攻击代码,核心代码片段截取如下:



设置环境


要真正的实施攻击,需要搭建一整个 DeFi 的运行环境。目前,我们需要 Lendf.me 合约,imBTC 合约,ERC1820 合约。我们分别把这些合约部署到 Remix 上。


● 部署 Lendf.me 合约的时候由于合约太大,需要把 gas 上限设置的高一些。

● 部署 ERC1820 合约,并记录合约地址。

● 部署 imBTC 合约,把 ERC1820 合约地址赋值到 imBTC 合约的 IERC1820Registry 变量。

● 部署攻击合约,攻击合约需要依赖以上 3 个合约的地址。

  ·exchange address 是 Lendf.me 合约的地址

  ·token address 是 imBTC 合约的地址          

  ·ERC1820 address 是 ERC1820 合约的地址


此时主要合约已经部署完成,但是,如果想让整个 DeFi 环境运转起来,还需要 2 个 Lendf.me 所依赖的合约:获取币价的合约 Oracle,和获取利率的合约 InterestRateModel。这两个合约的源码 Lendf.me 并未给出,需要自己编写,或者可以参考一些开源项目。


假设读者已经准备好了 Oracle 合约和 InterestRateModel 合约。现在把这 2 个合约也部署到测试环境中。部署完毕之后,需要进行如下设置:


1. Lendf.me 合约需要依赖 Oracle 合约,获取币价,否则无法充值。部署好 Oracle 合约之后,调用 _setOracle() 接口设置 Oracle 合约地址。


2. Lendf.me 合约需要依赖 InterestRateModel 合约获取利率,否则无法充值。部署好 InterestRateModel 合约之后,调用 Lendf.me 的_setMarketInterestRateModel() 和 _supportMarket() 接口设置 InterestRateModel 地址。


注意上述步骤的顺序不可以颠倒,因为彼此之间有依赖关系。设置完成后,可以调用 market 接口查看是否成功。如下图所示,目前 isSupported  等关键变量已经设置成功。



3. imBTC 合约需要开启转账功能,调用合约的 enableTransfer() 接口设置。


4. imBTC 合约需要事先给 Lendf.me 和攻击者合约发送代币,直接调用合约的 mint() 接口铸币,每个合约发 10000 个币。


到此,环境设置完成,可以开始攻击。攻击只需要调用攻击合约的 startAttack() 函数即可。执行之前,攻击合约在 imBTC 的初始值是 10000,在 Lendf.me 的初始余额是 0 。


执行完毕之后可以看到:合约在 imBTC 的余额是 9999,在 Lendf.me 的余额是 101,攻击之后,攻击合约的代币数量增加了 100 个。这是因为第一次调用 supply() 充值了 100 个币,本次不进行重入,让充值操作成功执行。第二次调用 supply() 只充值了 1 个币,进入 tokensToSend() 函数后,called=1,执行重入逻辑,获取当前余额,此时当前余额是第一次 supply() 存入的 100 个币,把这些币全部提出,之后,继续回到第二次 supply() 的执行流程中,这时 supply() 会执行余额更新操作,把余额恢复成事先缓存的值,即 101。纵观整个流程,攻击合约仅仅损失了 1 个币,却在 Lendf.me 合约中存入了 101 个币。此次攻击,攻击者净赚 100 个币。


再次执行攻击后,在 imBTC 的余额由 9999 变成 10099 个,在 Lendf.me 的余额由 101 变成 202 个。再次利用同样的代码攻击,攻击者没有损失任何币,却净赚了 100+ 101 = 201 个币。攻击者可以不断执行攻击代码,直到 Lendf.me 的imBTC 全部被提走。



攻击代码中,为了方便说明攻击过程,第一次 supply() 只充值了 100 个币,实际上,攻击者可以充值更多的币,更快的把交易所中的币提走。


总结


此漏洞是诸多 DeFi 平台漏洞中影响最大的漏洞,虽然攻击者最终归还了被盗的币,但是 DeFi 的安全性还是要引起足够的重视。漏洞根因简单来讲是没有遵循“检查-生效-交互”的编码原则。如果开发者忠实的遵守了“检查-生效-交互”的安全编码原则,则此次攻击能够避免。我们通过这个漏洞也看出,尽管单一模块不会导致漏洞,然而攻击者的思路早已不限于单一模块,而是把多个模块融会贯通,当逻辑变得复杂,漏洞也就有藏身之处了。多个模块组合到一起时,安全人员需要站在更高的视角审查系统的安全性。


参考文献

https://paper.seebug.org/1183/
https://medium.com/@peckshield/uniswap-lendf-me-hacks-root-cause-and-loss-analysis-50f3263dcc09


天宸安全实验室灵巧博士

蚂蚁金服安全专家

毕业于中科院信息工程研究所,

目前专注于区块链安全领域的工作


支付宝天宸安全实验室

隶属于支付宝安全实验室,致力于研究并落地下一代核电级安全防御和密码学基础设施,攻克业界系统安全、移动安全、IoT安全、密码学等重点领域的安全防御技术难题。


扫码关注我们


继续滑动看下一个
ATEC
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存