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位进行按位XOR运算来更新user position hash。
同时,countLegs会根据tokenId的数值范围进行调整:当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的leg增量为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是否对应于有效的期权仓位。小于的值带有0的countLegs和0的positionSize,意味着伪造的仓位不会产生任何债务。这使得攻击者可以用零抵押品需求通过健康检查,同时匹配目标指纹。
参考
附录
以下两个附录提供了对正文陈述的数学解释和证明,即XOR运算等同于向量加法(附录I),以及随机矩阵满秩的概率(附录II)。
附录I
在有限域中,加法定义为模2加法:
观察表明,这与异或(XOR,符号)逻辑运算完全相同。
对于维向量空间(在此例中为),两个向量和的加法定义为分量模2加法:
因此,在向量空间中,向量加法等同于向量分量的按位XOR运算。
附录II
为了使矩阵满秩,这个向量必须是线性无关的。
第一个向量可以是中的任意非零向量,提供种选择;第二个向量不能存在于张成的子空间中,该子空间包含个向量,剩下种选择;按照这个逻辑,第个向量不能存在于前个向量张成的子空间中,导致有种可能选择。
这样的矩阵(即的阶数)总数为:
而所有可能的矩阵的总数为 。
因此,概率为:
令 ,我们可以将此表达式重写为:
当 时,该乘积收敛到一个常数:
这意味着对于大的,随机矩阵满秩的概率约为28.9%。
关于BlockSec
BlockSec是一家全栈区块链安全与加密合规提供商。我们构建产品和服务,帮助客户在协议和平台的整个生命周期中进行代码审计(包括智能合约、区块链和钱包)、实时拦截攻击、分析事件、追踪非法资金,并满足AML/CFT合规要求。
BlockSec在顶级会议上发表了多篇区块链安全论文,报告了多起DeFi应用的零日攻击,阻止了多次黑客攻击挽回了超过2000万美元,并保护了数十亿美元的加密货币。
-
官方Twitter账号:https://twitter.com/BlockSecTeam
-
🔗 BlockSec审计服务: 提交请求
-
🔗 Phalcon安全APP: 预约演示



