yamory Blog

油断ならない脆弱性 XXE への対策

XXE(XML External Entity: XML 外部エンティティ参照, XML 外部実体) は、アプリケーションが XML を解析した際に、XML の特殊構文を悪用されて発生する脆弱性です。

この脆弱性は、DoS やディレクトリトラバーサル(パストラバーサル)SSRF(Server Side Request Forgery / サーバサイドリクエストフォージェリ)、Port Scan(ポートスキャン)といった攻撃にも繋がる脆弱性です。

本記事では XXE の仕組みから対策方法までそれぞれ解説していきます。

XXE の概要

XXE は、XML の特殊構文をアプリケーションが解析する際に発生する脆弱性・攻撃です。

XML の構文には、内部リソース・外部(URL)リソースを読み込む構文が存在します。
この構文は「実体参照」と呼ばれ、XML の構文である DTD(Document Type Definition)内にて使用します。

DTD が何であるかについては、実体参照の説明を行なった後に解説していきます。

実体宣言と実体参照

早速ですが、以下に実体参照を行う XML を記載します。

<!DOCTYPE user [
<!ENTITY fileData SYSTEM "file:///var/www/app/static/user.xml">
]>
<user>
  &fileData;
</user>

注目すべき箇所は 2 行目の <!ENTITY fileData SYSTEM "file:///var/www/app/static/user.xml"> と、5 行目の &fileData; という部分です。

<!ENTITY fileData SYSTEM "file:///var/www/app/static/user.xml">

&fileData;

これらの行では、XML の構文である DTD を使い、 fileData と呼ばれる実体宣言しています。

実体宣言とは、(少し適切ではない表現ですが)データを XML に直接記載せず、それらのデータを変数のように扱える機能です。
この宣言されたものを実体(Entity)と言い、後続の XML インスタンス内にて参照・挿入できます。

上記の例だと「実体 fileData"file:///var/www/app/static/user.xml" というファイルである」という意味となります。

この実体参照を含んだ XML を元に、サーバがレスポンスを返却する場合、先ほどの宣言の通り /var/www/app/static/user.xml の一覧が表示される場合があります。

上記の XML で実体を参照している箇所は、 &fileData; という箇所です。
この &; による実体名を挟み込む記法により、実体宣言した値を参照できます。

では、実体宣言はどのように記載すればよいでしょうか?
これを知るためには、DTD を理解する必要があります。

DTD とは

DTD(Document Type Definition)は、XML の構造を定義するための構文です。

XML の構造とは、「どのような要素が」「どのような型で定義され」「何回登場するか」「その実体は何か(どのリソースか)」といった情報です。

なお、DTD の詳細な説明を始めるとそれだけで本記事が終わってしまう可能性があるため割愛させていただき、あくまで本節では DTD における「実体宣言・実体参照」にのみフォーカスを当てて解説を進めます。

例として以下に DTD と、DTD に従った形式の XML インスタンスを記載します。
XML インスタンスは <!DOCTYPE> タグ以降の <productData>...</productData> の部分になります。

