OPT:百万美金的火线排雷:深度解读DeFi资产授权漏洞_OPTI

本文来自AmberGroup,作者吴家志。原文标题:《ExploitingPrimitiveFinanceApprovalFlaws》。

事件摘要:2月24日,一篇关于PrimitiveFinance?的漏洞分析报告在圈内引发关注,报告描述了三个白帽攻击以及漏洞原理。一个多月后的4月14日,以AmberGroup区块链安全专家吴家志博士为代表的团队,发现了一个钱包地址有超过100万美金的资产存在风险。在本地复现攻击后,其团队通过Immunefi联系了Primitive项目方,并成功协助潜在的受害者重置了WETH授权,解除危机。这篇文章将介绍该团队如何在模拟环境中利用此漏洞,以及如何通过区块链数据分析找到潜在的受害者钱包地址。

原理:智能合约中的裂隙

在目前EVM及ERC-20的架构中,当用户与智能合约交互时,智能合约本身缺少一个能从代码层面捕捉到ERC-20转账事件的回调机制。例如当Alice给Bob发100个XYZ代币时,Bob的XYZ余额会被更新在XYZ合约里。但是Bob如何知道他的XYZ变多了呢?他可以查Etherscan或者其钱包App自动从以太坊节点取得的最新余额。如果Alice将100XYZ发给一个智能合约Charlie,Charlie如何得知他的XYZ余额增加了呢?

事实上Charlie没办法在收到100XYZ的当下主动取得他最新的余额,原因是这个转账是在XYZ合约上发生的,不在Charlie合约。智能合约部署完成后就像操作系统一样,是一堆代码放在某个地方,需要被调用了才会发生作用。为了解决这个难题,在ERC-20标准有一个被广泛使用的机制—approve()/transferFrom()。

举例来说,当Alice需要往Charlie存入100个XYZ代币时,Alice可以事先授权Charlie使用她的100XYZ额度,此时Charlie的deposit()函数就可以在一个交易里通过transferFrom()主动将Alice钱包里的100XYZ取出,并且更新Charlie合约的状态。为了减少摩擦,很多DApp甚至会让用户授权无限多的XYZ额度给项目方地址,这样可以让后续的transferFrom()调用直接成功,免除掉多次授权的点击以及手续费,这等同于将Charlie加白。这个方案留下了一个隐患,万一Charlie作恶或是被攻击了,Alice的资产就会有危险。

这个发生于2020年6月18日的意外证实了一个被控制或存在问题的智能合约可以如何被利用并且造成资产损失,如下代码所示,safeTransferFrom()虽然名为safe的transferFrom却意外被宣告成公开函数,导致任何人都可以使用Bancor合约的身份转移任意用户任意数量的任意资产到任意的地址。

简单举例来说,如果Alice正好使用过Bancor并且授权Bancor无限额度使用她的DAI,则一旦她的钱包里DAI余额大于零时,黑客就可以立即把她的DAI转走。

诊断:黑客是怎样绕开“安检”的?

根据上文的漏洞分析报告所述,这个外部函数有一个类似的漏洞,但无法像Bancor的漏洞一样被直接利用。事实上,攻击者需要伪造两个ERC20代币合约,一个Uniswap资金池,并且发起一笔Uniswap闪电贷绕过下图标注的?msg.sender==address(this)?检查。听起来复杂,但对于有经验的黑客来说,这并不是太困难。

Primitive为何需要实现?flashMintShortOptionsThenSwap()?这样一个接口呢?其实是有特定使用场景的,在?openFlashLong()函数可以看到,flashMintShortOptionsThenSwap()?会被封装在一个Uniswap的flash-swap调用参数里,在第1371行触发flash-swap之后,由回调函数?UniswapV2Call()?调起。此时由于?UniswapV2Call()?在Primitive合约里,便可以通过上述?msg.sender==address(this)?检查。

值得注意的是,在?openFlashLong()函数里,第1360行写的是?msg.sender,表示在正常的情况下,Primitive只能使用调用者本身的资金,然而攻击者可以通过伪造的pair以及params用类似于1371行的方式直接调用Primitive合约的UniswapV2Call()并绕过?flashMintShortOptionsThenSwap()?的检查。由于params在这情况下可以完全被控制,1360行的msg.sender便可以被替换成任意曾经授权Primitive的钱包地址,然后通过?flashMintShortOptionsThenSwap()?里的?transferFrom()?调用盗取资产。

追踪:找出可能的受害者

如果一个黑客碰巧知道某位“大户”曾授权有问题的合约,他可以轻易利用这个漏洞盗取受害人大量的资金。然而,这件事情如果仅使用区块浏览器是很难做到的,尤其在合约已经部署了较长时间,并有大量用户量的情况下。其中需要分析的数据并非是靠人工搜索Etherscan能够实现的。

GoogleCloudPublicDatasets在此时可发挥作用。由于每一个成功的approve()调用都会在以太坊上发出一个Approval()事件,我们可以通过BigQuery服务找出所有事件并且通过一些方法过滤出我们感兴趣的部分,例如_spender是Primitive合约的所有事件。

