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.

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.

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

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.

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.

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.

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

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.

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

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