<!DOCTYPE productData [
<!ELEMENT productId ANY>
<!ELEMENT productComment (#PCDATA)>
<!ELEMENT userVariable "置換される文字" >
]>
<productData>
  <productId>12345</productId>
  <productComment>&userVariable;</productComment>
</productData>

まず、XML に記載された <!DOCTYPE> 内が DTD にあたります。
この DTD 内にて構造の定義や実体宣言が行われ、 <!ENTITY> という記述により実体宣言が行えます。
構造の定義は <!ELEMENT ... > 形式で宣言します。

上記の例だと、各要素(productId, productComment, userVariable)が、XML インスタンス内に存在することを表します。

このようにして DTD によって XML インスタンスの構造を定義したり、データを挿入することで XML を構築していきます。

実体参照を利用した攻撃手法

ここまでで、DTD と実体参照についておおよその動きは掴めてきたかと思います。
ここからは実体参照を更に掘り下げて、実体参照を利用した攻撃手法について見ていきます。

実体参照を使った攻撃には色々なケースがあります。
本記事では有名な以下のケースについて見ていきます。

  • DoS
  • LFI / RFI (Local / Remote File Inclusion)
  • SSRF (Server Side Request Forgery)
  • Port Scan

XXE による DoS

XXE による DoS では、細工した XML をパースさせた際に、大量の処理を行わせることで、パースを行なったサーバに高負荷をかけます。

まずは細工された XML を見ていきましょう。

<!DOCTYPE data [
<!ENTITY a0 "aaaaaaaaaa......すごく長い文字.......aaa" >
<!ENTITY a1 "&a0;&a0;&a0;....すごい多い参照回数....&a0;">
]>
<data>&a1;</data>

上記の XML では、大量のデータ量を持つ実体 a0 を宣言し、2 つ目の実体 a1 で、先ほど宣言した大量のデータ a0 を何度も参照するという構図になっています。

これにより XML を解析する際に、大量のデータに何度もアクセスする事による DoS を発生します。

もうひとつのケースを見てみましょう。
こちらのケースでは、ネストした参照を実施するため、先ほどの例より文字数が少なく、スマートです。

<!DOCTYPE data [
<!ENTITY a0 "dos" >
<!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;">
<!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;">
<!ENTITY a3 "&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;">
<!ENTITY a4 "&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;">
]>
<data>&a4;</data>

こちらの例では、 a4 を実体参照すると、10 個の a3 が展開されます。
そして、10 個の a3 はそれぞれ 10 個の a2 を展開し、それを繰り返していくといったネスト構造になっています。
これにより、解析を行なったサーバに負荷を与えます。

XXE による LFI / RFI

XXE による LFI / RFI(Local File Inclusion / Remote File Inclusion)では、外部実体参照を利用することで、本来アクセスできない領域(ローカル・リモート)にあるファイルへアクセスします。

ただ、RFI は後述する SSRF と重複する部分があるため、本節では LFI のみを説明します。

XXE による LFI では、記事前半にて利用した外部実体参照の記法を用います。
例えば以下の XML を利用すると、脆弱なサーバ内の機密データにアクセスできます。

<!DOCTYPE data [
<!ENTITY secretData SYSTEM "file:///etc/passwd">
]>
<data>
  &secretData;
</data>

ここでは、 file:// スキームを利用することで、ローカルファイル内にある /etc/passwd というセンシティブなファイルを表示させるような XML となっています。

XXE による SSRF

SSRF(Server Side Request Forgery)は、本来アクセスできない領域(例えばイントラネット)に対して、脆弱なサーバを経由してリクエストを送る脆弱性・攻撃です。

例えばサーバとなるインスタンスが、 http://10.xxx.xxx.xxx/ と言ったリクエストを発したとします。

その場合、サーバの属するネットワーク領域の中で、対象のローカル IP に HTTP リクエストを送信します。
攻撃者は、この行為を対象のネットワーク領域外から、被害者となるサーバに対して指示します。

その結果攻撃者は、アクセス不可能な領域に対して、リクエストを送れるようになります。

XXE を使った SSRF では、次のような XML を利用し、内部ネットワーク上にホストされた機密データ・API などにリクエストを送らせます。

<!ENTITY ssrf SYSTEM "http://10.xxx.xxx.xxx/secret.txt">

これらの攻撃により、サーバとは違う外部インスタンスのデータを取り込んだ場合、その攻撃は SSRF と RFI の 2 つの意味を持った攻撃となります。

SSRF の発展的な攻撃としては、AWS などの API を活用した攻撃があります。

例えば AWS の EC2 インスタンスでは http://169.254.169.254/latest/meta-data/ と言ったアドレスに HTTP リクエストを送ることで、自身のクレデンシャルを返す API が用意されています。

こういった機能は他のクラウドサービスも提供されています。
そのため SSRF の脆弱性が存在した場合に、当該 API にリクエストを送らせることで、クレデンシャル情報が盗まれます。

XXE による Port Scan

XXE を使った Port Scan では、前節にて説明した SSRF のテクニックを活用して実施されます。

例えば次のような XML を利用した場合、Port が Open になっている場合と Close になっている場合で、レスポンスタイムが変わることがあります。

<!DOCTYPE portScan SYSTEM "http://127.0.0.1:12345">]>

このレスポンスタイムを計測することによって、対象サーバの Port 状況や、イントラネット内サーバの Port Scan などが実施される可能性があります。

