AI 审计代理在 Cloudflare CIRCL 中发现 7 个漏洞

Hacker News 热门(buzzing.cc 中文翻译)·2026-07-08 12:29·46天前·duha
AI 导读

zkSecurity 的 AI 审计代理 zkao 持续扫描 Cloudflare 的 CIRCL 密码学库,使用 Opus 4.6 + skills 和 GPT-5.3 + skills 等模型发现并确认了 7 个真实漏洞。其中包括阈值 RSA 中 float64 精度丢失(AI 自评 Critical)和属性基加密(CP-ABE)访问控制完全失效(Critical,由 zkao 自行发现)。所有漏洞已在上游修复,多数在 HackerOne 上获得确认和奖励。AI 生成的候选发现仍需人工验证,但 zkao 已能自动完成大部分验证工作。

Hacker News 热门(buzzing.cc 中文翻译)
精选
71AI 编辑部评分,满分 100

AI 审计代理在 Cloudflare CIRCL 中发现 7 个漏洞

2026-07-08 12:29· 46天前· duha
AI 导读

zkSecurity 的 AI 审计代理 zkao 持续扫描 Cloudflare 的 CIRCL 密码学库,使用 Opus 4.6 + skills 和 GPT-5.3 + skills 等模型发现并确认了 7 个真实漏洞。其中包括阈值 RSA 中 float64 精度丢失(AI 自评 Critical)和属性基加密(CP-ABE)访问控制完全失效(Critical,由 zkao 自行发现)。所有漏洞已在上游修复,多数在 HackerOne 上获得确认和奖励。AI 生成的候选发现仍需人工验证,但 zkao 已能自动完成大部分验证工作。

推荐理由

zkSecurity用AI扫了Cloudflare的密码学库,挖出7个真实漏洞,从浮点数精度损失到访问控制完全破防。这是AI在密码学审计里第一次证明自己能找到能用的漏洞,不是纸上谈兵。虽然后面发现AI对严重性的判断还很瞎,但整体值得安全从业者一读。

正文 · AI 翻译

我们将 AI 审计管线对准了 Cloudflare 的 CIRCL 实验性密码学库,并确认了七个真实漏洞,从阈值 RSA 中严重的 float64 精度丢失,到基于属性的加密中完全失效的访问控制。这七个漏洞现均已在上游修复。这是本系列文章的第一篇,后续将介绍我们的智能体在开源密码学项目中发现的更多漏洞。

在 zkSecurity,我们正在构建 zkao,一个 AI 审计智能体。其目标说起来简单做起来难:让 AI 持续审视你的代码,直到其他 AI 工具能发现的所有漏洞都被清除。我们曾在《zkao:持续累积的安全》一文中阐述了这一方法为何重要。

构建 zkao 是一个迭代过程,最终目标是打造一个能够发现所有 AI 可检测漏洞的自动化审计器。这包括构思新思路和新方法,将 zkSecurity 安全研究人员的专业知识系统性地编码进 zkao,确保它能检测到最新、最严重的漏洞而不偏向于基准测试,并且——关键的是——持续进行实验,以理解哪些方法有效、哪些无效、模型如何演进,并加深我们对 AI 漏洞发现的理解。其中一些实验本身产出了值得分享的内容,与产品本身无关,这正是本系列文章的主题。

此外还有第二个动机。这些实验是我们为 zkao 构建基准测试套件的方式,在此过程中,它们不断揭示出大语言模型实际上是如何推理密码学的:它们擅长什么、在哪些方面存在盲区,以及如何放大前者、控制后者。虽然漏洞是可见的输出,但推理模式才是我们最关心的部分。

几个月前,我们开始在选定的代码库上运行实验。我们使用大语言模型扫描了几个开源密码学项目,采用两种配置:

  1. 仅使用大语言模型,配合简单的提示词。

  2. 大语言模型配合技能包,这些技能包由我们团队中的专家维护。

随后,针对大语言模型发现真实漏洞的重要项目,我们还运行了 zkao,看它能否自主检测出同样的问题。在大多数情况下,zkao 不仅发现了所有这些问题,还识别出了更复杂、更严重的漏洞。

