2025年8月25日,在Cantina和Seal911的协助下,Panoptic进行了一次白帽救援行动,成功挽回了约40万美元的风险资金[1]。根本原因是s_positionsHash的构建存在缺陷:协议使用XOR来聚合仓位ID的Keccak256哈希值,形成一个单一的指纹。虽然单个Keccak256哈希值是抗碰撞的,但XOR的数学线性使其复合指纹变得不安全。攻击者可以生成一组伪造的仓位ID,其XOR聚合哈希值与任何目标指纹匹配,从而绕过协议的仓位验证,在不偿还债务的情况下提取抵押品。
背景
Panoptic是一个构建在以太坊上的去中心化永续期权交易协议,允许用户交易看跌期权和看涨期权。
withdraw()函数有一个输入参数positionList,它由一组tokenId组成。每个tokenId代表一个仓位。withdraw()函数根据s_positionsHash检查每个仓位的债务状态,然后提取用户的抵押品。
为了节省Gas,协议不存储用户的每一个仓位(即tokenId)。相反,它根据用户所有tokenId计算一个指纹,并将其记录在s_positionsHash中。每个s_positionsHash的最高8位代表numLegs,而较低的248位代表user position hash。
对于每个通过的tokenId,协议会计算其哈希值,取最低248位,并通过与当前s_positionsHash的最低248位进行位异或操作来更新user position hash。
同时,countLegs根据tokenId的数值范围进行调整:当tokenId小于时保持不变,当在、和的范围内时,分别增加1、2或3。最后,这些值被写入s_positionsHash的最高8位以更新numLegs。
漏洞分析
根本原因是合约构建s_positionsHash的算法存在缺陷,特别是使用XOR操作来聚合Keccak256哈希结果。虽然单个哈希函数是安全的,但XOR操作的数学线性使得整体指纹算法(即哈希的XOR和)不安全[2]。
线性意味着攻击者不需要破解哈希函数本身(即从哈希反向推导tokenId)。相反,他们可以采用组合策略:生成大量随机tokenId,计算它们的Keccak256(tokenId),然后从这些哈希值中选择一个特定子集,使得这些tokenId哈希值的XOR和精确匹配受害者的目标指纹。
这使得攻击者在调用withdraw()时能够传入一组伪造的tokenId,以通过健康检查并提取与s_positionsHash对应的所有抵押品。
理论
假设用户的仓位指纹s_positionsHash具有user position hash 和numLegs ,用户tokenId为。因此:
这里,每个248位哈希值可以看作一个248维向量。
因此,位于一个由0和1组成的248维空间()中。在这个空间中,XOR操作等同于向量加法(附录I)。
攻击者的目标是找到个248维向量,这些向量来自所有可用向量,使得它们的XOR和的低248位等于。因此,我们可以将攻击者的目标表述为一组线性方程:
具体来说,我们不需要直接尝试构建。相反,我们选择个线性无关的哈希向量(其中等于维度248),并使用它们作为列向量来构建一个矩阵:
问题就转化为找到一个系数向量,使得:
其中,且。
根据线性代数理论,只要矩阵是满秩的,它就张成了整个维空间。这意味着对于任何目标,方程组都有唯一解。求解后,我们只需保留那些的向量;它们的XOR和就是。
示例
为便于理解,我们通过一个案例研究来演示这个构建过程。假设向量是3维的,每个维度只包含0或1,目标哈希值为101,即。
接下来,我们随机生成3个哈希值:
我们使用这三个向量作为列来构建矩阵,并设置方程:
通过高斯消元求解,我们得到。这意味着我们需要选择和(对应于),它们的XOR和恰好等于目标。
选择n个线性无关向量
如前所述,为了求解,我们需要构建一个满秩矩阵。考虑到我们处理的是一个极大的向量空间(),核心问题是:如何从这个庞大的空间中快速选择个线性无关的向量?
考虑最简单的方法:随机生成个tokenId,并选择它们的哈希结果作为向量。
在上,个随机选择的维向量构成一个满秩矩阵的概率为:
当很大时(在本例中,),这个概率收敛到一个常数(附录II):
这意味着每次随机尝试大约有**28.9%**的成功率。平均而言,攻击者只需约3.5次尝试即可找到一组线性无关的向量。因此,计算成本极低,攻击者可以快速构建一个满足条件的矩阵。
为了控制最高8位中的countLegs,我们只需根据目标countLegs调整随机生成的tokenId值的范围。
例如,如果我们希望伪造指纹中的numLegs为0,我们只需确保随机生成的个tokenId都小于。由于该范围内tokenId的legs增量为0,无论高斯消元解选择哪些向量,最终累加的numLegs都将不可避免地为0。
攻击分析
白帽救援者发起了多次救援交易。为简化起见,以下讨论仅基于其中一笔交易[3]。
核心逻辑包含以下5个步骤:
- 通过Aave的闪电贷借入0.23e8 WBTC和28e18 WETH。
- 将0.23e8 WBTC和28e18 WETH存入poWBTC合约和0x1f8d_poWETH合约。
- 调用
PanopticPool合约的mintOptions(),使用一个正常的positionIdList来借入资金并开立杠杆仓位。 - 调用
withdraw(),传入伪造的tokenId。由于这些伪造的仓位positionSize为0,该函数返回的tokenRequired为0,意味着所有仓位所需总抵押品被错误地计算为零。同时,由这些tokenId生成的s_positionsHash与步骤3中生成的完全相同,允许救援者在不偿还任何债务的情况下提取所有抵押品。 - 偿还闪电贷并执行下一轮。
总结
此次事件暴露了两个叠加的缺陷,共同导致了未经授权的抵押品提取。
- XOR不是安全的哈希聚合函数。Keccak256是抗碰撞的,但XOR的线性意味着多个哈希的XOR和不具备这种属性。构建一组哈希值XOR为任何目标值的输入集,归结为解决上的线性方程组,这在计算上是微不足道的。哈希组合需要保留抗碰撞性的操作,例如拼接后重新哈希。
- 仓位ID验证缺失。协议未能验证传入的
positionId是否对应于有效的期权仓位。低于的值带有countLegs为0和positionSize为0,意味着伪造的仓位不产生任何债务。这使得攻击者能够以零抵押品要求通过健康检查,同时匹配目标指纹。
参考
附录
以下两个附录提供了主文本陈述的数学解释和证明,即XOR操作等同于向量加法(附录I),以及随机矩阵满秩的概率(附录II)。
附录I
在有限域中,加法定义为模2加法:
观察表明,这与逻辑运算异或(XOR,符号)相同。
对于维向量空间(在本例中),两个向量和的加法定义为分量模2加法:
因此,在向量空间中,向量加法等同于向量分量的按位异或运算。
附录II
为了使矩阵满秩,这个向量必须是线性无关的。
第一个向量可以是中除了零向量以外的任何向量,有种选择;第二个向量不能落在张成的子空间中,该子空间包含个向量,剩余种选择;按照这个逻辑,第个向量不能落在前个向量张成的子空间中,有种可能选择。
这样(即的阶数)的矩阵总数为:
所有可能的矩阵的总数为。
因此,概率为:
令,我们可以将此表达式重写为:
当时,该乘积收敛到一个常数:
这意味着,对于大的,随机矩阵满秩的概率约为28.9%。
关于BlockSec
BlockSec是一家全栈式区块链安全和加密合规提供商。我们构建产品和服务,帮助客户在协议和平台的整个生命周期内进行代码审计(包括智能合约、区块链和钱包)、实时拦截攻击、分析事件、追踪非法资金,并满足AML/CFT合规要求。
BlockSec已在著名会议上发表了多篇区块链安全论文,报告了多起DeFi应用零日攻击,阻止了多次黑客攻击并挽回了超过2000万美元的资金,保护了数十亿美元的加密货币。
-
官方Twitter账号:https://twitter.com/BlockSecTeam
-
🔗 BlockSec审计服务 : 提交申请
-
🔗 Phalcon安全APP: 预约演示



