
Vulnérabilité de déni de service dans ecdsa (PyPI)
Vulnérabilité de déni de service dans ecdsa (PyPI)
J'ai identifié et divulgué de manière responsable une vulnérabilité de sévérité modérée dans python-ecdsa, une bibliothèque de cryptographie Python largement utilisée avec 47,8 millions de téléchargements au cours du dernier mois.
J'ai trouvé ce problème en examinant python-ecdsa avec une question très précise en tête :
Que se passe-t-il si un DER malformé ment sur sa longueur et que le parseur lui fait trop confiance ?
Dans ce cas, cette question a conduit à un vrai bug.
Les helpers d'analyse DER dans ecdsa.der acceptaient des données tronquées dans les cas où la longueur encodée prétendait contenir plus d'octets qu'il n'y en avait réellement. Cette entrée malformée aurait dû être rejetée immédiatement. Au lieu de cela, elle pouvait passer plus profondément dans la logique d'analyse et finalement déclencher une IndexError interne lors de l'analyse des clés.
Ce problème est devenu CVE-2026-33936.
Projet : python-ecdsa sur GitHub
Paquet : ecdsa (pip)
CVE : CVE-2026-33936
DER malformé contrôlé par l'attaquant → longueur tronquée acceptée comme valide → le parseur continue au-delà de la frontière de confiance → SigningKey.from_der() atteint un chemin d'exception interne → IndexError inattendue / risque de déni de service au niveau applicatif
python-ecdsa est une bibliothèque Python largement utilisée pour la cryptographie sur courbes elliptiques.
Entre autres, elle gère :
Cela signifie que son code d'analyse se trouve directement sur une frontière de sécurité.
Chaque fois qu'une bibliothèque accepte du matériel de clé fourni de l'extérieur ou une entrée binaire structurée, la correction n'est pas seulement une question de qualité. C'est une propriété de sécurité.
Si une entrée malformée est acceptée alors qu'elle devrait être rejetée, le code en aval commence à faire des hypothèses sur un état invalide. C'est là que les bugs cessent d'être « de simples erreurs d'analyse » et deviennent des vulnérabilités.
L'analyse DER est l'un de ces domaines où de petites erreurs de validation peuvent avoir des effets disproportionnés.
La classe de bug est simple :
C'est exactement le genre de défaillance de frontière qu'il vaut la peine de vérifier lors d'une revue de sécurité.
Je ne cherchais pas un comportement cryptographique étrange ici. Je cherchais des défaillances de confiance dans le traitement des entrées structurées.
C'était le bon endroit où regarder.
Le problème racine était une validation incorrecte des champs de longueur DER lors de l'analyse d'entrées malformées ou tronquées.
Plus précisément, ecdsa.der.remove_octet_string() acceptait des entrées où la longueur DER déclarée dépassait le nombre d'octets réellement disponibles dans le tampon.
Ainsi, au lieu de rejeter un DER malformé comme celui-ci :
40963le helper l'acceptait et renvoyait un contenu tronqué comme s'il était valide.
C'est déjà un bug.
Mais l'impact le plus fort est apparu en aval.
Parce que l'entrée malformée était acceptée au lieu d'être rejetée à la frontière, SigningKey.from_der() pouvait ensuite atteindre un chemin d'exception interne et lever :
IndexError: index out of bounds on dimension 1
C'est important parce que ce n'est pas le genre de défaillance qu'un appelant attend d'une entrée malformée.
Le comportement correct est un rejet propre lors de l'analyse, comme UnexpectedDER ou ValueError.
La vulnérabilité n'était donc pas « IndexError existe » isolément.
La véritable vulnérabilité était celle-ci :
C'est une seule chaîne de bug, pas deux problèmes indépendants.
Un parseur qui rejette une entrée malformée n'est pas une amélioration cosmétique. Cela fait partie du modèle de sécurité.
La distinction importante ici n'est pas de savoir si l'entrée était invalide. Bien sûr qu'elle était invalide.
La distinction importante est la façon dont la bibliothèque s'est comportée face à une entrée invalide.
Il y a une réelle différence entre :
Le premier est un comportement robuste.
Le second crée un risque au niveau applicatif si un logiciel analyse un DER non fiable et suppose que les défaillances de la bibliothèque restent dans les types d'exceptions attendus.
C'est pourquoi cela a été correctement classé comme une vulnérabilité plutôt que simplement comme un bug de qualité de parseur.
J'ai utilisé deux PoC parce qu'ils démontraient deux parties différentes de la même chaîne de bug.
Le premier PoC a montré que remove_octet_string() acceptait un DER tronqué dont la longueur déclarée dépassait le tampon disponible.
Cela a établi la défaillance de validation centrale :
Le second PoC a montré l'effet aval le plus important :
un DER malformé fourni à SigningKey.from_der() déclenchait de manière déterministe une IndexError interne avant le correctif.
Cela a établi l'impact pertinent pour la sécurité :
C'est un résultat bien plus solide que « le parseur a accepté des octets bizarres ».
Cela montre une défaillance de frontière plus une conséquence opérationnelle réelle.
Le premier PoC prouve la cause racine. Le second PoC prouve l'impact. Cette répartition compte.
Beaucoup de rapports s'arrêtent à :
« ce parseur accepte des données malformées. »
C'est utile, mais pas toujours suffisant pour montrer pourquoi le bug compte.
Dans ce cas, le rapport le plus solide était :
Cela rend l'histoire de sécurité beaucoup plus claire.
Le correctif était minimal et correct.
Le patch a ajouté la même règle de sécurité manquante déjà utilisée dans remove_sequence() :
la longueur déclarée doit tenir dans le tampon disponible
Cette vérification a été appliquée à :
remove_constructed()remove_implicit()remove_octet_string()Une fois ces vérifications de bornes ajoutées, le DER malformé/tronqué était rejeté immédiatement avec :
UnexpectedDER: Length longer than the provided buffer
Et le PoC qui déclenchait auparavant une IndexError n'atteignait plus le chemin d'exception interne.
Il échouait proprement pendant l'analyse, ce qui est exactement ce qui aurait dû se passer depuis le début.
C'est le genre de correctif que l'on veut voir dans une vulnérabilité de parseur :
Pas de refonte. Pas d'ambiguïté. Juste une validation correcte là où elle manquait.
J'ai également ajouté des tests de régression ciblés pour m'assurer que cette classe exacte de DER malformé reste rejetée.
Les nouveaux tests couvrent le rejet des longueurs tronquées pour :
remove_octet_stringremove_constructedremove_implicitC'était important parce que le bug ne concernait pas un chemin d'exécution étrange. Il s'agissait d'une règle de validation qui devait tenir de manière cohérente à travers les helpers DER associés.
Après l'ajout du correctif et des tests, la suite complète passait localement :
python -m pytest -q
# 2018 passed, 5 skipped
Cela compte dans un vrai travail de divulgation.
Un correctif est bien plus solide quand il est accompagné de tests qui verrouillent la frontière.
Ce problème a été raisonnablement classé en sévérité modérée.
L'impact clé ici est la disponibilité / robustesse, pas la confidentialité ou l'intégrité.
La classification de l'avis était :
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:LCela a du sens.
L'affirmation n'est pas qu'un DER malformé permet à un attaquant d'exécuter du code. L'affirmation est qu'un DER malformé pourrait déclencher des exceptions internes inattendues dans un logiciel qui analyse du matériel DER non fiable à l'aide de cette bibliothèque.
C'est un bug d'analyse de type déni de service réel et défendable.
Ce problème a été signalé en privé via les GitHub Security Advisories.
Le rapport comprenait :
IndexError en avalLe mainteneur a validé le problème, demandé que le correctif soit livré avec des tests unitaires, et le correctif coordonné a suivi le flux de travail du fork privé temporaire GHSA.
Lors du traitement de la CVE, GitHub a initialement refusé l'attribution parce que le texte de l'avis semblait décrire plus d'une vulnérabilité. La clarification était simple :
il s'agissait d'une vulnérabilité unique avec une cause racine unique — une validation incorrecte de la longueur DER — et l'IndexError de SigningKey.from_der() était une conséquence aval de cette même acceptation d'entrée malformée, et non un problème distinct pouvant être corrigé indépendamment.
Cette clarification a suffi, et le problème a été attribué :
CVE-2026-33936
La leçon ici n'est pas « le DER est piégeux ». Tout le monde sait déjà que le DER est piégeux.
La vraie leçon est la suivante :
une entrée structurée malformée doit être rejetée au point exact où le parseur sait qu'elle est invalide.
Si vous manquez cette frontière, le code ultérieur finit par opérer sur des hypothèses qui ne sont plus dignes de confiance.
C'est ainsi que les erreurs d'analyse de bas niveau deviennent des problèmes de sécurité.
Ce bug renforce également quelque chose d'important à propos des rapports et du triage :
« Entrée malformée acceptée » était le début de l'histoire. « Entrée malformée acceptée, puis propagée dans un chemin d'exception interne lors de l'analyse des clés » était l'histoire complète.
Cette distinction a aidé à présenter le dossier clairement et correctement.
Cette vulnérabilité ne concernait pas de la crypto exotique.
Il s'agissait d'un parseur qui faisait confiance à une entrée malformée plus longtemps qu'il n'aurait dû.
Un champ de longueur DER tronqué a franchi la frontière, a survécu à la validation alors qu'il aurait dû être rejeté, et a finalement provoqué des plantages lors de l'analyse des clés.
C'est pourquoi cela est devenu CVE-2026-33936.
Corrigé en appliquant des vérifications de bornes de longueur DER appropriées dans les parseurs helpers concernés.