XXE の対策方法について

XXE の対策方法は各プログラミング言語によって変わります。

しかし、対策をする際にどの言語でも変わらないタスクとしては「どの箇所で XML を扱っているか」を把握することです。

例えば「外部情報を取り込む機能」「SAML 認証を実施している箇所」と言った部分が、XML でよく利用される箇所ではないでしょうか。
そういった部分を特定した後で、各言語の対策を実施する必要が出てきます。

各言語の XXE 対策方法は OWASP のチートシートに記載されています。
https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html

今回はこの中から Java(の一部ライブラリ), PHP を例にそれぞれの対策を見ていきます。

PHP における対策方法

PHP での対策は非常に簡単です。
PHP のデフォルトで搭載された XML パーサを使用する際に、次の設定を行うだけです。

libxml_disable_entity_loader(true);

この設定により、実体参照の機能を停止させることができます。
XXE は、実体参照を悪用する攻撃であるため、この対策が有用に働きます。

Java における対策方法

Java には複数の XML パーサーがあります。

また、それらの XML パーサーは、デフォルトだと XXE に対して脆弱です。
そのため、使用する XML パーサーで XXE に悪用される設定を明示的に無効化する必要があります。

本節では Java の JAXP, SAXParser, DOM4J の DTD 対策方法について解説します。

Java の XXE の対策は、基本的に同じ対策が取られます。

その対策とは、XML パーサーとなる Class をインスタンス化する際に、「実体宣言の無効」「実体参照の無効」「DTD の無効」といった、XXE で悪用される機能を無効するというものです。

以下が脆弱な形式の XML のパースコードです。

...
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder insecureBuilder = dbf.newDocumentBuilder();

insecureBuilder.parse(Paths.get("xxe.xml").toFile());

このコードでは、XML パーサーを DocumentBuilderFactory 経由でインスタンス化しています。

しかし、この Factory で生成される XML パーサーは、DTD などが無効化されておらず、XXE に対して脆弱です。

次に XXE に対してセキュアな XML パーサーの生成コードをみていきます。

...
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
  // <!DOCTYPE> の無効化
  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);
  // DTD の無効化
  dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
  // XInclude 処理の無効化
  dbf.setXIncludeAware(false);
  // エンティティ参照ノードの展開を無効化
  dbf.setExpandEntityReferences(false);
  ...
} catch (...) {
  ...
}

// XML パーサーの生成
DocumentBuilder safebuilder = dbf.newDocumentBuilder();

上記のコードでは、DTD や外部実体参照などを無効化する設定が加えられており、XXE は発生しません。

このような設定を調整するといった対応はどのライブラリでも概ね変わりません。
脆弱性のあるライブラリを使う場合は常に安全な設定で XML のパースをするようにしましょう。

さいごに

近年 XML を扱うケースは減少している傾向にありますが、いまだに XXE の脆弱性は検出され、CVE が発行されております。

実際、2017 年の OWASP Top 10 にも XXE が登録され、油断ならない脆弱性として認知されました。

XXE が Web アプリケーションに存在した場合、内部の機密データの漏洩などに悪用される可能性があります。
その危険度から、XXE はアプリ開発者が、必ず封じ込めるべき脆弱性であると考えられます。

XXE が OSS ライブラリで発生した場合、ライブラリの脆弱性を定期的に集め、精査する必要が出てきます。

パッチ管理は非常に煩雑ですが、セキュアなアプリ開発においては避けては通れない道です。

yamory では、こういった煩雑なライブラリの脆弱性管理を楽にし、セキュアな開発を支援しております。
ぜひ一度お試しいただければ幸いです。

オープンソース脆弱性管理ツール yamory

  • 利用中のOSSを抽出し
    脆弱性を自動スキャン
  • 脆弱性への対応優先度を自動で分類
  • 組織規模に合わせたプランを選択可能

プロ

有料料金はお問い合わせください

中〜大規模組織向け

無制限の開発チーム作成

開発チームを俯瞰できるセキュリティチーム機能

危険な脆弱性の Slack 連携通知機能

Jira Cloud と連携

シングル・サインオン(SSO)連携*

* SSO 連携は無料トライアルではご利用できません。プロプラン契約後からご利用できます。

お問い合わせ