Почему плохие регулярные выражения - проблема?
Потому что компьютеры делают именно то, что вы им говорите, даже если это не то, что вы имели в виду или это совершенно необоснованно. Если вы попросите движок Regex доказать, что для некоторого заданного ввода существует или нет совпадение с заданным шаблоном, то движок попытается сделать это независимо от того, сколько различных комбинаций необходимо проверить.
Вот простой шаблон, вдохновленный первым примером в сообщении OP:
^((ab)*)+$
Учитывая ввод:
ababababababababababababab
Механизм регулярных выражений пытается что-то вроде, (abababababababababababab)и совпадение обнаруживается с первой попытки.
Но затем мы бросаем гаечный ключ:
abababababababababababab а
Двигатель сначала попытается, (abababababababababababab)но из-за этого ничего не получится a. Это приводит к катастрофическому разрыву скобок, потому что наш шаблон(ab)* , демонстрируя добросовестность, выпустит один из своих захватов (он будет "возвращаться") и позволит внешнему шаблону повторить попытку. Для нашего движка регулярных выражений это выглядит примерно так:
(abababababababababababab)- Нет
(ababababababababababab)(ab)- Нет
(abababababababababab)(abab)- Нет
(abababababababababab)(ab)(ab)- Нет
(ababababababababab)(ababab)- Нет
(ababababababababab)(abab)(ab)- Нет
(ababababababababab)(ab)(abab)- Нет
(ababababababababab)(ab)(ab)(ab)- Нет
(abababababababab)(abababab)- Нет
(abababababababab)(ababab)(ab)- Нет
(abababababababab)(abab)(abab)- Нет
(abababababababab)(abab)(ab)(ab)- Нет
(abababababababab)(ab)(ababab)- Нет
(abababababababab)(ab)(abab)(ab)- Нет
(abababababababab)(ab)(ab)(abab)- Нет
(abababababababab)(ab)(ab)(ab)(ab)- Нет
(ababababababab)(ababababab)- Нет
(ababababababab)(abababab)(ab)- Нет
(ababababababab)(ababab)(abab)- Нет
(ababababababab)(ababab)(ab)(ab)- Нет
(ababababababab)(abab)(abab)(ab)- Нет
(ababababababab)(abab)(ab)(abab)- Нет
(ababababababab)(abab)(ab)(ab)(ab)- Нет
(ababababababab)(ab)(abababab)- Нет
(ababababababab)(ab)(ababab)(ab)- Нет
(ababababababab)(ab)(abab)(abab)- Нет
(ababababababab)(ab)(abab)(ab)(ab)- Нет
(ababababababab)(ab)(ab)(ababab)- Нет
(ababababababab)(ab)(ab)(abab)(ab)- Нет
(ababababababab)(ab)(ab)(ab)(abab)- Нет
(ababababababab)(ab)(ab)(ab)(ab)(ab)- Нет
...
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abababab) - Нет
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ababab)(ab)- Нет
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(abab)- Нет
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(ab)(ab)- Нет
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ababab)- Нет
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(ab)- Нет
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)- Нет
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)- Нет
Количество возможных комбинаций экспоненциально масштабируется с длиной ввода, и, прежде чем вы это узнаете, механизм регулярных выражений съедает все ваши системные ресурсы, пытаясь решить эту проблему, пока, исчерпав все возможные комбинации терминов, он, наконец, не сдается и сообщает «Нет совпадения». Тем временем ваш сервер превратился в горящую кучу расплавленного металла.
Как распознать злые регулярные выражения
На самом деле это очень сложно. Я сам написал пару, хотя знаю, что это такое и как их избежать. Посмотрите, как Regex занимает удивительно много времени . Обертывание всего, что вы можете, в атомарной группе может помочь предотвратить проблему с возвратом. По сути, он говорит механизму регулярных выражений не пересматривать данное выражение - «заблокируйте все, что вы нашли с первой попытки». Обратите внимание, однако, что атомарные выражения не предотвращают возврат в пределах выражения, поэтому ^(?>((ab)*)+)$все еще опасно, но ^(?>(ab)*)+$безопасно (оно будет соответствовать(abababababababababababab) а затем откажется отдавать любой из совпадающих символов, тем самым предотвращая катастрофический возврат).
К сожалению, после написания очень сложно сразу или быстро найти проблемное регулярное выражение. В конце концов, распознавание плохого регулярного выражения похоже на распознавание любого другого плохого кода - это требует много времени и опыта и / или одного катастрофического события.
Интересно, что с тех пор, как этот ответ был впервые написан, команда Техасского университета в Остине опубликовала статью, описывающую разработку инструмента, способного выполнять статический анализ регулярных выражений с явной целью поиска этих «злых» шаблонов. Этот инструмент был разработан для анализа Java-программ, но я подозреваю, что в ближайшие годы мы увидим больше инструментов, разработанных для анализа и обнаружения проблемных шаблонов в JavaScript и других языках, особенно с учетом того, что частота атак ReDoS продолжает расти .
Статическое обнаружение уязвимостей DoS в программах, использующих регулярные выражения
Валентин Вюстхольц, Освальдо Оливо, Марин Дж. Хойле и Исил Диллиг
Техасский университет в Остине