Panoptic 协议“仓位欺骗”事件事后分析

  • panoptic
  • 发布于 2025-09-06 21:33
  • 阅读 46

Panoptic 协议在8月25日发现一个严重漏洞,攻击者可以利用该漏洞从 PanopticPool 智能合约中盗取所有资金。

最近的安全事件之前和期间发生了什么,以及接下来会发生什么。

发生了什么?

8 月 25 日,Cantina 的一位研究人员报告了 Panoptic 核心协议中的一个严重漏洞,该漏洞可能使攻击者能够通过欺骗仓位列表并绕过抵押品检查,从而耗尽 PanopticPool 智能合约中的所有资金。 在确认漏洞后,我们立即启动了 72 小时的紧急响应:通知用户提取资金,成功协调自愿提取超过 400 万美元,并在 8 月 28 日执行了白帽救援行动,从而保护了主网和 L2 上超过 98% 的剩余风险资金。

所有被救援的资金都已转移到 vault.panoptic.eth,并将通过 Merkle-root 声明系统全额返还给用户。没有用户资金损失。

初步响应和社区恢复

8 月 25 日星期一,我们收到来自 Cantina 研究人员的报告,声称 Panoptic 核心协议中存在一个严重漏洞,该漏洞可能使任何人都能耗尽锁定在 PanopticPool 智能合约中的几乎所有资金。

Panoptic 团队立即在周一晚上花费了 4 个小时来测试 Cantina 报告中的说法。 Cantina 研究人员提供了一个硬编码的仓位 ID 欺骗列表(spoof),该列表似乎绕过了 PanopticPool 智能合约内部的检查。 为了测试我们是否可以使用新的仓位重现此攻击,我们编写了一个 Python 脚本来实现 欺骗 挖掘,依赖于 Bellare & Micciancio 的研究。 我们确认我们的 Python 脚本可以生成新的列表并执行新的攻击,并且研究人员的发现是有效的。

至此,我们已经确认该漏洞是真实存在的,并且用户的实时资金面临风险。 我们在周一晚上 10:30 与我们的 Cantina 事件响应团队以及 SEAL 911 合作,创建了作战室。 在接下来的 2 天里,我们采取了以下行动。

  • 我们收集了尽可能多的潜在 Panoptic 用户的联系信息——那些已经联系我们并告诉我们他们正在使用该协议的人、那些已经询问有关该协议的研究问题的人、协议团队合作伙伴——并将情况告知他们,建议他们提取资金。
  • 截至周二早上 9 点,已有 235 万美元的资金从 Panoptic 协议中自愿提取。
  • 截至周二晚上,已通过 blockscan 上的直接消息联系了 50 多名用户,其中一些用户已提取资金。但是,在 Mainnet 上的 WETH-USDC-30bps 池中仍有近 160 万美元,其中 70% 似乎与两个多重签名钱包相关联。在周二的一整天里,链上调查和来自我们社区的帮助使我们能够将该帐户与 Euler Finance、Goldfinch 和 DYAD 等其他几个协议联系起来,但最初联系这些钱包所有者的尝试并未成功。
  • 周三早上,我们的 Discord 版主 Hunter 发现了其中一个多重签名签署人与我们的一位 Discord 用户之间的链接。此外,SEAL 911 联系了 Binance,Binance 能够成功联系多重签名所有者,以提醒他们正在发生的事件。此后立即从协议中提取了近 125 万美元,使协议层面上仍有 55 万美元的资金面临风险。

准备恢复

到周三中午,大多数用户资金已被提取,不再面临风险。 在那时,很明显下一步是尝试挽救仍然存在于 Panoptic 协议中的资金。

我们开始为每个市场设计白帽救援攻击。 我们将为每个 Panoptic 市场部署一个攻击者合约,该合约将 i) 使用闪电贷在 Panoptic 池中存入大量代币,ii) 借入最大数量的代币以开设 Panoptic 仓位,iii) 使用 欺骗 仓位列表提取借入的代币,以绕过抵押品要求,从而有效地耗尽每个市场中的所有资金。

在改进 Cantina 报告的概念验证以确保可以在一次操作中挽救每个市场的资金后,我们接下来按 TVL 对所有实时 Panoptic 市场进行排名,为每个市场挖掘 欺骗 列表,并测试每个市场的攻击者以确保:

  • 攻击者合约以 95%+ 的市场资金结束
  • 指定的提款人地址可以正确提款资金
  • 市场以最少的剩余资金结束,以避免重放风险

