Чому злі регулярні вирази є проблемою?
Оскільки комп’ютери роблять саме те, що ви наказали їм робити, навіть якщо це не те, що ви мали на увазі, або зовсім нерозумно. Якщо ви попросите механізм регулярних виразів довести, що для певного даного вводу існує або не відповідає даному шаблону, тоді движок спробує це зробити, незалежно від того, скільки різних комбінацій потрібно перевірити.
Ось простий зразок, натхненний першим прикладом у дописі ОП:
^((ab)*)+$
Враховуючи введені дані:
абабабабабабабабабабабаб
Двигун регулярного виразу намагається щось подібне (abababababababababababab) і з першої спроби знайдено відповідність.
Але тоді ми кидаємо гайковий ключ:
abababababababababababab a
Спочатку двигун спробує, (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 у програмах, що використовують регулярні вирази
Валентин Вюстхольц, Освальдо Оливо, Марійн Й.Х. Хейле та Ісіль Ділліг, Техаський
університет в Остіні