Zwei klassische OWASP-dokumentierte Schwachstellen-Klassen, die in keinem der vertiefenden Kapitel (XSS, Injection, Auth, Crypto) ein Zuhause haben — und trotzdem zu den wirkungsvollsten Web-Angriffen der letzten 15 Jahre gehören. XML External Entities (XXE) erlauben File-Read, SSRF und manchmal RCE über manipulierte XML-Eingaben. Unsafe Deserialization verwandelt eine harmlos aussehende Datenstruktur in einen Remote-Code-Execution-Vektor. Dieser Artikel ordnet beide.

XXE — was es ist

XML External Entity Injection nutzt eine wenig bekannte XML-Funktion aus: External Entities, die Inhalte aus externen Quellen laden können. Wenn eine Anwendung XML-Eingaben von Nutzer:innen entgegennimmt und einen XML-Parser nutzt, der External Entities standardmäßig zulässt, kann ein:e Angreifer:in:

  • Lokale Dateien lesen (/etc/passwd, AWS-Credentials, Application-Configs).
  • Server-seitige HTTP-Anfragen auslösen (SSRF — siehe api-top-10-konkret API7).
  • DoS über exponentielle Entity-Expansion (Billion-Laughs).
  • In manchen Konfigurationen sogar Remote Code Execution.

Klassisches XXE-Payload:

XML xxe-file-read.xml
<?xml version="1.0"?>
<!DOCTYPE root [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
  <data>&xxe;</data>
</root>

Wenn die Anwendung diesen XML-Body parst und den Wert von <data> irgendwo zurückgibt (Fehlermeldung, Response, generierte PDF), ist der Inhalt von /etc/passwd durchgereicht.

SSRF-Variante:

XML xxe-ssrf.xml
<?xml version="1.0"?>
<!DOCTYPE root [
  <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<root><data>&xxe;</data></root>

Der XML-Parser baut die HTTP-Anfrage im Namen des Servers auf — und kommt damit an interne Cloud-Metadaten oder andere nicht-öffentliche Endpunkte.

Wo XXE in der Praxis auftritt

Ein häufiges Missverständnis: „Wir nutzen kein XML mehr, also kein XXE-Risiko." Falsch. XML steckt in vielen Formaten und Schnittstellen, oft unsichtbar:

  • SOAP-APIs — XML ist Pflicht-Format.
  • SAML-Authentifizierung — Identity-Provider liefert XML-Tokens.
  • DOCX, XLSX, PPTX (Office Open XML) — gezippte XML-Strukturen.
  • SVG-Dateien — XML-basiert, oft als Bild-Upload akzeptiert.
  • RSS/Atom-Feeds.
  • XML-RPC-Endpunkte (z. B. WordPress).
  • PDF-Workflow-Komponenten.
  • EPUB, GPX, KML, RDF — viele Spezial-Formate.

Jede Eingabe eines dieser Formate ist ein potenzieller XXE-Vektor, wenn der Parser nicht gehärtet ist.

Reale Vorfälle:

  • Apache Struts2 (CVE-2017-9805) — XXE in REST-Plugin, führte zu RCE bei mehreren großen Unternehmen.
  • Cisco WebEx — XXE in SAML-Verarbeitung, 2017.
  • Microsoft Word — XXE über bösartige .docx-Dateien, mehrere CVEs über die Jahre.
  • Apple — iWork-XXE in iOS und macOS, 2018.

XXE ist seit 1999 dokumentiert und seit den 2010ern in der OWASP Top 10 — und trotzdem regelmäßig in neuen CVEs zu finden.

XXE verhindern

Konkrete Härtungs-Patterns pro Sprache:

Java (klassisches DocumentBuilder):

Java xxe-java-hardened.java
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

// Schadhaft: Default lässt External Entities zu (je nach JDK-Version)

// Sicher: Alle riskanten Features deaktivieren
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);

Python (lxml):

Python xxe-python-lxml.py
from lxml import etree

# Schadhaft
# tree = etree.parse(xml_file)

# Sicher
parser = etree.XMLParser(
    resolve_entities=False,
    no_network=True,
    load_dtd=False,
)
tree = etree.parse(xml_file, parser=parser)

Empfehlung Python: defusedxml (Drop-in-Ersatz für xml.etree, lxml, etc., mit sicheren Defaults).

PHP:

PHP xxe-php.php
// libxml < 2.9.0: External Entities Default an — riskant
// libxml >= 2.9.0 (heute Standard): Default aus

// Defensiv explizit setzen
libxml_disable_entity_loader(true);  // legacy bis PHP 8.0
// PHP 8.0+: External Entities sind generell Default deaktiviert

.NET:

C# xxe-dotnet.cs
var settings = new XmlReaderSettings
{
    DtdProcessing = DtdProcessing.Prohibit,
    XmlResolver = null,
};
var reader = XmlReader.Create(input, settings);

Universelle Empfehlung:

  • Wenn XML-Verarbeitung nötig: External Entities und DTD-Processing explizit deaktivieren.
  • Defusedxml-artige Libraries nutzen (Python defusedxml, Java OWASP Java Encoder, etc.).
  • Wenn möglich auf JSON migrieren — JSON hat kein External-Entity-Konzept.
  • Bei nötigen XML-Verarbeitungen in Sandboxes laufen lassen.

Unsafe Deserialization — was es ist

Programmiersprachen bieten Serialisierung — Mechanismen, Objekte in Strings/Bytes umzuwandeln und wieder zurück. Wenn eine Anwendung untrusted Daten deserialisiert, kann ein:e Angreifer:in Daten konstruieren, die beim Deserialisieren Code ausführen.

Klassische Sprach-spezifische Mechanismen:

  • Java: ObjectInputStream.readObject() — instanziiert Klassen mit beliebigen Werten, ruft readObject() / readResolve() der Klasse auf. „Gadget Chains" können RCE auslösen.
  • PHP: unserialize() — wenn Magic Methods (__wakeup, __destruct) in erreichbaren Klassen Side-Effects haben, RCE möglich.
  • Python: pickle.loads() — Pickle-Format kann beliebigen Python-Code beim Deserialisieren ausführen. Eingaben aus untrusted Quellen sind niemals sicher.
  • .NET: BinaryFormatter, LosFormatter, NetDataContractSerializer — alle gelten als deprecated, weil Deserialization-RCE strukturell möglich ist.
  • Ruby: Marshal.load.
  • Node.js: serialize-javascript und ähnliche Libraries hatten historisch Deserialization-Probleme.

Klassisches Java-Beispiel (vereinfacht):

Java unsafe-deserialization.java
// Schadhaft: Direkt aus Request lesen
public Object handleRequest(HttpServletRequest request) throws Exception {
  ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
  return ois.readObject();  // RCE möglich
}

Ein:e Angreifer:in baut mit ysoserial ein Payload, das beim Deserialisieren über Gadget-Chains (Commons-Collections, Spring, Hibernate) Runtime.exec() aufruft.

Reale Deserialization-Vorfälle

Apache Commons Collections (2015) — Gadget-Chain in einer der meistgenutzten Java-Libraries; ermöglichte RCE in vielen Java-Apps, die ObjectInputStream verwendeten und Commons-Collections im Classpath hatten. Stephen Breen veröffentlichte den ursprünglichen Exploit, später verallgemeinert in ysoserial.

Apache Shiro (CVE-2016-4437) — RememberMe-Cookie wurde mit hardcoded Default-Schlüssel verschlüsselt. Angreifer baute Java-Serialized-Payload mit ysoserial, RCE.

Oracle WebLogic — mehrere kritische Deserialization-CVEs über die Jahre (2017, 2018, 2020).

Jenkins — mehrere CVEs zu Deserialization in Plugins.

PHP unserialize() / phpMyAdmin — über Jahre wiederkehrende CVEs.

Python Pickle in ML-Modellen — siehe LLM Top 10 LLM03. PyTorch-.pt-Dateien sind Pickle. Wer ein Modell von HuggingFace lädt, lädt potenziell ausführbaren Code. Daher Safetensors-Format als Antwort.

Deserialization verhindern

Universelle Regel: Deserialisiere niemals untrusted Daten in Binärformaten.

Was das praktisch heißt:

  • JSON statt Java-Serialization / Pickle / unserialize. JSON deserialisiert auf primitiven Daten-Strukturen ohne Code-Ausführung. (Vorsicht: manche JSON-Libraries haben Polymorphism-Features, die Type-Confusion ermöglichen — siehe Jackson-enableDefaultTyping-Probleme.)
  • Schema-Validierung nach Deserialisierung — strenge Typen, keine unerwarteten Klassen.
  • ObjectInputFilter (Java 9+) — JVM-eingebauter Filter, der erlaubte Klassen für Deserialization whitelisten kann.
  • Safetensors statt Pickle für ML-Modelle.
  • Signaturen auf serialisierten Daten — wer sie verändert, ändert die Signatur; Server lehnt ab.

Konkrete Patterns:

Java ObjectInputFilter:

Java java-oif.java
ObjectInputStream ois = new ObjectInputStream(input);
ois.setObjectInputFilter(info -> {
  Class<?> clazz = info.serialClass();
  if (clazz == null) return ObjectInputFilter.Status.UNDECIDED;
  // Whitelist erlaubter Klassen
  if (clazz == MySafeDto.class) return ObjectInputFilter.Status.ALLOWED;
  return ObjectInputFilter.Status.REJECTED;
});
return (MySafeDto) ois.readObject();

Python — Pickle ersetzen:

Python python-json-statt-pickle.py
import json

# Schadhaft (nie tun mit untrusted Input)
# import pickle; data = pickle.loads(request.body)

# Sicher
data = json.loads(request.body)  # nur primitive Typen
# plus Pydantic-/jsonschema-Validierung darüber

Für ML-Modelle:

Python safetensors-statt-pickle.py
# Schadhaft
# import torch; model = torch.load('model.pt')  # Pickle, RCE möglich

# Sicher
from safetensors.torch import load_file
weights = load_file('model.safetensors')  # kein Code-Ausführung

Gemeinsame Schutz-Prinzipien

Sowohl XXE als auch Deserialization haben dieselbe strukturelle Wurzel: Daten und Verhalten vermischen sich. Eine vermeintliche Daten-Eingabe enthält Anweisungen, die der Parser bzw. der Deserialisierer als Verhalten interpretiert.

Drei strukturelle Empfehlungen:

  • Trennung von Daten und Code in Format-Wahl: JSON statt XML, Schema-validierte Strukturen statt Object-Streams.
  • Default-Deny in Parsern — riskante Features explizit aus.
  • Defense-in-Depth — selbst bei korrektem Parser nicht alle Eier in einen Korb: Eingabe-Validierung + Sandboxing + Monitoring.

Häufige Stolperfallen

XXE in SAML ist ein klassischer Vektor

SAML-basierte SSO-Implementierungen verarbeiten XML-Token vom Identity-Provider. Wenn der XML-Parser unsicher konfiguriert ist, kann ein:e Angreifer:in über manipulierte SAML-Responses XXE auslösen. Viele Bibliotheken haben historisch Defaults gehabt, die External Entities zuließen — auch wenn das aus Sicht der SAML-Spezifikation niemals nötig ist.

ysoserial: das Java-Deserialization-Standardwerkzeug

ysoserial ist seit 2015 das Standard-Tool für Java-Deserialization-Exploitation. Generiert Payloads basierend auf in der Anwendung verfügbaren Gadget-Chains (Commons-Collections, Spring, Hibernate, etc.). Wer Java-Web-Apps testet, kommt am Tool nicht vorbei.

Pickle ist niemals sicher mit untrusted Input

Python-Docs sagen es seit Jahren explizit: „The pickle module is not secure. Only unpickle data you trust." Trotzdem nutzen unzählige Anwendungen Pickle für Cache, Session-Speicherung, ML-Modelle, ohne sich der RCE-Implikation bewusst zu sein. Safetensors-Format ist die Antwort speziell für ML-Modelle.

Office Open XML als Trojaner-Vektor

DOCX, XLSX, PPTX sind ZIP-Archive mit XML-Inhalten. Ein präpariertes DOCX kann XXE auslösen, wenn das verarbeitende System (Word, LibreOffice, headless Office-Renderer auf Server) den XML-Parser nicht gehärtet hat. Bei Web-Apps mit Office-Upload-Funktion (CV-Portale, Cloud-Office) ist das ein häufiger Test-Vektor.

.NET BinaryFormatter ist deprecated

Microsoft hat 2020 offiziell erklärt, dass BinaryFormatter nicht zu retten ist — er wird in zukünftigen .NET-Versionen entfernt. Wer noch BinaryFormatter, LosFormatter, SoapFormatter oder NetDataContractSerializer nutzt, sollte migrieren — auf JSON (System.Text.Json) oder XML mit strikter Schema-Validierung.

XXE über DTDs auch ohne explizite XML-Eingabe

Manche XXE-Vektoren funktionieren über externe DTDs, die der Parser aus dem Netz lädt. Das passiert auch dann, wenn das Eingabe-XML selbst keine External Entity definiert — der Parser holt sich die DTD-Datei und verarbeitet darin definierte Entities. Daher: auch DTDs deaktivieren, nicht nur External Entities.

ObjectInputFilter ist eine späte JVM-Antwort

Java 9 hat den ObjectInputFilter eingeführt — eine eingebaute Allowlist-Mechanik für Deserialization. Wer auf Java 9+ läuft und Deserialization nicht völlig vermeiden kann, sollte ihn nutzen. Vor Java 9 mussten externe Libraries (z. B. Contrast SafeObjectInputStream) den Job machen.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu OWASP Top 10

Zur Übersicht