
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