2013年4月10日水曜日

eBook Japanの発表資料に見るパスワードリスト攻撃の「恐ろしい成果」と対策

eBook Japanに対する不正アクセスについて、詳細の発表があり、いわゆる「パスワードリスト攻撃」であることが発表されました。
  • 前回のご報告までは「複数のIPアドレスからログインページに対して機械的に総当たり攻撃等を行う大量アクセス行為(ブルートフォースアタック)」とご説明しておりましたが、詳細調査の結果、「不正の疑われる複数のIPアドレスからログインページに対して、予め持っていたログインIDとパスワードの適用可否を試行する大量アクセス行為」であることが判明いたしました。
  • つまり、大量アクセス行為を仕掛けてきた者は、当社以外の他のサービスなどで他のサービスのログインIDとパスワードを不正に入手し、ユーザーがログインIDとパスワードを共通に設定している可能性を狙って当社サービスに不正にログインしようとし、上記件数についてはログインに成功してしまったということです。
  • そのように判断した根拠は、一つのログインID(メールアドレス)について試行するパスワードの数の少なさです。
【重要なお知らせ】不正ログイン被害のご報告とパスワード再設定のお願いより引用
これに続いて、攻撃されたIDの数と、試行されたパスワードの数が発表されています。元資料を少し加工した表を以下に示します。
原注) 1つのログインIDについてPWを試行した回数は最大5回までであり、1回しか試行していないのに正当したアカウントが半数近くを占めている。
原注) 1つのログインIDについてPWを試行した回数は最大9回までであり、1回だけで断念しているアカウントが9割を占めている。

これらの表から、パスワードの試行回数は合計2821回、ログイン成功したIDは 779個ですから、成功率は約27.6%となります。4分の1を少し超える「打率」は、パスワードに対する攻撃としては驚異的です。
奥一穂さんから指摘をいただました。無関係のサイトからのリストで、4分の1超のIDが重複することが不自然なので、上記「ログイン失敗分」とは、ログインには成功していないがIDとしては同じものが存在するもの、などのフィルタリングが入っているのではないか、とのことです。その可能性はありますが、発表資料にそのような記載はないので、この記事はそのままとし、可能性の言及にとどめます(9:03追記)。

また、攻撃者が同一IDに対して最大9個のパスワードを試していることも興味深い。断定はできませんが、9以上のサイトから流出したIDとパスワードを組み合わせて攻撃に用いていると考えるのが自然でしょう。既に、ブラックマーケットなどで、この種のリストが流通している可能性もあります。
今年に入り、eBook Japan以外にも、パスワードリスト攻撃が疑わしい事例は多数あります。利用者側とサイト側で、とれる対策を以下に説明します。

利用者側の対策

パスワードリスト攻撃の場合、一番悪い当事者はパスワードを漏洩したサイトであるわけですが、漏洩元も分かっておらず責任を追及することも難しいので、利用者側で、サイト毎に異なるパスワードをつけることで自衛するのが賢明でしょう。加えて、最低8文字で、辞書に載っている単語そのもの、ログインIDと同じパスワード、サイト名やサービス名をパスワードとして使うことなどは避けましょう。

サイト側の対策1(パスワードリスト攻撃から利用者を守る)

パスワードリスト攻撃対策として、サイト側で簡単にとれるものは残念ながら思いつきません。eBookJapanの発表では、「同一のIPアドレスからログインフォームへのアクセスに制限をかけました。」とあります。これは被害の緩和策としては有効ですが、打率が4分の1を超えると想定すると、アクセス制限が掛かる前に、ある程度の攻撃は成立してしまいそうです。
効果的な対策としては、Googleなどが実施している2段階認証があります。サイト側も利用者側も負担がありますが、パスワードリスト攻撃への効果は十分です。
利用者側の負担の少ない方法としては、リスクベース認証があります。リスクベース認証の例として、郵貯ダイレクトの例を引用します。

