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

XXE脆弱性は「設定の死角」を突く:機密ファイル漏洩を阻止する実践的防衛術

「XMLはもう古い」なんて言っている現場ほど、実は古いレガシーなライブラリがシステムの奥深くに潜んでいたりするものです。

こんにちは。今日のテーマは、Webアプリの脆弱性の中でも、特に「なぜこんな初歩的な攻撃が2024年になっても通じるのか」と頭を抱えたくなるXXE(XML External Entity)攻撃についてです。これは単なるファイル読み取りのツールではなく、攻撃者にとっての「OSの覗き窓」となり得る極めて危険な脆弱性です。

なぜXXEは「防げない」のか

多くのエンジニアが陥る罠は、「自前でXMLをパースしていないから大丈夫」という思い込みです。しかし、裏側で使っているPDF生成ライブラリ、SAML認証モジュール、あるいはSOAPベースのレガシーAPIが、内部で脆弱なXMLパーサーを呼び出しているケースは後を絶ちません。

XXEの仕組みはシンプルです。XMLの仕様にある「外部エンティティ」という機能を悪用し、パーサーに対して「外部のファイルを読み込んで、それをドキュメントの一部として展開せよ」と命令します。

例えば、攻撃者は以下のようなペイロードを送り込みます。

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

パーサーがこの指示に従うと、サーバー内の重要な設定ファイルや認証トークンが、レスポンスとしてそのまま攻撃者の手元に返送されます。これだけで、システムの心臓部が丸裸になるのです。

実務で使えるセキュアな実装コード

「とりあえず動けばいい」という実装を捨て、パーサーを明示的に制御することが防御の第一歩です。言語ごとの「安全な設定」を以下にまとめました。

1. PHP (libxml) の場合

PHPの libxml_disable_entity_loader は古いバージョンで重要でしたが、現在のPHP 8.x系では外部エンティティの読み込みはデフォルトで無効化されています。しかし、明示的な保護を記述するのがプロの流儀です。

<?php
// PHP 8.0以降ではデフォルトで無効化されていますが、
// 安全のために明示的にプロトコルを制限するのが確実です。
libxml_use_internal_errors(true);
$dom = new DOMDocument();

// 外部エンティティの読み込みを完全にブロックする
$dom->loadXML($xml_input, LIBXML_NONET | LIBXML_DTDLOAD); 
// 注意: LIBXML_DTDLOAD を外すのが最も堅牢です
?>

2. Python (lxml) の場合

Pythonで最も普及している lxml は、デフォルト設定では非常に危険です。パーサーの初期化時に必ず以下の設定を行ってください。

from lxml import etree

def parse_xml_securely(xml_string):
    # 外部エンティティの解決を無効化し、DTDのロードも禁止する
    parser = etree.XMLParser(
        resolve_entities=False, 
        no_network=True, 
        dtd_validation=False
    )
    return etree.fromstring(xml_string, parser)

インフラ・ネットワーク層での防御(多層防御)

アプリのコード修正が間に合わない場合や、レガシーコードに触れない場合は、WAFやインフラ側で防御を固めます。

  • WAFでの検知:

DOCTYPE, ENTITY, SYSTEM といったキーワードが含まれるリクエストを正規表現で遮断します。特に file:/// や http:// を含むエンティティ定義は、即座にブロック対象とすべきです。

  • NW分離:

サーバーから外部へのアウトバウンド通信(Egress)を制限してください。SSRF(Server-Side Request Forgery)を伴うXXE攻撃を防ぐため、アプリサーバーからインターネットへの直接接続はホワイトリスト方式で厳格に管理するのが理想です。

最後に:セキュリティは「諦め」の積み重ね

「もしXMLを処理する必要がないなら、そもそもパーサーを有効にするな」――これが私の結論です。JSONが主流の現代において、XMLを扱う必然性は極めて低くなっています。

もしどうしてもXMLが必要な場合は、パーサーのデフォルト設定を疑うことから始めてください。「ライブラリが勝手にやってくれる」という甘えが、最大のリスクです。

今日、あなたのプロジェクトで利用しているXMLパーサーの設定を一度見直してみてください。それが、来週のインシデントニュースに名前を載せないための、最も泥臭く、そして最も確実な防衛策です。

コメント

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