Czy istnieje sposób umieszczenia złośliwego kodu w wyrażeniu regularnym?

138

Chcę dodać możliwość wyszukiwania wyrażeń regularnych do mojej publicznej strony internetowej. Czy oprócz kodowania danych wyjściowych w formacie HTML muszę robić cokolwiek, aby zabezpieczyć się przed wprowadzaniem danych przez złośliwego użytkownika?

Wyszukiwania Google są zalewane przez ludzi rozwiązujących odwrotny problem - używając wyrażeń regularnych do wykrywania złośliwych danych wejściowych - co mnie nie interesuje. W moim scenariuszu dane wejściowe użytkownika wyrażeniami regularnymi.

Będę używać biblioteki Regex w .NET (C #).

MatthewMartin
źródło
4
Może to zależeć od używanego języka i / lub biblioteki wyrażeń regularnych.
aschepler
Więcej materiałów do czytania: ReDoS na OWASP , ReDoS na Wikipedii
joeytwiddle

Odpowiedzi:

216

Obawy związane z odmową usługi

Najczęstszym problemem związanym z wyrażeniami regularnymi jest atak typu `` odmowa usługi '' za pośrednictwem patologicznych wzorców, które są wykładnicze - lub nawet super-wykładnicze! - i wydaje się, że rozwiązanie zajmuje wieki. Mogą się one pojawiać tylko na określonych danych wejściowych, ale ogólnie można je utworzyć, w przypadku których nie ma to znaczenia.

Które z nich będą zależeć w pewnym stopniu od tego, jak inteligentny jest kompilator wyrażeń regularnych, którego używasz, ponieważ niektóre z nich można wykryć w czasie kompilacji. Kompilatory Regex, które implementują rekursję, zwykle mają wbudowany licznik głębokości rekursji do sprawdzania braku progresji.

Doskonały artykuł Russa Coxa z 2007 r. Na temat dopasowywania wyrażeń regularnych może być prosty i szybki (ale jest powolny w Javie, Perlu, PHP, Pythonie, Ruby, ...) mówi o sposobach, w jakie większość nowoczesnych NFA, które wszystkie wydają się wywodzić z kodu Henry'ego Spencera , ulegają znacznemu pogorszeniu wydajności, ale NFA typu Thompson nie ma takich problemów.

Jeśli przyznajesz tylko wzorce, które można rozwiązać za pomocą DFA, możesz je skompilować jako takie, a będą działać szybciej, prawdopodobnie znacznie szybciej. Jednak zrobienie tego wymaga czasu . Dokument Cox wspomina o tym podejściu i związanych z nim kwestiach. Wszystko sprowadza się do klasycznego kompromisu czasowo-przestrzennego.

W przypadku DFA spędzasz więcej czasu na jego budowaniu (i przydzielaniu większej liczby stanów), podczas gdy w przypadku NFA spędzasz więcej czasu na jego wykonywaniu, ponieważ może to być wiele stanów w tym samym czasie, a cofanie może zjadać twój lunch - i procesor.

Rozwiązania typu Denial-of-Service

Prawdopodobnie najrozsądniejszym sposobem rozwiązania tych wzorców, które są na przegranym końcu wyścigu ze śmiercią wszechświata, jest owinięcie ich zegarem, który skutecznie wyznacza maksymalny dozwolony czas na ich wykonanie. Zwykle będzie to dużo, dużo mniej niż domyślny limit czasu zapewniany przez większość serwerów HTTP.

Istnieją różne sposoby ich implementacji, począwszy od prostego alarm(N)na poziomie C, poprzez pewnego rodzaju try {}blokowanie wychwytywania wyjątków typu alarmowego, aż do odrodzenia się nowego wątku, który został specjalnie utworzony z wbudowanym ograniczeniem czasowym.

Objaśnienia kodu

W językach regex, które dopuszczają objaśnienia kodu, powinien być zapewniony mechanizm zezwalania lub blokowania ich w ciągu, który zamierzasz skompilować . Nawet jeśli objaśnienia kodu mają kodować tylko w języku, którego używasz, powinieneś je ograniczyć; nie muszą mieć możliwości wywołania zewnętrznego kodu, chociaż jeśli mogą, masz znacznie większe problemy.

Na przykład w Perlu nie można mieć objaśnień kodu w wyrażeniach regularnych utworzonych z interpolacji ciągów (tak jak byłyby one kompilowane w czasie wykonywania), chyba że use re "eval";w bieżącym zakresie jest aktywna specjalna pragma o zasięgu leksykalnym .

W ten sposób nikt nie może wkraść się do wywołania kodu, aby uruchomić programy systemowe, takie jak rm -rf *na przykład. Ponieważ objaśnienia kodu są tak wrażliwe na bezpieczeństwo, Perl domyślnie wyłącza je na wszystkich interpolowanych ciągach i musisz zrobić wszystko, aby je ponownie włączyć.

Zdefiniowane przez użytkownika \ P {roperties}

Pozostaje jeszcze jedna kwestia bezpieczeństwa wrażliwych związane z właściwościami Unicode stylu - jak \pM, \p{Pd}, \p{Pattern_Syntax}, lub \p{Script=Greek}- że może istnieć w niektórych kompilatorów regex, że wsparcie to notacji.

Problem polega na tym, że w niektórych z nich zestaw możliwych właściwości jest rozszerzalny przez użytkownika. Oznacza to, że możesz mieć właściwości niestandardowe, które są rzeczywistymi wywołaniami kodu do nazwanych funkcji w określonej przestrzeni nazw, takich jak \p{GoodChars}lub \p{Class::Good_Characters}. Warto przyjrzeć się temu, jak Twój język obsługuje te elementy.

Piaskownica

W Perlu przedział piaskownicy za pośrednictwem Safemodułu dawałby kontrolę nad widocznością przestrzeni nazw. Inne języki oferują podobne technologie piaskownicy. Jeśli takie urządzenia są dostępne, warto się do nich przyjrzeć, ponieważ są one specjalnie zaprojektowane do ograniczonego wykonywania niezaufanego kodu.

tchrist
źródło
4
Konwersja NFA-> DFA może powodować wykładniczą eksplozję stanu, zamieniając czasowy DoS w przestrzenny DoS, a także koszt czasu generowania wykładniczej liczby stanów.
Barry Kelly,
ale prawdopodobnie nie będzie potrzebował wszystkich możliwości wyrażeń regularnych, co myślisz o ograniczaniu mocy wyrażeń regularnych, takich jak Google: google.com/intl/en/help/faq_codesearch.html#regexp
systemsfault
1
@Barry Całkiem dobrze. Myślałem o strategii Russa Coxa opisanej w jednym z jego artykułów, polegającej na stopniowym kompilowaniu części NFA w równoważny DFA, ale wyrzucaniu go, jeśli stał się zbyt duży. Ale w DFA nie ma srebrnej kuli, nawet jeśli Thompson udowodnił, że jest to odpowiednik NFA, ponieważ w pewnym momencie musisz zapłacić dudziarzowi. Czas spędzony na błaganiu systemu operacyjnego o więcej miejsca i związane z tym koszty przygotowania tabeli stronicowania mogą czasami przesunąć skalę równoważenia dalej w drugą stronę i sprawić, że konwersja z czasu na przestrzeń będzie mniej atrakcyjna niż byłaby.
tchrist
20

Dodając do doskonałej odpowiedzi tchrista: ten sam Russ Cox, który napisał stronę „Wyrażenia regularne”, również opublikował kod! re2 to biblioteka C ++, która gwarantuje O (length_of_regex) czas wykonywania i konfigurowalny limit wykorzystania pamięci. Jest używany w Google, dzięki czemu możesz wpisać wyrażenie regularne w wyszukiwarce kodu Google - co oznacza, że ​​zostało przetestowane w walce.

Brian Bloniarz
źródło
2
Rzeczywiście tak. Możesz zamienić re2 na silnik wyrażeń regularnych Perla za pomocą modułu i użyje on re2, jeśli to możliwe, i Perla, jeśli nie. Działa całkiem dobrze.
tchrist
6

Będziesz chciał przeczytać ten artykuł:

Niezabezpieczone przełączanie kontekstu: inokulowanie wyrażeń regularnych w celu zapewnienia przetrwania Artykuł jest bardziej o tym, co może się nie udać w silnikach wyrażeń regularnych (np. PCRE), ale może pomóc ci zrozumieć, z czym masz do czynienia.

Bruce Ediger
źródło
1
Oto poradnik bezpieczeństwa dotyczący kodu GNU libc regcomp (3): securityreason.com/achievement_securityalert/93 Jak na czas! Przynajmniej pod Linuksem luka jest łatwa do zademonstrowania: grep -E ". * {10,} {10,} {10,} {10,} {10,}"
Bruce Ediger,
5

Musisz martwić się nie tylko o samo dopasowanie, ale także o to, jak to zrobić. Na przykład, jeśli dane wejściowe przechodzą przez jakąś fazę ewaluacji lub podstawianie poleceń w drodze do silnika wyrażeń regularnych, może istnieć kod, który jest wykonywany wewnątrz wzorca. Lub, jeśli twoja składnia wyrażeń regularnych pozwala na osadzone polecenia, również musisz być tego ostrożny. Ponieważ nie podałeś języka w swoim pytaniu, trudno powiedzieć z całą pewnością, jakie są wszystkie konsekwencje dla bezpieczeństwa.

Bryan Oakley
źródło
1

Dobrym sposobem na przetestowanie RegEx pod kątem problemów z bezpieczeństwem (przynajmniej dla Windows) jest narzędzie do fuzzingu SDL RegEx wydane niedawno przez Microsoft. Może to pomóc uniknąć patologicznie złej konstrukcji wyrażeń RegEx.

RandomNickName42
źródło