区块链安全

功亏一篑:挖洞思路记录

昨天遇到的非常可惜的一个洞,只能记录下这种挖洞思路了。

目标是一个 dexX,这个合约是他的 router。

这个 router 合约主要有以下3个函数:

  • querySwap():查询我用amountA 的 tokenA 去 swap 成 tokenB,可以换出的 amountB 为多少
  • swap():没什么好说的,用 tokenA 去换 tokenB
  • externalSwap():跟上面这个 swap 的区别是上面这个是在 dexX 的池子换的,而这个函数是接入了外部的 dex 去换的,比如 dodo,1inch。

然后来看代码(以下代码做了一些隐私性处理,但不影响原来的逻辑):

这个函数的特点就是 external 而且外部可控地址类型变量多,给人一种有可趁之机的感觉。

19 行是外部 swap 的关键函数。25 行是转出给收款地址。

思路一

一开始我觉得是没有漏洞的,因为一个常见的套利思路是,没有计算 swap 之前的余额,而只是用 swap 之后的合约内的余额直接转出,这样可以带出合约内的某种代币余额。这种情况常见于合约本身的逻辑设计的是不持有钱,只是钱的中转站,但是因为某些空投或意外转入有了被薅羊毛的空间。而造成我不是合约 owner 确可以带出其中的部分代币。

但这里不是做了校验吗,计算的是 swap 前后的合约 toToken 差值,自然也就不存在这种漏洞。

思路二

来看看 _internalFallbackSwap() 函数是怎么实现的。

这个函数非常有意思,我们跟进去看 TransferHelper 合约的这两个函数怎么实现的。

漏洞这不就来了。熟悉 Qubit Finance Hack 事件的人都能看出来,这个 TransferHelper 库的实现是有问题的,问题在于,它没有判断 address token 是不是 eoa,而直接使用了底层调用 call 函数,这样如果 token address 被传入了 eoa,那么是不会报错的!所以就导致其实什么都没做,并没有 transfer 或者 approve,看上去却成功了。

我直接来个证明,以 safeTransferFrom() 函数为例:

我把 token 传为了我模拟的 eoa 地址,右侧调用成功。

所以再回到上层函数 _internalFallbackSwap(),现在情况变成了这样:

因为这些参数我都可用,所以这些代码都被绕过了,我把 fromToken 传一个 eoa 进去就行了:

然后再回到上层函数看,我整个处于一个什么情况:

这些我全都可绕过,给一个思路,比如我在 _internalFallbackSwap() 函数中,我根本不去处理 toToken,那么最终 preBalance == postBalance,后面不全都直接绕过了?

是我现在我想当于,我只要去构造这一个利用就行了:

而且如果去掉用 swapTarget 的函数的话,我这个 msg.sender 是 router 合约,data 也是我自己构造的,相当于我是高权限的任意执行。

但这里有一个限制,在第9行,swapTarget 必须是白名单,白名单为 dodo 和 1inch:

所以现在的思路变成,在4个白名单内,找到能构造 msg.sender 给 to 转账的函数调用,可能需要继续绕过。

note:这里不要因为这四个都是知名项目的就感到害怕,1inch 我们就曾经找到一个0day漏洞。

但到这里我就觉得无路可走了,这个洞差不多这里结束了,为什么呢,因为这四个白名单+一个router合约,余额都没钱。因为这四个合约的设计都不是 treasury 或者会 deposit token 的 pool,全都是 swap 功能的中转站。所以既然没钱自然不必深入下去,但是不排除这几个合约某天收到大额空投。

总结下思路最后:外部可控 address 类型参数,校验点可绕过。

嗯,还得看合约有没有钱,是不是只是钱的中转站。