
Уязвимость типа «Отказ в обслуживании» в ecdsa (PyPI)
Уязвимость типа «Отказ в обслуживании» в ecdsa (PyPI)
Я выявил и ответственно раскрыл уязвимость умеренной степени серьезности (Moderate) в python-ecdsa — широко используемой криптографической библиотеке Python с 47,8 млн загрузок за последний месяц.
Я нашел эту проблему при анализе python-ecdsa, задавшись очень конкретным вопросом:
Что произойдет, если некорректный DER лжет о своей длине, а парсер доверяет ему слишком долго?
В данном случае этот вопрос привел к реальной ошибке.
Вспомогательные функции разбора DER в ecdsa.der принимали усеченные данные в тех случаях, когда закодированная длина заявляла больше байт, чем фактически присутствовало. Такой некорректный ввод должен был отклоняться немедленно. Вместо этого он мог пройти глубже в логику разбора и в конечном счете вызвать внутреннее исключение IndexError при разборе ключа.
Эта проблема стала CVE-2026-33936.
Проект: python-ecdsa на GitHub
Пакет: 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 — широко используемая библиотека Python для криптографии на эллиптических кривых.
Помимо прочего, она обеспечивает:
Это означает, что ее код разбора находится непосредственно на границе безопасности.
Когда библиотека принимает внешние ключевые материалы или структурированные бинарные данные, корректность — это не просто вопрос качества. Это свойство безопасности.
Если некорректный ввод принимается тогда, когда он должен быть отклонен, нижестоящий код начинает строить предположения на основе невалидного состояния. Именно здесь ошибки перестают быть «просто ошибками разбора» и начинают становиться уязвимостями.
Разбор DER — одна из тех областей, где небольшие ошибки валидации могут иметь непропорционально серьезные последствия.
Класс ошибки прост:
Это именно тот вид нарушения границы, который стоит проверять при аудите безопасности.
Я не искал здесь странного криптографического поведения. Я искал ошибки доверия в обработке структурированного ввода.
Это было правильное место для поиска.
Корневая проблема заключалась в некорректной валидации полей длины DER при разборе некорректно сформированного или усеченного ввода.
В частности, ecdsa.der.remove_octet_string() принимала ввод, в котором объявленная длина DER превышала фактическое количество доступных в буфере байт.
То есть вместо отклонения некорректного DER, например:
40963вспомогательная функция принимала его и возвращала усеченное содержимое как валидное.
Это уже ошибка.
Но более сильное влияние проявилось ниже по цепочке.
Поскольку некорректный ввод был принят, а не отклонен на границе, SigningKey.from_der() впоследствии могла достичь внутреннего пути исключения и возбудить:
IndexError: index out of bounds on dimension 1
Это важно, потому что это не тот вид отказа, который вызывающий код ожидает от некорректного ввода.
Правильное поведение — чистое отклонение при разборе, например UnexpectedDER или ValueError.
Таким образом, уязвимость заключалась не в самом по себе «существовании IndexError».
Реальная уязвимость заключалась в следующем:
Это одна цепочка ошибок, а не две не связанные между собой проблемы.
Отклонение некорректного ввода парсером — не косметическое улучшение. Это часть модели безопасности.
Здесь важное различие не в том, был ли ввод невалидным. Разумеется, он был невалидным.
Важное различие в том, как библиотека повела себя перед лицом невалидного ввода.
Существует реальная разница между:
Первое — это надежное поведение.
Второе создает риск на уровне приложения, если программное обеспечение разбирает непроверенный DER и предполагает, что сбои библиотеки остаются в пределах ожидаемых типов исключений.
Именно поэтому данная проблема была корректно классифицирована как уязвимость, а не просто как ошибка качества парсера.
Я использовал два PoC, поскольку они демонстрировали две разные части одной и той же цепочки ошибок.
Первый PoC показал, что remove_octet_string() принимала усеченный DER, объявленная длина которого превышала доступный буфер.
Это установило основной сбой валидации:
Второй PoC показал более важный нижестоящий эффект:
некорректно сформированный DER, переданный в SigningKey.from_der(), детерминированно вызывал внутреннее IndexError до исправления.
Это установило значимое для безопасности влияние:
Это гораздо более сильный результат, чем «парсер принял странные байты». Он показывает нарушение границы плюс реальные операционные последствия.
Первый PoC доказывает корневую причину.
Второй PoC доказывает влияние.
Такое разделение важно.
Многие отчеты останавливаются на:
«этот парсер принимает некорректные данные».
Это полезно, но не всегда достаточно, чтобы показать, почему ошибка важна.
В данном случае более сильный отчет был таким:
Это делает историю безопасности гораздо более ясной.
Исправление было минимальным и корректным.
Патч добавил то же недостающее правило безопасности, которое уже использовалось в remove_sequence():
объявленная длина должна помещаться в доступный буфер
Эта проверка была применена к:
remove_constructed()remove_implicit()remove_octet_string()После добавления этих проверок границ некорректно сформированный/усеченный DER отклонялся немедленно с:
UnexpectedDER: Length longer than the provided buffer
А PoC, который ранее вызывал IndexError, больше не достигал внутреннего пути исключения.
Он завершался сбоем чисто во время разбора — именно так и должно было быть с самого начала.
Это тот тип исправления, который вы хотите видеть в уязвимости парсера:
Никакого редизайна. Никакой двусмысленности. Просто корректная валидация там, где она отсутствовала.
Я также добавил целенаправленные регрессионные тесты, чтобы гарантировать, что именно этот класс некорректного DER остается отклоненным.