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