XXEの深淵:なぜ「XMLパーサー」はあなたのサーバーの鍵を渡してしまうのか
現場でセキュリティ診断をしていると、「XMLなんて古い技術だし、今どき使っていないよ」という言葉をよく耳にする。しかし、それは大きな誤解だ。SOAP API、古いレガシーシステムとの連携、あるいはWordファイルの解析など、XMLは現代のWebインフラの裏側で驚くほど深く根を張っている。
そして、そのXMLを不用意にパースするプログラムは、攻撃者にとって「サーバー内のファイルを何でも読み取れる魔法の杖」になり得る。今日は、XXE(XML External Entity)攻撃という、古典的でありながら今なお強力なこの脆弱性の正体を暴いていく。
—
XXE攻撃の本質:パーサーの「親切心」を悪用する
XMLには、外部の文書や定義を読み込むための DOCTYPE や ENTITY という仕様がある。本来は文書の再利用性を高めるためのものだが、パーサーのデフォルト設定が「親切すぎる」ことが問題だ。
攻撃者は、細工したXMLをアプリに送りつけ、パーサーに「ローカルファイルを読み込んで、その内容をレスポンスとして返せ」と命令する。
攻撃のメカニズム(PoC)
例えば、以下のようなXMLをPOSTリクエストで送りつけられたとしよう。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!-- 外部エンティティの定義:/etc/passwdを読み込ませる -->
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>&xxe;</data>
サーバー側のパーサーがこのDTDを解釈すると、&xxe; の部分が /etc/passwd の内容に置換され、その結果が画面に表示されてしまう。これが、攻撃者がサーバーの機密情報を盗み出す最短ルートだ。
—
現場で即効性のある防御策:パーサーを「冷酷」にせよ
XXEを防ぐ唯一にして最大の鉄則は、「外部エンティティの読み込みをパーサーレベルで遮断すること」だ。開発者は、XMLライブラリを使う際、デフォルト設定を盲信してはならない。
PHP (libxml) の場合
PHPでXMLを扱うなら、libxml_disable_entity_loader を利用するか、LIBXML_NONET オプションを適切に設定する必要がある。
<?php
// libxml2.9.0以降はデフォルトで無効化されているが、念のため明示的に設定する
// 外部エンティティのロードを無効化
libxml_disable_entity_loader(true);
$xmlString = file_get_contents('php://input');
// 設定を反映してパース
$doc = new DOMDocument();
$doc->loadXML($xmlString, LIBXML_NONET | LIBXML_DTDLOAD);
// これで外部エンティティを呼び出しても空かエラーになる
?>
Python (lxml) の場合
Pythonの標準的な lxml ライブラリを使う場合、resolve_entities を False に設定することが鍵だ。
from lxml import etree
def secure_parse(xml_input):
# パーサーの設定で外部リソースへのアクセスを完全に遮断する
parser = etree.XMLParser(
resolve_entities=False, # エンティティ解決を無効化
no_network=True, # ネットワークアクセスを拒否
dtd_validation=False # DTD検証を無効化
)
return etree.fromstring(xml_input, parser)
JavaScript (Node.js) の場合
libxmljs を使用している場合も同様だ。外部エンティティを解決しない設定を強制する。
const libxmljs = require('libxmljs');
function parseSecure(xmlString) {
// dtdload, noent を false にすることで XXE を防御
return libxmljs.parseXml(xmlString, {
dtdload: false,
noent: false,
nonet: true
});
}
—
防御の最後の砦:パーサー以外の防御層
コードレベルでの修正がすぐに行えない場合、あるいは多層防御を構築したい場合は、インフラ層での制限も有効だ。
1. WAFの活用:
XMLリクエストボディをスキャンし、<!DOCTYPE や <!ENTITY を含むリクエストを検知してブロックするルールを追加する。AWS WAFであれば、正規表現マッチセットを利用してこれらの文字列を拒否することが可能だ。
2. IAM/権限分離:
そもそもWebサーバーのプロセスが /etc/passwd や設定ファイルにアクセスできないよう、OSユーザーの権限を最小化しておくこと。たとえXXEが成功しても、アクセス権限がなければ読み取れるファイルは限られる。
3. 入力バリデーション:
XMLを受け取る必要があるのか?可能であれば、より安全な JSON への切り替えを検討してほしい。JSONには外部エンティティのような仕様は存在しない。
—
セキュリティチーフからの助言
脆弱性とは、多くの場合「便利すぎる機能」と「それをデフォルトで許可する実装」の間に生まれる。あなたが書いているそのコード、そのパーサー設定は、本当に信頼できる設定になっているだろうか?
「動けばいい」ではなく「どうすれば悪用できるか」という視点を常に持ち続けてほしい。セキュリティとは、技術の深淵を覗き込み、その穴を一つずつ埋めていく地道な作業の積み重ねだ。
もし自社システムでXMLを扱っている箇所があれば、今日のうちにパーサーの設定を見直してほしい。それが、明日発生するかもしれない重大なインシデントを未然に防ぐ、一番の近道になるはずだ。
コメント