Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
Problemy z XXE (XML eXternal Entity)
Podatności związane z XXE (XML eXternal Entity) ostatnimi czasy zdobywają coraz większą „popularność” w aplikacjach internetowych. Najczęściej wykorzystanie XXE jest sposobem na wykonanie ataku Path Traversal, czasem może jednak dawać większe możliwości. Przyjrzyjmy się tematowi z bliska.
Kilka słów o XML-u
XML jest językiem formalnym, którego celem jest reprezentowanie danych w strukturalizowany sposób. Typowy dokument XML może wyglądać następująco:
<?xml version="1.0" encoding="UTF-8"?> <strony> <strona id="sekurak"> <nazwa>Sekurak</nazwa> <url>http://www.sekurak.pl/</url> <komentarz>I <3 Sekurak!</komentarz> </strona> <strona id="owasp"> <nazwa>OWASP</nazwa> <url>https://owasp.org/</url> <komentarz /> </strona> </strony>
W pierwszej linii mamy deklarację XML. Obowiązkowy jest jedynie atrybut version, ale najczęściej spotyka się ją także z parametrem encoding. Następnie zdefiniowany zostaje element główny (root element), który składa się z dwóch elementów <strona>. Elementy <strona> zawierają po jednym atrybucie (id) oraz trzech elementach-dzieciach: <nazwa>, <url> oraz <komentarz>.
Przyjrzyjmy się dokładniej elementowi: <komentarz>I <3 Sekurak!</komentarz> w którym pojawia się encja <. De facto encje możemy traktować jako operację w rodzaju „znajdź i zamień”: podczas interpretacji wartości elementu <komentarz>, encja < zostanie zamieniona na znak <. W powyższym przykładzie encja < była obowiązkowa ze względu na specjalne znaczenie znaku <. Gdyby wpisać I <3 Sekurak, otrzymalibyśmy błąd parsera, ponieważ potraktowałby on <3 jako otwarcie tagu.
Parsujemy XML
Weźmy bardzo prosty skrypt w PHP do parsowania XML-a z powyższego przykładu.
<?php $xml = file_get_contents('test.xml'); $strony = new SimpleXMLElement($xml); foreach($strony->strona->komentarz as $komentarz) echo "$komentarz\n";
Kod realizuje następujące operacje:
- Czytamy plik test.xml.
- Interpretujemy jego zawartość jako XML.
- Wypisujemy na wyjściu wartości wszystkich elementów <komentarz>.
Zobaczmy co się stanie, jeśli w pliku test.xml umieścimy nasz przykład.
Zgodnie z oczekiwaniem, został wyświetlony tekst I <3 Sekurak – encja < została zamieniona na znak <.
Oczywiście standard XML przewiduje możliwość definiowana własnych encji; można to zrobić w elemencie <!DOCTYPE>. Poniżej modyfikujemy przykład, dodając encję &shp; rozwijaną do „Sekurak Hacking Party” (dla zwięzłości, usunąłem też rekord owasp).
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE strony [ <!ENTITY shp "Sekurak Hacking Party"> ] > <strony> <strona id="sekurak"> <nazwa>Sekurak</nazwa> <url>http://www.sekurak.pl/</url> <komentarz>I <3 Sekurak! &shp;</komentarz> </strona> </strony>
Dodałem wspomnianą encję oraz odniesienie do niej w tagu <komentarz>. Zobaczmy jak teraz zachowa się skrypt.
Świetnie! Encja &shp; została zamieniona na „Sekurak Hacking Party”. Jak widać, definiując takie encje możemy sobie oszczędzić pisania ;).
Dopiero teraz zacznie się jednak robić ciekawie. Encje XML-owe mogą być także importowane z plików. Założeniem tego mechanizmu była możliwość utworzenia pewnych definicji encji, które mogą być współdzielone między wieloma dokumentami, bez potrzeby definiowania ich w każdym z osobna. Spróbujmy jednak trochę nadużyć tej funkcji…
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE strony [ <!ENTITY shp SYSTEM "/etc/passwd"> ] > <strony> <strona id="sekurak"> <nazwa>Sekurak</nazwa> <url>http://www.sekurak.pl/</url> <komentarz>I <3 Sekurak! &shp;</komentarz> </strona> </strony>
Zmieniłem definicję encji &shp; – dołożyłem słowo SYSTEM (to informacja, że encja ma być pobierana z pliku), a jako plik wskazałem /etc/passwd. Zobaczmy co się stanie.
Voila! Wykorzystaliśmy niepozorną interpretację plików XML do przeprowadzenia ataku Path traversal.
Atak ma jednak ograniczenia, otóż dołączane pliki:
- muszą być poprawne w kontekście gramatyki XML,
- nie mogą zawierać bajtu zerowego (\x00).
Zatem czytanie plików binarnych w ogólnym przypadku odpada. Istnieją różne możliwości obchodzenia tych ograniczeń – są one jednak bardzo mocno zależne od używanej technologii i biblioteki.
Pokażę jak w PHP można obejść problem z użyciem wrappera php://filter. Wpierw jednak sprawdźmy jak zachowa się aplikacja w przypadku próby dołączenia binarnego pliku /bin/bash.
Próba nieudana. Przygotujmy więc filtr, który enkoduje wyjście z czytanego pliku do base64. Wygląda następująco: php://filter/convert.base64-encode/resource=/bin/bash
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE strony [ <!ENTITY shp SYSTEM "php://filter/convert.base64-encode/resource=/bin/bash"> ] > <strony> <strona id="sekurak"> <nazwa>Sekurak</nazwa> <url>http://www.sekurak.pl/</url> <komentarz>I <3 Sekurak! &shp;</komentarz> </strona> </strony>
I zobaczmy co się stanie teraz:
Tym razem bez błędów – możemy więc czytać dowolny plik na dysku.
Warto zdawać sobie sprawę, że XXE umożliwia nie tylko czytanie plików lokalnych; zwykle da się również wykonywać zapytania http(s), ftp, czasem również inne protokoły. Stąd prosta droga do skanowania sieci lokalnej (np. enumeracji hostów) z użyciem tzw. Server Side Request Forgery. Możliwości jakie daje atak XXE są silnie zależne od używanych technologii, np.
- W niektórych bibliotekach Javy, odwołanie się do nazwy katalogu (np. /etc/) powoduje wylistowanie jego zawartości.
- W pewnych implementacjach można używać protokołu gopher://, który umożliwia wysyłanie danych de facto w dowolnym protokole pod dowolny port.
Jak się bronić
Ochrona przed atakiem XXE Injection polega po prostu na blokowaniu możliwości importowania zewnętrznych encji – zwykle w danych pobieranych od użytkownika taka funkcja nie jest do niczego potrzebna. Przykładowo w PHP można użyć funkcji:
libxml_disable_entity_loader(true);
Inne języki również mają swoje odpowiedniki, odsyłam do odpowiednich dokumentacji. W części interpretatorów XML-a dołączanie zewnętrznych encji jest domyślnie wyłączone.
Podsumowanie
Przyjmując dane w postaci dokumentu XML musimy pamiętać o podstawowej zasadzie bezpieczeństwa: nigdy nie ufaj danym otrzymanym od użytkownika. W tym przypadku zagrożenie niesie importowanie encji z zewnętrznych plików, efektywnie umożliwiające atak Path traversal.
Właściwie jedynym powszechnie używanym zabezpieczeniem jest całkowite wyłączenie ładowania zewnętrznych encji w parserze XML-a.
Dalsza lektura
Kilka ciekawych linków do dalszej lektury:
- przykłady błędów XXE znajdowanych w ramach programów bug bounty,
- eskalacja XXE do zdalnego wykonywania kodu,
- ciekawa prezentacja OWASP-u o XXE.
— Michał Bentkowski
Jeszcze jeden dodatek do dalszej lektury: największa nagroda w historii facebookowego bug bounty została wypłacona za błąd, którego bazą był właśnie XXE:
– https://www.facebook.com/BugBounty/posts/778897822124446
– http://www.ubercomp.com/posts/2014-01-16_facebook_remote_code_execution
To jest to co tygryski lubią najbardziej! Następny art tego typu niech będzie o php:unserialize()
Nie ukrywam, że mam ten temat ostatnio na oku ;)
Coraz ciekawiej na tym sekuraku, dzięki tobie Michał
Brawo ! Zwięźle i na temat. Poziom 1 liga !
Ludzie : „Keep Calm and Carry On” -^~
Olo: „stay tuned”.
Michał ma już małą bombkę na kolejny tydzień :-) Już jest w zasadzie opisana, tylko czekamy na zapatchowanie problemu przez zainteresowanych – czyli firmę Google :-P
Super artykuł. Okazuje się, że w bibliotekach Javy pojawia się potencjalnie insecure default. Nic dziwnego, że programiści o tym zapominają. Bardzo łatwo to przeoczyć.
Porobiłem parę testów na bibliotekach JDom oraz Dom4J celem rozpracowania tematu dla Javy. Gdyby ktoś był zainteresowany – kod umieszczony został na githubie: https://github.com/marpuch/Java-Sec-Examples
Ja bym powiedział, że programiści nawet nie tyle zapominają, co w ogóle nie mają świadomości problemu ;-)
Do kompletu do ataków na XML-a możesz dorzucić jeszcze Billion laughs: http://en.wikipedia.org/wiki/Billion_laughs
Hmm… Czy pewno to jest „Path traversal”? Nie przypadkiem RFI?
Tak, XXE pozwala czytać dowolne pliki („path traversal”), nie pozwala jednak bezpośrednio ich wykonywać (nie ma więc mowy o „remote file inclusion”). Ewentualnie, o czym jest też mowa w artykule, oprócz samym „path traversalu” można tutaj mówić o „server side request forgery”.
„W pewnych implementacjach można używać protokołu gopher://, który umożliwia wysyłanie danych de facto w dowolnym protokole pod dowolny port.”
Z tego co mi się wydaje, to można używać tylko protokołów w TCP, chyba że się mylę?