下面是我们在GCP上实际用来找出潜在受害者使用的SQL语句,其中第五行可以看到我们限定搜索的以太坊数据库及记录事件的表,第七行过滤出Approval()事件,第八行过滤了特定的_spender。此外,第六行将区块高度范围设定在Pirmitive合约部署之后,这可以大幅降低BigQuery扫过的数据量,这类的SQL优化会直接反应在你的GCP账单里。

接下来,我们可以进一步优化SQL查询将已经通过approve(_spender,0)重置授权的账号从清单中刨除,得到最终的账号列表。有了最终的列表,我们利用一个脚本监控着这些账号,并且在这些危险账号收到大量资产时发出预警,因为这很可能会造成严重的损失。

在一个星期三的清晨,机器人发出了预警,有一个可能的受害人在北京时间4月13日清晨5点24分收到了将近500WETH的资产,价值超过一百万美金。相较于已公开的三次白帽攻击,这个受害人如果被成功攻击,所损失的金额将高于稍早的三个案例的总和。

我们在北京时间9:32紧急联系了Primitive项目的漏洞赏金计划运营方Immunefi并且向他们展示我们如何利用这个漏洞在模拟环境中盗取受害人的500WETH,并且提供包括下面的截屏等证据。

在Primitive团队的帮助下,潜在的受害人于10:03将WETH授权重置,解除危机。

两天后,Primitive团队也针对此发现给予漏洞奖励并发布公开致谢。该笔赏金发稿前已捐助给CryptoRelief。

复现:分布拆解漏洞的利用

漏洞利用的第一步,我们需要准备两个ERC20合约:Redeem及Option。

其中Redeem合约是一个标准的ERC20,我们只需要基于OpenZeppelin的实现将mint()接口暴露出来,方便我们控制代币数量,如下所示:

Option合约会相对复杂一点,从下面的代码片段可以看到,我们需要刻意构造一些全局变量,以及公开函数,这些都是在Primitive的业务逻辑会用到的。此外,我们还需要传入三个参数来初始化Option合约:

·???????redeemToken:稍早构造的Redeem合约地址

·???????underlyingToken:攻击目标账号所持有的资产合约地址

·???????beneficiary:受益人地址,也就是攻击成功后将受害人资产转移的目标地址

这里需要特别说明的是mintOptions()这个函数,从上面的代码可以看到,它会直接把所有的underlyingToken发给beneficiary地址。这是因为下面的内部函数mintOptionWithUnderlyingBalance()函数在被?flashMintShortOptionsThenSwap()时会将underlyingToken发给Option代币合约,并且通过mintOptions()调用铸造Option代币。因此,我们在伪造的Option合约里,可以直接把mintOptions()当作一个提币调用,将underlyingToken发给beneficiary,用于之后归还闪电贷的资金。

接下来,我们可以用刚刚创建的Redeem及Option代币创建一个Uniswap的流动性池子,这个池子的地址将用来接收从受害人钱包转出的资金。事实上,每个Uniswap池子里有等价的两种资产,例如WETH及Redeem,为了完成漏洞利用,我们必须为池子注入流通性。Redeem是我们自己创建的,可以铸造无限量的代币,但WETH呢?

在闪电贷的帮助下,我们基本上可以利用无限数量的资金来做如何事情,只要确保能在一个交易中归还资金即可。在这个案例中,我们使用AaveV2的闪电贷借了相当于受害人总资产99.7%的资金存入上述的流动性池。

根据Aave的设计,需要实现一个回调函数executeOperation()执行获得贷款资金后的操作,并且在最后通过approve()调用授权Aave合约取走闪电贷的资产以及手续费。

总结

在基于EVM的智能合约世界里,approve()/transferFrom()是长久以来存在的固有问题。对于DeFi用户而言,需要多留意你的钱包地址是否正允许着其他人使用你的资产,并且定期重置资产使用权。对于项目方而言,需要在上线之前花更多心思和时间从各种可能的角度测试,甚至攻击你的代码,因为你正在编程的,是每位用户的真金白银。

关于作者

吴家志受聘于全球领先的加密金融智能服务提供商AmberGroup,作为区块链安全专家。他毕业于美国北卡州立大学计算机专业,获得博士学位,师从安卓安全领域领军者蒋旭宪教授,在美国读书期间一直从事系统安全研究,主要方向为虚拟化安全、安卓系统安全。吴家志博士在全球安卓安全领域有很大的影响,发表过多篇科技论文,在安卓系统漏洞安全方面经验丰富。他于2017年开始转战至区块链安全领域,曾担任全球第一家去中心化匿名众测平台DVP负责人,号召全网白帽黑客一起寻找开源底层代码中的漏洞。

郑重声明: 本文版权归原作者所有, 转载文章仅为传播更多信息之目的, 如作者信息标记有误, 请第一时间联系我们修改或删除, 多谢。

区块博客

[0:0ms0-3:649ms