长推:被盗4800万美元,Kyber 深入研究合约漏洞

合约 漏洞 美元 2023-11-24 96

摘要:2/首先要注意的是,此漏洞是针对Kyber实施中心化流动性而特有的。没有理由相信其他信誉良好的中心化流动性dex,例如Ambient或Uniswap,会受到此漏洞的威胁(尽管Kyber分叉显然是)。第一个掉期是剥削者以00157ETH的价格出售1056wstETH,将价格压低至00000146(略低于其流动性价格范围)。...

1/ 完成了对 Kyber 漏洞的初步深入研究,我想我现在对发生的事情有了很好的了解。

这无疑是我见过的最复杂、最精心设计的智能合约漏洞利用……

2/ 首先要注意的是,此漏洞是针对 Kyber 实施中心化流动性而特有的。

没有理由相信其他信誉良好的中心化流动性 dex,例如 Ambient 或 Uniswap,会受到此漏洞的威胁(尽管 Kyber 分叉显然是)。

3/ 我们将看看攻击者在以太坊上耗尽的第一个矿池 ETH/wstETH。尽管所有其他池都遵循类似的策略。

对这个矿池的攻击可以在这个交易中找到:

https://etherscan.io/tx/0x09a3a12d58b0bb80e33e3fb8e282728551dc430c65d1e520fe0009ec519d75e8/advanced

4/ 这个特定的交易消耗了三个独立的池,但现在我们只关注 ETH/wstETH 的消耗。

每个池的排水过程是独立的,因此我们只需要了解一个即可。每个池的利用都是在闪电贷中进行的,以操纵价格和流动性。

长推:被盗4800万美元,Kyber 深入研究合约漏洞

5/ 在 wstETH/ETH 案例中,漏洞利用从 1 万个 wstETH(价值 2300 万美元)的闪电贷开始。

下一步是将 2800 个 wstETH(价值 600 万美元)交换到池中,将价格从 1.05 ETH 推高到 0.0000152。与大多数闪电贷不同,其原因不是为了操纵预言机。

6/ 关键是将池价格移动到中心化流动性曲线的区域,该区域现有流动性为 0。由于攻击依赖于对 Kyber 集中流动性数学的极其精确的操纵,这基本上创建了一个“新画布”。

7/ 该漏洞以 0.0000146 至 0.0000153 的价格范围铸造 3.4 wstETH 流动性。然后,利用者出于某种原因燃烧了 0.56 wstETH 的流动性(可能是为了使后续的数值计算完美地排列)。

长推:被盗4800万美元,Kyber 深入研究合约漏洞长推:被盗4800万美元,Kyber 深入研究合约漏洞长推:被盗4800万美元,Kyber 深入研究合约漏洞

8/ 该漏洞利用该价格执行两次掉期。现在请记住这里没有其他流动性。在没有数字错误的情况下,这样做的人只会用自己的流动性来回交易,并且所有流量将净为零(减去费用)。

9/ 然而,发生的是无限的金钱故障。

第一个掉期是剥削者以 0.0157 ETH 的价格出售 1056 wstETH,将价格压低至 0.0000146(略低于其流动性价格范围)。

长推:被盗4800万美元,Kyber 深入研究合约漏洞

10/ 第二次交换方向相反。剥削者以 0.06 ETH 从池中购买 3911 wstETH,将池的价格推回至 0.00001637(略高于其流动性范围的上限)。

11/ 就是这样。漏洞利用已完成。资金池已耗尽。

请注意,在第二次交换中,他收到的钱多于第一次交换中支付的金额(3911 vs. 1052 wstETH)。请记住,这里唯一的流动性是他一开始铸造的 ~3 wstETH。额外的钱从哪里来?

12/ 这就是事情变得非常棘手的地方。我花了几个小时才弄清楚发生了什么。

第一条线索来自于第二次互换结束时池流动性的静态状态。注意到什么奇怪的事情了吗?

长推:被盗4800万美元,Kyber 深入研究合约漏洞

13/ 在 Kyber 中,剩余范围内流动性的值位于“baseL”poolData 变量中。