所有攻击者开发都在一个开发人员的机器上隔离完成。 我们避免上传到 Github 或其他协作工具,以避免泄露攻击。 到周四下午,我们已经准备好所有攻击者脚本,并计划在当天结束前使用 forge 脚本广播每次攻击。

白帽救援

我们计划通过 mainnet Flashbots RPC 端点进行部署,以确保白帽攻击不会广播给其他构建者,并且如果事务在模拟中恢复,则不会广播该事务。

我们设法将 2 个 mainnet 攻击装入一个事务中,以挽救高达 38.3 万美元的 USDC、WETH 和 WBTC。 所有其他攻击都将通过每个事务一次攻击的方式执行。 大多数风险资金都在 mainnet 上,因此首要任务是首先执行该白帽攻击。

当我们最初尝试在 mainnet 上执行白帽攻击时,我们的 forge 脚本遇到了来自 Flashbots 的速率限制,因为该攻击需要比 Flashbots 的事务提交 API 允许的更多的状态读取。 我们联系了 Flashbots 团队,询问他们是否可以提高我们的速率限制,以适应执行 forge 脚本的初始计划。 他们非常乐于助人和响应迅速,使我们能够在联系后不久执行攻击。

在 Aug-28-2025 05:55:23 PM EDT 的区块 23242436,我们执行了 mainnet 白帽攻击:https://etherscan.io/tx/0x67a45dfe5ff4b190058674d7c791bbdc48e889f319f937c24fa13a5f9093f088

我们让整个 Panoptic 团队参加了一个实时的 Google 会议,确认攻击成功,监控攻击者合约中存在的资金以及市场 TVL 按预期下降。 我们确认 mainnet 白帽攻击成功,然后我们继续对最大的 Unichain 和 Base 市场进行白帽攻击。

由于超过 98% 的客户资金已经得到保护,团队可以大大松一口气了。 当晚晚些时候,我们返回对 L2 上的另外 5 个市场进行白帽攻击。 剩余市场的 TVL 足够小,可以直接从国库进行补偿,但我们也欢迎想要去检索这些资金的白帽攻击者。 所有白帽救援资金随后都转移到 vault.panoptic.eth

接下来会发生什么?

更新:所有用户资金都已成功救出,可供重新分配。请 在此处 申领你的资金。

用户将能够在下周末之前申领被救出的资金,在我们设置好 Merkle-root 申领系统 之后。 我们将把所有被救出的代币转账到具有存储的 merkle 根的专用合约中,然后使用户能够从合约中申领回他们的代币。

我们将提供一个链下系统,用于为每个用户地址生成正确的 Merkle 证明,从而使每个用户都可以通过有权访问的代币证明来调用此专用合约,并接收其资金的转移。

我们将上传一个 CSV 文件到我们的 Github 页面,其中包含我们计算的每个帐户的可申领资金。 我们要求任何受影响的用户向 support@panoptic.xyz 发送电子邮件或在 Discord 上创建支持单,以解决有关你的重新分配配额的争议。 我们的目标是使每个人都 100% 完整,实物 - 我们不会将任何基础资产兑换为 USDC 或采取类似行动。

此外,我们还向 Cantina 记者确认,他们将获得最高的 250,000 美元,以表彰他们负责任地披露此漏洞。 我们非常感谢他们的研究,以及在此紧张时期我们收到的来自几个团队的实时帮助:

  • Cantina 事件响应团队
  • SEAL 911
  • Flashbots
  • Panoptic 用户、投资者和顾问

结论与经验教训

我们要感谢我们的社区的支持,以及我们的合作伙伴和审计师的尽职调查。 我们对导致此漏洞的 V1 中的设计决策承担全部责任。

