如果不能在一起,就学会分开 — AnySwap 漏洞分析

区块链安全 3年前 (2022) admin
803 0 0

前言

2022 年 1 月 18 日, 不知道据哪里的消息,Anyswap 说它自己的桥有些问题,其中部分代币存在被黑的风险,劝用户尽快取消授权。早上看到消息的时候还以为是中继器的问题,就没怎么管,下午看到twitter消息风向不对,已经出现利用了。赶紧去分析了一下是什么问题,发现原来是合约的问题,那既然是合约的问题,那就和我有关系了。话不多说,直接开始分析。

如果不能在一起,就学会分开 — AnySwap 漏洞分析

技术分析

本次攻击交易可以参考 https://etherscan.io/tx/0xd07c0f40eec44f7674dddf617cbdec4758f258b531e99b18b8ee3b3b95885e7d,这里的攻击的是官方提示的六种代币中的 WETH 代币。按照惯例,首先先把交易丢进 ethtx.info 中进行分析,看看情况

如果不能在一起,就学会分开 — AnySwap 漏洞分析


从图上来看,不难发现攻击者是直接批量调用了 AnyswapV4Route 的 anySwapOutUnderlyingWithPermit 函数,然后通过函数直接转移了对应存在授权用户的资产。那既然问题出在 anySwapOutUnderlyingWithPermit 函数上,我们就需要对对应函数分析就好了,下面来看 anySwapOutUnderlyingWithPermit 的代码

function anySwapOutUnderlyingWithPermit(
address from,
address token,
address to,
uint amount,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s,
uint toChainID
) external {
address _underlying = AnyswapV1ERC20(token).underlying();
IERC20(_underlying).permit(from, address(this), amount, deadline, v, r, s);
TransferHelper.safeTransferFrom(_underlying, from, token, amount);
AnyswapV1ERC20(token).depositVault(amount, from);
_anySwapOut(from, token, to, amount, toChainID);
}

整个函数看下来其实逻辑非常简单,该函数会获取用户指定的 token 地址返回的 underlying,然后调用该 underlying 的 permit 函数,附带上对应的签名对合约进行授权,然后会将对应的 underlying 转入本合约中,最后充值到 Vault 中。然后在 _anySwapOut 函数中燃烧掉对应的 token 并声明一个事件,该事件将用于跨链。这个流程对于正常的 AnyswapERC20Token 是没有问题的。但是这个流程应用到了 WETH 就出现了问题,为什么呢?我们回顾上图,不难发现 anySwapOutUnderlyingWithPermit 函数中的 underlying()deppsitVault 和 burn 函数调用的都不是 AnyswapV1ERC20Token 本身,而是攻击者自己部署的合约 0xb4f89d6a8c113b4232485568e542e646d93cfab1。这说明什么呢?说明攻击者在调用 anySwapOutUnderlyingWithPermit 函数时传入的 token 参数不是正常的 AnyswapERC20Token,而是一个自定义的恶意代币,但是对应的 underlying 函数的返回值却是 WETH,并且由于 anySwapOutUnderlyingWithPermit 函数调用 AnyswapV1ERC20Token的方式是 interface 的方式,自然对应的 underlying()deppsitVault 和 burn 函数调用其实都是在调用攻击者自己的合约。

可是问题似乎并不是在这里,从 ethtx.info展现的流程图中,攻击者是直接将授权用户的 WETH直接转移到自己部署的恶意合约中的,通过回顾函数逻辑,不难发现,从函数的实现上,是要有一步 WETH 对 AnySwapRouter 合约的一次授权,才会有接下来的 transferFrom 操作。而且这个授权是需要对应的签名的,难道用户拿到了所有授权用户的私钥吗?其实并不是,我们看下调用 permit 函数所传入的对应的参数

如果不能在一起,就学会分开 — AnySwap 漏洞分析

从图中,不难发现传入的用于验证签名的 vr 和 s 都是一个0值,意味着其实 permit 这个函数其实根本没有起作用,也就是说 WETH.permit() 这个流程被绕过了。分析到这里,我在想难道是因为 WETH 的 permit 函数用了 if-else 的形式来判断签名吗,所以就算签名不通过,也不会报错。为了验证这个想法,就需要去看 WETH 的 permit 函数的实现。但是通过查询,AnyswapRouter 使用的 WETH9 合约根本没有 permit()函数。


如果不能在一起,就学会分开 — AnySwap 漏洞分析

那没有这个函数的话,这行调用是怎么通过的呢?这里就有一个很古老的 trick 了,我们知道,如果一个函数在合约中不存在,那么对应的调用就会去调用合约的 fallback 函数,而从上图,不难发现 WETH 确实是实现了 fallback 函数,而这个 fallback 函数调用的是 deposit 函数,这波就舒服了,我们知道 WETH 的 deposit 函数是允许 msg.value 为 0 的情况进行充值来兑换 WETH 的,也就是说在 AnyswapRouter 这里,这个 permit 的调用其实是调用了 WETH 的 deposit 函数,充值数量为0 :D。那么这个调用显然是可以通过的,所以剩下的条件就只剩下用户授权了。用户一旦授权,任何人都可以通过 anySwapOutUnderlyingWithPermit 来进行窃取受影响的资产。

为了好理解一点,我画了个图,大家可以将就看看


如果不能在一起,就学会分开 — AnySwap 漏洞分析


总结

本次的问题在于和 WETH 的兼容性的问题,如果WETHpermit函数本身存在并正确实现,或者如果 Anyswap团队意识到这个兼容性的问题,那么这次事故就不会发生。


原文始发于微信公众号(蛋蛋的区块链笔记):如果不能在一起,就学会分开 — AnySwap 漏洞分析

版权声明:admin 发表于 2022年1月19日 上午7:21。
转载请注明:如果不能在一起,就学会分开 — AnySwap 漏洞分析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...