结果足够理想,我们决定将其整理成文。我们以 Cloudflare 的 CIRCL(一个高级和后量子密码学库)作为本系列的开篇。针对 CIRCL,我们的流水线产生了大量候选发现,其中有七个值得在此报告。这七个漏洞均已在上游修复。其中大部分漏洞已通过 Cloudflare 在 HackerOne 上的漏洞奖励计划得到确认并获得奖金。

需要说明的是:AI 产出的是候选发现,而非最终报告。我们团队的人类成员仍然对每个问题进行了验证,检查了可利用性,在必要时精简了概念验证代码,并负责了披露流程。这种人在回路的步骤仍然非常重要,因为 AI 候选发现成本低廉,而可信赖的报告则不然。

精简这一步骤正是 zkao 的主要设计目标之一。虽然它仍在完善中,但当前版本已经承担了大部分此类验证工作。

严重程度与修复概览

在详细说明之前,有一点值得指出:AI 为其自身发现所分配的严重程度并不可靠。以下是每个漏洞的 AI 评级,以及 Cloudflare 在修复后确认的严重程度。我们还验证了当前版本的 zkao 能够稳定复现全部七个漏洞。

# 漏洞 AI 严重程度 Cloudflare 严重程度 修复提交 发现者
1 TSS/RSA 多项式求值中的 float64 精度损失 严重 f7d2180 Opus 4.6 + 技能
2 通过证明者控制的 SecParam 实现的 qndleq 伪造 757dde4 Opus 4.6 + 技能
3 BLS 聚合缺少消息唯一性 9798df7 Opus 4.6 + 技能
4 通过 FillBytes 符号碰撞导致的 DLEQ 可靠性破坏 19848a5 Opus 4.6 + 技能
5 通过按位或开关绕过 HPKE PSK 验证 中(重复) a3b4fa3 GPT-5.3 + 技能
6 int64 中的 TSS/RSA 拉格朗日系数 751e372 Opus 4.6 + 技能
7 通过与共享漏洞导致的 CP-ABE 访问控制失效 严重 严重 def2fd3 zkao

AI 评估的严重程度与确认的严重程度之间的差距本身就是一个有趣的洞见,我们将在文末回到这一点。现在来看这七个漏洞,逐一分析。

漏洞 1:float64 中的多项式求值

该漏洞存在于 CIRCL 的阈值 RSA 实现(tss/rsa)中。阈值签名使用 Shamir 秘密共享方案将秘密分割给 n 个参与者。Deal() 函数在每个参与者的索引处对一个秘密多项式进行求值。系数本应为 big.Int 类型,但 x^i 项的计算方式如下:

// tss/rsa/rsa_threshold.go
xi:=int64(math.Pow(float64(x),float64(i)))

float64 有 53 位尾数。当 x^i 超过 2^53(约 9×10^15)时,结果在转换回整数之前就会被静默舍入。例如,对于 100 个参与者和 27 的阈值,在 x=100、i=26 时计算 100^26=10^52,这超出了 2^53 达 36 个数量级。即使 x=20、i=16 也已经破坏了该计算。

其后果是多项式求值错误,因此分发给参与者的密钥分片是错误的。根据参数的不同,签名组合要么完全失败,要么产生看似正常但无法重构出预期密钥的分片。我们的智能体将此标记为严重,因为它会导致生成错误的密钥分片,破坏协议的正确性。Cloudflare 最终将该问题评估为低严重性,理由是受影响的条件在实际中出现的可能性很低。

修复方案将浮点指数运算替换为代码自身的 TODO 注释一直建议的霍纳求值法,全程使用 big.Int。提交记录:f7d2180。

漏洞 2:通过证明者控制的安全参数伪造 DLEQ 证明

该漏洞存在于 zk/qndleq 中,即 CIRCL 针对 (Z/nZ)* 中平方子群的 DLEQ(离散对数相等性)证明。DLEQ 证明用于证明两个数对共享相同的离散对数;如果攻击者能让验证者接受一个关于虚假陈述的证明,那么该证明系统就被攻破了。

该证明中的挑战值采用 Fiat-Shamir 方式推导,其比特长度由 SecParam 控制。问题在于 SecParam 位于 Proof 结构体内部:

typeProofstruct{
Z,C*big.Int
SecParamuint
}

