我们团队在整理和复盘
Sanshu Inu 事件 时,发现通缩代币(Deflation Token)或 FoT 代币(Fee-on-Transfer Tokens)是造成这次攻击的罪魁祸首之一。
我们对通缩代币可能造成的安全漏洞进行了进一步分析,发现 UniswapV2 在对 Router01 的升级修复版
Router02 的实现中引入了漏洞,使得其中的代币可以被任意用户取出。我们初步统计发现,有几个地址持续从中获利,仅其中一个
地址 就从中获利 1000+ETH,价值超过 1600 万人民币。
而且,该问题不仅局限于 Uniswap,在所有采用类似 UniswapV2 Router02 的代码的平台都存在这个问题,其中包括以太坊上的 SushiSwap,BSC 链上的 PancakeSwap 等。为帮助相关项目方了解该问题,在联系了相关项目方后,我们对该缺陷进行公开。
问题揭露与利用方法
Uniswap 是 DeFi 领域举足轻重的去中心化交易所,对于每一对代币,Uniswap 设置了对应的 Pair (流动性池,下称 Pair 或池子)实现了自动做市机制。然而直接调用 UniswapV2 的 Pair 进行交互(如图中的 Choice 1)对普通用户而言是有一定的难度的。因此 UniswapV2 为用户进行了一层封装,即用户可以通过 UniswapV2 的 Router 实现代币的交换(Swap)及流动性的增减(Add/Remove Liquidity)操作,而不必操心 Router 与 Pair 具体如何实现交互(如图中的 Choice 2)。
(要点 1) Router 在代替用户与 Pair 进行代币交换(Swap)和增减流动性(Add/Remove Liquidity)的时候,要么会直接将钱在用户和 Pair 之间 transfer,而不经过 Router;或者 Router 会用变量 amount 记录用户和 Pair 转进来的代币数量,再分别地转给对应的 Pair 或者用户。也就是说,正常情况下,Router 在一笔交易执行之后,是不会留存有任何 Token 的。
(要点 2)问题出在 Router02 新增的减少流动性的函数实现。Router02 在实现函数 removeLiquidityETHSupportingFeeOnTransferTokens() 的时候,并没有记录应当返还给用户多少代币,而是调用该种代币的 balanceOf(address(this)) 函数,从而将 Router02 当前持有的所有该种代币都转给用户。代码如图所示。
正常情况下,这样并不会产生任何问题,因为 Router02 所有的钱在该笔交易内,都是来源于用户从 Pair 中取出的流动性,所以都是用户的。但是,如果某种情况下导致了 Router02 中事先就存有了该种代币呢?这个时候用户就可以调用该问题函数实现不当获利了。
经过我们的调查发现,现实中这种情况屡见不鲜。我们根据获利数量初步分析发现,导致 Router02 中不正常存有代币的情况有以下几种:
- Token 的机制设置。
- 部分 Token 为了给 Token 的使用者进行分红或补偿,会定期或不定期给 Token 的用户发该种 Token。如稳定币 Fei (TVL 1.66 亿美元),我们将在下文的实例介绍中详细介绍该项目。
- 项目空投。
用户误转。我们发现大量交易不小心将 Token 直接转入 Router02 中,而不当获利者(我们姑且这么称呼)会在接下来的几个块内(甚至同一块内)迅速利用 Uniswap 的该缺陷函数移走用户资产,实现获利。如交易 0 x1951efc5ea628503f02d5f233aee9bdeb9594ee9164947af49c99c202ce05f41 中
一个地址向 Router02 转了价值 1000 美元的 FXF Token,而在紧随其后的另一笔交易(0 xbbcaf5069e21ee457fe4083c6ecc728d5a71443a377a0c700db6ed6a69fdbd5d)中这些 Token 就被另一个地址采用上述方法取走了。
由此,我们只需要监测 Router02 中的 Token 数量,如果某种 Token 的价值能够覆盖发起交易的交易费用,就可以实现获利。具体交易实现如下
- 向对应 Pair (必须是ETH和该种 Token 的池子)提供少量流动性,为接下来移除流动性做好准备。
- 监控 Router02 地址,获取其每一个 Token 的 Balance。当某个 Token 的 balance 总价值能够覆盖交易费用和相关成本时,发起下一步交易。
- 通过调用 Router02 合约的函数函数
removeLiquidityETHSupportingFeeOnTransferTokens() 移除少量该池子的流动性,这个时候 Router02 会根据 balanceOf(address(this)) 将你应得的部分和 Router02 中所有的该种 Token 一起 transfer 给交易发起者。
实例详解(以稳定币 Fei 为例)
研究过程中,我们发现其中两笔交易中其调用者获利了将近 300 个 ETH,总价值约 160 万美元。
Fei 项目介绍
这两笔交易中涉及的 Token 为 Fei (一种对标美元的稳定币,即 1 Fei == 1 USD)。其中 Fei 的 Incentive 机制是导致 Router02 中出现大量 Fei 代币的原因。该机制可以为 UniswapV2 的 FEI/WETH 交易池设置一个 Incentive 合约。当有人调用 Fei 进行转账的时候,Fei 合约会根据 sender 是否注册 incentive 合约决定在转账完成后是否会回调对应 Incentive 合约的 Incentivize 函数。该函数根据条件判断后会调用 Fei.mint,向 recipient,也就是 Fei 的接收者额外 mint 出一笔钱,如图。
也就是说,这个 incentive 合约的功能就是,任何人在 UniswapV2 的 FEI/WETH 交易池取走 FEI 的时候,除了会获得 UniswapV2 的 FEI/WETH 交易池 transfer 的本金外,还会额外收到一笔奖励。而 Router02 代替用户进行 removeLiquidityETH 和 removeLiquidityETHWithPermit 操作的时候,就会由于转手了 Fei 代币,而被发放奖励。
获利实现分析
下面我们以交易 0 x46a8a8eb2fcf75e0a4874d0049d833eaa6432d4b28cb558dc631806be431618b 为例,具体介绍获利是怎么实现的。
在该交易发生之前,Fei 在交易 0 x9f4e2995481fa10cba40ac013d06e2352db5323bd2deae81b89444be2e88a1be 中通过调用了
EthUniswapPCVController 合约的
reweight(),其中调用了 UniswapV2Router02 的 removeLiquidityETH 函数,导致大量的 Fei (约 69 万个)截留在 Router02 中。
该用户监测到该获利机会,在随后的几个块实现了该交易(部分执行截图如下)。
从图中的第 2、3 个红框可以看到,用户调用该特殊的移除流动性的函数后,Pair 转给 Router02 的钱仅约为 2.6e16 wei (不到 1 美元),而 Router02 转给用户的钱却约有 6.9e23 wei,即纯获利 Fei 约 69 万个。而产生差距如此巨大的原因就在于 Router02 中本来就有将近这么多钱(见第 1 个红框)。(图中可以看到 Fei 在 transfer 的时候调用了对应的 Incentive 函数,感兴趣的朋友可以自行查看交易。图源 https://tx.blocksecteam.com/)
用户在获得这些大量的 Fei Token 之后在同一个交易内直接通过 Uniswap 将其转成 WETH (超过 297 个)后转给获利地址 0 x4d1d758f0966c6e6de873958e62788300c13f60e。
后续
Fei 项目已经在 2021 年 7 月 4 号移除了该交易对的 Incentive 合约,即 Fei 项目不会再向 Router02 发放奖励,交易 0 xfa6e50b964f57a7fd9451af694d18b851a30e58649d878d21b10c20f810723d1。
漏洞分析及建议
漏洞来源
Router02 是 UniswapV2 中引入的第二版 Router,为了解决 Router01 无法处理 FoT 代币(Fee-on-Transfer Tokens)的问题(如图,来源
Uniswap 官方文档)。
FoT 代币会在 transfer 过程中收取手续费,导致调用 transfer()、transferFrom() 时,接收方实际获得代币数量小于发送方发出的代币数量,致使 Router01 无法根据 Pair 传给 Router 的数量原封不动地转给用户。所以 Router02 实现了 removeLiquidityETHSupportingFeeOnTransferTokens() 用于解决该问题,将该转给用户的,都转给用户。
但是该实现没有对该函数的假设(即处理的 FoT 代币)进行检验,而是粗暴地使用了 balanceOf(address(this)),导致用户(包括项目方)受害,大量财产暴露甚至散落在公众场合,吸引了恶意用户来不当获利。
受害者分析
我们根据 Router02 出现代币的主要几种情况进行分析,可以看到受害者如下:
- Token 的机制设置导致 Router02 中存在部分 Token:直接受害者是 Uniswap 的用户,用户在 Uniswap 交换的时候本来应该得到的 Token 部分被 Router02 截获,导致用户损失。而这进一步导致 Token 项目方设计的机制无法将利益分发给其用户,使得其相关机制失效,导致项目可用性受损,因此间接受害者是项目方。
- 用户误转导致 Router02 中出现 Token:受害者是相关用户,不可否认用户有其一定的责任,但是 Uniswap 由于缺乏必要的校验,导致其将不属于 Uniswap 的币转给第三方。
建议
defi 项目之间的可组合性导致了 defi 领域的繁荣,也带来大量安全风险。因此项目方在项目开发的过程中需要特别注意与其他项目之间的交互,是否会由于适配性等问题导致安全风险,比如与通缩代币的交互等。
影响分析
由于 Uniswap 的流动性大,且其分叉项目广泛,该漏洞的影响的项目除了 UniswapV2 之外,还包括 SushiSwap,BSC 链上的 PancakeSwap 等。
非法获利交易数量
| 平台 | 交易数量 | 链 |
|---|
| UniswapV2 | 1823 | ethereum |
| SushiSwap | 35 | ethereum |
| PancakeSwap | 2344 | BSC |
根据我们初步统计,仅在以太坊上恶意地址,总共获利就超出 1000ETH。下面附上部分地址及相关不当获利情况统计(以 ETH 计价,粗略统计,当前 ETH 价格约为 2600 美元(2021)。
| 获利地址 | 获利(以 ETH 计价) | 主要涉及平台 |
|---|
| 0 x4d1d758f0966c6e6de873958e62788300c13f60e | 1018 | Uniswap |
| 0 x34927d8bebe7e83e479d8999c988b4f5c13bb7a9 | 15.9 | Uniswap |
| 0 x991e498d4285ea71641a18ff3f1a417a8964afe0 | 13.2 | Uniswap |
| 0 x09507D71685A9Abfa753949eF9bC318f40dc686f | 5.9 | SushiSwap |
| 0 xf8b567789e0d5896e0a7f53d030720f42aae79f3 | 15.1 | Uniswap、SushiSwap |
附录及引用
上文提到的 UniswapV2 中采用 balanceOf(address(this)) 实现的移除流动性的函数,除了 removeLiquidityETHSupportingFeeOnTransferTokens(),还有另一种函数内部调用了前面所说函数的实现——removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(),即在特定情况下,这两个函数的调用都可以实现不当得利。详见 UniswapV2Router02 合约代码。
各平台合约地址及不当获利交易举例
| 平台合约 | 地址 | 交易举例 |
|---|
| UniswapV2Router02 | 0 x7a250d5630B4cF539739dF2C5dAcb4c659F2488D | 0 x46a8a8eb2fcf75e0a4874d0049d833eaa6432d4b28cb558dc631806be431618b |
| SushiSwap Router | 0 xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f | 0 xc532ffae82d662d6bf5a2b73dc441289a357c46d0f5bb775401190b5b6d3d323 |
| PancakeSwap: Router v2 | 0 x10ED43C718714eb63d5aA57B78B54704E256024E | 0 x36765df5911549b0b3302966eeabecb0b09ed7e3a928a3582558c3158abe7441 |
Fei 代币地址:0 x956F47F50A910163D8BF957Cf5846D573E7f87CA
Fei Incentive 合约:0 xfe5b6c2a87A976dCe20130c423C679f4d6044cD7
UniswapV2 的 FEI/WETH 交易池地址:0 x94B0A3d511b6EcDb17eBF877278Ab030acb0A878
本文所用价格及 TVL 数据来源为 coingecko 及 etherscan。