Dlaczego złe reguły są problemem?
Ponieważ komputery robią dokładnie to, co im każesz, nawet jeśli nie o to ci chodziło lub jest to całkowicie nierozsądne. Jeśli poprosisz silnik Regex o udowodnienie, że dla pewnych danych wejściowych istnieje lub nie pasuje do danego wzorca, silnik spróbuje to zrobić bez względu na to, ile różnych kombinacji musi zostać przetestowanych.
Oto prosty wzór inspirowany pierwszym przykładem w poście OP:
^((ab)*)+$
Biorąc pod uwagę dane wejściowe:
abababababababababababab
Silnik wyrażeń regularnych próbuje czegoś podobnego (abababababababababababab)
i dopasowanie zostaje znalezione za pierwszym razem.
Ale potem wrzucamy klucz do małpy:
abababababababababababab a
Silnik najpierw spróbuje, (abababababababababababab)
ale to się nie powiedzie z powodu tego dodatkowego a
. Powoduje to katastrofalne śledzenie nawiasów, ponieważ nasz wzorzec (ab)*
, w dobrej wierze, zwolni jedno ze swoich przechwyceń („cofnie się”) i pozwoli zewnętrznemu wzorowi spróbować ponownie. W przypadku naszego silnika wyrażeń regularnych wygląda to mniej więcej tak:
(abababababababababababab)
- Nie
(ababababababababababab)(ab)
- Nie
(abababababababababab)(abab)
- Nie
(abababababababababab)(ab)(ab)
- Nie
(ababababababababab)(ababab)
- Nie
(ababababababababab)(abab)(ab)
- Nie
(ababababababababab)(ab)(abab)
- Nie
(ababababababababab)(ab)(ab)(ab)
- Nie
(abababababababab)(abababab)
- Nie
(abababababababab)(ababab)(ab)
- Nie
(abababababababab)(abab)(abab)
- Nie
(abababababababab)(abab)(ab)(ab)
- Nie
(abababababababab)(ab)(ababab)
- Nie
(abababababababab)(ab)(abab)(ab)
- Nie
(abababababababab)(ab)(ab)(abab)
- Nie
(abababababababab)(ab)(ab)(ab)(ab)
- Nie
(ababababababab)(ababababab)
- Nie
(ababababababab)(abababab)(ab)
- Nie
(ababababababab)(ababab)(abab)
- Nie
(ababababababab)(ababab)(ab)(ab)
- Nie
(ababababababab)(abab)(abab)(ab)
- Nie
(ababababababab)(abab)(ab)(abab)
- Nie
(ababababababab)(abab)(ab)(ab)(ab)
- Nie
(ababababababab)(ab)(abababab)
- Nie
(ababababababab)(ab)(ababab)(ab)
- Nie - Nie - Nie
(ababababababab)(ab)(abab)(abab)
- Nie
(ababababababab)(ab)(abab)(ab)(ab)
- Nie
(ababababababab)(ab)(ab)(ababab)
- Nie
(ababababababab)(ab)(ab)(abab)(ab)
- Nie
(ababababababab)(ab)(ab)(ab)(abab)
- Nie
(ababababababab)(ab)(ab)(ab)(ab)(ab)
- Nie
...
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abababab)
- Nie
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ababab)(ab)
- Nie
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(abab)
- Nie
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(ab)(ab)
- Nie
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ababab)
- Nie
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)(ab)
- Nie
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(abab)
- Nie
(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)(ab)
- Nie - Nie
Liczba możliwych kombinacji skaluje się wykładniczo wraz z długością danych wejściowych i zanim się zorientujesz, silnik wyrażeń regularnych pochłania wszystkie zasoby systemowe, próbując rozwiązać ten problem, aż po wyczerpaniu wszystkich możliwych kombinacji terminów w końcu się poddaje i raporty „Brak dopasowania”. W międzyczasie twój serwer zamienił się w płonący stos stopionego metalu.
Jak rozpoznać złe reguły
W rzeczywistości jest to bardzo trudne. Sam napisałem kilka, chociaż wiem, czym one są i ogólnie, jak ich unikać. Zobacz, jak Regex zajmuje zaskakująco dużo czasu . Zawinięcie wszystkiego, co się da, w grupę atomową może pomóc w zapobieganiu problemowi ze śledzeniem. Zasadniczo mówi silnikowi regex, aby nie wracał do danego wyrażenia - „zablokuj wszystko, co dopasowałeś za pierwszym razem”. Należy jednak pamiętać, że wyrażenia atomowe nie zapobiegają cofaniu się w wyrażeniu, więc ^(?>((ab)*)+)$
nadal są niebezpieczne, ale ^(?>(ab)*)+$
są bezpieczne (dopasują się, (abababababababababababab)
a następnie odmówią porzucenia któregokolwiek z dopasowanych znaków, zapobiegając w ten sposób katastrofalnym cofnięciom).
Niestety, po napisaniu bardzo trudno jest od razu lub szybko znaleźć problematyczne wyrażenie regularne. Ostatecznie rozpoznanie złego wyrażenia regularnego jest jak rozpoznanie każdego innego złego kodu - zajmuje dużo czasu i doświadczenia i / lub pojedynczego katastrofalnego zdarzenia.
Co ciekawe, odkąd ta odpowiedź została napisana po raz pierwszy, zespół z University of Texas w Austin opublikował artykuł opisujący rozwój narzędzia zdolnego do przeprowadzania statycznej analizy wyrażeń regularnych w wyraźnym celu znalezienia tych „złych” wzorców. Narzędzie zostało opracowane do analizy programów Java, ale podejrzewam, że w nadchodzących latach zobaczymy więcej narzędzi opracowanych do analizy i wykrywania problematycznych wzorców w JavaScript i innych językach, zwłaszcza w miarę wzrostu liczby ataków ReDoS .
Statyczne wykrywanie luk w zabezpieczeniach DoS w programach używających wyrażeń regularnych
Valentin Wüstholz, Oswaldo Olivo, Marijn JH Heule i Isil Dillig
The University of Texas at Austin