在验证过程中,代码使用证明自身的 SecParam 参数重新计算了挑战值。该字段由攻击者控制。将 SecParam 设为 1,挑战值就坍缩为单个比特,值为 $0$ 或 $1$:每次伪造尝试相当于抛一次硬币。将 SecParam 设为 8,暴力破解大约需要 $2^8 = 256$ 次尝试。无论哪种情况,可靠性都不复存在。

这是一个反复出现的模式中的典型实例:本应由验证者固定的安全参数,却被从证明者提供的数据中读取出来。修复方案是将 SecParam 从证明中移除,并让 Verify 函数将其作为显式参数传入,从而由验证者来设定该值。提交编号 757dde4。

漏洞 3:缺少消息区分性的 BLS 聚合验证

这是该批次中 AI 低估的一个漏洞。智能体将其标记为中等严重程度。但实际上这是一个教科书式的恶意密钥攻击,属于广为人知的严重级别缺陷;我们将其报告为严重,而 Cloudflare 确认其为高危。

sign/bls 中的 VerifyAggregate 函数实现了 BLS BASIC 聚合模式。该模式仅在批次中所有消息互不相同时才是安全的,这是其防御恶意密钥攻击的机制。该函数检查了聚合配对等式,但从未检查消息是否互不相同,将这一关键要求留给了调用方。

缺少这一检查,标准的恶意密钥攻击便得以实施。攻击者看到受害者的公钥 $\mathsf{pk}_v$ 和消息 $m$ 后,可以注册 $\mathsf{pk}_a = g^{\mathsf{sk}_a} - \mathsf{pk}_v$,并在完全不知道受害者私钥的情况下,伪造针对 $(\mathsf{pk}_v, m)$ 和 $(\mathsf{pk}_a, m)$ 的聚合签名。CIRCL 没有附带任何可用的持有证明基础设施,这使得缺失检查的问题更加危险。

为什么 AI 将其判定为中等?我们不得而知。阅读其推理过程,它正确发现了缺失的消息区分性检查,甚至提到了恶意密钥攻击,但随后它锚定在 BASIC 模式将消息区分性要求交由调用方负责这一约定上。它将"调用方本应处理此问题"视为一种缓解措施,从而降低了严重等级。

修复方案使 VerifyAggregate 函数拒绝包含重复消息的批次。提交编号 9798df7。

漏洞 4:通过 FillBytes 签名碰撞导致的 DLEQ 可靠性破坏

回到 zk/qndleq 来看这个批次中最微妙、坦白说也最有趣的漏洞。它完全不需要触碰证明本身。

取一个针对陈述 $S_1 = (g, g_x, h, h_x)$ 的诚实有效证明 pi,该证明证实 $\log_g(g_x) = \log_h(h_x) = x$。一个不知道 $x$ 的攻击者向验证者出示完全相同的 pi,但将其与另一个不同的陈述 $S_2 = (g, -g_x, h, h_x)$ 配对,其中 $-g_x$ 是负数 big.Int new(big.Int).Neg(gx)。

只要挑战值 $c$ 是偶数,这个伪造的陈述就会被接受,因为此时两件事同时成立。

代数消去。验证者从 $-g_x$ 重新计算其值,符号因子直接提出来:

$$(-g_x)^c \bmod N = (N - g_x)^c \bmod N = (-1)^c \cdot g_x^c \bmod N.$$

当 $c$ 为偶数时,$(-1)^c = 1$,因此验证者重建出与诚实证明者完全相同的中间值。

哈希中的符号碰撞。挑战值是通过哈希陈述得出的,而哈希过程使用了 FillBytes,该函数写入 big.Int 的绝对值并丢弃符号。因此 doChallenge(..., -gx, ...) 和 doChallenge(..., gx, ...) 会哈希出相同的结果。

这里,$c$ 为偶数的概率至少为 $1/2$(它只是哈希输出的低位比特),因此攻击在大约一半的诚实生成证明上都能成功。验证者会相信 $\log_g(-g_x) = \log_h(h_x)$,而这是错误的。以下是概念验证的核心:

// honest proof for (g, gx, h, hx), selected to have an even challenge c
gxNeg:=new(big.Int).Neg(gx)// -gx, attacker needs no knowledge of x
forgedAccepted:=proof.Verify(g,gxNeg,h,hx,N)// accepted!

