Cetus AMM 2亿美元黑客攻击:有缺陷的“溢出”检查如何导致灾难性损失

  • Dedaub
  • 发布于 2025-05-23 21:52
  • 阅读 15

Cetus AMM 在 Sui 网络上遭受了 2 亿美元的黑客攻击,根本原因是流动性计算函数中的一个缺陷,该缺陷未能正确检测大数值计算中的溢出。攻击者利用此漏洞以极低的成本创建了大量的流动性头寸,并随后排干了资金池。该漏洞之前在 Aptos 的代码中就被发现过,但在移植到 Sui 时被重新引入。

Dedaub

Cetus AMM 2 亿美元攻击事件:一个有缺陷的“溢出”检查如何导致灾难性损失

2025 年 5 月 22 日,Sui 网络上的 Cetus AMM 遭受了一次毁灭性的攻击,导致超过 2 亿美元的损失。这一事件是近期历史上最重大的 DeFi 攻击事件之一,起因是“溢出”保护中一个微妙但至关重要的缺陷。本分析剖析了该漏洞的技术细节,并 بررسی 当时何时引入、修复和重新引入此问题的。

概要

攻击者利用了 Cetus AMM 中截断流动性计算函数中最有效位的漏洞。当用户打开 LP 仓位时,会调用此计算。当打开这样的仓位时,用户可以通过指定“流动性”参数(你希望获得的池子的比例)并提供相应数量的代币来打开一个大或小的仓位。通过将流动性参数操纵到一个极高的值,他们在中间计算中导致了一个溢出,由于一个有缺陷的截断检查,这个溢出没有被检测到。这使得他们可以用仅仅 1 个单位的代币输入添加大量的流动性仓位,随后耗尽了总共包含数亿美元价值代币的池子。

注意:此问题的技术术语不是“溢出”,而是 MSB(最高有效位)截断,但为了简单起见,我们称其为“溢出”。

攻击序列

攻击以精心策划的序列展开。以下是其中一个攻击交易的示例(简化):

  1. 闪电兑换启动:攻击者通过闪电兑换借入了 1000 万 haSUI,具有最大滑点容忍度
  2. 仓位创建:以 [300000, 300200] 的 tick 范围打开一个新的流动性仓位——一个在上限的极窄范围
  3. 流动性添加:只添加了 1 个单位的 A 代币的流动性,但收到了 10,365,647,984,364,446,732,462,244,378,333,008 的巨大流动性价值。由于未被检测到的按位截断,此操作成功。
  4. 流动性移除:立即在多个交易中移除了流动性,耗尽了池子
  5. 闪电贷偿还:偿还了闪电兑换,并保留了大约 570 万 SUI 作为利润

技术深入: “溢出”漏洞

根本原因在于 clmm_math.move 中的 get_delta_a 函数,它计算给定流动性数量所需的 A 代币数量:

public fun get_delta_a(
    sqrt_price_0: u128,
    sqrt_price_1: u128,
    liquidity: u128,
    round_up: bool
): u64 {
    let sqrt_price_diff = sqrt_price_1 - sqrt_price_0;

    let (numberator, overflowing) = math_u256::checked_shlw(
        // Dedaub: 结果不适合 192 位
        full_math_u128::full_mul(liquidity, sqrt_price_diff)
    );
    // Dedaub: checked_shlw “溢出” 结果,因为它 << 64
    assert!(overflowing);

    let denominator = full_math_u128::full_mul(sqrt_price_0, sqrt_price_1);
    let quotient = math_u256::div_round(numberator, denominator, round_up);
    (quotient as u64)
}

数学分解

使用交易中的实际值:

  • liquidity:10,365,647,984,364,446,732,462,244,378,333,008(大约 2^113)
  • sqrt_price_0:60,257,519,765,924,248,467,716,150 (tick 300000)
  • sqrt_price_1:60,863,087,478,126,617,965,993,239 (tick 300200)
  • sqrt_price_diff:605,567,712,202,369,498,277,089 (大约 2^79)

关键计算:

numerator = checked_shlw(liquidity * sqrt_price_diff)
          = checked_shlw(~2^113 * ~2^79)
          = checked_shlw(2^192 + ε)
          // checked_shlw 将一个 256 位寄存器左移 64 位
          = ((2^192 + ε) * 2^64) mod 2^256
          = ε

此乘法产生的结果超过 192 位。当此值在 checked_shlw 中左移 64 位时(即,“checked shift left by one 64-bit word”),它会溢出一个 256 位整数,但为此检查设计的溢出检查失败。

但是等等。不是应该有一个经过检查的操作来防止这个问题吗?

有缺陷的溢出检查

关键缺陷在于 checked_shlw 函数:

public fun checked_shlw(n: u256): (u256, bool) {
    let mask = 0xffffffffffffffff << 192;  // 这是不正确的!
    if (n > mask) {
        (0, true)
    } else {
        ((n << 64), false) // 溢出的确切位置
    }
}

掩码计算 0xffffffffffffffff << 192 没有产生预期的结果。开发人员可能想要检查 n >= (1 << 192),但实际的掩码并没有达到这个目的。因此,大多数大于 2^192 的值都会未经检测地通过,随后的左移 64 位会在 Move 中导致静默溢出(这不会触发移位操作的运行时错误)。

整数注意事项

在 Move 中,围绕整数运算的安全性旨在防止溢出和下溢,这些溢出和下溢可能会导致意外行为或漏洞。具体来说:

  • 如果结果对于整数类型来说太大,加法 (+) 和乘法 (*) 会导致程序中止。这种情况下的中止意味着程序将立即终止。
  • 如果结果小于零,减法 (-) 会中止。
  • 如果除数为零,除法 (/) 会中止。
  • 左移 (<<) 在发生溢出时不会中止,这是独一无二的。这意味着如果移位的位超过整数类型的存储容量,程序将不会终止,从而导致不正确的值或不可预测的行为。

对于具有选中算术的语言来说,当位移截断结果时,不触发错误是正常的。大多数智能合约审计师都明白这一点。

漏洞利用的影响

由于溢出,numerator 回绕成一个非常小的值。当除以分母时,它会产生一个接近于 0 的商。这意味着该函数返回只需要 1 个单位的 A 代币即可铸造大量的流动性仓位。

用数学术语来说:

  • 预期:需要非常大量的代币
  • 实际(由于溢出):需要 1 个代币

值得注意的是,攻击中涉及的数值是经过精确计算的——攻击者利用合约中的一些现有函数来计算这些数值,特别是 get_liquidity_from_a

审计追踪:之前发现过类似的问题

Ottersec 的审计 在早期版本的代码(2023 年初)中发现了一个非常相似的溢出漏洞,专门为 Aptos 设计:

“在 numberator 值上运行 u256::shlw 之前,没有对其进行验证。因此,非零字节可能会被删除,从而导致错误的值计算。”

他们建议用 u256::checked_shlw 替换 u256::shlw 并添加溢出检测,这解决了该问题。请注意,此版本的代码具有 256 位无符号整数运算的自定义实现,因为 Aptos 当时不支持此原生实现。Move 2 / Aptos CLI ≈ v1.10 于 2024 年初推广到主网。

非常不幸的是,当团队几个月后将代码移植到 SUI 时(Sui 始终支持 256 位整数),checked_shlw中引入了一个错误OttersecMoveBit 对此版本的 AMM 的审计没有发现此问题。Zellic 在 2025 年 4 月进行的后续审计 没有发现超出信息性发现的问题。执行数值计算的库代码可能超出了范围,而且,由于原生支持 256 位运算,因此可能会忽略这些问题。

开发人员的教训

1. 了解你的语言的整数语义

  • 了解哪些操作会中止,哪些操作会静默溢出
  • 特别注意位移操作
  • 使用实际溢出条件测试你的溢出检查

2. 数学上的严谨性是不容商量的

  • DeFi 协议需要通过设计来处理极端值
  • 需要清楚地理解每个数学运算的界限
  • 考虑使用形式化方法来验证关键计算(我们的团队可以提供帮助)

3. 详尽地测试边缘情况

  • 最大值不是理论上的——它们是攻击向量
  • 组合多个边缘情况

4. 审核修复,而不仅仅是更改

  • 考虑对关键修复进行独立验证

5. 领域专业知识至关重要

  • AMM 数学涉及复杂的不变量
  • 与了解 DeFi 边缘情况的审计师合作

在 DeFi 中,边缘情况不是边缘情况——它们是攻击向量。AMM 特别容易受到攻击,因为它们涉及跨越极端范围的复杂数学运算。Cetus 攻击表明,即使是“checked”操作也需要仔细验证。

结论

Cetus 攻击有力地提醒我们,DeFi 中的安全性很难,但并非无法实现。一个有缺陷的溢出检查,加上闪电贷的可组合性和集中流动性机制,导致了超过 2 亿美元的盗窃。

对于在 Sui 和 Aptos 等基于 Move 的链上构建的开发人员来说,此事件强调了理解你的语言的整数语义、严格测试边缘情况以及与深入了解平台和 DeFi 领域的审计师合作的重要性。

如果你需要帮助保护你的 Aptos 或 Sui 网络项目,请通过 Dedaub 与我们联系——我们的团队专注于复杂 DeFi 协议中出现的数学复杂性和边缘情况。

  • 原文链接: dedaub.com/blog/the-cetu...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Dedaub
Dedaub
Security audits, static analysis, realtime threat monitoring