防止此类攻击的保护措施将包括验证用户在与 PanopticPool 交互时实际上拥有他们声称拥有的仓位,而不是仅检查仓位列表指纹输出。 本着公开学习的精神,我们想分享我们从这次事件中吸取的更一般的教训:

  • 尽早发现漏洞。 对于每个审计发现,你都需要花时间放慢速度并扩大每个报告发现的可能损害。我们收到了来自 2024 年 4 月 Code4rena 竞赛的两份报告,可能导致我们发现此问题 - 一份 描述了某人如何通过 欺骗 仓位列表来清算其他用户,而他们本身并不偿付能力,另一份 描述了用户如何在无力偿债的情况下继续铸造期权。两者都没有完全将 欺骗 方法放在一起,但两者都强调了 TokenId 在指纹识别过程中如何验证的弱点,以及完全检查 TokenId 列表的完整性的弱点。如果我们自己花时间尝试提出一种仓位列表 欺骗 方法,我们可能已经能够预见到这个漏洞。

  • 做好执行完美恢复的准备。 执行白帽攻击的开发运营非常棘手。你可能认为你已做好准备 - 你之前已执行过协议部署甚至治理行动,并且可以推测使用相同的工具来执行你的白帽攻击。但是编写智能合约只是成功的一半:黑帽可能会发现你的白帽攻击的方式有很多种 - Github 窥探、受感染的开发机器、RPC 侦听、mempool 监视、恢复监视。作为安全准备工作的一部分,你必须有一个安全、经过测试的执行计划,该计划解决了隔离的开发环境、安全广播方法 (如 Flashbots) 以及跨多个链的协调。安全准备工作必须扩展到代码之外,以包括整个救援操作管道。

  • 不要低估社区恢复的重要性。 虽然技术白帽操作保护了大部分资金,但我们快速联系用户并与之协调的能力避免了潜在的灾难。在 48 小时内,我们成功联系了控制超过 405 万美元存款的用户,社区成员、协议创始人和甚至我们的 Discord 版主都在追踪多重签名所有者方面发挥了重要作用。协议的安全性还在于维护准确的用户联系信息、建立强大的社区关系以及在危机发生之前准备好清晰的通信渠道。

  • 始终使用可用的工具和方法。 我们选择推出我们自己的加密哈希聚合方案,而没有对现有方法进行适当的研究。我们建立了一种指纹识别方法,该方法依赖于将哈希值 异或 在一起 (XORing),从而实现无序的仓位列表并使更新仓位列表具有 gas 效率。此方法称为 XHASH。如果我们进行了适当的研究,我们就会知道 XHASH 的问题。更一般地说,Panoptic 团队的专业知识在于去中心化金融。我们应该专注于为期权交易协议开发最佳规则,并依靠密码学专家来提供我们需要的经过实战检验的安全原语。

  • 以紧迫和清晰的方式进行沟通。 一些用户延迟采取保护措施,因为我们最初的消息没有充分传达情况的严重性。至少在一个案例中,用户认为“调查”一词意味着该漏洞尚未得到确认,因此坚持了一个仓位而不是立即采取行动。这说明了危机沟通消除犹豫的重要性:消息必须毫不怀疑问题的严重性、行动的紧迫性以及用户所需的准确步骤。

最后,关于 Panoptic 作为协议和产品的后续步骤的一些说明。 我们很快将重新推出 Panoptic V2,这是一个从根本上解决此漏洞的升级,同时还发布了计划中的强制执行机制、PLP 经济学和仓位管理灵活性的改进。 我们仍然致力于构建一个安全的期权协议,并将在未来几周内分享有关 V2 架构和我们增强的安全措施的更多详细信息。

安全注意事项

请注意以下安全提示:

  • 所有官方通信都通过 Panoptic 的 Twitter、Panoptic 的 Discord 和 Panoptic 的博客进行。
  • 不要点击任何人通过 DM 发送给你的任何链接。你应该点击的唯一链接将从我们的官方通信渠道发布。
  • 仅点击来自我们官方渠道的链接,并避免点击在直接消息中发送的链接。
  • Panoptic 的申领门户可以在 app.panoptic.xyz/claim/rescued-funds 找到。任何其他声称是 Panoptic 申领门户的网站都是欺诈性的。
  • Panoptic 团队成员绝不会通过 DM 向你发送有关退款的信息。如果有人这样做,那是一个骗局。
  • Panoptic 目前没有进行任何空投。如果有人通过 DM 向你发送有关空投的信息,那是一个骗局。

附录:如何在 Panoptic v1 中 欺骗 你的仓位列表?

仓位 ID 的位封装

首先,我们必须了解 Panoptic 中如何表示仓位。

在 Panoptic 中,你持有的每个仓位都可以描述为打包的 uint256。 我们将存储 uint256 的 256 位分成几个部分。 在每个部分中,我们存储有关你的仓位的信息 - 我们在此处使用 8 位来存储一个 leg 的期权比率的 uint8 数字,在那里使用一位来表示它是看涨期权还是看跌期权,等等。 完整的架构可以在 TokenId.sol 中找到。

// TOKENID 的打包规则:
// 这是 token Id 如何打包到包含仓位信息的位组成部分中的。
// 位模式通常为:
//
// (第 3 个 leg 的行权价刻度)
// | (第 2 个 leg 的宽度)
// | |
// (8)(7)(6)(5)(4)(3)(2) (8)(7)(6)(5)(4)(3)(2) (8)(7)(6)(5)(4)(3)(2) (8)(7)(6)(5)(4)(3)(2) (1) (0)
// <---- 48 位 ----> <---- 48 位 ----> <---- 48 位 ----> <---- 48 位 ----> <- 16 位 -> <- 48 位 ->
// Leg 4 Leg 3 Leg 2 Leg 1 刻度间距 Uniswap 池模式
//
// <--- 最高有效位 最低有效位 --->
//