这个漏洞之所以突出,是因为它并非某一行代码的粗心大意。它是一个代数恒等式($(-1)^{\text{偶数}} = 1$)与一个看似无害的序列化选择(FillBytes 丢弃符号)之间的相互作用。两者单独来看都没有错,但合在一起就破坏了可靠性。跨越这种边界进行推理,正是模型所发现的最令我们惊讶的地方。

在严重性方面,智能体将其评为高,因为这是一个可靠性破坏;但 Cloudflare 确认其为低,因为攻击复杂度高。

修复方法是在挑战值计算中增加了一个 checkBounds 步骤,要求每个输入都满足 0 < x < N。负数 -gx 带有负号,在造成任何损害之前就会被拒绝。提交 19848a5。

Bug 5:HPKE PSK 验证被按位或开关绕过

这个几乎是一个语言层面的陷阱。在 HPKE 的 verifyPSKInputs 函数中,开关标签使用了按位或来编写:

// hpke/util.go
casemodeBase|modeAuth:// 0x00 | 0x02 == 0x02, i.e. only modeAuth
casemodePSK|modeAuthPSK:// 0x01 | 0x03 == 0x03, i.e. only modeAuthPSK

在 Go 语言中,`case a | b:` 是一个单独的 case,其值为两个常量的或运算结果,而不是两个独立的 case。因此 `case modePSK | modeAuthPSK` 实际上等同于 `case 0x03`,而 `modePSK (0x01)` 则无法匹配任何 case。本应在 PSK 模式下拒绝缺失 PSK 的分支被直接跳过了。

影响:`SetupPSK(..., nil, nil)` 会使用空 PSK 继续执行,而不是被拒绝。PSK 模式本应要求提供 PSK 材料;这个漏洞静默地丢弃了身份验证前置条件,让部署在比预期更弱的模式下运行。修复方法是将 OR 改为逗号分隔的 case(`case modePSK, modeAuthPSK:`),这是一个字符级别的改动。提交 a3b4fa3。该漏洞被确认为重复报告。

Bug 6:int64 中的拉格朗日系数

回到 tss/rsa,一旦份额分配完成,组合签名需要拉格朗日插值。computeLambda 函数使用 int64 类型构建每个拉格朗日系数的分子和分母:

// tss/rsa/rsa_threshold.go
num:=int64(1)
den:=int64(1)
for_,s:=rangeS{
jprime:=int64(s.Index)
ifjprime==j{continue}
num*=i-jprime// overflows int64 for moderate player counts
den*=j-jprime
}
lambda.Div(big.NewInt(num),big.NewInt(den))// truncating integer division
lambda.Mul(delta,&lambda)

这里实际上存在两个完全独立的 bug,任何一个都足以破坏签名。第一个是溢出:当大约有 21 个参与者时,乘积会超过 int64 的上限(约 $9.2 \times 10^{18}$)并静默回绕,因为 Go 在整数溢出时不会触发 panic。computeLambda 随后会返回错误的、通常为负数的系数。

第二个是截断问题,即使没有发生溢出也会出现。代码先计算 `num / den`,然后才乘以 `delta`。在 Shoup 的方案中,$\delta \cdot \text{num}$ 保证能被 `den` 整除,但仅当份额索引连续时 `num` 本身才能被 `den` 整除,而对于 $t$-of-$n$ 子集来说,非连续索引才是常态。以 3-of-5 方案组合份额 $\{1, 3, 5\}$ 为例:对于某个系数,$\text{num} = (0-3)(0-5) = 15$,$\text{den} = (1-3)(1-5) = 8$。错误的计算顺序得到 $\delta \cdot \lfloor 15/8 \rfloor$,当 $\delta = 120$ 时结果为 $120$;而正确的值 $\delta \cdot 15 / 8$ 应为 $225$。

该修复将整个计算迁移到 big.Int,并重新排列了乘法与除法的顺序,从而保证了精确可整除性。这是提交 751e372。这两个问题并无关联,但智能体将它们作为单一发现一并报告,我们出于对其工作的尊重,保留了这样的提交方式。

漏洞 7:由一行 AND 共享错误导致的 CP-ABE 访问控制失效

