2017年版OWASP TOP 10がリリースされました。新しくA4としてXXE、A10としてInsufficient Logging & Monitoringが入りました。今回はXXE対策を紹介ます。XXE対策は簡単です。
XXEは「リクエストのインジェクション」と考えると解りやすく、「リクエストのインジェクション」と理解すれば他の類似攻撃パターンにも応用できます。
自分で直接XMLモジュールのクラス/関数を使ってXML処理している場合は問題箇所は判り易いですが、ライブラリなどを使う場合は知らずにXXEに脆弱になりえます。外部XML文書を処理する場合、XML処理ライブラリは盲信するのではなく、XXEに脆弱でないか検証してから使わないとなりません。
XXE攻撃とは?
XML eXternal Entities(XXE)攻撃はクロスサイト攻撃/SSRF攻撃と同類の攻撃です。XML文書の外部DTDを利用してファイアーウォール内の他のサーバーに攻撃を行います。
攻撃に利用するXML文書パターン
<!-- Scenario #1: The attacker attempts to extract data from the server: --> <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" >]> <foo>&xxe;</foo> <!-- Scenario #2: An attacker probes the server's private network by changing the above ENTITY line to: --> <!ENTITY xxe SYSTEM "https://192.168.1.1/private" >]> <!-- Scenario #3: An attacker attempts a denial-of-service attack by including a potentially endless file: --> <!ENTITY xxe SYSTEM "file:///dev/random" >]>
上記のSenario #2のXXE攻撃はSSRF攻撃の一種です。攻撃を図にすると以下のような攻撃になります。その他の攻撃パターンは以下のページが役立ちます。
https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XXE%20injections
信頼できないXML文書を外部DTD読み込みをサポートするXMLパーサー(PHPの場合、DomDocumentやSimpleXMLなど)で処理すると
- 処理したシステムのファイルを読み取られれる
- 他のシステムに有害なリクエストが送信される(データ改ざん&漏洩)
- サービス不能状態になる
などの問題が発生します。Scenario #1のローカルファイル読み取り攻撃はSELinuxで防げる場合があります。
XXEはPHPでも問題になり2013年にSOAPはデフォルトでDTDを読み込まないよう修正されています。この修正より前にlibxml2のDTDロードはデフォルトで無効化されています。XXE自体は何年も前から知られ、対策されている問題です。
今回、新しくXXEがOWASP TOP 10にランクインしたのはXXE対策がないアプリケーションが多数あることが理由だと思われます。
XXE防御攻撃の防御
先に紹介した攻撃パターンから解るように、攻撃にはXML文書の外部エンティティ定義をロードする仕様が悪用されています。つまり悪意のある外部エンティティ定義をロードしないようにすれば攻撃されません。(注意:許可するエンティティ定義はホワイトリスト型で定義すること!)
PHPのXML処理はlibxml2を使っています。現在のlibxml2はデフォルトでは外部エンティティをロードしないようになっています。古いlibxml2を使っている場合でも、PHPは外部エンティティをデフォルトではロードしません。
ロードしないので問題なし!で終われば良いのですが、デフォルトでロードしていないくてもDTDをロードすることが可能です。DTDをロードしてXML文書をバリデーションしなければならない場合もあるでしょう。
XXE攻撃を防御するには以下の手順でXML文書を処理します。
- DTDを読み込まずにXML文書を読み込む
- DTD定義を取り出し、読み込み先をバリデーションする
- 大丈夫な場合、外部DTDを利用してXML文書をバリデーションする
DTDを読み込まずにXML文書を読み込む為には、DTDがロードされる状態を知る必要があります。
DTDがロードされる場合
libxml2定数のLIBXML_DTDLOADフラグを有効にしてXML文書を読み込む
例:
- DOMDocument::loadの第二引数で指定
- SimpleXMLElementをnewする場合に第二引数で指定
libxml2を利用したモジュールはPHPにバンドルされている物だけ、とは限りません。3rdパーティー製のモジュールも利用している場合があります。XML文書読み込み時にLIBXML_DTDLOADをフラグとして渡す関数/メソッドはリスクがある、と考えてください。
XML処理で外部DTDを読込む機能を利用する
例:
- DOMDocument::validateを実行する
- DOMDocument::$resolveExternalフラグをTrueにしてXML文書を読み込む
XML文書のバリデーションにはDTDが必要です。明示的にDTDを読み込む処理や設定利用して実行すると、外部DTDの読み込みが発生します。
まとめ
PHPでXXE対策するのは難しくありません。DTDを読み込んだり、DTDバリデーションをしていない場合、特に何もしなくてもXXE攻撃に脆弱にはなりません。
ただし、DOMDocument::validateのように自動的にDTD読み込みを行うメソッドもあります。XML文書のバリデーション、外部エンティティの解決などの機能を持つメソッドを利用する場合、どのような仕様なのか確かめて使うと良いでしょう。(特に、3rdパーティー製のXML処理モジュール)
DTDを読み込む必要がある場合、先にDTDを読み込まずにXML文書をロードし、DTDのURLを取得&バリデーション、OKならDTDを使ったバリデーションする、でXXE攻撃を防止できます。
URLをバリデーションする際には、間違える可能性がある正規表現は使わない方が良いです。parse_url関数を利用しましょう。