AI与密码学的邂逅1:AI在Cloudflare的CIRCL中发现了什么

Hacker News Top 新闻

摘要

使用AI审计代理,zkSecurity在Cloudflare的CIRCL密码学库中发现了七个真实漏洞,包括严重的精度损失和访问控制破坏。所有漏洞已在上游修复。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/07/07 20:14

# AI 遇上密码学 1:AI 在 Cloudflare 的 CIRCL 中发现了什么 来源:https://blog.zksecurity.xyz/posts/circl-bugs/ 缩略图 我们将 AI 审计流水线指向 Cloudflare 的实验性密码学库 CIRCL,确认了七个真实漏洞——从阈值 RSA 中严重的 float64 精度损失,到属性基加密中完全的访问控制突破。目前所有七个漏洞均已在上游修复。这是本系列的第一篇文章,介绍我们的智能体在开源密码学中发现的漏洞。 在 zkSecurity,我们正在构建 [zkao](https://zkao.io/),一款 AI 审计智能体。目标说起来简单做起来难:让 AI 持续审查你的代码,直到不再有其它 AI 工具能发现的漏洞。我们在 [zkao: Security That Compounds](https://blog.zksecurity.xyz/posts/zkao-launch) 中阐述了为什么这种方法至关重要。 构建 zkao 是一个迭代过程,最终目标是创建一款能够自动发现所有 AI 可检测漏洞的审计器。这包括构思新想法和技术、将 zkSecurity 安全研究人员的专业知识系统化地编码到 zkao 中、确保它能够检测到最新最严重的漏洞而不被基准测试所偏见,以及——重要的是——持续进行实验,以理解什么有效、什么无效、模型如何演变,并深化我们对 AI 找漏洞的理解。其中一些实验本身就有值得分享的内容,独立于产品,这正是本系列文章的主题。 还有第二个动机。这些实验是我们为 zkao 构建基准测试套件的方法,在此过程中,它们不断揭示 LLM 实际上是如何推理密码学的:它们在哪些方面敏锐,在哪些方面盲目,以及如何放大前者、约束后者。虽然漏洞是可见的输出,但推理模式才是我们最关心的部分。 几个月前,我们开始在选定的代码库上进行实验。我们使用 LLM 扫描了几个开源密码学项目,采用两种配置: 1. 仅使用 LLM,配合简单提示。 2. LLM 配合技能(skills),由我们团队的专家维护这些技能。 然后,对于 LLM 发现真实漏洞的重要项目,我们也运行了 [zkao](https://zkao.io/),看它是否能独立检测到同样的问题。在大多数情况下,zkao 不仅发现了所有问题,还识别出了更复杂、更严重的漏洞。结果足够好,我们决定将其写下来。 我们将以 Cloudflare 的 [CIRCL](https://github.com/cloudflare/circl) 开始这个系列,这是一个先进和后量子密码学库。在 CIRCL 上,我们的流水线生成了许多候选发现,其中七个值得在此报告。目前所有七个漏洞均已在上游修复。其中大多数已在 Cloudflare 的 [HackerOne](https://hackerone.com/cloudflare) 项目下得到确认并获得赏金。 > **说明**:AI 生成的是候选发现,而不是最终报告。我们团队的人员仍然验证了每个问题、检查可利用性、在需要时最小化 PoC,并负责披露。这种人在回路中的步骤仍然非常重要,因为 AI 候选发现成本低廉,而可信报告则不然。减少这一步骤是 [zkao](https://zkao.io/) 的主要目标之一,虽然它仍在开发中,但当前版本已经承担了许多这类验证工作。 ## 严重性与修复概览 在详细介绍之前,有一件事需要指出:AI 为其自身发现分配的严重性是有噪声的。以下是每个漏洞的 AI 评级和 Cloudflare 确认修复后的评级。我们还检查了所有七个漏洞都能被当前版本的 [zkao](https://zkao.io/) 稳定复现。 | # | 漏洞 | AI 严重性 | Cloudflare 严重性 | 修复提交 | 发现者 | |---|------|-----------|-------------------|----------|--------| | 1 | [TSS/RSA 多项式求值中的 float64 精度损失](https://blog.zksecurity.xyz/posts/circl-bugs/#bug-1-polynomial-evaluation-in-float64) | 严重 | 低 | `f7d2180` (https://github.com/cloudflare/circl/commit/f7d2180d6a77cfb283379ec6ad357ebf1d444aed) | Opus 4.6 + 技能 | | 2 | [通过证明者控制的 SecParam 实现 DLEQ 证明伪造](https://blog.zksecurity.xyz/posts/circl-bugs/#bug-2-a-dleq-proof-forgery-via-a-prover-controlled-security-parameter) | 高 | 低 | `757dde4` (https://github.com/cloudflare/circl/commit/757dde480dd0ad313fe2a4b2b6dc802266722a6b) | Opus 4.6 + 技能 | | 3 | [BLS 聚合缺少消息区分性](https://blog.zksecurity.xyz/posts/circl-bugs/#bug-3-bls-aggregate-verification-without-message-distinctness) | 中 | 高 | `9798df7` (https://github.com/cloudflare/circl/commit/9798df7b83ca7b94e7ccd37aa25c91dfaec1fb31) | Opus 4.6 + 技能 | | 4 | [通过 FillBytes 符号碰撞导致 DLEQ 可靠性破坏](https://blog.zksecurity.xyz/posts/circl-bugs/#bug-4-a-dleq-soundness-break-via-a-fillbytes-sign-collision) | 高 | 低 | `19848a5` (https://github.com/cloudflare/circl/commit/19848a5fac78) | Opus 4.6 + 技能 | | 5 | [通过按位或 switch 绕过 HPKE PSK 验证](https://blog.zksecurity.xyz/posts/circl-bugs/#bug-5-hpke-psk-validation-bypassed-by-a-bitwise-or-switch) | 中 | 中(重复) | `a3b4fa3` (https://github.com/cloudflare/circl/commit/a3b4fa341554) | GPT-5.3 + 技能 | | 6 | [TSS/RSA 的 int64 中拉格朗日系数](https://blog.zksecurity.xyz/posts/circl-bugs/#bug-6-lagrange-coefficients-in-int64) | 高 | 中 | `751e372` (https://github.com/cloudflare/circl/commit/751e37241d5a) | Opus 4.6 + 技能 | | 7 | [通过 AND-共享错误导致 CP-ABE 访问控制突破](https://blog.zksecurity.xyz/posts/circl-bugs/#bug-7-a-cp-abe-access-control-break-from-a-one-line-and-share-mistake) | 严重 | 严重 | `def2fd3` (https://github.com/cloudflare/circl/commit/def2fd35b8535b0b8fe84f904936ebfd84b5552b) | zkao | AI 严重性与确认严重性之间的差距本身就是有趣的见解,我们将在最后回到这一点。现在,逐一介绍这七个漏洞。 ## 漏洞 1:float64 中的多项式求值 这个漏洞存在于 CIRCL 的阈值 RSA 实现(`tss/rsa`)中。阈值签名使用 Shamir 风格秘密共享将秘密分给 `n` 个参与者。`Deal()` 在每个参与者的索引处求值一个秘密多项式。系数应该是 `big.Int`,但项 `x^i` 是这样计算的: ```go // tss/rsa/rsa_threshold.go xi := int64(math.Pow(float64(x), float64(i))) ``` `float64` 只有 53 位尾数。一旦 $x^i$ 超过 $2^{53}$(大约 $9 \times 10^{15}$),结果在转换回整数之前就会被静默舍入。例如,如果有一百个参与者,阈值为 27,在 $x = 100$ 且 $i = 26$ 时求值,需要计算 $100^{26} = 10^{52}$,这超出了 $2^{53}$ 的 36 个数量级。即使 $x = 20, i = 16$ 也会破坏它。 后果是多项式求值不正确,因此分发给参与者的密钥份额是错误的。根据参数的不同,签名组合要么直接失败,要么产生看起来正确但无法恢复预期密钥的份额。我们的智能体将其标记为**严重**,因为它导致生成错误的密钥份额,损害了协议的正确性。Cloudflare 最终评估为低严重性,理由是受影响条件在实践中发生的可能性很低。 修复方案是用代码自己的 TODO 注释一直建议的霍纳法求值替换浮点指数运算,全程使用 `big.Int`。提交 `f7d2180` (https://github.com/cloudflare/circl/commit/f7d2180d6a77cfb283379ec6ad357ebf1d444aed)。 ## 漏洞 2:通过证明者控制的安全参数实现 DLEQ 证明伪造 这个漏洞存在于 `zk/qndleq` 中,这是 CIRCL 的 DLEQ(离散对数相等性)证明,用于 $(\mathbb{Z}/n\mathbb{Z})^*$ 中的平方子群。DLEQ 证明断言两个元组共享相同的离散对数;如果攻击者能让验证者接受虚假语句的证明,那么证明系统就崩溃了。 该证明中的挑战是通过 Fiat-Shamir 风格派生的,其位长由 `SecParam` 控制。问题在于 `SecParam` 存在于 `Proof` 结构体内部: ```go type Proof struct { Z, C *big.Int SecParam uint } ``` 在验证过程中,代码使用证明自身的 `SecParam` 重新计算挑战。这个字段是攻击者可控的。将 `SecParam` 设为 1,挑战就会坍缩成单个比特,值为 0 或 1:每次伪造尝试就像抛硬币。将 `SecParam` 设为 8,暴力破解大约需要 $2^8 = 256$ 次尝试。无论哪种方式,可靠性都消失了。 这是一个反复出现的模式的典型实例:一个必须由验证者固定的安全参数,却被从证明者提供的数据中读取出来。修复方案是将 `SecParam` 从证明中移除,让 `Verify` 将其作为显式参数,这样验证者就能设置它。提交 `757dde4` (https://github.com/cloudflare/circl/commit/757dde480dd0ad313fe2a4b2b6dc802266722a6b)。 ## 漏洞 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` (https://github.com/cloudflare/circl/commit/9798df7b83ca7b94e7ccd37aa25c91dfaec1fb31)。 ## 漏洞 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)$,而这是错误的。 以下是概念验证的核心: ```go // 诚实证明针对 (g, gx, h, hx),选择了具有偶数挑战 c 的证明 gxNeg := new(big.Int).Neg(gx) // -gx,攻击者不需要知道 x forgedAccepted := proof.Verify(g, gxNeg, h, hx, N) // 被接受! ``` 这个漏洞的突出之处在于它并不是一行粗心的代码。它是代数恒等式 $(-1)^{\text{even}} = 1$ 与一个看似无害的序列化选择(`FillBytes` 丢弃符号)之间的相互作用。单独来看两者都没有问题,但组合在一起就破坏了可靠性。跨越这种边界进行推理,正是我们对模型所能揭示的内容感到最惊讶的地方。 关于严重性,智能体将其评为高,因为这是可靠性破坏,但 Cloudflare 确认为低,因为攻击复杂度高。修复方案在挑战计算中添加了 `checkBounds` 步骤,要求每个输入满足 $0 < x < N$。负数 $-gx$ 带有负号,会在造成任何损害之前被拒绝。提交 `19848a5` (https://github.com/cloudflare/circl/commit/19848a5fac78)。 ## 漏洞 5:通过按位或 switch 绕过 HPKE PSK 验证 这几乎是一个语言层面的陷阱。在 HPKE 的 `verifyPSKInputs` 中,`switch` 标签是用按位或写的: ```go // hpke/util.go case modeBase | modeAuth: // 0x00 | 0x02 == 0x02,即只有 modeAuth case modePSK | modeAuthPSK: // 0x01 | 0x03 == 0x03,即只有 modeAuthPSK ``` 在 Go 中,`case a | b:` 是一个单独的 case,其值为两个常量的 OR,而不是两个 case。所以 `case modePSK | modeAuthPSK` 实际上是 `case 0x03`,而 `modePSK`(`0x01`)不匹配任何 case。本应在 PSK 模式下拒绝缺失 PSK 的分支直接被跳过。效果是:`SetupPSK(..., nil, nil)` 会以空 PSK 继续执行,而不是被拒绝。PSK 模式本来应该要求 PSK 材料;这静默地丢弃了认证前提条件,让部署在比配置更弱的模式下运行。 修复方案是将 OR 改为逗号分隔的 case(`case modePSK, modeAuthPSK:`),仅一个字符的改变。提交 `a3b4fa3` (https://github.com/cloudflare/circl/commit/a3b4fa341554)。它被确认为重复。 ## 漏洞 6:int64 中的拉格朗日系数 回到 `tss/rsa`,一旦份额被分发,组合签名需要拉格朗日插值。`computeLambda` 在 `int64` 中构建每个拉格朗日系数的分子和分母: ```go // tss/rsa/rsa_threshold.go num := int64(1) den := int64(1) for _, s := range S { jprime := int64(s.Index) if jprime == j { continue } num *= i - jprime // 在中等参与者数量下超出 int64 范围 den *= j - jprime } lambda.Div(big.NewInt(num), big.NewInt(den)) // 截断整数除法 lambda.Mul(delta, &lambda) ``` 这里实际上有两个完全独立的错误,任何一个都足以破坏签名。 第一个是溢出:大约有 21 个参与者时,乘积会超过 `int64` 上限(约 $9.2 \times 10^{18}$)并静默回绕,因为 Go 不会在整数溢出时 panic。`computeLambda` 随后返回错误的、通常是负的系数。 第二个是截断,即使没有溢出也会发生。代码先计算 `num / den`,然后才乘以 `delta`。在 Shoup 的方案中,$\delta \cdot \text{num}$ 保证能被 `den` 整除,但仅当份额索引不连续时 `num` 自身并不能保证,而 $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` 并重新排序操作。

相似文章

Anthropic 新模型一个月内发现超一万个安全漏洞

Reddit r/ArtificialInteligence

Anthropic 的新 AI 模型 Claude Mythos 在一个月内识别出全球系统软件中超过一万个高危和严重安全漏洞,其误报率优于人类测试人员,显著推动了 AI 驱动的网络安全。

他们发现的所有安全漏洞

Hacker News Top

本文详细介绍了AI代理在Epsilon(一个用Go编写的小型WASM运行时)中发现的超过20个安全漏洞,其中包括多个沙箱逃逸漏洞,允许恶意模块突破隔离。