这是 zkao 自行发现的漏洞。在确认了上述六个问题后,我们将其指向同一个库,看它还能发现什么,它便报告了此漏洞。该漏洞完全破坏了 CIRCL 的密文策略属性基加密(abe/cpabe/tkn20)中的访问控制保证,Cloudflare 已确认其有效性。

密文策略属性基加密(CP-ABE)允许你根据策略对消息进行加密,例如(位置:美国 AND 部门:财务)OR(角色:管理员)。用户持有与其自身属性绑定的密钥,该方案保证用户当且仅当其属性满足策略时才能解密。身处美国的财务员工和管理员可以读取消息,而其他任何人都无法读取,即便所有人都收到相同的密文。

在内部,tkn20 将策略转化为一个由 AND 和 OR 门组成的树,叶子节点为属性,并将一个秘密(即保护消息的密钥)沿该树进行秘密共享。共享过程必须遵循布尔逻辑:

  • OR 门将完整秘密赋予两个子节点,因为满足任意一个分支就足够了。
  • AND 门则分割秘密,因此你需要两个子节点才能重建它。一个子节点获得随机值 r,另一个获得 parent - r,只有 r + (parent - r) 才能恢复父节点。

要理解这个漏洞,你只需记住 AND 门:每个子节点必须获得部分份额,且任何一个子节点单独都无法重建父节点。

以下是 share 实际处理 AND 情况的方式:

// abe/cpabe/tkn20/internal/tkn/formula.go
caseAndgate:
shares[gate.In0],err=randomMatrixZp(rand,k.rows,k.cols)// In0 = random r
...
shares[gate.In1]=newMatrixZp(k.rows,k.cols)// In1 = 0
shares[gate.In0].sub(shares[gate.Out],shares[gate.In1])// In0 = parent - 0

随机份额生成后立即被丢弃。In1 被设为零,最后一行用 parent - In1 覆盖 In0,结果就是 parent。因此,一个子节点获得了完整秘密,而另一个一无所获。AND 门不再是 AND:它的第一个叶子节点独自重建了父节点。

请注意,这并不会破坏正确性。两个分片相加仍然等于父节点(父节点 + 0 = 父节点),因此任何满足策略的密钥仍然可以解密,旧代码生成的密文也保持兼容。它破坏的是保密性:单个 AND 叶子节点现在就能恢复出原本需要两者才能获取的秘密。

真正导致全面攻破的,是这个有缺陷的 AND 门最终所处的位置。为了实现 CCA 安全性,tkn20 应用了 Boneh-Katz 变换,将每个策略包裹在一个新的外部 AND 门中,而该门的左子节点是一个内部“通配符”叶子节点。权威机构颁发的每个属性密钥都携带这个通配符,因此每个密钥都能满足该叶子节点。现在把两个事实结合起来:通配符叶子节点是 AND 门的第一个子节点(In0),而 In0 恰好是接收完整秘密的那个子节点。因此,无论策略如何,每个密钥都持有一个能单独重建消息密钥的叶子节点!

虽然这看起来像是一个简单的拼写错误,但令我们印象深刻的是 zkao 能够推理 CP-ABE 这样复杂的概念,并正确评估其影响。许多大语言模型仍然能识别出这个拼写错误,但会将其视为“纵深防御”或“代码规范”问题而不再深入推理。这可能导致开发者低估或忽视该漏洞。

修复只需一行代码。正确地对父节点进行分片,使得随机分片保留在 In0 中,而 In1 成为其补数:

shares[gate.In1].sub(shares[gate.Out],shares[gate.In0])// In1 = parent - random

现在 In0 保留其随机值,In1 持有 parent - In0,因此两个 AND 叶子节点都无法单独携带秘密。提交编号 def2fd3。

我们学到的一些东西

有三点观察令我们印象深刻。

AI 在严重性评估方面表现不佳,而且这种不佳是单向的。请再看一下顶部的表格。如果我们以 Cloudflare 确认的严重性作为“真实基准”,那么在大多数情况下,智能体高估了其发现的影响。但在 BLS 漏洞上,情况则相反,它低估了一个广为人知的关键缺陷,将一个明显的恶意密钥攻击标记为仅中等严重。我们目前还没有完整的解释或解决方案。我们的工作假设是,当目标是一个被许多不同应用程序使用的库(如 CIRCL)时,评估严重性确实很困难,因为影响取决于模型无法看到的下游调用方。我们认为,添加一个一致的严重性矩阵,再加上一个明确的威胁建模步骤,将有助于模型从整个系统(及其潜在集成)的层面来推理影响,而不是仅局限于局部代码。目前,严重性评估仍然是我们在分类处理中信任人类来负责的部分。

