こんにちは!セキュリティに初めて触れる開発者の皆さん、そして日々のシステム運用でお忙しいIT担当者の皆さん、お疲れ様です。
突然ですが、皆さんのご自宅の「鍵」について考えたことはありますか?
「うちは頑丈な鍵をかけているから大丈夫!」と思っていても、もし、玄関の郵便受けの隙間がパカーッと開いていて、そこから長い棒を入れたらリビングのテーブルの上に置いてある合鍵を自由に取り出せてしまう構造だったら……どうでしょう?ゾッとしますよね。
実は、Webの世界で作られるプログラムの中でも、これとそっくりな「うっかりミス」が起きることがあります。それが今回お話しする「XXE(XML External Entity)攻撃」というものです。
今回は、このXXE攻撃がどのような仕組みで起きるのか、そしてどうやって防げばいいのかを、身近な例えを交えながら一歩ずつ優しく紐解いていきたいと思います!
—
1. XXE攻撃ってそもそも何?(家の郵便受けに例えて解説)
Webアプリケーションの世界では、システム同士がデータをやり取りするために、しばしば「XML」というデータ形式が使われます。例えば、お問い合わせフォームやAPI通信などで、人間には少し読みづらいけれど、プログラムにとっては処理しやすいタグで囲まれたデータが飛び交っています。
このXMLを読み込むための仕組み(プログラム)を「XMLパーサー」と呼びます。いわば、届いた荷物を開封して中身を確認してくれる「優秀な郵便受取人」のような存在です。
便利な機能が、まさかの弱点に…
XMLには、「外部エンティティ」という、ちょっと賢い機能があります。これは、「ここに書いてあるデータは大きすぎるから、あそこのファイルから直接読み込んできてね」と、別の場所にあるデータを指し示すことができる機能です。
普通の正当な使い方であれば、「共通のよく使う定型文のデータを別のファイルから読み込もう」といった便利なものなのですが、ここにセキュリティの落とし穴があります。
もし、この「郵便受取人(XMLパーサー)」が、意地悪な人から届いた手紙に書かれていた、
> *「おいおい、あそこの書庫にある『パスワードが書かれた秘密のファイル』の中身を読み取って、私に見せてくれよ!」*
という指示を、疑うことなくそのまま実行してしまったらどうなるでしょうか?
攻撃者は、この仕組みを悪用して、サーバーの奥深くにある「本来なら絶対に他人に見せてはいけない機密ファイル( /etc/passwd や設定ファイルなど)」を盗み見てしまうのです。これがXXE攻撃の正体です。
—
2. 実際の攻撃はどうやって行われるの?(コードで見る仕組み)
百聞は一見にしかず。実際にどんなデータが送られてくると、この事件が起きてしまうのか、簡単なPHPのサンプルコードを見てみましょう。
危険なXMLを受け取るプログラムの例
以下のPHPコードは、ユーザーから送られてきたXMLデータを、そのままプログラムで処理(パース)してしまう危険な例です。
<?
// ユーザーから送信されたXMLデータを取得する想定のコード
$xmlInput = file_get_contents('php://input');
// XMLパーサーの安全設定をしていない「丸腰」の状態
$dom = new DOMDocument();
// 受け取ったXMLをそのまま読み込ませる(ここで外部エンティティの読み込みが許可されてしまっている)
$dom->loadXML($xmlInput, LIBXML_NOENT);
// 読み込んだ結果を表示する
echo "受信したデータ処理結果:\n";
echo $dom->saveXML();
?>
ここに、攻撃者が次のような「仕掛け」をした悪意あるXMLデータを送りつけてきます。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!-- 外部エンティティを定義:サーバー内の秘密のファイルを指定する -->
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
<!-- 通常のデータ送信に見せかけて、先ほど定義した「&xxe;」を呼び出す -->
<message>&xxe;</message>
</root>
このリクエストを受け取った危険なPHPプログラムは、指示通りにサーバーの奥底にある /etc/passwd (Linuxのユーザーアカウント情報が書かれたファイル)をこっそり読み込み、画面の応答メッセージの中にその内容を混ぜ込んで返してしまいます。
開発者としては「ただのテキストデータを受け取って表示しているだけ」のつもりでも、パーサーの裏側でこんな不正アクセスが行われてしまうのです。
—
3. 一歩ずつ対策を学んでいこう!
「うわっ、怖い!じゃあXMLなんて使えないじゃん……」と思ったかもしれませんが、安心してください。ちゃんと正しい設定をすれば、この郵便受けは堅固なものに生まれ変わります。
対策の基本方針はシンプルです。
「XMLパーサーに対して、『外部のファイルや怪しいネットワークを勝手に覗き見しちゃダメ!』と厳しく言いつける」これだけです。
PHP(libxml)の場合の安全な設定
PHPでXMLを安全に処理するには、外部エンティティの読み込み機能を明示的に無効化(オフ)します。実際のコードを見てみましょう。
<?
// 【安全対策】外部エンティティの読み込みを無効化する関数を呼び出す
// これにより、勝手にサーバー内のファイルを読みに行かなくなります
libxml_disable_entity_loader(true);
$xmlInput = file_get_contents('php://input');
$dom = new DOMDocument();
// 安全な設定が有効な状態でXMLを読み込む
// 万が一、悪意ある <!ENTITY> が含まれていても、無視またはエラーになります
$dom->loadXML($xmlInput, LIBXML_NONET);
echo "安全に処理されました。";
?>
ポイントは、libxml_disable_entity_loader(true); や、ネットワーク経由のアクセスを禁止する LIBXML_NONET といったオプションを組み合わせることです。これだけで、先ほどのような泥棒の侵入をピタッと防ぐことができます。
※なお、PHPのバージョンや環境によっては、デフォルトで外部エンティティが無効化されている場合もありますが、古いシステムやフレームワークをそのまま使っている場合は必ずこの設定を確認・実装するようにしましょう。
—
4. 他の言語やフレームワークを使っている場合の注意点
「うちはJavaやPython、Node.jsを使っているから関係ないよね?」と思ったそこのあなた!
言語が変わっても、XMLを扱うライブラリの「初期設定が危険(デフォルトで外部エンティティが有効)」になっているケースは非常に多いです。
- Java (SAX / DOM / DocumentBuilder など):
フィーチャー設定で http://apache.org/xml/features/disallow-doctype-decl を true に設定し、DTD(文書型定義)自体を禁止するのが最も確実な防御策です。
- Python (xml.etree.ElementTree など):
標準の機能ではXXEの影響を受けにくいものもありますが、 lxml などの高機能なライブラリを使う場合は、パーサーの設定で外部参照をオフにする必要があります。
実務で新しいライブラリやパーサーを導入する際は、公式ドキュメントで必ず「XXE対策(Disable External Entities)」の項目を確認する癖をつけましょう。
—
まとめ
今回は、XXE攻撃の仕組みと、その怖さ、そして具体的な対策についてお話ししました。
1. XXEとは: XMLパーサーの設定不備を突き、サーバー内の機密ファイルを勝手に読み出されてしまう攻撃。
2. 仕組み: 「外部エンティティ」という便利な機能を悪用され、郵便受けの隙間から奥の鍵を取られるようなもの。
3. 対策: パーサーの設定で、外部ファイルやネットワークの読み込み機能をしっかりと「無効化」する。
セキュリティの対策と聞くと難しく身構えてしまうかもしれませんが、「システムの入り口で、怪しい荷物は開けない・変な指示は聞かないように設定する」という基本の積み重ねです。
一歩ずつ、安全なコードの書き方をマスターして、信頼されるエンジニアを目指していきましょう!それではまた次回のセキュリティ解説でお会いしましょう。
コメント