我对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合约上打补丁,在交换步骤中加入类似的断言,以防止将来出现这种漏洞。
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载
深入剖析Kyber合约漏洞,一起涉案金额高达4800万美元的盗窃案浮出水面。
立即下载