首页 > 欧易 >

深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。

更新时间:2024-12-18 10:30:54 来源:互联网

  我对Kyber的漏洞进行了深入研究,现在对事件有了清晰的理解。这是一起非常复杂和精心设计的智能合约攻击。

  这个漏洞特指Kyber的中心化流动性实施,没有理由认为其他信誉良好的中心化流动性DEX,如Ambient或Uniswap,会受到这个漏洞的影响,尽管Kyber的分叉显然容易受到攻击。

  我们将分析攻击者在以太坊上耗尽的第一个矿池ETH/wstETH。尽管所有其他池都遵循类似的策略,我们重点关注ETH/wstETH的消耗情况。

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

  请注意,如果链接无法访问,可能是由于网络问题或链接本身的问题。建议检查链接的合法性并适当重试。

  这次特定的交易耗尽了三个独立的池,但我们只关注ETH/wstETH的消耗。每个池的耗尽过程是独立的,因此我们只需要了解一个即可。每个池的攻击都是在闪电贷中进行的,目的是操纵价格和流动性。

  在wstETH/ETH案例中,攻击从1万个wstETH(价值2300万美元)的闪电贷开始。下一步是将2800个wstETH(价值600万美元)交换到池中,将价格从1.05 ETH推高到0.0000152。与大多数闪电贷不同,其目的不是为了操纵预言机。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  现在一切都已就绪。

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

  如果它坏了会怎样?

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

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

  无限资金故障。

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

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

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

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

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

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

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

  这是因为“达到数量”是达到tick边界的上限,计算值为...22080000,而漏洞利用者设置的交换数量为...220799999。由此可见,这个漏洞的设计是多么精心。检查失败率<0.00000000001%。

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

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

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

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

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

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

相关游戏

游戏排行