25-09-2018 / Od 0 do pentestera

XXE - XML External Entity

Popatrzmy na przykładowy kod w Javie.

import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
public class Main {
    public static void main(String[] args) {
        try {
            File xmlFile = new File("c:\\od0dopentestera\\3\\3.xml");
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(xmlFile);
            System.out.println(doc.getDocumentElement().getTextContent());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Pobiera on treść pliku 3.xml a następnie przy pomocy DocumentBuilder1 przetwarza jego treść.

W ostatniej linijce pobieramy element główny tego dokumentu i wyświetlamy jego treść na ekranie.

Jak wygląda przykładowy plik wejściowy? Rozpoczyna się od deklaracji xml a następnie atrybutu wersji, który jest tutaj wymagany.

Zawartość pliku 3.xml

Jeśli nie podamy wersji - plik nie może zostać przetworzony, dostaniemy komunikat o błędzie.

Następnie zdefiniowany zostaje element główny test z przykładową wartością: demo.

Jeśli więc nasz przykładowy kod działa prawidłowo, na ekranie powinniśmy zobaczyć ciąg: demo.

Wynik działania funkcji

Standard XML jest jednak dużo bardziej rozbudowany2.

Jedną z dodatkowych funkcjonalności jest możliwość tworzenia encji.

Przypominają one makra na przykład z języka C.

Krótko mówiąc możemy zdefiniować jakiś szablon z długą treścią.

Przykład encji

Zamiast w każdym dokumencie wklejać tą samą treść - wystarczy użyć encji.

Parser automatycznie znajdzie wszystkie encje i zamieni ich treść na ten długi ciąg danych, wcześniej przez nas zdefiniowany.

Zobaczmy przykład.

Encje tworzy się przy pomocy słowa kluczowego DOCTYPE oraz ENTITY.

W moim przypadku nowo stworzona encja nazywa się zamień.

Jej wartość natomiast to: bardzo długi tekst.

Teraz w elemencie głównym odnoszę się do encji używając: &zamień;.

Po uruchomieniu parsera widzimy, że encja zamień została zastąpiona naszym wcześniej zdefiniowanym tekstem.

Wynik działania encji

Gdzie zatem jest ukryta dzisiejsza podatność?

Twórcy formatu stwierdzili, że fajnie by było, gdyby encje mogły być definiowane w zewnętrznych plikach.

Dzięki temu długie ciągi tekstu nie będą przeszkadzały w czytaniu plików xml.

Ta funkcjonalność została umożliwiona dzięki słowu kluczowemu SYSTEM.

Następnie podajemy nazwę pliku, który zawiera treść encji.

Zewnętrzna encja

I tutaj leży sedno całego problemu. Jak możesz się bowiem spodziewać atakujący może podać tutaj dowolną ścieżkę do pliku.

W moim przypadku istnieje plik sekret, z tajną zawartością. Tworze więc encje XXE, która odczyta zawartość właśnie tego pliku.

Zawartość pliku sekret

Teraz gdy patrzymy na wynik - widzimy treść tego tajnego pliku, pochodzącego z serwera na którym uruchomiona jest dana aplikacja.

Przykład ataku XXE

Atak ten nazywa się XXE - czyli XML External Entity.

Jak się zatem przed nim zabezpieczyć?

Jednym z rozwiązań może być uruchomienie specjalnego trybu: Secure processing3, który standardowo jest wyłączony.

dbFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

Teraz podczas próby ataku dostaniemy informację o błędzie.

Błąd

Jak widać przetwarzania plików XML może być skomplikowane.

Może właśnie dlatego coraz większą popularność zdobywa format json?