首次漏洞!libsodium被曝低阶函数验证缺陷,技术细节深度解析

本文深度剖析了libsodium库13年来首个CVE漏洞CVE-2025-69277,该漏洞源于低阶函数`crypto_core_ed25519_is_valid_point()`在验证椭圆曲线点时的逻辑缺陷。文章详解了Ed25519的数学背景、漏洞产生的具体技术原因,并提供了修复方案和应用级规避方法。

libsodium 密码学库首曝安全漏洞:CVE-2025-69277 技术深度分析

漏洞概述

libsodium,这个致力于简化密码学应用、拥有13年辉煌历史且此前从未记录过CVE漏洞的知名密码学库,在2025年底曝出了其首个安全漏洞,编号为 CVE-2025-69277。该漏洞影响库中的低阶函数 crypto_core_ed25519_is_valid_point()

该函数本应严格验证一个给定的椭圆曲线点是否属于爱德华兹曲线Ed25519上的主密码学子群(prime-order subgroup)。然而,由于验证逻辑不完整,在某些特定条件下,该函数会错误地将一些不属于主群的“非法”点判定为有效。

根据MITRE的评估,该漏洞的CVSS 3.1评分为 4.5(中危),攻击向量本地化,攻击复杂度较高。其根本原因被归类为 CWE-184:不完整的禁止输入列表

受影响范围与状态

  • 受影响的版本:所有在 2025年12月30日之前发布 的 libsodium 版本均包含此缺陷。具体包括官方tarball、Visual Studio和MingW二进制文件、NuGet包、swift-sodium框架、rustlibsodium-sys-stable绑定以及libsodium.js。
  • 受影响的衍生库:此漏洞通过依赖关系影响了多个生态中的包,包括:
    • PyPI: PyNaCl (< 1.6.2), hdwallet (< 3.6.1)
    • Composer: paragonie/sodium_compat (2.x系列 < 2.5.0; 1.x系列 < 1.24.0)
  • 修复状态:漏洞在发现后已被立即修复。所有在2025年12月30日之后发布的稳定包均已包含修复补丁。主流Linux发行版(如Debian)也已发布安全更新。

技术背景:Ed25519与子群

要理解此漏洞,需要一些椭圆曲线密码学的背景知识。Edwards25519(简称Ed25519)曲线被广泛用于数字签名(如Ed25519签名方案)。

这条曲线上的点构成了多个具有不同“阶”(order,可理解为点的数量)的循环子群

  • 阶为L的主子群:这是密码学操作(如签名和密钥交换)实际发生的安全区域。L是一个约2^252的大素数。
  • 小阶子群:例如阶为1、2、4、8的子群。
  • 混合阶子群:例如阶为2L、4L、8L的子群。

安全的协议必须确保所有运算都在主子群中进行。攻击者如果能够将一个小阶点或混合阶点注入运算,可能破坏协议的安全假设,导致潜在的私钥泄露或签名伪造(尽管对于Ed25519签名本身,由于设计,此类攻击通常难以直接利用)。

函数 crypto_core_ed25519_is_valid_point() 的设计目的正是为了过滤掉那些不在主子群中的点。

漏洞技术细节

1. 正确的验证原理

验证一个点 P 是否在阶为 L 的主子群上的标准数学方法是:计算 [L] * P(即点 P 与标量 L 的标量乘法)。如果 P 的阶确实是 L,那么 [L] * P 的结果应该是无穷远点(或称恒等点、零元)

在libsodium的内部实现中,椭圆曲线点使用扩展坐标 (X, Y, Z) 表示。无穷远点在该坐标系下的表示为:X坐标为0,且Y坐标等于Z坐标(即 X = 0Y = Z)。Z 可能因之前的运算而异,不一定为1。

2. 有缺陷的代码

漏洞出现前的旧验证代码仅检查了乘法结果点的 X 坐标是否为0:

1
2
// 有缺陷的旧代码
return fe25519_iszero(pl.X); // 仅检查 X == 0

遗漏了检查 Y == Z 这一关键条件。这意味着,如果一个点经过 [L] 乘法后,其 X 坐标恰好为0,但 Y 坐标不等于 Z 坐标,它仍然会被错误地接受。

3. 如何构造一个能“骗过”旧检查的非法点?

从任何一個主子群上的点 Q(例如通过 crypto_core_ed25519_random() 生成)出发,将其与一个阶为2的点 (0, -1) 相加(或等效地,同时取反其X和Y坐标)。得到的点 Q' = Q + (0, -1) 不属于主子群,但它经过 [L] 乘法后,其 X 坐标恰好为0,而 Y 坐标不等于 Z 坐标。这个点 Q' 就能通过有缺陷的验证。

修复方案

修复非常简单直接,就是补上缺失的检查:

1
2
3
// 修复后的新代码
fe25519_sub(t, pl.Y, pl.Z); // 计算 t = Y - Z
return fe25519_iszero(pl.X) & fe25519_iszero(t); // 检查 X == 0 且 Y-Z == 0

现在,该函数会严格验证 X = 0Y = Z 两个条件,确保结果确实是无穷远点。

实际影响与建议

谁受影响?

绝大多数libsodium用户不受影响。

该漏洞仅影响直接调用低阶函数 crypto_core_ed25519_is_valid_point() 并用于验证来自不可信来源的点的应用程序。libsodium的所有高级API(如 crypto_sign_* 系列签名函数)均不受影响,因为它们根本不使用这个有问题的函数。

受影响的应用场景主要是那些使用libsodium作为算法工具箱、自行构建定制化密码学协议的开发者。

给开发者的建议

  1. 立即升级:将libsodium升级至2025年12月30日之后发布的任何版本。
  2. 优先使用Ristretto255:如果您的自定义协议需要在曲线上进行群运算,强烈建议使用libsodium自2019年起支持的 Ristretto255 群。Ristretto255抽象掉了复杂的子群问题,只要一个点能成功解码,它就保证在安全的素数阶群上,无需额外验证。
  3. 临时缓解措施:如果无法立即升级,可以在应用层实现以下替代验证函数:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    int is_on_main_subgroup(const unsigned char p[crypto_core_ed25519_BYTES]) {
        /* l - 1 (群阶减1) */
        static const unsigned char L_1[crypto_core_ed25519_SCALARBYTES] = { ... };
        /* 恒等点编码: (x=0, y=1) */
        static const unsigned char ID[crypto_core_ed25519_BYTES] = { ... };
        unsigned char t[crypto_core_ed25519_BYTES];
        unsigned char r[crypto_core_ed25519_BYTES];
        if (crypto_scalarmult_ed25519_noclamp(t, L_1, p) != 0 ||
            crypto_core_ed25519_add(r, t, p) != 0) {
            return 0;
        }
        return sodium_memcmp(r, ID, sizeof ID) == 0;
    }
    

关于维护与可持续性的思考

此漏洞的披露也引发了社区对关键开源基础设施可持续性的讨论。libsodium主要由创始人Frank Denis一人维护,是一项无偿且耗时的工作。他在公告中呼吁,如果项目对您有用,请考虑通过 Open Collective 进行赞助,以帮助他投入更多时间维护项目,确保其长期健康发展。

许多企业虽认可其价值,但现有的公司捐赠流程往往难以适配这类非标准化的个人赞助模式,这构成了开源可持续发展中的一个普遍挑战。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计