XMLには外部実体参照という機能があり、外部ファイルをXMLに取り込むことができます。XXE(XML外部実体参照)攻撃はその機能を悪用することで、サーバ内のファイルなどを不正に取り込み、機密情報の漏洩などを引き起こす攻撃です。
Contents
XXEについて
以下はXXEが起こり得る状態のXMLのサンプルです。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE books[
<!ELEMENT book (title,author,publisher)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
<!ENTITY book1 SYSTEM "book1.xml">
<!ENTITY book2 SYSTEM "book2.xml">
]>
<books>
&book1;
&book2;
</books>
前回説明したのXMLの例と形が大きく異なるため、本当に同じXMLなのか疑問に思うかもしれません。そう思ってしまうのも当然で、これはDTDというXMLのデータ定義機能を使った書き方だからです。
そしてこのDTDの理解が、XXE攻撃の理解につながります。よってまずはDTDについて解説します。
DTD (Document Type Definition)
DTDについて解説しようかと思ったんですが、以下のサイトが非常に分かりやすかったので丸投げします。
参考サイト:DTDとは | JavaDrive
要約すると、DTDを使うことでXMLのデータフォーマットを統一することができます。以下にその例を記します。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE books[
<!ELEMENT book (title,author,publisher)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
]>
<books>
<book>
<title>foo</title>
<author>5</author>
<publisher>bar</publisher>
</book>
<book>
<title>hoge</title>
<author>10</author>
<publisher>hoge</publisher>
</book>
</books>
上記であれば、DTDで定義した型に従って実際のデータを記述していくため、bookというデータに余計な情報(例えばISBNを追加する等)が定義されたり、情報が足りなかったり、といった事態を防ぐことができます。
DTDにおける外部実体参照
DTDでは以下のように別のファイルを呼び出すことが可能です。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE books[
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE books[
<!ELEMENT book (title,author,publisher)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
<!ENTITY book1 SYSTEM "book1.xml">
<!ENTITY book2 SYSTEM "book2.xml">
]>
<books>
&book1;
&book2;
</books>
<book>
<title>foo</title>
<author>5</author>
<publisher>bar</publisher>
</book>
<book>
<title>hoge</title>
<author>10</author>
<publisher>hoge</publisher>
</book>
これを「外部実体参照」と言います。
参考サイト:DTDで要素型宣言を定義する | @IT
つまりXXE攻撃とは、このDTDの外部実体参照の動きを利用して、開発者の意図しないファイルが参照されてしまうという攻撃です。
外部実体参照はほとんどのブラウザでは表示されない
上記のXMLファイルを開いた際のキャプチャが下図です。
このように、外部実体参照がされていないように見えます。これは構文等が間違っているわけではなく、モダンなブラウザは全てこうなります。
現代のブラウザは非検証XMLプロセッサだからだ、みたいなネット記事はいくつか見つかりましたが、公式のドキュメント等は探せなかったのでこの話は割愛します。
参考サイト:知恵袋(笑)
ただの実体参照
ちなみに、外部実体参照はユーザの任意のデータを参照していますが、定義済みの実体を参照することもできます。どういうことかというと、以下のように特定の記号を参照で表すことができます。
<?xml version='1.0' standalone='yes' ?>
<books>
<book>
<title>foo&bar</title>
<author>5</author>
</book>
</books>
なお、XMLにおける実体参照は以下の5つだけが用意されているようです。
- &
- <
- >
- “
- ‘
参考サイト:XMLにおける文字参照と実体参照 | JavaDrive
文字参照
実体参照の他に文字参照というものもあります。これはISO10646で定義されている文字番号を参照するという意味です。
<?xml version='1.0' standalone='yes' ?>
<books>
<book>
<title>foo&bar</title>
<author>5</author>
</book>
</books>
参考サイト:htmlChars | lynchjim.com
改めてXXE攻撃について
XMLにおけるDTD、それから外部実体参照について整理したところで、改めてXXE攻撃について解説します。
実際の攻撃コードを見てみましょう。以下は徳丸本を参考に書いたコードです(というよりほぼ同じ)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>example.com</title>
</head>
<body>
<p>XMLファイルを指定してください</p>
<form action="register.php" method="post" enctype="multipart/form-data">
<input type="file" name="user">
<input type="submit">
</form>
</body>
</html>
<?php
$doc = new DOMDocument();
$doc->load($_FILES['user']['tmp_name']);
$name = $doc->getElementsByTagName('name')->item(0)->textContent;
$addr = $doc->getElementsByTagName('address')->item(0)->textContent;
?>
<body>
<p>以下の内容で登録しました</p>
<p>氏名:<?= htmlspecialchars($name); ?></p>
<p>住所:<?= htmlspecialchars($addr); ?></p>
</body>
上記フォームに以下のXMLファイルをアップロードします。
<?xml version="1.0" encoding="utf-8" ?>
<user>
<name>テストマン</name>
<address>沖縄県那覇市</address>
</user>
この場合、上図のように問題なく名前と住所が表示されます。
ここで悪意のあるユーザが以下のように/etc/hostsを参照するXMLをアップロードしたとします。理論上は、/etc/hostsが表示されてしまいます。これがXXE攻撃です。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test [
<!ENTITY hosts SYSTEM "/etc/hosts">
]>
<user>
<name>テストマン</name>
<address>&hosts;</address>
</user>
ただし、実際は下図のように何も表示されません。
このような結果になるのは、PHPのXML処理に使われているライブラリが外部実体参照をデフォルトで停止するようになっているからです。
(PHPにおけるXXE対策)libxml2のバージョン2.9以降を用いるPHPのXML処理には、内部でlibxml2というライブラリが用いられています。libxml2の2.9以降では、デフォルトで外部実体参照を停止する設定となっており、XXEに対して脆弱ではありません。ただし、PHP側で外部実体参照を許可する設定にしている場合は例外です。
本稿執筆時点でメジャーなLinuxディストリビューション(Red Hat、CentOS、Debian、Ubuntu)に関して、サポート継続中のバージョンでかつ最新のパッチを適用していれば、libxml2側で外部実体参照を停止していることを確認します。
徳丸本
上記にあるように、外部実体参照を明示的に許可することは可能です。具体的には以下のように1行追加するだけです。
<?php
$doc = new DOMDocument();
$doc->substituteEntities = true;
$doc->load($_FILES['user']['tmp_name']);
$name = $doc->getElementsByTagName('name')->item(0)->textContent;
$addr = $doc->getElementsByTagName('address')->item(0)->textContent;
?>
<body>
<p>以下の内容で登録しました</p>
<p>氏名:<?= htmlspecialchars($name); ?></p>
<p>住所:<?= htmlspecialchars($addr); ?></p>
</body>
参考サイト:PHPプログラマのためのXXE入門 | 徳丸浩の日記
これでXXE攻撃が成立してしまいます。
JSONの主流化と言語側の対策によって、XXE攻撃が成立する状況は今後少なくなっていくと思います。実際に”OWASP top 10″の2017にはランクインしていたXXEですが、2021だと統合される形で消えています。ただし、まだまだ存在する脆弱性なので注意が必要ですね。