在 zkao 中,我们通过允许开发者通过用户配置文件(zkao.md)阐明其威胁模型和严重性偏好来临时解决这个问题,并且我们持续迭代改进 zkao 的严重性设置。zkao 能够持续生成概念验证代码(PoC)这一事实也减少了误报,因此我们对真实问题有信心。尽管如此,我们仍在努力以更系统化的方式改进其默认严重性分配,因为这对开发者至关重要。

同样值得注意的是,Cloudflare 是通过其漏洞赏金计划的视角来评估严重性的,该计划衡量漏洞是否影响其在线服务。例如,漏洞 2 被评为低严重性,尽管它完全破坏了证明的可靠性。这个评级仅表明受影响的代码要么未被 Cloudflare 服务使用,要么在 Cloudflare 环境中影响有限。这并不意味着在其他部署中影响也同样小,这对于一个可能被许多项目作为基础的库来说尤其重要。

模型配对并非对称,角色也可能互换。六个漏洞中有五个是由 Claude Opus 4.6 配合我们的技能发现的。在相同的技能和相同的系统提示词下,GPT-5.3 主要是在验证而非发现漏洞;表格中的 HPKE 漏洞是它自己发现的。我们原本以为这种分工不会保持稳定,事实也确实如此。几周后,我们用当时最新的配对——Opus 4.7 和 GPT-5.4——重新运行了扫描,结果角色基本上发生了反转:GPT-5.4 发现了更多漏洞,而 Opus 4.7 只能验证它们。这很好地提醒我们,不要过度依赖任何特定模型名称来下结论。前沿模型此后又向前迈进了,而且还会继续前进。我们将在另一篇文章中探讨这一点。

正是这种模式促使我们构建 zkao,使其“与模型无关”,始终保持最佳性能,而无需你猜测下个月哪个模型会最好。

AI 会收集问题,但并不总是将它们串联起来。漏洞 6 就是明证。智能体将两个完全独立的漏洞——一个溢出和一个整数截断——打包成了一个发现。两者都是真实存在的,所以这确实是有用的工作。但它只是将它们并列呈现,而没有推理它们之间的关系,我们在其他地方也看到了同样的模式:多个真实的观察结果被收集在一起,却没有给出任何解释,也没有尝试将它们串联成一个更具影响力的利用链。

这正是我们在 zkao 中构建全新流程的地方,以实现那种将独立问题串联成真正端到端利用链的漏洞组合能力。

下一步计划

感谢 CIRCL 维护者,他们迅速修复了所有报告的问题。这是系列文章的第一篇;随着其他项目的已确认漏洞得到修复,我们将持续发布相关内容。

我们扫描了超过 200 个加密项目(从下载量最高的加密库/包中选取),最终获得了上千条候选发现。结果,当前最大的瓶颈是分类筛选。每个报告的漏洞在提交给项目方之前,都必须由我们的专家进行技术有效性核查,因为我们和其他人一样讨厌 AI 垃圾信息。这需要大量人力投入,因为我们对自己正在构建的自动化分类流程尚未完全信任(它正在逐步改进)。因此,我们优先处理了一些维护良好且最受欢迎的项目。

如果您维护一个加密项目并且对此感兴趣,我们很乐意与您一同验证,以便及时发现并修复严重漏洞。如果尚未进行过扫描,或者自上次扫描后您的代码库发生了重大变化,我们很乐意重新进行一次全新扫描。这种持续的 AI 覆盖正是 zkao 的构建目标。请通过 zksecurity.xyz/contact 联系我们。

zkSecurity 为加密系统提供审计、研究和开发服务,涵盖零知识证明、多方安全计算(MPC)、全同态加密(FHE)、共识协议等领域。

来源:Hacker News 热门(buzzing.cc 中文翻译)· blog.zksecurity.xyz