
Vulnerabilità di Denial of Service in ecdsa (PyPI)
Vulnerabilità Denial of Service in ecdsa (PyPI)
Ho identificato e divulgato in modo responsabile una vulnerabilità di gravità Moderata in python-ecdsa, una libreria Python di crittografia ampiamente utilizzata con 47,8M di download nell'ultimo mese.
Ho trovato questo problema mentre esaminavo python-ecdsa con una domanda molto specifica in mente:
Cosa succede se un DER malformato mente sulla propria lunghezza e il parser si fida troppo?
In questo caso, quella domanda ha portato a un bug reale.
Gli helper di parsing DER in ecdsa.der accettavano dati troncati nei casi in cui la lunghezza codificata dichiarava più byte di quelli effettivamente presenti. Quel input malformato avrebbe dovuto essere rifiutato immediatamente. Invece, poteva passare più a fondo nella logica di parsing e alla fine innescare un IndexError interno durante il parsing delle chiavi.
Quel problema è diventato CVE-2026-33936.
Progetto: python-ecdsa su GitHub
Pacchetto: ecdsa (pip)
CVE: CVE-2026-33936
attacker-controlled malformed DER → truncated length accepted as valid → parser continues past trust boundary → SigningKey.from_der() reaches internal exception path → unexpected IndexError / application-level DoS risk
python-ecdsa è una libreria Python ampiamente utilizzata per la crittografia a curve ellittiche.
Tra le altre cose, gestisce:
Ciò significa che il suo codice di parsing si trova direttamente su un confine di sicurezza.
Ogni volta che una libreria accetta materiale di chiave fornito esternamente o input binario strutturato, la correttezza non è solo una questione di qualità. È una proprietà di sicurezza.
Se un input malformato viene accettato quando dovrebbe essere rifiutato, il codice a valle inizia a fare assunzioni basate su uno stato non valido. È lì che i bug smettono di essere "semplici errori di parsing" e iniziano a diventare vulnerabilità.
Il parsing DER è una di quelle aree in cui piccoli errori di validazione possono avere effetti sproporzionati.
La classe di bug è semplice:
È esattamente il tipo di fallimento di confine che vale la pena verificare in una revisione di sicurezza.
Non stavo cercando comportamenti crittografici strani qui. Stavo cercando fallimenti di fiducia nella gestione di input strutturati.
Era il posto giusto dove guardare.
Il problema alla radice era una validazione impropria dei campi di lunghezza DER durante il parsing di input malformati o troncati.
Nello specifico, ecdsa.der.remove_octet_string() accettava input in cui la lunghezza DER dichiarata superava il numero di byte effettivamente disponibili nel buffer.
Quindi, invece di rifiutare DER malformati come questo:
40963l'helper lo accettava e restituiva contenuto troncato come se fosse valido.
Questo è già un bug.
Ma l'impatto più forte si è manifestato a valle.
Poiché l'input malformato veniva accettato invece di essere rifiutato al confine, SigningKey.from_der() poteva successivamente raggiungere un percorso di eccezione interno e sollevare:
IndexError: index out of bounds on dimension 1
Questo è importante perché non è il tipo di errore che un chiamante si aspetta da un input malformato.
Il comportamento corretto è un rifiuto pulito del parsing, come UnexpectedDER o ValueError.
Quindi la vulnerabilità non era "esiste un IndexError" in isolamento.
La vera vulnerabilità era questa:
È una singola catena di bug, non due problemi non correlati.
Un parser che rifiuta input malformati non è un miglioramento estetico. È parte del modello di sicurezza.
La distinzione importante qui non è se l'input fosse non valido. Ovviamente lo era.
La distinzione importante è come la libreria si è comportata di fronte a un input non valido.
C'è una differenza reale tra:
Il primo è un comportamento robusto.
Il secondo crea un rischio a livello applicativo se il software analizza DER non attendibili e presume che i guasti della libreria rimangano entro i tipi di eccezione previsti.
Ecco perché è stato correttamente classificato come vulnerabilità piuttosto che semplicemente come un bug di qualità del parser.
Ho usato due PoC perché dimostravano due parti diverse della stessa catena di bug.
Il primo PoC ha mostrato che remove_octet_string() accettava DER troncati la cui lunghezza dichiarata superava il buffer disponibile.
Questo ha stabilito il fallimento di validazione centrale:
Il secondo PoC ha mostrato l'effetto a valle più importante:
DER malformati forniti a SigningKey.from_der() innescavano deterministicamente un IndexError interno prima della correzione.
Questo ha stabilito l'impatto rilevante per la sicurezza:
È un risultato molto più forte di "il parser ha accettato byte strani".
Mostra un fallimento di confine più una reale conseguenza operativa.
Il primo PoC dimostra la causa radice.
Il secondo PoC dimostra l'impatto.
Questa suddivisione è importante.
Molti report si fermano a:
"questo parser accetta dati malformati."
È utile, ma non sempre sufficiente per mostrare perché il bug sia importante.
In questo caso, il report più forte era:
Questo rende la storia di sicurezza molto più chiara.
La correzione è stata minima e corretta.
La patch ha aggiunto la stessa regola di sicurezza mancante già utilizzata in remove_sequence():
la lunghezza dichiarata deve rientrare nel buffer disponibile
Il controllo è stato applicato a:
remove_constructed()remove_implicit()remove_octet_string()Una volta aggiunti quei controlli dei limiti, i DER malformati/troncati venivano rifiutati immediatamente con:
UnexpectedDER: Length longer than the provided buffer
E il PoC che in precedenza innescava IndexError non raggiungeva più il percorso di eccezione interno.
Falliva in modo pulito durante il parsing, che è esattamente ciò che sarebbe dovuto accadere dall'inizio.
Questo è il tipo di correzione che si vuole vedere in una vulnerabilità di parsing:
Nessuna riprogettazione. Nessuna ambiguità. Solo una validazione corretta dove mancava.
Ho anche aggiunto test di regressione mirati per assicurarmi che questa esatta classe di DER malformati continui a essere rifiutata.
I nuovi test coprono il rifiuto di lunghezza troncata per:
remove_octet_stringremove_constructedremove_implicitQuesto era importante perché il bug non riguardava un singolo strano percorso di esecuzione. Riguardava una regola di validazione che doveva valere in modo coerente su tutti gli helper DER correlati.
Dopo l'aggiunta della correzione e dei test, l'intera suite è passata localmente:
python -m pytest -q
# 2018 passed, 5 skipped
Questo è importante nel lavoro di divulgazione reale.
Una correzione è molto più solida quando arriva con test che bloccano il confine.
Questo problema è stato ragionevolmente classificato come Moderato.
L'impatto chiave qui è la disponibilità / robustezza, non la riservatezza o l'integrità.
La classificazione dell'avviso era:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:LHa senso.
L'affermazione non è che DER malformati permettano a un attaccante di eseguire codice. L'affermazione è che DER malformati potrebbero innescare eccezioni interne impreviste in software che analizza materiale DER non attendibile usando questa libreria.
È un bug di parsing reale e difendibile in stile DoS.
Questo problema è stato segnalato privatamente tramite GitHub Security Advisories.
Il report includeva:
IndexError a valleIl manutentore ha validato il problema, ha richiesto che la correzione arrivasse con test unitari, e la correzione coordinata è proceduta tramite il flusso di lavoro del fork privato temporaneo GHSA.
Durante la gestione della CVE, GitHub inizialmente ha rifiutato l'assegnazione perché il testo dell'avviso sembrava descrivere più di una vulnerabilità. La precisazione è stata semplice:
questa era una singola vulnerabilità con un'unica causa radice — validazione impropria della lunghezza DER — e l'IndexError di SigningKey.from_der() era una conseguenza a valle di quella stessa accettazione di input malformato, non un problema separato e indipendentemente correggibile.
Quella precisazione è stata sufficiente, e al problema è stata assegnata:
CVE-2026-33936
La lezione qui non è "il DER è complicato".
Tutti sanno già che il DER è complicato.
La vera lezione è questa:
l'input strutturato malformato deve essere rifiutato nel punto esatto in cui il parser sa che non è valido.
Se manchi quel confine, il codice successivo finisce per operare su assunzioni che non sono più affidabili.
È così che gli errori di parsing di basso livello diventano problemi di sicurezza.
Questo bug rafforza anche qualcosa di importante su writeup e triage:
"Input malformato accettato" era l'inizio della storia.
"Input malformato accettato, poi propagato in un percorso di eccezione interno durante il parsing delle chiavi" era la storia completa.
Quella distinzione ha aiutato a presentare il caso in modo chiaro e corretto.
Questa vulnerabilità non riguardava crittografia esotica.
Riguardava un parser che si fidava di input malformati più a lungo di quanto avrebbe dovuto.
Un campo di lunghezza DER troncato ha attraversato il confine, è sopravvissuto alla validazione quando avrebbe dovuto essere rifiutato, e alla fine ha causato un comportamento instabile nel parsing delle chiavi.
Ecco perché questo è diventato CVE-2026-33936.
Corretto imponendo controlli adeguati dei limiti di lunghezza DER nei parser helper interessati.
