ecdsa (PyPI) 中的拒绝服务漏洞
我在 python-ecdsa 中识别并负责任地披露了一个 中等严重性 的漏洞,这是一个广泛使用的 Python 加密库,过去一个月有 4780 万次下载。
我在审查 python-ecdsa 时发现了这个问题,心中带着一个非常具体的问题:
如果格式错误的 DER 虚假地声称其长度,而解析器过度信任该长度,会发生什么?
在这个案例中,这个问题引出了一个真实漏洞。
ecdsa.der 中的 DER 解析助手在编码长度声称的字节数多于实际存在的字节数时,接受了被截断的数据。这种格式错误的输入本应立即被拒绝。相反,它可以深入到解析逻辑中,并最终在密钥解析过程中触发内部的 IndexError。
该问题被分配为 CVE-2026-33936。
项目: GitHub 上的 python-ecdsa
包: ecdsa (pip)
CVE: CVE-2026-33936
攻击者控制的格式错误 DER → 截断的长度被接受为有效 → 解析器越过信任边界继续执行 → SigningKey.from_der() 触及内部异常路径 → 意外的 IndexError / 应用程序级 DoS 风险
python-ecdsa 是一个广泛使用的 Python 椭圆曲线密码学库。
除其他功能外,它还处理:
这意味着它的解析代码直接位于安全边界上。
当一个库接受外部提供的密钥材料或结构化二进制输入时,正确性不仅仅是质量问题。 它是一个安全属性。
如果格式错误的输入在应该被拒绝时被接受,下游代码就会在无效状态的基础上做出假设。 这就是错误从“仅仅是解析错误”转变为漏洞的地方。
DER 解析是这样一个领域,小的验证错误可能会产生不成比例的影响。
这个漏洞类别很简单:
这正是在安全审查中值得检查的边界故障。
我不是在寻找这里奇怪的加密行为。 我是在寻找结构化输入处理中的信任失败。
这正是该关注的地方。
根本问题是解析格式错误或截断输入时 DER 长度字段的验证不当。
具体来说,ecdsa.der.remove_octet_string() 接受了声明的 DER 长度超过缓冲区实际可用字节数的情况。
因此,它不是像这样拒绝格式错误的 DER:
40963而是助手接受了它,并返回截断的内容,仿佛它是有效的。
这本身就是一个漏洞。
但更严重的影响出现在下游。
由于格式错误的输入在边界处被接受而非拒绝,SigningKey.from_der() 后来可能触及一个内部异常路径并抛出:
IndexError: index out of bounds on dimension 1
这很重要,因为这不是调用者期望从格式错误输入中得到的那种失败。
正确的行为是清晰的解析拒绝,例如 UnexpectedDER 或 ValueError。
因此,这个漏洞并非孤立的“存在 IndexError”。
真正的漏洞是这样的:
这是一个漏洞链,而不是两个无关的问题。
解析器拒绝格式错误的输入不是外观上的改进。 它是安全模型的一部分。
这里重要的区别不在于输入是否无效。 它显然是无效的。
重要的区别在于 库在面对无效输入时的行为。
以下两者之间存在真正的区别:
第一个是健壮的行为。
第二个会带来应用程序级别的风险,如果软件解析不受信任的 DER 并假设库的失败保持在预期的异常类型范围内。
这就是为什么这被正确归类为漏洞,而不仅仅是解析器质量的 bug。
我使用了两个 PoC,因为它们展示了同一个漏洞链的两个不同部分。
第一个 PoC 展示了 remove_octet_string() 接受了声明的长度超过可用缓冲区的截断 DER。
这确立了核心验证失败:
第二个 PoC 展示了更重要的下游影响:
修复前,提供给 SigningKey.from_der() 的格式错误 DER 确定性地触发了一个内部的 IndexError。
这确立了与安全相关的影响:
这比“解析器接受奇怪字节”要强得多。
它展示了边界失败以及实际的操作后果。
第一个 PoC 证明了根本原因。
第二个 PoC 证明了影响。
这种划分很重要。
很多报告止步于:
“这个解析器接受格式错误的数据。”
这很有用,但并不总能说明这个漏洞为什么重要。
在这个案例中,更强的报告是:
这使得安全故事清晰得多。
修复简洁且正确。
补丁添加了 remove_sequence() 中已经使用的相同缺失安全规则:
声明的长度必须适合可用缓冲区
该检查应用于:
remove_constructed()remove_implicit()remove_octet_string()一旦添加了这些边界检查,格式错误/截断的 DER 立即被拒绝:
UnexpectedDER: Length longer than the provided buffer
而之前触发 IndexError 的 PoC 不再触及内部异常路径。
它在解析过程中干净地失败,这正是从一开始就应该发生的事情。
这是你在解析器漏洞中希望看到的那种修复:
没有重新设计。 没有歧义。 只是在缺失的地方进行了正确的验证。
我还添加了有针对性的回归测试,以确保这类格式错误的 DER 持续被拒绝。
新测试涵盖了以下功能的截断长度拒绝:
remove_octet_stringremove_constructedremove_implicit这很重要,因为漏洞并非关于一个奇怪的运行时路径。 而是关于一个需要跨相关 DER 助手保持一致的验证规则。
修复和测试添加后,整个测试套件在本地通过:
python -m pytest -q
# 2018 passed, 5 skipped
这在实际披露工作中很重要。
当修复附带锁定边界的测试时,它强大得多。
该问题被合理地分类为 中等。
这里的关键影响是可⽤性/健壮性,而非机密性或完整性。
公告的分类是:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L这很有道理。
声明并非格式错误的 DER 让攻击者执行代码。 声明是格式错误的 DER 可能在使用此库解析不受信任 DER 材料的软件中触发意外的内部异常。
这是一个真实且合理的 DoS 式解析漏洞。
此问题通过 GitHub 安全公告私下报告。
报告包括:
IndexError 复现器维护者验证了问题,要求补丁附带单元测试,并通过 GHSA 临时私有分支工作流进行了协调修复。
在 CVE 处理过程中,GitHub 最初拒绝分配,因为公告文本看起来可能描述了一个以上的漏洞。 澄清很简单:
这是一个 单一漏洞,具有单一根本原因——DER 长度验证不当——而 SigningKey.from_der() 的 IndexError 是同一格式错误输入接受的下游后果,而非单独可独立修复的问题。
这个澄清足够了,分配了:
CVE-2026-33936
这里的教训并非“DER 很棘手”。
每个人都知道 DER 很棘手。
真正的教训是:
格式错误的结构化输入必须在解析器知道其无效的确切点上被拒绝。
如果你错过了那个边界,后续代码会在不再可信的假设上操作。
这就是低级解析错误如何成为安全问题。
这个漏洞还印证了关于报告和分类的重要一点:
“接受格式错误的输入”是故事的开始。
“接受格式错误的输入,然后在密钥解析过程中传播到内部异常路径”才是完整的故事。
这个区别有助于清晰且正确地陈述案例。
这个漏洞与奇特的加密无关。
它关于一个解析器过度信任格式错误输入,超过了应有的程度。
一个截断的 DER 长度字段越过了边界,在应该被拒绝时存活了验证,并最终在密钥解析中导致了崩溃行为。
这就是它成为 CVE-2026-33936 的原因。
通过在受影响的助手解析器中强制执行适当的 DER 长度边界检查来修复。