パスワードリスト攻撃は、「普段と異なるアクセス環境」からログインしてくるので、その場合パスワードとは別の手段で認証を要求するわけです。
※ゆうちょダイレクトの場合は、パスワードを入力する前にアクセス環境を判断しますが、IDとパスワードで認証してからアクセス環境を判断する実装もあります

アクセス環境の判断材料としては、ISP(特に地域)、ブラウザや端末の種類、アクセスの時間帯などがあります。また、上図の「合言葉」の代わりに、登録済みのメールアドレスにトークン(6桁程度のランダムな数字)を送信して、画面入力してもらってもよいでしょう。

あるいは、2段階認証やリスクベース認証を実装ずみの認証プロバイダを活用することも有力な解決策です。以下は、ガンホーゲームズのログイン画面です。
ガンホーの場合独自のパスワード認証も使えますが、OpenIDで会員登録する場合はガンホー独自のパスワードを設定する必要はありません。これにより、独自のパスワードを管理する負荷を減らし、認証の安全性を高めることができます。

また、地味ですが、サイトの負荷などの監視も重要です。eBookJapanの場合も、攻撃に気づいたきっかけはサイトの負荷急増でした。加えて、パスワード試行回数や認証失敗の回数・率なども監視するとよいでしょう。

サイト側の対策2(パスワードそのものの保護)

ユーザのパスワードを預かるWebサイトの責務として、パスワードの保護も重要です。最低でもソルトつきハッシュ、できればストレッチングを追加して保護したいところです。詳しくは、徳丸の過去記事本当は怖いパスワードの話を参照下さい。

まとめ

最近急増しているパスワード認証に対する攻撃の例として、eBookJapanに対するパスワードリスト攻撃について紹介しました。自サイトで脆弱性対処をしっかりやっていても、他サイトで漏洩したIDとパスワードを悪用されると、対策が難しくなります。
 以下の対策を推奨します。
  • 利用者は、自衛のためサイト毎に異なるパスワードを設定する
  • サイト運営者は、2段階認証やリスクベース認証の導入を検討する
  • 自力で高度な認証の実装が困難な場合は、外部認証の導入を検討する
  • パスワード設定画面に、他サイトとは異なるパスワードをつけるように注意喚起する(追記)
  • IPアドレス単位で認証試行回数や認証失敗回数によるアクセス制限を行う(緩和策として追記)
  • サイトの負荷、ログイン試行数、ログイン失敗数などの監視を実施する
  • パスワードの漏洩に備えて、パスワードの保護(ソルトつきハッシュ、ストレッチング)を導入する

2013年4月4日木曜日

PHPのdisplay_errorsが有効だとカジュアルにXSS脆弱性が入り込む

先に、「CVE-2008-5814を巡る冒険」にて、CVE-2008-5814脆弱性があるとdisplay_errorsがOnの環境下でXSS脆弱性となる場合があることを説明しました。しかし、display_errorsがOnの環境下ではCVE-2008-5814脆弱性がなくても、XSS脆弱性となる場合がしばしばあります。
これは、display_errorsによるエラーメッセージ表示がHTMLエスケープされていないことが原因です。簡単なサンプルを以下に示します。
<?php
  ini_set('display_errors', 1); // display_errorsを有効にする
  $a = array();                 // 配列の生成
  $index = $_GET['x'];          // 配列のインデックスを得る
  $b = $a[$index];              // 配列の要素にアクセス
このスクリプトに、x=<script>alert(1)</script> というクエリ文字列をつけて呼び出すと下記の表示になります(error_reporting設定でE_NOTICEが有効になっている場合です。E_NOTICEはphp.ini上の設定がない場合は無効ですが、多くのphp.ini設定では有効となっています)。


この際のHTMLソースは以下の通りです。
Notice: Undefined index: <script>alert(1)</script> in /var/www/de.php on line 5
Undefined indexとして、外部から与えたパラメータが「そのまま」(HTMLエスケープされずに)出力されています。 

