XSLTインジェクション

攻撃者が制御するXSLTスタイルシートのコンパイル

説明

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 のため、すべての環境でコード実行が可能とは限りません。

対処方法

  1. スタイルシートをサーバー側で管理してください。 信頼できないユーザーが提供・変更した完全なスタイルシートをコンパイルしないでください。形式を選ばせる場合は、少数の選択肢をアプリケーション所有の完全で変更できないリソースに対応付け、入力をパスへ連結しないでください。汎用的なエスケープや sanitize ヘルパーでは、任意のXSLTを安全にできません。
  2. 多層防御としてJAXPを明示的に制限してください。 XMLConstants.FEATURE_SECURE_PROCESSING を true にし、XMLConstants.ACCESS_EXTERNAL_DTD と XMLConstants.ACCESS_EXTERNAL_STYLESHEET を空文字列に設定して外部プロトコルを拒否してください。jdk.xml.enableExtensionFunctions は有効にしないでください。URIResolver がnull 以外の Source を返す場合、その解決には外部アクセス属性が適用されません。リゾルバーはアクセスを拒否するか、承認されたローカルリソースだけを返す必要があります。
  3. 保守されたランタイムと処理制限を使ってください。 Java 25では <java_home>/conf/jaxp-strict.properties.template を別の設定ファイルへコピーし、-Djava.xml.config.file=... で厳しい設定との互換性を試せます。処理量、深さ、XPath演算子数、時間、メモリを要件に応じて制限してください。影響の軽減策であり、攻撃者のスタイルシートを信頼できるものに変える機能ではありません。
  4. スタイルシート作成機能が必要なら隔離してください。 認可されたユーザーだけに許可し、必要最小限の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とスタイルシートへのアクセス拒否は多層防御であり、信頼境界の代わりにはなりません。

参考資料