Skip to content
research

Обманчивая ошибка в Python оказалась всего лишь одним вызовом функции

Недавно исправленная уязвимость в Python была связана не со сложным алгоритмом, а с самым обычным строковым методом. Эта скрытая ошибка создает опасную лазейку, позволяя злоумышленникам обходить фильтры безопасности, заставляя системы доверять вредоносным доменам.

Aki Tanaka
Обманчивая ошибка в Python оказалась всего лишь одним вызовом функции

«Безобидная» строка кода, которая сломала Python

Недавно для Python вышло критическое обновление безопасности для CVE-2026-17084 — уязвимости, скрывавшейся в удивительно банальном фрагменте кода: одном вызове метода str.lower(). Эта, казалось бы, безобидная функция, являющаяся стандартом в бесчисленных скриптах Python, неожиданно стала источником серьезного сбоя.

Ошибка находилась глубоко внутри модуля stringprep в Python, который является краеугольным камнем для обработки интернационализированных доменных имен (IDNA). IDNA — это жизненно важный механизм, позволяющий доменным именам содержать не-ASCII символы, что дает пользователям по всему миру доступ к веб-сайтам с использованием кириллицы или арабского алфавита. stringprep преобразует эти Unicode-домены в формат, совместимый с ASCII, для системы доменных имен (DNS).

stringprep строго придерживается стандарта IDNA 2003, который требует соблюдения правил Unicode 3.2.0 для обработки символов, включая приведение к нижнему регистру (case folding). Однако злополучный вызов lower() обошел этот зафиксированный стандарт, вместо этого используя современную версию Unicode интерпретатора (например, Unicode 17.0). Это тонкое расхождение может превратить идентичные Unicode-доменные имена в различные ASCII-представления — «дифференциал парсера», который может обойти проверки безопасности.

Этот инцидент преподает суровый урок: некоторые из самых опасных уязвимостей скрываются на виду. Обычный, повседневный код, который часто игнорируется из-за своей кажущейся простоты, может таить в себе серьезные риски безопасности, заставая врасплох даже опытных разработчиков.

Когда стандарты Unicode расходятся

StringPrep, стандарт (RFC 3454), лежащий в основе IDNA 2003, предписывает явные правила приведения регистра. Эти правила не являются динамическими; они строго зафиксированы на Unicode 3.2.0. Это статическое требование обеспечивает согласованное преобразование символов для интернационализированных доменных имен, предотвращая неоднозначность в различных реализациях, опирающихся на стандарт IDNA.

Метод str.lower() в Python критически отклоняется от этого требования. Вместо статического Unicode 3.2.0 он использует современную, развивающуюся версию Unicode, встроенную в интерпретатор. Например, текущая установка Python может использовать Unicode 17.0. Это означает, что поведение приведения регистра str.lower() меняется с каждым новым стандартом Unicode, что прямо противоречит фундаментальному статическому требованию StringPrep.

Это несоответствие создает своеобразную и опасную проблему: идентичное Unicode-доменное имя может разрешаться в два разных ASCII-домена. Одна версия Python может обрабатывать "example.com" иначе, чем другая, в зависимости от внутренней базы данных Unicode. Такие «дифференциалы парсера» создают значительные риски безопасности, позволяя злоумышленникам обходить проверки валидации или авторизации, представляя якобы доверенное имя хоста, которое разные компоненты системы интерпретируют по-разному.

По иронии судьбы, Python уже поставляется со специальной базой данных Unicode 3.2 именно для этой цели. Модуль stringprep даже импортирует эти зафиксированные данные в начале файла, признавая их необходимость. Однако единственный, казалось бы, безобидный вызов .lower() полностью обошел этот специализированный инструмент, сделав его бесполезным и внедрив критическую уязвимость CVE-2026-17084 из-за незаметного дрейфа стандартов.

Опасность одного отличающегося символа

Это тонкое различие в приведении регистра напрямую приводит к критической уязвимости в реальных условиях. Одно и то же доменное имя в формате Unicode, предназначенное для Internationalized Domain Names (IDNA), может преобразовываться в два совершенно разных домена ASCII (Punycode). Результат зависит исключительно от того, какая версия Python обрабатывает строку, что создает расхождения в интерпретации того, что должно быть единым идентификатором.

Такое расхождение создает опасный parser differential (дифференциал парсера). Это происходит, когда различные компоненты цепочки безопасности — например, межсетевой экран, приложение на Python и серверная служба — интерпретируют одну и ту же входную строку как принципиально разные имена хостов. Эта несогласованность — не просто редкий случай; она активно подрывает границы доверия.

