【実務・中級編】 XML外部エンティティ(XXE)攻撃による内部ファイル読み取りとSSRF – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

XXEは「枯れた技術」か?いいえ、今なおシステムの急所です

「XXE(XML External Entity)攻撃なんて、今のモダンなWeb開発ではもう関係ないだろう」——そう思っているなら、今すぐ考えを改めてください。確かに、最近のフレームワークはデフォルトで安全な設定になっていることが多い。しかし、現場では「レガシーなXML APIとの連携」や「独自実装のパーサー設定漏れ」が、今もなお攻撃者の格好の入り口になっています。

今日は、XXEがいかにして内部ファイルを抜き取り、さらにはクラウド環境の心臓部(Metadata Service)を突き刺すのか、その実態と「絶対に抜かれない」実装術を解説します。

—

1. XXEの仕組み:パーサーを「操り人形」にする

XXE攻撃の正体は、XMLパーサーの「外部エンティティ参照機能」を悪用することです。攻撃者はXMLデータの中に悪意あるドキュメント型定義(DTD)を埋め込みます。

例えば、以下のようなペイロードを送りつけたとします。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>

パーサーがこれを解釈すると、&xxe; の部分が /etc/passwd の中身に置換されます。もし、アプリケーションがこのXMLの内容をそのままレスポンスとして返していれば、攻撃者は一撃でサーバー内部の機密ファイルを掌握できます。

なぜこれがSSRFに繋がるのか?

恐ろしいのはファイル読み取りだけではありません。file:// スキームの代わりに http:// を指定すれば、サーバー自身をプロキシとして利用したSSRF(Server-Side Request Forgery)が成立します。特にクラウド環境では、http://169.254.169.254/latest/meta-data/ にアクセスさせることで、IAMロールの認証情報(AccessKey/SecretKey)を盗み出すのが定石です。これが決まれば、そのサーバーだけでなく、AWSアカウント全体が乗っ取られることを意味します。

—

2. 実務で使える「防御の鉄則」

XXEを防ぐための唯一にして最大の原則は「外部エンティティとDTDの処理をパーサー設定で無効化する」ことです。多くの言語ではデフォルトでオフになっていますが、古いライブラリや特定の構成では明示的な制御が必要です。

PHP (libxml) でのセキュアな実装例

PHPで SimpleXML や DOMDocument を使う際は、必ず libxml_disable_entity_loader(true) を呼び出します(PHP 8.0以降はデフォルトで無効ですが、明示的に書くのがプロの流儀です)。

<?php
// PHP 8.0以上であっても、明示的に設定を無効化する慣習を持つこと
libxml_use_internal_errors(true);
libxml_disable_entity_loader(true);

$xmlString = file_get_contents('php://input');
$dom = new DOMDocument();

// 外部エンティティを読み込まない設定を適用
$dom->loadXML($xmlString, LIBXML_NONET | LIBXML_DTDLOAD);

if ($dom === false) {
    // エラーハンドリング:ログを吐くが、詳細なエラーメッセージをフロントに返さない
    error_log("Invalid XML processed.");
    exit("Bad Request");
}
// 正常な処理を継続
?>

Python (lxml) でのセキュアな実装例

Pythonの lxml は非常に強力ですが、デフォルトでは外部エンティティを処理する設定になっていることがあります。以下のようにパーサーを定義してください。

from lxml import etree

def parse_xml_safely(xml_content):
    # 外部エンティティの解決を拒否するパーサーを構築
    parser = etree.XMLParser(
        resolve_entities=False,  # 外部エンティティを解決しない
        no_network=True,         # ネットワークアクセスを禁止
        dtd_validation=False,    # DTD検証を無効化
        load_dtd=False           # DTDの読み込みを無効化
    )
    
    try:
        tree = etree.fromstring(xml_content, parser)
        return tree
    except etree.XMLSyntaxError as e:
        # ここで例外をキャッチし、ログに記録
        print(f"安全な解析失敗: {e}")
        return None

—

3. インフラレイヤーでの多層防御

アプリケーションコードの修正が難しいレガシー環境や、念のためのバックアップとして、WAFでのブロックも検討してください。

WAFでの対策(AWS WAFの例)

DOCTYPE や ENTITY といった文字列を正規表現で検出し、リクエストを拒否するルールを作成します。

  • Rule:
  • Request body をターゲットにする
  • Regex match: <!DOCTYPE|<!ENTITY|SYSTEM|PUBLIC にマッチするものをブロック
  • 注意: XMLを正当に扱うAPIエンドポイントの場合、誤検知が発生するため、特定のURIパスに対してのみ適用するようにしてください。

—

最後に:エンジニアとしての矜持

XXEは、技術的な脆弱性であると同時に「パーサーに対する過信」という心理的な隙を突く攻撃です。

「外部からの入力をそのままパーサーに食わせない」。これが現代のエンジニアリングにおける最低限の作法です。もし可能であれば、XMLの使用を止め、JSONへの移行を検討してください。JSONにはXXEのような「外部リソースを勝手に読み込む」という仕様は存在しません。

システムは「性悪説」で設計し、防御は「多層」で固める。このブログを読んだ今日から、皆さんのコードがより堅牢なものに進化することを期待しています。何か不明点があれば、コードレビューを依頼するような感覚で、いつでも技術的な議論をしましょう。安全な開発を。

コメント

タイトルとURLをコピーしました