PHPの脆弱性なのか?

これはPHPの脆弱性なのでしょうか。CVE-2008-5814が脆弱性なのであれば、こちらも脆弱性と見るべきだと思います。そうしないと辻褄があいません。 
一方、display_errorsはデバッグ用の設定なのだから、本番環境に適用する方が悪いという意見もあるでしょう。この立場に立てば、上記はPHPの脆弱性ではないという見方もあり得ます。その場合は、CVE-2008-5814も(実は)脆弱性ではないとしなければなりませんが…
しかし、仮にPHPの脆弱性でないとしても、display_errorsによるエラー表示はHTMLエスケープすべきだと考えます。そうしないと、正しい表示にならないからです。
先の例では、Undefined indexとして表示すべき内容は、<script>alert(1)</script> という文字列であって、JavaScriptの実行ではありません。display_errorsによるエラー表示はHTMLエスケープしなければ、正しい表示にならない場合があります。すなわち、HTMLのエスケープ漏れはPHPの仕様上の不具合と言えます。そして、仕様上の不具合(バグ)が環境依存とはいえ脆弱性になり得るとすれば、結局PHP自体の脆弱性と言わざるを得ないと徳丸は考えます。

プログラミングでなんとかならないのか?

配列の未定義のキーによるアクセスを防ぐこと自体は難しくありません。たとえば、array_key_exists関数を使って、キーの存在を事前に確認する方法。
$b = array_key_exists($index, $a) ? $a[$index] : null;
同じくisset()を使う方法。
$b = isset($a[$index]) ? $a[$index] : null;
エラー制御演算子(@)を使ってエラー表示を抑止する方法。
$b = @$a[$index];
すべての箇所で上記を徹底すれば、Undefined indexの警告によるXSSは防げるはずです。しかし、その他のエラー・警告も含め、すべてを漏れなくなくすことは難しいため、display_errorsはやはり無効にすべきです。

display_errorsは本番環境ではOffにしよう

