Python под прицелом: как обнаружить и устранить уязвимость в регулярных выражениях

Python под прицелом: как обнаружить и устранить уязвимость в регулярных выражениях

Когда шаблон поиска становится слабым местом

Регулярные выражения помогают быстро находить в тексте нужные фрагменты, проверять ввод и извлекать данные.

Но неудачно составленный шаблон способен создать серьезную проблему: вместо обработки запроса приложение может надолго занять процессор. Такой сценарий называют ReDoS - отказом в обслуживании, вызванным регулярным выражением.

Риск возникает, когда движку приходится проверять множество возможных вариантов совпадения.

Особенно часто это происходит с шаблонами, где повторяющиеся группы допускают разные способы разбить одну и ту же строку. Если подходящий результат не находится, движок может перебирать варианты снова и снова. В итоге достаточно передать специально сформированную строку, чтобы проверка заняла слишком много времени.

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

Почему Python возвращает разные результаты

В Python поведение регулярных выражений во многом зависит от того, какой движок используется. Модуль `re`, входящий в стандартную библиотеку, применяет алгоритм с возвратом назад: при неудаче он пробует альтернативные варианты.

В некоторых случаях это приводит к чрезмерно долгой проверке. Модуль `regex` поддерживает больше возможностей и позволяет создавать атомарные группы и повторения с запретом возврата.

Может быть интересно: Kaspersky Swift: защита платежной инфраструктуры от целевых атак

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

Однако само наличие таких инструментов не гарантирует защиты. Результат зависит от конкретного выражения, входных данных и версии библиотеки.

Поэтому оптимизированный шаблон важно не только написать, но и проверить на примерах, которые раньше вызывали задержки.

Как проверить выражение на уязвимость

Для первичного анализа можно воспользоваться онлайн-сервисом OWASP ReDoS Checker. Он принимает регулярное выражение и тестовую строку, после чего оценивает время выполнения. Такой способ удобен, если нужно быстро проверить подозрительный шаблон до внесения изменений в код. Практическую проверку можно провести и в Python.

Для этого следует сравнить, сколько времени разные версии выражения тратят на обработку одной и той же строки. Лучше использовать входные данные, специально подобранные для сложного случая: например, длинную последовательность символов, похожую на допустимую, но заканчивающуюся неподходящим знаком. Замеры стоит выполнять с помощью модуля `time` или `timeit`.

Если длительность проверки резко увеличивается при небольшом росте длины входной строки, это повод внимательно изучить выражение. Важно проводить тесты в контролируемой среде: потенциально опасный шаблон способен надолго занять процессор даже при проверке вручную.

Сравнение поведения разных шаблонов

В качестве примера можно взять регулярное выражение `^(a+)+$`. Оно проверяет строки, состоящие из букв `a`, но при определенных входных данных может потребовать от движка перебора большого числа комбинаций.

Чем длиннее цепочка символов и чем неожиданнее ее окончание, тем заметнее может стать задержка. В модуле `regex` можно попробовать ограничить возврат с помощью атомарной группы или специального повторения, например `^(?>a+)+$` либо `^(a++)+$`.

Такие варианты уменьшают число альтернатив, которые движок рассматривает при проверке. Сравнение стоит проводить не только на строке, подходящей под шаблон, но и на той, которая почти подходит.

Именно во втором случае проявляется неэффективный перебор: движок долго ищет совпадение, а затем все равно сообщает, что его нет. Также полезно проверять несколько длин входа и фиксировать время каждого запуска.

Как исправить проблему и закрепить результат

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

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

Для модуля `regex` можно применить атомарные группы или possessive-повторения, если они подходят задаче. Но перед заменой важно убедиться, что новый шаблон сохраняет нужную функциональность и корректно обрабатывает все ожидаемые данные. Еще одна мера - задавать пределы для входных строк и не запускать потенциально тяжелую проверку без ограничений.

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

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

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