説明
XSLTスタイルシートはXML形式ですが、通常のXMLデータではなく変換命令を持つ実行可能なテンプレート言語です。リクエスト本文、パラメーター、アップロード、制限のないパス指定などがコンパイル対象を決めると、攻撃者が変換処理を制御できるおそれがあります。
Javaでは、次のAPIなどがスタイルシートをコンパイルしたり、変換命令として使用したりします。
- JAXPの
TransformerFactory.newTransformer(Source)とnewTemplates(Source) SAXTransformerFactory.newTransformerHandler(Source)とnewXMLFilter(Source)- Saxonの
XsltCompiler.compile(Source)とcompilePackage(Source)
一方、TransformerFactory.newTransformer() はスタイルシートを使わない恒等変換を作ります。信頼されたスタイルシートから作った変換器では、Transformer.transform(Source, Result) の第1引数は変換するXMLデータです。このXMLの安全な解析は、スタイルシートを信頼できるかどうかとは別に確認してください。
想定される影響
- 変換処理や出力の変更: テンプレート、条件式、XPath式、出力構造を操作されるおそれがあります。
- ファイルやネットワークへのアクセス: 外部アクセスを許可するプロセッサーは、外部DTD、
xsl:import、xsl:include、document()のURIを通じてローカルファイルやネットワークリソースへアクセスする場合があります。 - リソースの枯渇: 複雑な処理や繰り返しによってCPUやメモリを過剰に消費する可能性があります。
- コードの呼び出し: 拡張関数などの機能が有効なら、アプリケーションや外部ライブラリのコードを呼び出せる場合があります。JDK 25の
jdk.xml.enableExtensionFunctionsは既定でfalseのため、すべての環境でコード実行が可能とは限りません。
対処方法
- スタイルシートをサーバー側で管理してください。 信頼できないユーザーが提供・変更した完全なスタイルシートをコンパイルしないでください。形式を選ばせる場合は、少数の選択肢をアプリケーション所有の完全で変更できないリソースに対応付け、入力をパスへ連結しないでください。汎用的なエスケープや
sanitizeヘルパーでは、任意のXSLTを安全にできません。 - 多層防御としてJAXPを明示的に制限してください。
XMLConstants.FEATURE_SECURE_PROCESSINGをtrueにし、XMLConstants.ACCESS_EXTERNAL_DTDとXMLConstants.ACCESS_EXTERNAL_STYLESHEETを空文字列に設定して外部プロトコルを拒否してください。jdk.xml.enableExtensionFunctionsは有効にしないでください。URIResolverがnull以外のSourceを返す場合、その解決には外部アクセス属性が適用されません。リゾルバーはアクセスを拒否するか、承認されたローカルリソースだけを返す必要があります。 - 保守されたランタイムと処理制限を使ってください。 Java 25では
<java_home>/conf/jaxp-strict.properties.templateを別の設定ファイルへコピーし、-Djava.xml.config.file=...で厳しい設定との互換性を試せます。処理量、深さ、XPath演算子数、時間、メモリを要件に応じて制限してください。影響の軽減策であり、攻撃者のスタイルシートを信頼できるものに変える機能ではありません。 - スタイルシート作成機能が必要なら隔離してください。 認可されたユーザーだけに許可し、必要最小限のXMLデータと権限を持つ別サービスで実行してください。外部リソースと拡張関数は無効にしてください。
例
変更前
java
import java.io.StringReader;
import jakarta.servlet.http.HttpServletRequest;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public final class InsecureXslt {
public void transform(HttpServletRequest request) throws Exception {
Source stylesheet = new StreamSource(request.getInputStream());
Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesheet);
Source xml = new StreamSource(new StringReader("<report/>"));
transformer.transform(xml, new StreamResult(System.out));
}
}
リクエスト本文全体が変換命令としてコンパイルされ、送信者がスタイルシートを直接制御します。
変更後
java
import java.io.InputStream;
import java.io.StringReader;
import jakarta.servlet.http.HttpServletRequest;
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public final class SecureXslt {
private static final String REPORT_STYLESHEET = "/xsl/report.xsl";
public void render(HttpServletRequest request) throws Exception {
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
try (InputStream stream = SecureXslt.class.getResourceAsStream(REPORT_STYLESHEET)) {
if (stream == null) {
throw new IllegalStateException("Missing trusted stylesheet");
}
Transformer transformer = factory.newTransformer(new StreamSource(stream));
Source xml = new StreamSource(new StringReader(request.getParameter("xml")));
transformer.transform(xml, new StreamResult(System.out));
}
}
}
スタイルシートは固定のアプリケーションリソースで、リクエスト値は変換対象のXMLとしてのみ使用します。外部DTDとスタイルシートへのアクセス拒否は多層防御であり、信頼境界の代わりにはなりません。