従来から、セキュリティ上の理由からデバッグ目的のエラー表示は抑止すべしと言われています。その主な理由は下記の通りです。
  • デバッグ用のエラーメッセージが攻撃者にとってヒントになる場合がある
  • エラーメッセージを通じて情報漏洩させる手口がある(→参考
  • そもそもユーザフレンドリでないし、みっともない(セキュリティ上の理由ではない)
加えて、クロスサイトスクリプティングという具体的な脅威になり得ると言うことです。一般論としても、エラー画面にはクロスサイトスクリプティング脆弱性が生じやすいという経験則があります。よって以下にお気をつけ下さい。
  • display_errorsなどデバッグ用のエラー表示は本番リリース前にオフにする
  • エラー表示は利用者向けに必要最低限の表示にとどめ、問題解決用の情報はログとして出力する
  • エラーメッセージにはクロスサイトスクリプティングが入りやすいので注意(と言っても、特別なことをするわけではなく、エラー表示だからと言って手を抜かずにHTMLエスケープしろということです)

2013年4月1日月曜日

twitterに学ぶなりすまし投稿対策

先日もtwitter上の犯行予告により20歳の青年が逮捕されたようですが、なりすましによる誤認逮捕ではなかったのか気になるところです。そこで、twitterが、なりすまし投稿をどの程度対策しているかを調べてみることにしました。twitterの安全性を確認することが目的というよりも、twitterが実施している対策を知ることにより、皆様のWebサイトを安全にする参考にしていただければと思います。

今回調べた「なりすまし投稿」の手法は下記の通りです。
  • クロスサイト・リクエスト・フォージェリ(CSRF)
  • クロスサイトスクリプティング(XSS)
  • HTTPヘッダーインジェクション
  • クリックジャッキング
  • DNSリバインディング
  • クッキーモンスターバグ
このうち、上の5つの解説は拙稿「“誤認逮捕”を防ぐWebセキュリティ強化術」、最後のクッキーモンスターバグについては、過去のエントリ「クッキーモンスターバグがあると、IPアドレス偽装防止のCSRF対策が回避される」をご覧下さい。
それでは、はじめます。

CSRF

CSRF対策として、twitterは標準的なトークンによる対策を採用しています。以下は、書き込み時にPOSTされるデータの例です。大文字のXは伏せ字です。
authenticity_token=ee09XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX63faec&place_id=&status=【投稿内容】
authenticity_tokenがトークンですね。ワンタイムではない、セッション限りのトークンを使っているようですが、これで問題ありません。

試みに、authenticity_tokenを一文字だけ改変してツイートしてみると、以下のようにエラーになります(Ajax/JSONのリクエストなので、エラー表示は呼び出し側でします)。
HTTP/1.1 403 Forbidden
【以下略】
ということで、CSRFについては対策されています。

XSS

twitterはXSSの悪用事例が2009年(JS.Twettir)と2010年にあるので、XSS攻撃を完璧に防御しきっているわけではありませんが、世界中の著名なセキュリティ研究者やバグハンターがtwitterの脆弱性を調べ、twitter側もそれを改修していので、現在では、未知のXSSを見つけるのは難しいと思います。
twitter社は年度毎に脆弱性報告の貢献者を表彰しており、このページから見ることができます。日本人も、小菅さん、kinugawaさん、malaさん、はせがわようすけさんなど、日本の錚々たるバグハンターが名を連ねているので、彼らが見つけていないXSS脆弱性を探して犯行予告なりすましに使うのは相当困難ではないか思います。

HTTPヘッダーインジェクション

HTTPヘッダーインジェクションについてはきちんと調べていないのですが、HTTPヘッダーインジェクション脆弱性が入る局面は、HTTPレスポンスヘッダ(Set-Cookie、Locationその他)の出力内容に外部からコントロールできる値を含む場合であり、twitterのレスポンスには、そのような箇所が見あたりません。おそらく大丈夫だろうということと、仮にHTTPヘッダーインジェクション脆弱性がtwitterにあれば、先のバグハンター達が見つけてくれるでしょう(他力本願)。

クリックジャッキング

先のエントリ「IPAから「クリックジャッキング」に関するレポート出ました」にて、X-FRAME-OPTIONSヘッダを出力している数少ないサイトの1つがtwitterであることを説明しました。このため、利用者がモダンなブラウザを使う限り、クリックジャッキングには対策済みということになります。
問題は、利用者がIE6やIE7を使っている場合です。これらはまだメンテナンスされているブラウザですが、X-FRAME-OPTIONSには対応していません。そこで、twitterがIE6/IE7に対して、クリックジャッキング対策をどのようにしているのか、あるいはしていないのかを調べてみました。
まず前提として、現在IE6/IE7でtwitterを利用しようとすると、mobile.twitter.comにリダイレクトされます。


このため、IE6/IE7ユーザ向けの罠としては、mobile.twitter.com用の罠に変更する必要があります。これをIE7で閲覧すると下記の状態になります。


後は、これをクリックジャッキング用の罠のiframeに入れてやる訳です。IE7はX-FRAME-OPTIONSに対応していないので大丈夫だろうと試してみると…以下のようになります。


twitterにログインした状態なのに、iframeの中はログイン画面になっていますね。これは、twitterがP3Pコンパクトポリシーを出力していないので、iframeの中のページはCookie(第三者Cookie)の送信がブロックされているためです。ブラウザのステータスバー中央に目のアイコンに停止マークがついていますが、ここをダブルクリックして詳細を確認しましょう。



たしかに、P3Pコンパクトポリシーが送信されていないために、罠サイトからは第三者CookieとなるtwitterのCookieがブロックされています。大丈夫のようですね…
でも、絶対に大丈夫でしょうか。IEの設定を変更している場合は、P3Pコンパクトポリシーがなくても第三者Cookieを送信する可能性があります。
その設定は、インターネットオプションのプライバシータブで変更可能です。まず、IEのデフォルトを確認しましょう。



デフォルトではCookieの設定は「中」で、「コンパクトなプライバシーポリシー(注:P3Pコンパクトポリシーのこと)のないサードパーティのCookieをブロックします」とあります。これが効いている訳ですね。
ここで、好ましくはありませんが実験のため、プライバシーの設定を最低レベルにしてみましょう(試す場合は、試験環境で実行するか、元に戻すのを忘れずに)。


これで、Cookieの制約はなくなりましたので、先ほどの罠をもう一度閲覧してみましょう。


あれあれ、アドレスバーに注目ください。iframeに入れたはずなのに、iframeを飛び出してしまいました。これでは、騙される人はいなさそうです。どうもJavaScriptによる対策が入っているようですね。調べてみると、以下のコードがあります。
if (window.top !== window.self) {document.write = "";window.top.location = window.self.location; // 以下略
これは、IPAの『「クリックジャッキング」に関するレポート』にも紹介されているコードで、iframeなどに入れられていないかチェックして、入れられている場合は、自身をトップレベルに表示させるようにするものです。但し、この種の手法の回避策も研究されているため、これだけで完全な対応というわけではありません。だからこそ、X-FRAME-OPTIONSという機能追加が必要であったわけですが、詳しくはIPAの『「クリックジャッキング」に関するレポート』を参照下さい。

ここでは、元々利用者がJavaScriptを無効にしていたというシナリオで、罠のサイトを閲覧してみます。


ようやく、twitterの画面をiframeに入れることができました。あとは、今は見ているtwitterの表示を「透明にして」、その下に、利用者が思わずクリックしたくなる罠の表示を入れるだけです。

ここまでを振り返って、IEで、twitterの画面をiframeに入れるために何をしなければならなかったを振り返って見ると、以下の通りです。
  • 古いバージョンのIE(IE7以前)を使う
  • プライバシーの設定を最低(すべてのCookieを受け入れ、送信)
  • JavaScriptを無効にする
これは率直に言って、利用者側の自業自得感が相当ありますね。
立場を変えて、twitterがどのような対策をしたかを以下にまとめます。
  • HTTPレスポンスヘッダ x-frame-options: SAMEORIGIN を送信
  • P3Pコンパクトポリシーを送信しない
  • JavaScriptによるクリックジャッキング対策コード
現時点の最高レベルの「なりすまし犯行予告対策」をしたいWebサイトは、上記を参考にすると良いでしょう。

DNSリバインディング

一般的にDNSリバインディング攻撃でも「なりすまし犯行予告」は可能ですが、DNSリバインディングでは利用者のセッションをハイジャックすることはできません。その理由は、DNSリバインディング攻撃の場合、利用者な罠のドメイン名で攻撃対象サイトをアクセスするため、利用者のセッションは有効にならないためです。
それでは、なりすましできないじゃないかと思ってしまいますが、なりすまし犯行予告で重要な「なりすまし」要素はリモートIPアドレスであって、利用者のアカウントではありません。この前提であれば、DNSリバインディング攻撃が使えます。その手順は以下の通りです。
  1. 攻撃者は自分の用意したアカウントでログインしてCookieを調べる
  2. 攻撃者はDNSリバインディングの罠を用意する。罠を閲覧した利用者のブラウザに対して、前項で調べたCookieをセットする
  3. 利用者からの閲覧があった直後に、罠のドメイン名に対するAレコードを書き換え、攻撃対象のIPアドレスをセットする。TTLは元々短い値(1秒など)とする
  4. 利用者が閲覧した罠から、時間をおいて(10秒~数分)罠のドメイン名にXMLHttpRequestでアクセスする。罠のドメイン名は攻撃対象サイトのIPを指しているので、実際には攻撃対象にアクセスする
  5. 利用者のブラウザは、攻撃者の用意したアカウントで攻撃対象サイトにログインした状態になる
  6. XMLHttpRequestでCSRF対策のトークンを調べ、正しいトークンをセットして「なりすまし投稿」を行う(twitterの場合であれば、セッション毎に固定のトークンなので、攻撃者があらかじめてトークンを調べて罠にセットしてもよい)
この、DNSリバインディング + セッションフィクセイションのような攻撃については、過去に「誤報訂正:『2010年に最も警戒すべきセキュリティ脅威は「DNSリバインディング」』報道について」で取り上げています。当時は、『なんとなくトホホ感の漂う攻撃手順だ。ホンマカイナ。』と批評した手順ですが、思わぬところで利用価値(?)が出てきました。

さて、DNSリバインディングへの対策ですが、前述の通り攻撃対象サイトには「罠のドメイン名」がHostヘッダに入ります。XMLHttpRequestではHostヘッダは変更できないため、HostヘッダのチェックでDNSリバインディング対策できます。
それでは、twitterはどうでしょうか。自分の持つドメイン名に twitter.comのIPアドレスをセットしてアクセスしてみると下記の応答になります。
HTTP/1.0 404 Not Found
date: Sun, 31 Mar 2013 15:34:54 UTC
server: tfe
strict-transport-security: max-age=631138519
Content-Length: 0
ちゃんと対応していますね。随分素っ気ないレスポンスですが、攻撃者に対してこれでよいのでしょう。
簡単にDNSリバインディング対策する方法として、Apacheのバーチャルホスト機能を使う方法があります。
NameVirtualHost *:80
# ダミーのバーチャルホスト
<VirtualHost _default_:80>
  DocumentRoot /var/www/dummy
  ErrorDocument 404 /index.html
</VirtualHost>

# 本番のバーチャルホスト
<VirtualHost>
  ServerName www.example.jp
# 以下略
VirualHostで _default_ を指定することで、「その他の」Host指定に対するリクエストをここに集めることができます。通常はエラー表示を出すようにしておけばいいでしょう。

クッキーモンスターバグ

以前のエントリ「クッキーモンスターバグがあると、IPアドレス偽装防止のCSRF対策が回避される」にて、地域型JPドメイン名や都道府県型JPドメイン名(などクッキーモンスターバグのおこるドメイン名)を使うと、トークンによるCSRF対策が回避されると説明しました。認証のあるサイトでも、前項と同じように攻撃者のアカウントでログインさせて、利用者(被害者)のリモートIPアドレスで「犯行予告」を書き込みできる場合があります。詳しくは、先のエントリをご覧ください。
では、twitterはどうかというと、.comドメイン名なので、クッキーモンスターバグの影響はない、すなわち大丈夫です。

まとめ

twitterを題材として、「なりすまし投稿対策」がどのように実施されているかどうかを調べました。
まとめると以下の通りです。
  • トークンによるCSRF対策
  • XSS対策(脆弱性報告者の表彰)
  • HTTPヘッダーインジェクション対策(あるいは該当箇所なし)
  • クリックジャッキング対策(下記)
    • X-FRAME-OPTIONSヘッダの送信
    • P3Pコンパクトポリシーを送信しない
    • JavaScriptによるクリックジャッキング対策
  • Hostヘッダのチェック
  • クッキーモンスターバグの影響を受けないドメイン名の採用
なりすまし投稿が重大な影響を及ぼす場合、twitterの取り組みが参考になるでしょう。
一方、以下のような場合はtwitterによるなりすまし投稿があり得ます。こちらは利用者が気をつけるしかなさそうです。
  • 利用者がフィッシング詐欺に引っかかってIDとパスワードを盗まれた場合
  • 悪質なtwitterアプリの連携を利用者が許可してしまった場合
  • 使用しているtwitterアプリに脆弱性があり、悪用された場合

フォロワー

ブログ アーカイブ