
Уязвимость типа «Отказ в обслуживании» в 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 остается отклоненным.
Новые тесты покрывают отклонение усеченной длины для:
remove_octet_stringremove_constructedremove_implicitЭто было важно, потому что ошибка была не в одном странном пути выполнения. Она заключалась в правиле валидации, которое должно было единообразно соблюдаться во всех связанных вспомогательных функциях DER.
После добавления исправления и тестов полный набор тестов локально прошел:
python -m pytest -q
# 2018 passed, 5 skipped
Это важно в реальной работе по раскрытию уязвимостей.
Исправление гораздо убедительнее, когда оно сопровождается тестами, которые фиксируют границу.
Эта проблема была обоснованно классифицирована как умеренная (Moderate).
Ключевое влияние здесь — доступность/устойчивость, а не конфиденциальность или целостность.
Классификация в уведомлении была следующей:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:LЭто логично.
Утверждение не в том, что некорректный DER позволяет атакующему выполнить код. Утверждение в том, что некорректный DER может вызывать непредвиденные внутренние исключения в программном обеспечении, которое разбирает непроверенный DER-материал с помощью этой библиотеки.
Это реальная и обоснованная ошибка разбора DoS-типа.
Эта проблема была сообщена приватно через GitHub Security Advisories.
Отчет включал:
IndexErrorМейнтейнер подтвердил проблему, запросил, чтобы исправление вышло вместе с модульными тестами, и скоординированное исправление прошло через временный приватный форк-процесс GHSA.
В ходе обработки CVE GitHub первоначально отказал в назначении, поскольку текст уведомления выглядел так, будто мог описывать более одной уязвимости. Разъяснение было простым:
это была одна уязвимость с одной корневой причиной — некорректная валидация длины DER, — а IndexError в SigningKey.from_der() был нижестоящим следствием того же самого принятия некорректного ввода, а не отдельной независимо исправимой проблемой.
Этого разъяснения оказалось достаточно, и проблеме был присвоен идентификатор:
CVE-2026-33936
Урок здесь не в том, что «DER — сложная штука».
Все и так знают, что DER сложен.
Настоящий урок таков:
некорректно сформированный структурированный ввод должен отклоняться ровно в той точке, где парсер знает, что он невалиден.
Если вы упустите эту границу, последующий код в конечном счете начнет работать на основе предположений, которым больше нельзя доверять.
Именно так низкоуровневые ошибки разбора становятся проблемами безопасности.
Эта ошибка также подтверждает нечто важное о написании отчетов (writeups) и триаже:
«Некорректный ввод принят» — это начало истории.
«Некорректный ввод принят, а затем распространен на путь внутреннего исключения при разборе ключа» — это полная история.
Это различие помогло изложить аргументацию ясно и корректно.
Эта уязвимость была не про экзотическую криптографию.
Она была про парсер, который доверял некорректному вводу дольше, чем следовало.
Усеченное поле длины DER пересекло границу, пережило валидацию, когда должно было быть отклонено, и в конечном счете вызвало аварийное поведение при разборе ключа.
Именно поэтому это стало CVE-2026-33936.
Исправлено путем внедрения надлежащих проверок границ длины DER в затронутых вспомогательных функциях разбора.