结果数字是一个唯一的仓位 ID,当解码时,它会告诉我们定义期权仓位所需的一切信息。

每个用户最多可以持有 25 个这样的仓位。 但是为了提高 gas 效率,我们不会直接将数字仓位 ID 列表存储在 Panoptic 智能合约中。 相反,我们:

  • 将每个仓位 ID 的哈希值组合成一个指纹。
  • 然后,当用户想要采取涉及其当前未平仓仓位的操作时,他们会提供一个他们声称准确的仓位列表。
  • 然后,智能合约将组合用户提供的仓位列表的哈希值,将该指纹与正式存储的用户仓位指纹进行比较,如果用户的列表未生成正确的指纹,则会恢复事务。

这应该意味着,每当用户采取影响其帐户的清算能力或活动仓位列表的操作时,我们的合约都会准确地了解用户的未平仓仓位。

指纹识别仓位 ID

Panoptic v1(和 v1.1)中 Panoptic 仓位列表的指纹是使用以下代码 生成 的:

function updatePositionsHash(
    uint256 existingHash,
    TokenId tokenId,
    bool addFlag
) internal pure returns (uint256) {
    // 通过将现有哈希与新的 tokenId 进行异或来更新哈希
    uint256 updatedHash = uint248(existingHash) ^
        (uint248(uint256(keccak256(abi.encode(tokenId)))));

    // 如果 addFlag=true,则递增高 8 位(leg 计数器),否则递减
    uint256 newLegCount = addFlag
        ? uint8(existingHash >> 248) + uint8(tokenId.countLegs())
        : uint8(existingHash >> 248) - tokenId.countLegs();

    unchecked {
        return uint256(updatedHash) + (newLegCount << 248);
    }
}

该代码执行以下操作:

  • 获取代表你的仓位的打包 uint 列表,以其 uint 解释格式(换句话说,获取数字列表)。
  • 获取一个“空的”哈希值作为开始(例如,数字 0)。将此值称为 accumulatedFingerprint
  • 循环遍历列表中的每个仓位(这是一个数字),然后:
    • 获取数字的 keccak 哈希值
    • 获取该哈希输出的低 248 位
    • 将这些低 248 位与 accumulatedFingerprint 的低 248 位进行 异或
    • 使用 tokenId.countLegs() 方法 获取仓位中的 leg 数量,可以存储在 uint8 中,并将这些数量添加到 accumulatedFingerprint 的高 8 位
    • 保存你的 accumulatedFingerprint 并继续到下一个仓位
  • 在循环过程中,这应该导致 accumulatedFingerprint 的高 8 位包含所有仓位的总 leg 数,而低 248 位存储每个仓位的 keccak 哈希的异或。

仓位哈希漏洞

这里有两个关键缺陷需要注意。

  • 我们没有验证传递的仓位是有效的期权仓位,实际上可以由 Panoptic 铸造。具体来说,这些 uint256 的 leg 计数可以包含 0

    • 在正常情况下,用户提供的预期仓位列表中的累积总 leg 计数必须与存储的指纹中的 leg 计数匹配。如果列表中的每个仓位的 leg 计数必须至少为 1,并且最大仓位数是 25,每个仓位至少拥有 1 个 leg,那么可传递的用户提供的仓位列表的搜索空间被限制为 25 个项目或更小的列表。
    • 但是,由于未能检查提供的仓位 ID 是否至少有 1 个 leg,我们删除了此约束,并且攻击者在寻找指纹冲突时可以考虑任何长度的任何列表。
  • 欺骗 仓位 ID 列表的挖掘实际上可以非常有效地完成,因为我们依赖于将哈希值 异或 在一起。

    • 异或作为对二进制数字的算术运算,具有三个关键属性:
    • 它是关联的:a XOR (b XOR c) == (a XOR b) XOR c
    • 它是可交换的:a XOR b == b XOR a
    • 它是自取消的:a XOR a == 0
    • 因此,对哈希输出进行异或运算可以被视为 线性组合,也就是说,对 向量 进行异或运算,其中每个哈希输出都以其二进制字符串的形式排列在一列中,并且在对所有列进行异或运算之前,每列都乘以 0 或 1 的系数。直观地:

... 第一个向量 h_0 乘以系数 x_0,第二个向量 h_1 乘以系数 x_1,依此类推。

  • 为什么我们现在要添加这些系数?
    • 我们这样做是故意的,因为有一些众所周知的方法可以回答以下问题:“给定这个我们正在组合一些输入向量的目标输出向量,我们应该为每个向量因子设置什么系数?”
    • 高斯消元法 是我们将要使用的方法。
    • 请注意,在线性代数中,系数 0 和 1 意味着“在最终运算中排除或包含整个输入向量”,而不是“将向量的每个元素乘以 0 或 1”。
  • 请注意,这个输入向量列表可以预先廉价地计算出来 - 你只需随机挑选 uintkeccak 它们。输入向量的数量也可以任意大,因为你只会选择其中的一个子集 - 即收到系数 1 的向量。
  • 从高斯消元过程中收到系数 1 的初始大型输入向量列表中的向量将是你的 欺骗 列表,并且当 异或 在一起时,将产生目标指纹。

欺骗 仓位 ID 列表

因此,我们现在已经解决了寻找这些 欺骗 TokenId 列表的问题:

  • 给定一个大型向量池 - 也就是预先计算的候选 TokenId 的哈希值,甚至不必符合真正的 TokenId 标准 - 设置一个很长的向量序列,你希望将这些向量 异或 在一起,这样你最终会得到目标输出指纹。
  • 使用高斯消元法找到每个目标系数。
  • 选择高斯消元法给出系数 1 的向量。
  • 考虑对最终选择中的向量数设置一个上限(也就是说,收到系数 1 的向量数),这样你就不会最终得到一个无法忍受的大列表作为你的 欺骗 列表(更大的列表会花费更多的 gas,并且可能会碰到区块 gas 限制)。如果你首先找到的解决方案太大,请重试。但请记住:没有 leg 计数限制,因此列表大于 25 个项目也可以。
  • 一旦你有一个足够小的解决方案,那就是你的 欺骗 列表。

我们严重依赖 Bellare & Micciancio 的研究来了解将哈希输出 异或 在一起的这种不安全性。好奇的读者可能想研究一下 David Wagner 的 k-tree 算法 及其 开源实现,以获得更完整的通用生日问题描述。

我们将这种 欺骗 搜索实现为一个 Python 脚本,该脚本将发布 在此处。它是在紧要关头构建的,并且可以进行多个优化。但是,即使在低预算的笔记本电脑上,我们也能够找到任意真实仓位的 欺骗 列表,因为高斯消元法可以在多项式时间内执行,从而可以对所有池进行白帽攻击。

使用 欺骗 列表进行的经济攻击

通过 欺骗 你持有的 PanopticPool 仓位的能力,你通常可以:

  • 开设具有大量抵押品要求的仓位
  • 参与通常会考虑大量抵押品要求的操作,但在执行操作时提供一个抵押品要求为零的仓位的虚假列表
    • 由于我们没有确保仓位 ID 在抵押品要求检查期间归攻击者所有,因此 欺骗 仓位 ID 只是 在此处 返回了一个 0 余额,并且导致该仓位没有抵押品要求。

研究人员提供的一个有形的例子是一种执行以下步骤的攻击:

  • 闪电贷 中借入资产
  • 使用该资金存入大量抵押品
  • 以杠杆化的规模开设两个深度 实值 仓位,一个看涨期权和一个看跌期权
    • 在 Panoptic 中,你可以在 PanopticPool 智能合约中 借入高达 5 倍 的存款抵押品,以增加仓位规模。
    • 当 Panoptic 正常运行时,实值期权具有内在价值,因此杠杆化的实值期权会向你的帐户贷记:
    • 你存入的抵押品的价值,
    • 加上借入的资金,
    • 减去你期权的抵押品要求,这应该意味着减去你借入的任何资金
  • 在我们的合约中调用提款方法,并提供一个零抵押品要求的仓位的虚假列表,使你能够提取已贷记到你的帐户中用于杠杆化实值期权的资金,而不会触发借入资金的抵押品扣除。

最终,这就是白帽攻击者合约所做的,使用链下生成的硬编码 欺骗 列表。

恢复交易哈希

加入不断增长的 Panoptimist 社区,并通过在我们的 社交媒体平台 上关注我们,成为第一个听到我们最新更新的人。要了解更多关于 Panoptic 和所有关于 DeFi 期权的信息,请查看我们的 文档 并访问我们的 网站

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

0 条评论

请先 登录 后评论
panoptic
panoptic
江湖只有他的大名,没有他的介绍。