这不应该是0吗?请记住,第二次掉期的价格“超出”了攻击者的流动性范围。这里不应该有流动性。

14/ 不知何故,该漏洞使矿池认为其流动性比这些价格范围内的实际流动性要多。现在我们明白了无限资金故障从何而来。

如果资金池认为流动性比实际多,那么它就会为大额掉期支付过高的费用。

15/ 第二条线索来自比较第一次和第二次交换的调用跟踪堆栈。

在 Kyber 中,当跨越刻度边界时,会调用“
updateLiquidityAndCrossTick”。它根据该时刻的 LP 范围头寸调整曲线的流动性值。

长推:被盗4800万美元,Kyber 深入研究合约漏洞长推:被盗4800万美元,Kyber 深入研究合约漏洞

16/ 第二次掉期处理正确。开始时,价格超出了 LP 位置的范围。然后,掉期以低于 LP 仓位的价格结束。因此,当价格超出范围时会再次调用。

17/ 然而,在第一次交换中,
updateLiquidityAndCrossTick 从未被调用。请记住,曲线价格一开始是在区间内的,然后掉期移动了价格,直到价格略微超出区间(请注意是 "略微")。

它本应被调用,但在第 1 次掉期时从未被调用。

18/ 现在一切都已就绪。

当 LP 仓位超出范围时,
updateLiquidityAndCrossTick 负责将流动性从曲线中移除。当它移回范围内时,就会将流动性重新加入曲线。

如果它坏了会怎样?

19/ 如果你能让它在你的 LP 仓位超出范围时不调用,那么流动性就永远不会从曲线中移除。你现在欺骗了资金池,让它以为自己有更多的流动性。

20/ 但当你重新回到这个范围时,你要确保它被调用。流动性就会重新加入,尽管第一次时它从未被移除。这就是双重计算!资金池会重复计算原始 LP 仓位的流动性。

无限资金故障。

21/ 漏洞利用者是如何绕过掉期 1 的 updateLiquidity 调用的?这才是真正的技术性问题。

在集中流动性的 AMMs 中,掉期是作为一系列步骤计算的。在每一步中,您必须确定是否会达到 tick 边界或用尽掉期。

22/ Kyber 运行此交换步骤,并检查该步骤的结束价格是否与下一个刻度价格相同。如果不一致,则认为掉期已用尽,没有到达刻度线,因此不需要调用 `updateLiq`。

长推:被盗4800万美元,Kyber 深入研究合约漏洞长推:被盗4800万美元,Kyber 深入研究合约漏洞

23/ 然而,请注意检查的是不等式,而不是方向比较。如果您以某种方式执行了一个交换步骤,并使价格结束在刻度线之外,那么即使您跨越了刻度线,检查也会失败,而且 "更新流动性 "永远不会被调用。

24/ 通常这种情况不会发生,因为 `computeSwapStep` 函数会首先计算在到达刻度线之前可交换的金额上限。

如果该金额小于掉期的剩余部分,它就会有把握地预测期末价格不会达到刻度线。

25/ calcReachAmount 预测掉期数量不会达到刻度线,但不知何故,最终价格却略微超出了刻度线。

26/ 这是因为 "达到数量 "是达到 tick 边界的上限,计算值为...22080000,而漏洞利用者设置的交换数量为...220799999。

由此可见,这个漏洞的设计是多么精心。检查失败率

27/ 这与 Kyber 如何实现数量计算(计算上限,直到达到界限)和价格变化有关。两者使用的运算方式略有不同。

28/ 在非常小心控制和精确设计的情况下,界限检查会告诉你,小于 X 的掉期数量将使你保持在刻度线价格之内。

但并行计算的价格变动计算将应用 X 互换数量,并最终超出刻度线界限。

29/ 我记得在为 @ambient_finance 编写智能合约时,我对这个问题有无尽的偏执。

出于这个原因,我们的代码在每一步都包含了明确的检查,如果交换数量耗尽,这些步骤就会明确在 tick 边界内。

30/ 好消息是,至少可以直接在现有的 Kyber 合约上打补丁,在交换步骤中加入类似的断言,以防止将来出现这种漏洞。

相关推荐