Злоумышленники используют этот дифференциал для обхода надежных мер безопасности. Представьте, что вы создаете домен Unicode, который периметровый межсетевой экран правильно идентифицирует как ненадежный и блокирует доступ. Однако, когда тот же домен достигает уязвимого приложения на Python, его ошибочная логика stringprep нормализует строку в доверенный внутренний домен, полностью обходя SSRF filters, списки разрешенных адресов или проверки подлинности. Дополнительные технические подробности об этой конкретной уязвимости можно найти в [oss-sec: CPython [CVE-2026-17084] StringPrep algorithm considered Unicode codepoint attributes outside Unicode 3.2.0](https://seclists.org/oss-sec/2026/q3/104).

Нравится статья? Получайте такие каждое утро на почту.

одно письмо в день · отписка в два клика · без сторонних трекеров

Исправление и предупреждение для разработчиков

Устранение CVE-2026-17084 потребовало тщательного ручного вмешательства. Разработчикам предстояло выявить каждую code point (кодовую точку), где поведение современного метода str.lower() в Python расходилось с зафиксированным стандартом Unicode 3.2.0, который явно требуется спецификацией StringPrep. Они кропотливо собрали эти расхождения в новую таблицу исключений, внедрив их непосредственно в модуль stringprep. Это заставило модуль игнорировать стандартное современное приведение регистра для данных конкретных символов, обеспечивая строгое соблюдение старых обязательных правил.

Эта уязвимость, свидетельствующая о скрытых сложностях в казалось бы простых операциях, далеко не единичный случай. Запутанная среда свойств символов Unicode, форм нормализации и эволюции стандартов Internationalized Domain Names (IDNA) неоднократно становилась благодатной почвой для уязвимостей безопасности. Расхождения между различными версиями Unicode или спецификациями IDNA, либо их реализациями, создают «дифференциалы парсеров», которые могут быть использованы для обхода защиты или спуфинга.

Подобные повторяющиеся проблемы служат серьезным предупреждением для всех разработчиков. Никогда не предполагайте, что стандартная библиотечная функция, даже такая фундаментальная, как str.lower(), работает универсально или согласованно во всех контекстах. При создании систем, строго придерживающихся технической спецификации — например, RFC 3454 для IDNA 2003 — становится крайне важным тщательно проверять, чтобы каждый базовый вызов функции соответствовал точной версии и правилам этой спецификации. Опора на поведение библиотеки по умолчанию («последняя версия») вместо указанного исторического стандарта провоцирует критические недостатки безопасности.

Часто задаваемые вопросы

Что такое уязвимость Python CVE-2026-17084?

Это недостаток безопасности в модуле stringprep языка Python. Модуль некорректно использовал метод .lower(), который следует современным правилам Unicode, вместо зафиксированного стандарта Unicode 3.2, требуемого для обработки интернационализированных доменных имен, что создает риск для безопасности.

Как простой вызов .lower() может представлять угрозу безопасности?

Риск возникает из-за контекста. Стандарт IDNA 2003 строго требует использования правил Unicode 3.2 для приведения к нижнему регистру. Использование метода .lower(), который применяет более новую версию Unicode интерпретатора, привело к несоответствию, которое можно было использовать для обхода фильтров безопасности.

Что такое атака типа «parser differential»?

Атака типа «parser differential» использует ситуации, когда две разные системы (или даже две версии одной и той же системы) интерпретируют одни и те же данные по-разному. В данном случае вредоносный домен мог быть распознан одной системой как доверенный, а другой — нет, что позволяло обойти защиту.

Как была исправлена эта ошибка в Python?

Исправление заключалось в выявлении всех символов, для которых современное поведение приведения к нижнему регистру отличается от Unicode 3.2, и добавлении их в качестве явных исключений в модуль stringprep, что заставило его соответствовать более старому, обязательному стандарту.

Found this useful? Share it.

For builders

Want Stork to write one of these about your product?

Send us a URL. We use the product, form a view, and publish what we actually think — in 8 languages, labeled Sponsored, with no copy approval on your side. That last part is what makes it worth quoting.

See how it works$500 · AI tools & software only

Для билдеров

Эта страница работает на чужой инструмент.

Её читают AI-агенты. На неё приходят покупатели. Она отвечает на восьми языках и через MCP. У вашего инструмента может быть такая же — в эфире за 24 часа.