2013年5月22日水曜日

phpMyAdmin3.5.8以前に任意のスクリプト実行を許す脆弱性(CVE-2013-3238)

phpMyAdmin3.5.8以前にはpreg_replace関数の呼び出し方法に不備があり、ログインしたユーザが指定した任意のスクリプトを実行できる脆弱性CVE-2013-3238があります。

概要

phpMyAdminの「テーブル名の接頭辞を付け替える」および「接頭辞を付け替えてテーブルをコピーする」操作の際に、付け替え元および付け替え先のパラメータを操作することにより、任意のPHPスクリプトを実行できます。

影響

phpMyAdminのログインユーザが任意のPHPスクリプトを実行できます。phpMyAdminのユーザは通常サーバー管理者と想定されますが、データベース管理者という権限を越えて、任意のPHPスクリプトをphpMyAdminの動作する権限(apache等に与えられた権限)で実行できることが問題です。

影響を受けるバージョン

影響を受けるのは、phpMyAdmin3.5.8以前です。
NVDの情報では3.5.8には脆弱性がないように読めますが、ソースを確認したところ、3.5.8にも脆弱性があります。
phpMyAdmin3.5.8.1以降、およびphpMyAdmin4.0.0(正式版)以降で対処されています。

ただし、後述するように、PHP5.4.7以降を用いている場合は、この脆弱性の影響を受けません。

再現方法

phpMyAdminにログインして適当なデータベースを選択し、テーブル一覧を表示します。

テーブルを1つ選択し、チェックしたものを:のプルダウンから「テーブル名の接頭辞を付け替える」を選択します。以下の画面が表示されます。

「付け替え元」に「/e」、「付け替え先」に「phpinfo();」を入力します。このまま実行せずに、Proxyツールなどで、/eの後に%00を付加して下さい(下図オレンジ色の部分)。


実行後の表示は下記となり、phpinfo()関数が実行されていることが分かります。(PHPのバージョンが古いのは検証環境なので気にしないで下さい。)


脆弱性の発生するメカニズム

脆弱性の原因はlibraries/mult_submits.inc.php の以下の部分です。
case 'replace_prefix_tbl':
  $current = $selected[$i];
  $newtablename = preg_replace("/^" . $from_prefix . "/", $to_prefix, $current);
上記再現手順の場合、preg_replace関数の呼び出しは以下の引数となります。
preg_replace("/^/e\0/", "phpinfo();", "test");
preg_replace関数(PHP5.4.6以前)の第1引数はバイナリセーフでないため、NULLバイト以降が無視され、結局以下の引数で呼び出されたのと同じになります。
preg_replace("/^/e", "phpinfo();", "test");
ここで、preg_replace関数の第1引数に注目ください。/eは、e修飾子というもので、これが指定された場合preg_replace関数の第2引数をPHPのスクリプトと見なし、解釈実行します。すなわち、eval("phpinfo();")としたのと同じです。このため、phpinfo()関数が実行されることになります。

対策

phpMyAdmin3.5.8.1以降、またはphpMyAdmin4.0.0(正式版)以降にバージョンアップすることで対策となります。当該箇所の差分を確認したところ、preg_replaceをやめ、substrで接頭辞の確認を行い、文字列連結で新しい接頭辞をつけるという手堅い方式に変更されています。賢明な対処と考えます(参照)。


また、回避策としてPHP5.4.7以降の導入が挙げられます。特別な理由がない限りPHPの最新版(現在PHPの最新版は5.4.15)を導入することをお勧めします。ただし、PHP5.3の最新版(5.3.25)では効果がありません。

phpMyAdminの利用者に悪意がある状況を想定しなくてよいケース(phpMyAdminの利用者全員がPHPスクリプト実行と同等以上の権限を元々持っている場合等)では、この脆弱性を許容するという判断もあり得るかと思いますが、できるだけバージョンアップをお勧めします。

脆弱性の根本原因

この脆弱性の根本原因は、preg_replace関数の第1引数の呼び出し方(下図の色をつけたところ)にあります。
preg_replace("/^" . $from_prefix . "/", $to_prefix, $current);
PCRE系関数では正規表現のデリミタ(スラッシュ「/」等)を指定しますが、正規表現の中にデリミタがある場合、デリミタをエスケープする必要があります。例えば、スラッシュ一文字を検索する場合は以下のようにします。
/\//
これを怠っていることが根本原因です。脆弱性のメカニズムとしてはSQLインジェクションのシングルクォートのエスケープ漏れと同じです。
攻撃者の視点で考えると、$from_prefixを単に「/e」とすると、preg_replaceの第1引数は「/^/e/」となり、最後の「/」が余計です。このため、その前にNULL文字を入れて、余計な「/」が見えないようにしています。

また、$from_prefixに [a-z]と言う文字列があれば、任意の英小文字にマッチするという結果になります。応用によっては「それは仕様です」となるでしょうが、phpMyAdminの場合は、そうではなさそうです。
したがって、preg_replaceを活かしたままで脆弱性対処するには、正規表現の特殊文字とデリミタの両方をエスケープする必要がありますが、PHPには、このための関数preg_quoteが用意されています。従って、以下のようにすれば問題ないはずです。
preg_replace("/^" . preg_quote($from_prefix, '/') . "/", $to_prefix, $current);
実際にはpreg_replace自体を避ける形で変更されているわけですが、その理由は以下のようなものではないかと推測します。
  • preg_replaceの仕様が危なっかしいものであり、過去にも脆弱性の原因になってきた
  • preg_quoteの信頼感があまりなかった(憶測)
  • 元々正規表現を必要としない処理である

このような理由から、先に「賢明な対処」と評したわけです。

preg_replaceの代替処理

先に述べたように、preg_replaceには潜在的に危険性があります。この問題について詳しく知りたい方は、寺田さんの素晴らしい記事「preg_replaceによるコード実行」を参照下さい。
preg_replaceの危険性を避けるためには、以下を検討するとよいでしょう。
  • 正規表現は原則として固定とする
  • preg_replace関数のe修飾子の代わりに、preg_replace_callbackを使用する
  • preg_replaceの代わりに、mb_ereg_replaceまたはmb_ereg_replace_callback(PHP5.4.1以降)の使用を検討する

PHP5.4.7におけるPCRE関数の変更

PHP5.4.7以降の環境ではこの問題の影響を受けないことは、以下の記事から知りました。
なお、システムで PHP 5.4.6 以前のバージョンを利用する環境のみが、この脆弱性の影響を受けます。
phpMyAdmin の preg_replace() 関数の不適切な利用に起因する任意コード実行の脆弱性(Scan Tech Report)
PHP5.4.6以前のみで影響を受けるという意味が最初分からなかったので、調べたところPHP5.4.7以降ではPCRE関数(preg_replace等)の正規表現中のNULLバイトをチェックして、もしあればエラーにしています。以下は、preg_replace関数の第1引数にNULLバイトを含む文字列を指定した場合のエラーメッセージの例です。
PHP Warning:  preg_replace(): Null byte in regex in /home/…/preg-nullbyte.php on line 2
PHPのChnage Log等を見てもこの変更は載っていないので、Scanのレポートを書いた方はすごいと思いました。

まとめ

phpMyAdmin3.5.8以前に含まれる、任意のスクリプト実行を許す脆弱性について説明しました。前述のように、この脆弱性の危険度の評価は微妙ですが、極力バージョンアップすることをお勧め致します。
また、preg_replaceの呼び出し方によっては、スクリプト実行の脆弱性を含みやすいという問題があります。phpMyAdminは管理者向けのシステムなので、管理者がPHPスクリプトを実行できても大きな問題ではない環境もあり得ますが、一般利用者が使うアプリケーションで任意スクリプト実行を許す脆弱性が入ると大問題です。この機会に、preg_replace関数の使い方を見直すことを推奨します。
また、PHP5.4.7以降で、PCRE関数の安全性が強化されています。この問題に限らず、極力PHPの新しいバージョンの使用を推奨します。

2013年5月20日月曜日

JSONをvbscriptとして読み込ませるJSONハイジャック(CVE-2013-1297)に注意

はせがわようすけ氏のブログエントリ「機密情報を含むJSONには X-Content-Type-Options: nosniff をつけるべき」にて、巧妙な罠を仕掛けることにより、別ドメインのJSONデータをvbscriptとして読み込み、エラーハンドラ経由で機密情報を盗み出すという手法が紹介されました。これは、IEの脆弱性CVE-2013-1297を悪用したもので、MS13-037にて解消されていますが、MS13-037はIE6~IE8が対象であり、IE9以降では解消されていません。
また、MS13-037を適用いていないIE6~IE8の利用者もしばらく残ると考えられることから、この問題を詳しく説明致します。サイト側の対策の参考にして下さい。

問題の概要

JSON形式のデータは、通常はXMLHttpRequestオブジェクトにより読み出しますが、攻撃者が罠サイトを作成して、vbscriptを指定したscript要素により呼び出す攻撃です。JSONはvbscriptとして実行するとエラーになりますが、エラーハンドラに渡される引数としてJSONデータが入るため、通常は読み出せないJSON経由で秘密情報が漏洩します。

影響を受けるサイト

秘密情報を含むJSONを生成しているサイト。

影響

攻撃対象サイトにログイン中である利用者が、攻撃者の用意した罠サイトを閲覧すると、秘密情報を含むJSONデータが漏洩します。

説明のためのサンプルサイトの説明

攻撃手法を説明するためのサンプルアプリケーションを用意致しました。これは、秘密のメモを登録して、後から参照できるというものです。Cookieによるセッション管理をしていて、セッション変数に「秘密のメモ」が入っています。
まずは、入力フオーム(form.html)です。
<form action="upmemo.php" method="post">
秘密メモ<input name="memo"><br>
<input type="submit" value="登録">
</form>
これは簡単ですね。次に、投稿を受け付けるupmemo.phpのソースです。受け取ったメモをセッション変数に保存するだけです。
<?php
  session_start();
  $memo = isset($_POST['memo']) ? $_POST['memo'] : '';
  $_SESSION['memo'] = $memo;
?>
<body>
メモを受け付けました:<?php echo htmlspecialchars($memo, ENT_NOQUOTES, 'UTF-8'); ?><br>
<a href="display.html">メモを表示する</a>
</body>
メモを表示するdisplay.html。メモの実体はgetMemo.phpからJSONとして受け取って表示します。JavaScriptの処理はjQueryを用いています。
<html>
<head>
<script src="jquery-1.9.1.min.js"></script>
<script>
$(function() {
  $.ajax({
    dataType: 'json',
    url: 'getMemo.php'
  }).done(function(json) {
    $('#memo').text(json[0].memo);
  });
});
</script>
</head>
<body>
secret memo:<div id="memo"></div>
</body>
</html>
getMemo.phpのソースです。セッションに保存した「秘密のメモ」をJSON形式に変換して表示しています。
<?php
  header('Content-Type: application/json; charset=utf-8');
  session_start();
  $json['memo'] = $_SESSION['memo'];
  echo json_encode(array($json));
皆様がこのサンプルを動かせる環境を用意しました(http://ex.tokumaru.org/form.html)。実行例を以下に示します。秘密のメモとしては、「The king has donkey ears」(王様の耳はロバの耳)を用いました。


登録ボタンを押すと、以下のようにメモが受け付けられます。


「メモを表示する」というリンクをクリックすると、以下のようにメモが表示されます。


この際に、getMemo.phpが返すJSONは下記となります。
[{"memo":"The king has donkey ears"}]

JSONはなぜ安全か?

ここで、今回の攻撃手法を説明するための前提条件として、JSONを用いて秘密情報を交換できる理由を先に説明しましょう。
JSONを用いて秘密情報をやり取りできる根拠は以下によります。

  • ブラウザには同一生成元ポリシー(Same Origin Policy; SOP)、CORS(Cross-Origin Resource Sharing)という保護機能がある
  • アプリケーション側で、ブラウザ側保護機能を活用する呼び出し方をしている

以下、具体的な脅威2点を紹介する形で、JSONの安全性について説明します。

脅威1. クロスドメインのXMLHttpRequest

通常、JSONの呼び出しにはXMLHttpRequestオブジェクトを用います。古典的なブラウザでは、XMLHttpRequestでは同一オリジン(ホスト名、ポート番号、スキームが全て同じ)のリソースからのみ読み込みができるようになっていました。これが、同一生成元ポリシー(SOP)です。
しかし、クライアントサイドのマッシュアップをクロスドメインで行いたいというニーズか高いことから、クロスドメインでXMLHttpRequstが使えるように拡張されています。これをXMLHttpRequest Level 2(XHR2)と呼びますが、オブジェクトの名称はXMLHttpRequestのままです。このため、従来安全だったアプリケーションが急に脆弱になると困るため、安全性の互換性を保つ目的で、XHR2には以下の制限があります。これがCORS(Cross-Origin Resource Sharing)です。
  • XHR2では、異なるオリジンに対してもリクエストは送信される
  • ただし、デフォルトではリクエストにCookieは付かない
  • デフォルトでは、レスポンスが返ってきてもXHR2はそれを捨ててしまい、JavaScriptコンテンツでは受け取れない
  • レスポンスにAccess-Control-Allow-Originヘッダがあり、これに指定されたオリジンが呼び出し元とマッチした場合は、XHR2はレスポンスを返し、JavaScriptコンテンツが受け取ることができる
  • リクエストにCookieをつけたい場合は以下を全て行う
    • 呼び出し側がXHR2のwithCredentialsプロパティにtrueをセットする(これだけでリクエストにCookieがつく)
    • 呼び出された側はAccess-Control-Allow-Originヘッダにワイルドカードを指定できず、オリジンを明示的に指定する(http://example.jp 等)(これがないとレスポンスが受け取れない)
    • 呼び出された側はレスポンスヘッダAccess-Control-Allow-Credentials: trueを出力する(withCredentialsプロパティをtrueにした場合、これがないとレスポンスが受け取れない)
デフォルトではリクエストは送るけど、レスポンスは捨ててしまうという挙動を不自然に思う人がいるかもしれませんが、例えばJSONのリクエストをimg要素やiframe要素で呼び出す場合も、リクエストは送られるがレスポンスは受け取れない(JavaScriptからは読めない)ので、このあたりの従来の挙動とバランスをとり、リスクが増えないように考慮したのでしょう。

さて、少し話がそれますが、XHR2を用いてクロスドメインでJSONを呼び出すサンプルを紹介します。 まず、getMemo.phpの冒頭に2行追加して、getMemo2.phpとします。冒頭のみ紹介します。
<?php
  header('Access-Control-Allow-Origin: http://evil.dwd.jp'); // 追加
  header('Access-Control-Allow-Credentials: true');  // 追加

  header('Content-Type: application/json; charset=utf-8'); // 以下同じ
呼び出し側のdisplay.htmlのajaxメソッド呼び出し部分を以下のように修正します。
  $.ajax({
    dataType: 'json',
    url: 'http://ex.tokumaru.org/getMemo2.php',
    xhrFields: {
       withCredentials: true
    }
  }).done(function(json) {
この修正後のdisplay.htmlをhttp://evil.dwd.jp/display.htmlとして用意致しました。IE10か、IE以外のブラウザで参照下さい。先に、メモを登録することをお忘れなく。
さて、攻撃者の視点で考えますと、上記の改変のうち、呼び出し側は「攻撃者の罠」になるので、上記のようにすることは容易です。一方、データの提供側、すなわち攻撃対象は、攻撃者が改変することはできません。このため、(アプリケーションに脆弱性がない限り)データを想定外のドメインから読み出される心配はありません。

脅威2. script要素によるJSON読み込み

次に検討するのは、JSONをscript要素から読み出されることはないか、という脅威です。具体的には、以下のような罠を作れないかと言うことです。
<!-- JSONをscript要素のsrc属性で指定して読み出す -->
<script src="http://ex.tokumaru.org/getMemo.php"></script>
<script>
// 呼び出したJSONを読み出す仕掛け
</script>
結論としては、このような罠(JSONを読み出す仕掛け)を作ることできません。JSONはJavaScriptとしてバリッドではありますが、単なるデータであるため、動作としては何もしません。以下のようなオブジェクトがゴロンと転がっているだけであり、なにも作用がないので、読み出せないのです。
[{"memo":"The king has donkey ears"}]
このように書くと、読者の中には、「そうは言っても、JSONPと言うものがあって、script要素を使ってクロスドメインの呼び出しができるではないか」と思われる方もおられると思います。しかし、JSONPの場合は、データを取り出せるように、データ提供側で関数呼び出しの形にするわけです。
foo([{"memo":"The king has donkey ears"}]);
この関数fooをコールバック関数と呼び、呼び出し側が関数名を指定し、データ提供側がその関数を呼び出すようにJSONPデータを生成します。
すなわち、この場合でも、データ提供側がクロスドメインで呼び出させることを考慮しない限り、クロスドメインでJSONデータを読み出されることはありません。

古典的なJSONハイジャック

前述のように、JSONをscript要素で呼び出しても、呼び出し側のJavaScriptでJSONを読み出すことはできないはずなのですが、これを読み出すテクニックが考案されました。これをJSONハイジャックと言います。
具体的には、以下の2種類が知られています。
  • Arrayオブジェクトのコンストラクタを再定義する
  • setterを使う
以下は、setterを定義して、memoプロパティを読み出すスクリプトです。
Object.prototype.__defineSetter__("memo",
 function(v) {
  // ここで memo プロパティの値が v として読み出せる
 });
おいおい、話が違うではないか、と思われるかもしれませんが、現在では上記でJSONが読み出せるのはブラウザの脆弱性と考えられていて、モダンなブラウザでは対策済みです。しかし、古いAndroidの標準ブラウザでは、まだこの脆弱性が残っていて、アプリケーション側での対処が望ましい状況でした。詳しくは、私の以前のエントリを参照して下さい。

vbscriptとしてJSONを読み出す方法

ようやく本題です。古典的なJSONハイジャックはモダンなブラウザでは対策されていますが、IE限定ながら、script要素で読み込んだJSONデータを読み出す方法が発見されました。
それは、JSONをvbscriptと指定して読み込む方法です。JSONは通常はvbscriptとして実行できないのでエラーになりますが、そのエラーメッセージに、JSONの一部を含む場合があります。概念的なサンプルを以下に示します。
<script>
  window.onerror = function(e) {
    // エラーメッセージeにJSONの一部が入る場合がある
  }
</script>
<script src="http://ex.tokumaru.org/getMemo.php" language="vbscript"></script>
先のJSONオブジェクトをvbscriptとして読ませた場合のエラーメッセージは下記となります。
型が一致しません。: '{"memo":"The king has donkey ears"}'
ばっちり、JSONの中味が入っていますね。これを使って、第三者の秘密情報を盗み出す罠を作れます。罠の例を以下に示します。
<html>
<head>
<script>
  window.onerror = function(e) {
    if (e.match(/'({"memo".*)'$/)) {
      var msg = 'JSONハイジャックに成功:' + RegExp.$1;
    } else {
      var msg = 'JSONハイジャックに失敗:' + e;
    }
    var divmemo = document.getElementById('memo');
    var text = document.createTextNode(msg);
    divmemo.appendChild(text);
  }
</script>
</head>
<body>
これは罠サイトです(IE限定)
<div id="memo"></div>
<script src="http://ex.tokumaru.org/getMemo.php" language="vbscript"></script>
</body>
</html>
window.onerrorイベントで、前述のエラーを捕捉しています。この中で、エラーメッセージからJSON部分を切り出して、DOM操作で画面に表示しています。http://evil.dwd.jp/wana.htmlにデモを用意しました。

※注意
  • 罠を閲覧するのは、攻撃者ではなく、攻撃対象サイトの一般利用者です
  • 言うまでもないことですが、実際の罠サイトには「これは罠サイトです」と断ってはありません
  • 実際の罠サイトでは、盗んだ情報は画面に表示せず、攻撃者が管理するサーバーに送信します

MS13-037での修正

前述のように、上記攻撃が成立するのはIEのバグ(脆弱性)であり、実際IE6~IE8については、MS13-037で修正されています。具体的には、エラーハンドラに渡されるメッセージが単に「Script error」となるように修正されました(下図)。


しかし、MS13-037が適用されていないブラウザがしばらく残ることと、IE9以降では修正されていないことから、以下、アプリケーション側でとれる対策について検討します。

X-Content-Type-Options: nosniffヘッダを使う

はせがわようすけ氏が「機密情報を含むJSONには X-Content-Type-Options: nosniff をつけるべき」にて推奨している方法は、JSON生成時にレスポンスヘッダ X-Content-Type-Options: nosniff をつけるというものです。PHPを使う場合は、以下をプログラムの先頭付近に追加します。
header('X-Content-Type-Options: nosniff');
それでは、何故これをつけると脆弱性を防げるのでしょうか。IEのF12開発ツールを起動してエラーメッセージを見ると理由がよく分かります。
SEC7112: http://ex.tokumaru.org/getMemo.php からのスクリプトは、MIME の種類が一致しないため、ブロックされました 
vbscriptを読もうとしているのに、JSONのコンテンツヘッダがapplication/jsonであるため、MIMEタイプが違うとしてブロックされています。私はこれを見て、ある種の感動を覚えました。今までContent-Typeを散々無視してきたIEが、Content-Typeが違うとして読み込みをブロックするとは…
これは、ブラウザの挙動としてまったく正しいものですので、ぜひこのヘッダをつけるようにしましょう。ただし、既存のコンテンツがいいかげんなContent-Typeを返している場合にもエラーになるので、これを機に正しいContent-Typeを返すようにしましょう。私が調べた範囲では、JSONPを使う場合に、text/htmlとかapplication/jsonを返すと、同じエラーになります。JSONPはJSONではないのでapplication/jsonはおかしいです。application/javascriptなど、JavaScriptを示す正しいContent-Typeなら問題ありません。

X-Content-Type-Options: nosniff ヘッダはIE8以降で対応しているものですが、IE8で確認したところ、ブロックはされていません。IE9にて、このヘッダのチェックが、より厳密になったというところでしょうか。IE8もMS13-037の対象になった理由は、X-Content-Type-Options: nosniff ヘッダでは対応できないから、という可能性が高そうです。
他の問題にも備えて、IEを使う場合は、常にX-Content-Type-Options: nosniff ヘッダを出すことが望ましいでしょう。

参考:

その他の対策方法

vbscriptとしてJSONを読み込む手法が見つかる前からJSONハイジャッキングという攻撃手法はあり、以下のような対策が提唱されていました(1つ以上を実施)。
  1. POSTリクエストのみ受け付ける
  2. JavaScriptとして不完全なJSONを返す
  3. while(1);等を先頭に入れて、script要素として読み込むと無限ループにする
  4. X-Requested-Withヘッダのチェック
このうち、Gmailは、1.と2.を採用しています。すなわち、機密情報を含むJSONはPOSTによりリクエストするとともに、レスポンスの先頭に「)]}'」を入れています。XHRにより読み込む際にはPOSTの指定もできますし、先頭の「)]}'」は取り除いてからJSONとしてパースするのでしょう。facebookのJSONデータは、先頭に for(;;); が入っていて、script要素として読み込もうとして無限ループになります。ブラクラのような動作ですが、情報は漏洩しないことになります。
これらを実施すると、vbscriptとして読みこむ攻撃についても一応有効なようです。しかし、1. はRESTの原則に反すること、2.と3.はいかにもバッドノウハウ的であり美しくないことから、これらはあまり採用したくない「対策」です。
しかも、2. と3. は「たまたま防御できる」という感があります。例えば、Google流の防御文字列「)]}'」を反転させて「'}])」と言う文字列を使ったとします。生成されるJSONは以下のようになります。
'}])

[{"memo":"The king has donkey ears"}]
実はこれだと攻撃が成立してしまいます。'で始まる文字列はvbscriptのコメントとみなされるので無視され、結局防御には寄与しません。

徳丸としては、4.のX-Requested-Withヘッダをサーバー側でチェックするという方法をお勧めします。jQueryやprototype.jsを使ってAJAX/JSONのリクエストを送信すると、自動的に以下のリクエストヘッダがつきます。
X-Requested-With: XMLHttpRequest
これを積極的にチェックしようとするものです。script要素によるリクエストにはこのヘッダは付きません。このチェックにより、JSONハイジャックの他、JSONをブラウザに直接読ませてXSSを起こす攻撃や、JSONをUTF-7と指定してscript要素で読み込ませる攻撃も防ぐことができます。詳しくは、こちらを参照下さい。
IE6~IE8に対してはMS13-037にてIE側で対策されましたが、まだしばらくは脆弱性のあるIEを使い続けるユーザが残ると予想されること、従来からAndroidの古いバージョンにてJSONハイジャックができることから、この方法の採用をお勧めします。

あなたのJSONは大丈夫?

vbscriptのエラーメッセージからJSONデータ内の秘密情報が漏洩するかどうかは、JSONの構造に依存します。冒頭に紹介したマイクロソフトの脆弱性情報MS13-037では、「JSON 配列の情報漏えいの脆弱性」と説明されており、基本的にはJSONの配列の形式の場合に情報が漏れるようです。ただし、配列以外の形式についても、漏洩させるテクニックが出てくる可能性があるので、油断は禁物です。私としては、JSONの構造に関わらず、ここで紹介した対策をとることを推奨します。

まとめ

JSONをvbscriptとして読み込ませるテクニックによるJSONハイジャック(CVE-2013-1297)について説明しました。本当に影響があるかどうかは、JSONの構造にも依存しますが、他の攻撃による情報漏洩の可能性も考慮して以下の対策を推奨致します。
  • JSONを返すスクリプトの先頭でX-Requested-WithヘッダがXMLHttpRequestとなっていることを確認(ヘッダがあるという確認でも良い)
  • JSONを返すレスポンス(できれば全てのコンテンツ)に、X-Content-Type-Options: nosniff ヘッダをつける


[PR]
上記のような、ややこしいWebセキュリティについて深い基礎知識を身につけたい方には「めんどうくさいWebセキュリティ」をお勧めします。


「めんどうくさい本」に進む前に、Webセキュリティの基礎を身につけたい方には、拙著をお薦めします。

2013年5月15日水曜日

リセット後のパスワードをメール送信するパスワードリセット方式の注意点

先日のエントリパスワードリマインダが駄目な理由にて、現在のパスワードをメール送信する「パスワードリマインダ」がパスワードリセット方式に比べてリスクがある(リスクのコントロールが弱い)という説明をしました。その際に説明したパスワードリセット方式は、パスワード変更の画面URLをメール送信するというものでしたが、もう一つのパスワードリセット方式としては、サイト側でパスワードをリセットして、リセット後のパスワードをメール送信するという方式もあります。このエントリでは、後者の仕様上の注意点について説明します。

とあるサイトのパスワードリセット方式

あるサイトのパスワードリセットの仕様を確認したところ下記の通りでした。
  • パスワードリセット画面では、ユーザIDとメールアドレスを入力する
  • ユーザIDとメールアドレスが共に該当するアカウントがないとエラーになる
  • サイト側でパスワードがリセットされ、リセット後のパスワードがメール送信される
  • 利用者はメール送信されたパスワードによりログインでき、通常の利用が可能となる
この仕様をモデルケースとして、パスワードリセット仕様の問題点について検討します。

問題1.メールのパスワードが盗聴された場合のコントロールがない

先のエントリで指摘したように、メールは盗聴される可能性があり、パスワードリセット機能には、パスワードリセットのメールが盗聴された場合のコントロールを盛り込む必要があります。上記のパスワードリセットの仕様では、メール盗聴の場合のコントロールがないため、リセット後のパスワードを第三者が盗聴した場合、利用者が気づくことなく永続的にパスワードの悪用ができてしまいます。
このように、単純なパスワードリセットの場合、パスワードリマインダと共通の弱点があります。

対策1.仮パスワード方式を採用する

この問題の対策として、「仮パスワード」を使う方法があります。仮パスワードとは、リセット後のパスワード(仮パスワード)ではログイン後全ての機能は使えず、パスワードの変更のみができるというものです。よくある実装は、仮パスワードでログインした場合は、パスワード変更の画面に遷移し、まずパスワードを変更しなければ他の機能は使えないようになっています。
この方式でも、第三者が仮パスワードを盗聴した場合、先にログインしてパスワードを変更してしまう可能性はありますが、単純なパスワードリセットと比べて以下が異なります。
  • 運が良ければ利用者の方が先にパスワードを変更して、第三者の悪用を防げる
  • 運悪く第三者が先にパスワードを変更した場合は、本来の利用者は、ログインできないことで悪用の可能性に気づくことができる
  • パスワード変更のメール通知機能により、第三者のパスワード変更についてもメールで確認できる
上記については、パスワード変更の画面URLを送信するタイプと同じですね。加えて、仮パスワードの有効期限(1時間~半日程度)を設けておくと、仮パスワードが放置された場合のリスクを低減できます。

問題2.パスワードリセット機能悪用によるサービス妨害

第三者がメールを盗聴できない場合でも、ログインIDとメールアドレスがわかれば、第三者がパスワードをリセットできてしまいす。この場合、第三者に新しいパスワードを知られてしまうリスクはありませんが、パスワードを勝手に変えられてしまい、サイトの利用を妨害される点が問題です。利用者は、新しいパスワードはメールにより知ることはできますが、面倒を強いられることになります。

対策2-1.仮パスワード発行後も元のパスワードでもログイン可能とする

正式なパスワードと仮パスワードを別々に管理して、仮パスワードが発行された状態でも、元のパスワードでログインできるようにしておけば、パスワードリセット機能の悪用を緩和できます。さらに、以下の仕様としておけば、利用者がパスワードリセット機能の悪用に気づきやすくなります。
  • 仮パスワードが有効な状態で、正式パスワードでログインした場合は、仮パスワードを無効にする
  • 上記の場合、「仮パスワードが○月○日○時○分に発行されていましたが無効にしました」などのメッセージを表示する

対策2-2.パスワードリセットの前に秘密の情報を問い合わせる

パスワードリセット機能の悪用を緩和する方法として、ユーザIDやメールアドレスのみでパスワードリセットするのではなく、もう一つ秘密情報を問い合わせるという方法があります。以下は、拙著「安全なWebアプリケーションの作り方」(P354)からの画面遷移の引用です。
上記では「秘密の質問と答え」を使っていますが、「秘密の質問と答え」は忘れやすいのが難点ですね。このため、秘密の質問に代えて、電話番号や(予備の)メールアドレスを問い合わせる方法も考えられます。
「電話番号なんて公開情報では駄目だろう」と思われるかもしれませんが、あくまで「パスワードリセット機能悪用によるサービス妨害の緩和策」なので、厳密な秘匿性が必須というわけではありません。
そもそも、オンラインのパスワードリセット機能にはリスクが伴うものであり、それが許容できない場合は、オンラインパスワードリセットの機能をやめるべきです。多くのネット銀行がオンラインのパスワードリセット機能を実装せず、書面でパスワードリセットするようにしているのは、この理由からです。

まとめ

パスワードリセットの実現方法として、「リセット後のパスワードをメール送信する方法」の課題と対策について説明しました。
課題としては以下がありました。
  • パスワードリセットのメールが盗聴された場合のコントロールがない
  • パスワードリセット機能の悪用によるサービス妨害のリスク
このため、以下を実現するとよいでしょう。
  • 仮パスワード方式とする(必須)
  • パスワード変更をメールで通知する(必須)
  • 仮パスワードの有効期限を設ける(強く推奨)
  • 仮パスワード発行後も元のパスワードは有効とする(強く推奨)
  • 仮パスワードが有効な状態で、元パスワードでログインした場合は、仮パスワードをキャンセルして警告表示する(推奨)
  • パスワードリセットの前に秘密情報を要求する(推奨)

[PR]

2013年5月10日金曜日

パスワードリマインダが駄目な理由

昨日、某著名サイトのパスワードリマインダの方式が変更になっていることに気がつきました。
  • 旧: 現在のパスワードをメールで送信する(パスワードリマインダ)
  • 新: パスワード再設定の画面のURLをメールで送信する(パスワードリセット)
新しい方式(パスワードリセット)の方が優れていますが、それでは何故パスワードリマインダは駄目で、パスワードリセットの方がよいのでしょうか。このエントリではその理由について説明します。

パスワードリマインダのリスク

良く指摘されるように、パスワードリマインダの場合、2つの問題があります。
  • 現在のパスワードをメール送信できるということは、パスワードをハッシュ値で保存していない証拠である
  • メールは平文通信なので、パスワードを書いたメールが盗聴されると被害が甚大になる
これらのうち、パスワードの保存方法については別稿にゆずるとして、このエントリでは盗聴のリスクについて検討します。

パスワードが盗聴されると長期の悪用に気づかない

仮にパスワードリマインダのメールが盗聴されると、第三者にパスワードを知られ、そのパスワードにより不正アクセスされることになります(リスクベース認証や2段階認証がない場合)。これ自体とてもよろしくないわけですが、加えて以下が問題だと言えます。
  • パスワードが悪用されていることに利用者が気づくことが難しい
  • 悪用が長期間に及ぶ
パスワードリセットにメールを使う場合、メールが盗聴される前提だとパスワードリセット機能の悪用を完全に防止することは難しいので、せめて上記2項は避ける方法を検討すべきです。

パスワードリセット方式の優位点

一方、パスワードリセット方式の場合、パスワードをメール送信する方式と比べて以下のコントロールを実現できます。
  • パスワード再設定できる回数を1回限りとする
  • パスワード再設定の結果をメール通知する
これらにより、以下のメリットがあります。
  • 本来の利用者と第三者のうち、どちらがパスワードを再設定できるかは「早い者勝ち」になるが、運が良ければ第三者による再設定を防げる
  • 運悪く、先に第三者によりパスワードを再設定された場合、メール通知により、利用者がその事実に気づくことができる
すなわち、パスワードを送信する方法と比べて、最悪ケースではパスワードを第三者に知られるという点は変わりませんが、パスワードリセット方式の場合は、不正アクセスに利用者が気づくことができ、必要な対処を打てるという点が違います。
冒頭で紹介した某サイトのパスワードリセット機能には、パスワードが再設定されたことのメール通知がありませんでした。このため、せっかくパスワードリセット方式に変更したものの、そのメリットは十分に実現されていない状況です。

まとめ

パスワードリマインダとパスワードリセットの安全性を比較しました。どちらの場合でも、メールが盗聴されるという状況では第三者にパスワードを知られる(パスワードリセットの場合はパスワード再設定されることにより)可能性はありますが、パスワードリセット方式の場合、第三者にパスワードを知られたという事実を利用者が察知できるという点が異なります。このため、パスワードをメール送信する方式のパスワードリマインダは避け、パスワードリセット機能を採用するのがよいでしょう。この場合は、以下(再掲)を忘れずに実装しましょう。
  • パスワード再設定できる回数を1回限りとする
  • パスワード再設定の結果をメール通知する


追記(13:10)

パスワードリセットのパスワード再設定画面には、有効期間(1時間~1日程度)を設ける場合が一般的です。この機能が役立つ局面はあまり思いつきませんが、パスワードリセットを途中まで実行して、なんらかの理由でパスワードリセットしなかった(パスワードを思い出した等)後に、たまたま第三者がパスワードリセットのURLを見つけて悪用する、などのシナリオを緩和できます。

また、利用者の立場で、パスワードリマインダ機能を使ってパスワードを受信した場合は、ただちにパスワードを変更すれば、その後の悪用は防止することができます。



あわせて読みたい



2013年5月7日火曜日

パスワード攻撃に対抗するWebサイト側セキュリティ強化策

Webサイトのパスワード認証を狙った攻撃が大きな脅威になっています。
これらの事例のうちいくつか(あるいは全て)は、別のサイトで漏洩したIDとパスワードの一覧表を用いた「パスワードリスト攻撃(後述)」であると考えられています。パスワードリスト攻撃を含めて、パスワードを狙った攻撃が成立してしまう原因は、利用者のパスワード管理に問題がある場合が多く、攻撃を受けたWebサイト側には、直接の責任はないケースが多いと考えられます。
しかしながら、
  • 大半の利用者はパスワード管理に興味がない
  • パスワード認証を採用している理由は、コスト上の理由、すなわちサイト側の経済的な事情
  • インターネットが「とても危険なもの」となるとネットビジネスが成り立たなくなる
ということを考えると、Webサイト側でも、パスワード認証の安全施策を講じる必要性が出て来ます。 このエントリでは、パスワードに対する攻撃手法を紹介しながら、その対策について紹介していきます。

ブルートフォースアタック(Brute force Attack)

ブルートフォースアタック(総当たり攻撃)とは、その名の示すように、パスワードを力任せの総当たりで試す方法です。下図は、ログインIDをtanakaで固定して、パスワードを4文字英小文字全てのパターンを試す場合を図示したものです。


英子文字4文字のパスワードのパターンが何通りあるかというと、26 ^ 4 = 456,976通りです。1秒間に10個のパスワードを試せたとして、この全てのパターンを試すには、約13時間で終了することになります。これは現実的な脅威ですので、通常は以下で対処をします。
  • パスワードに使える文字種と文字数を増やす
現状よく使われている8文字英数字(大文字と小文字を区別)のパスワードですと、(26 + 26 + 10) ^ 8 = 218,340,105,584,896 パターンありますので、先ほどと同じ条件だと、
218,340,105,584,896 ÷ 10 ÷ 3600 ÷ 24 ÷ 365 = 692,352 年

約69万年を要することになります。平均するとこの半分で破られることになりますが、十分な強度ですね。
先に紹介したgooは、かつては4文字のパスワードを許容していたようです。現在でもその痕跡をヘルプ画面に見つけることができしました。
4. ログインパスワードの設定
gooIDに対応するログインパスワードを設定します。gooIDでご利用いただけるサービスを利用するにはgooIDとログインパスワードの両方が必要になります。ログインパスワードは英数字の4文字以上のものを使う必要があります。このパスワードは大切に保管し、忘れないようにしてください。
gooID登録・変更・削除 – gooヘルプより引用(強調は引用者)
脆弱性診断の実務では、「4文字の(短い)パスワードをつけることができる」ということを脆弱性だと指摘する事業者もありますが、本来は「たとえ短いパスワードをつけられたとしても、短いパスワードが原因で不正アクセスされたら利用者の自己責任」ということで、狭義の脆弱性ではないと考えます。
しかし、前記のような事情があるため、近年では利用者のつけるパスワードに積極的に介入するサイトが増えています。ちなみに、gooの現在のパスワードポリシーは、「8文字以上~32文字以下で、英字・数字・記号のうち、いずれか2種類の文字種を必ず混在」となっています。

辞書攻撃

ブルートフォースアタックはあまりに効率が悪く、成功確率も低いことから、「利用者がパスワードとして使いがちな単語」を辞書として登録しておき、パスワード試行する方法が考案されました。これが辞書攻撃(Dictionary Attack)です。
辞書攻撃のイメージを以下に示します。ここでは、ログインIDをtanakaで固定して、パスワードとしてpassword、qwerty、123456…など、「使いがちなパスワード」を順に試していきます。


「辞書」のサイズはさまざまでしょうが、数十から数千くらいと推測されます。ペネトレーション検査等では数千以上の「大きな辞書」を使いますが、実際の攻撃では、1つのIDでちょっと試して、だめだったら次のIDで試した方が効率的のような気がします(攻撃対象が誰でも良い場合)。

辞書攻撃の対策としては、アカウントロックがあります。パスワードの失敗が連続して数回~十数回失敗した場合、当該のアカウントをしばらく(30分~1時間程度)ロックするというものです。
アカウントロックは有効な防御策ではありますが、利用者が「password」や「123456」などの非常に悪いパスワードをつけている場合まで防御できるわけではありません。通常、辞書攻撃は、利用頻度の高いパスワードから順に試すからです。利用者が、とても弱いパスワードを設定している場合、ロックがかかる前にパスワード試行が成功する可能性が高くなってしまいます。

リバースブルートフォースアタック

攻撃対象の利用者が誰でも良い場合、辞書攻撃に用いる辞書のサイズは小さいほうが効率が良いと書きましたが、その究極形として、「1語しかない辞書」を使う攻撃を考えます。たとえば、辞書に「password」1語だけが載っているとして、パスワードはpassword固定で、ログインIDの方法を次々に変えながらパスワード試行する形となります。このような攻撃をリバースブルートフォースアタックと呼びます(下図)。

リバースブルートフォースアタックは、日本で逮捕者が出た事件があります。
調べでは、○○容疑者は昨年11月、「総当たり攻撃」用プログラムを使って検索した9人分のIDでジェット証券のHPに不正に接続した疑い。 同社ではパスワード(PW)入力を複数回間違えると、アクセスできなくなるが、PWを適当な4けたに固定し、IDだけを変える手口を使っていたため、ロックされなかった。
(2007/03/15 13:06)
産経WEB http://www.sankei.co.jp/shakai/jiken/070315/jkn070315007.htm
リンク先削除済みのため、2chより孫引き
上記にもあるように、リバースブルートフォースアタックは単純なアカウントロックが有効でないため、対策が難しくなります。
リバースブルートフォースアタックの場合、固定したパスワードは、利用頻度の高いパスワードが使われると予想されます。このため、利用者のパスワード設定時に、パスワード辞書による確認をするサイトが出てきました。従来から、文字種や文字数をチェックするサイトは珍しくありませんが、たとえこれらのポリシーを満たしたパスワードであっても、「password」や「123456」などの弱いパスワードは辞書で確認して弾いてしまうというものです。
この試みの先駆的な例としては、twitterがあります。また、最近確認したところでは、facebookやGoogleも、辞書によるパスワードチェックをしているようです。下図は、facebookのパスワード変更画面にて、「password1」というパスワードを設定しようとしてエラーになっている様子です。

「辞書にある単語はパスワードとして使えません」というエラーメッセージが表示されているので、辞書による確認をしていることが明らかです。
本来は、この種の「弱いパスワード」を避けることは利用者の責任ですが、サイト運営者側で、弱いパスワードを排除する動きが進んでいます。

ジョーアカウントへの攻撃

ユーザID(ログインID)と同じパスワードを使っているアカウントのことを俗に「ジョーアカウント(Joe account)」と呼ばれます。もっとも脆弱なパスワードと言われる一方で、ジョーアカウントという名前がついているくらいですから、一定の割合で使っている人がいると言われます。ジョーアカウントへの攻撃のイメージを下図に示します。
ジョーアカウントへの攻撃を避けるには、パスワード設定時に、ログインIDと同じパスワードをつけさせないようにチェックすることが確実です。こちらは、従来から実施しているサイトが多いですね。

パスワードリスト攻撃

冒頭にも紹介したように、現在問題となっているパスワード試行攻撃がパスワードリスト攻撃です。これは、攻撃対象とは別のサイトから得たIDとパスワードの一覧(パスワードリスト)を、別のサイトで試してみるという攻撃方法です。一定の割合で、1つのパスワードを使い回しているユーザがいることから、効率よくパスワードを破ることができると思われます。パスワードリスト攻撃の事件例については、『eBook Japanの発表資料に見るパスワードリスト攻撃の「恐ろしい成果」と対策』に詳しく説明しましたので参照下さい。
下図は、パスワードリスト攻撃を模式的に説明したものです。左側のピンクのDBが、脆弱性をもつサイトの会員DB、右側の水色のDBが、攻撃対象の会員DBです。
攻撃者は、まず左側の脆弱なサイトをSQLインジェクション等で攻撃して、会員DBからIDとパスワードの一覧(パスワードリスト)を抜き取ります。次に攻撃者は、このパスワードリストに載っているIDとパスワードを、攻撃対象に順に試していきます。下図では、ログインID: yamadaが、パスワード qw3z として一致しているため、ログインに成功する様子を示しています。


攻撃者にとってパスワードリスト攻撃を用いる動機と背景は以下のようなものだと推測されます。
  • 攻撃対象サイトには価値の高い情報や、金銭的な利益を得る手段がある
  • 攻撃対象サイトにはSQLインジェクション等の脆弱性は見あたらない
  • 攻撃対象と利用者が重なる「脆弱なサイト」があり、そこからIDとパスワードの一覧が入手可能
  • 両サイトで、同じIDとパスワードを使っているユーザが一定確率で存在する
パスワードリスト攻撃は、2010年後半から主にオンラインゲーム業界で報告され、「リスト型アカウントハッキング」と呼ばれています(「パスワードリスト攻撃」はIPAの使っている呼称です)。以下はガンホーのリリースの引用です。
【重要】アカウントハッキングにご注意ください。
2010/11/16
既に報道されておりますが、オンラインゲームを提供している他サービス会社においてIDおよびパスワードを始めとする会員情報の一部が不正アクセスの影響により流失したとの情報を確認いたしました。
これにより、不正に入手したIDおよびパスワードのリストを利用した「リスト型アカウントハッキング」が行われる可能性がございます。
【重要】アカウントハッキングにご注意ください。 - ガンホーゲームズ-無料で遊べるオンライン遊園地!
より引用(強調は引用者)
パスワードリスト攻撃は、オンラインゲーム以外にも一部攻撃が行われていたようですが、ここに来て一挙に攻撃の報告例が増えている状況です。パスワードリスト攻撃については、ここまで説明してきた「対策」が有効ではない点が悩ましいところです。

パスワードリスト攻撃の対策

冒頭で説明したように、パスワードリスト攻撃を受けて不正アクセスされた場合でも、攻撃を受けたサイトの責任とはいえません。この問題については、株式会社ラックCTO専務理事の西本氏による寄稿『危険な「パスワード使い回し」 不正アクセス防ぐのは消費者自身』も参考になります。しかしながら、これも冒頭に説明したように、ネットの安全性が崩れると、ネット上のビジネス自体が立ち行かなくなるという事情もあるため、サイト側での防御も進んできています。以下、パスワードリスト攻撃を含むパスワードに対する攻撃への防御策について説明します。

(1)2段階認証(2要素認証)
2段階認証とは、IDとパスワードの認証に加えて、もう一つの認証手段を追加するものです。パスワードが漏洩しても、2段目の認証を求めることで、不正アクセスを防ぎます。従来からネットバンキングなどではワンタイムパスワードのトークンなどで対応している銀行がありましたが、ネットサービスでも採用するところが増えています。
以下は、Googleで2段階認証を有効にして、IDとパスワードで認証した直後の画面です。

コードを入力というプロンプトが表示されているので、ここで「確認コード」を入力します。確認コードを得る方法としては、携帯電話のキャリアメール、スマートフォン向けのアプリ、音声電話が用意されています。以下は、iPhone上で動くGoogle認証システムというアプリケーションの画面です。



6桁の数字が3つ見えていますが、上から、Googleアカウント用のもの、LastPassというパスワード管理ソフトの確認コード、Microsoft(live.com)ようのものです。この例では、「258803」をWeb画面上に入力することになります。
また、確認コードの入力欄の下に「このパソコンでは今後、コード入力ウィンドウを表示しない」というチェックボックスがありますが、これをチェックすることにより、同じブラウザからのログインであれば、確認コードなしで認証するようになります。Cookieに認証済み端末であることを保存するのでしょうね。
2段階認証を採用しているサイトの例として、以下があります。
  • Google
  • facebook
  • ヤフー!
  • Dropbox
  • Microsoft (live.com)
  • Apple (米国より展開中)
  • twitter(テスト中)
  • Evernote(対応を表明)

(2)リスクベース認証
2段階認証は、パスワードリスト攻撃だけでなく、フィッシングなど他のパスワードに対する攻撃にも有効な強力な認証方式ですが、利用者の負担が少し大きいという課題があります。このため、2段階認証を少し緩めた方式としてリスクベース認証があります。これは、普段はパスワードのみの認証だが、通常と違う状況で認証を試みた場合、パスワード以外の情報を求めるというものです。

※17:20追記:
フィッシングに対してはリスクベース認証や2段階認証の効果は限定的ですので、「フィッシングなど」を削除し、「他のパスワードに対する攻撃」に改めました。フィッシングに対しては、中間者攻撃により、トークンを含めて利用者に入力させる攻撃が原理的に可能です。
※追記終わり

状況の変化を何で判断するかですが、一般的に以下が用いられるようです。
  • 従来と異なる端末(ブラウザ)
  • ISP(特に地域)
  • ログインの時間帯
  • ブラウザ(User-Agent)
  • 不自然な行動(東京でログインした5分後に大阪でログインした等)
また、追加の情報としては以下が用いられます。
  • 登録済みメールアドレスに送信したトークン(6桁程度の乱数)
  • facebookの“友達当てクイズ
  • 「秘密の質問」に対する答え
  • 事前に登録した「合い言葉」
個人的には、トークンがお勧めです。「友達当て」は本人にも分からない場合や、攻撃者がたまたま知っている可能性があります。「合い言葉」は、固定の文字列なので漏洩リスクがあるからです。追加情報としてトークンを使う場合、2段階認証に近い形になりますが、
  • 平常時はパスワード認証と変わらないので利用者の負担が軽い
  • 2段階認証と比べて、リスク判定処理が加わる分、実装の負担が重い
という違いがあります。
リスクベース認証は、近年オンラインバンキングの認証強化手段として採用する銀行が増えています。三菱東京UFJ銀行(説明)、みずほ銀行(記事)、りそな銀行(説明)、ゆうちょ銀行(説明)など主要行を始め、地銀などでも導入が進んでいるようです。
しかし、最近、リスクベース認証に用いられる「ワンタイムパスワード」が、ウイルスによって盗聴される事件が起こっているようです。
しかし、今年になり、ワンタイムを使った被害が確認された。被害に遭った複数の利用者のPCから、ワンタイムを盗み取る機能を持つウイルスが見つかったという。利用者の知らないうちにワンタイムが使われ、送金されていた。
朝日新聞デジタル:ネット口座不正送金、急増 使い捨てパスワードで被害もより引用
PC内にウイルスが入っているという時点で深刻な事態であり、Webアプリケーション側での被害軽減策は非常に難しいのですが、ワンタイムパスワードを受け取る端末をPCとは別にする(携帯電話など)ことや、ハードウェアトークン(ワンタイムパスワード生成器)を用いることで、被害に合いにくくすることは可能です。

(3)ログイン処理の監視
報道されている事件の多くが、パスワードの攻撃によってサーバー負荷が急増したことにより異常に気づいたとされています。このため、以下をリアルタイム監視することにより、攻撃を早期に発見して、被害を最小限に食い止められる可能性があります。
  • パスワード試行の回数
  • パスワード間違いの回数
  • パスワード間違いの率
しかしながら、負荷を急増させたのは攻撃側が功を焦った印象もあり、「ゆっくり攻撃」した場合、なかなか攻撃に気づけないという可能性もあります。ログイン処理の監視はぜひ実施した方がよいとは思いますが、攻撃発見の決め手とはならないと考えます。

(4)ログイン履歴の確認機能
利用者が、自分のログイン履歴を確認することにより、異常がないか確認するというものです。以下はヤフー!の提供しているログイン履歴の一部です。画面のさらに右側には、アクセス元(逆引きホスト名)、認証形式(再認証、ワンタイムパスワードなど)、入力IDが続きます。


(5)パスワードの保護
万一のパスワード情報の漏洩に備えて、パスワードを安全な形で保存することも重要です。最低でもソルトつきハッシュ、できればストレッチングを加えてパスワード情報の漏洩に備えましょう。詳しくは、拙稿「本当は怖いパスワードの話」を参照ください。

対策のまとめ

ここまで説明したパスワード認証の安全施策をいったんまとめます。
  • パスワードに使える文字種と文字数を増やす
  • アカウントロック
  • パスワードの辞書チェック
  • ジョーアカウントのチェック
  • 2段階認証
  • リスクベース認証
  • ログイン処理の監視
  • ログイン履歴の確認機能
  • ソルト、ハッシュ、ストレッチングによるパスワードの保護
このフルセットを全部実現するのは中々大変そうです。2要素認証やリスクベース認証を実現する商用製品もありますが、それなりの費用が掛かります。
そこでお勧めしたいのが、外部の認証プロバイダを活用するというアイデアです。

外部認証の勧め

ここまで説明してきたように、パスワード認証の安全性を高める施策は色々あるものの、それらをフルセットで実装することはかなり大変です。つまりコストが掛かります。このため、既にこれらを実装済みの外部認証ブロバイダを利用することも、有力な対策と考えられます。
以下は、ガンホーのログイン画面ですが、引用画像の下半分に「ID連携(OpenID)を利用する」とあるように、ガンホー独自の認証システムだけでなく、GoogleやYahoo!などの外部認証プロバイダを利用できます。


OpenID対応サイトの中には、OpenIDでもログインできるが独自のIDとパスワードの登録も必要な場合が多いのですが、ガンホーの場合はガンホーにパスワードを登録しなくてもOpenIDだけで会員登録できます。これを一歩進めて、独自のパスワード登録を捨てて、OpenIDのみに対応するという考え方もあります。すなわち、「OpenIDでログインできるサイト」には以下の3種類があることになります。
  • OpenIDを使う場合でも独自のIDとパスワードの登録が必要なサイト(remember the milkなど)
  • 独自のIDとパスワードもあるが、OpenIDを使う場合は、独自のIDとパスワードの登録は必要ないサイト(ガンホーなど)
  • OpenIDのみに対応しており、独自のパスワード管理をしていないサイト(旧ATNDなど)
現在はまだ多くありませんが、OpenID(など外部認証)のみのサイトの場合、パスワードを独自に管理しないので、パスワード漏洩のリスクもありませんし、パスワードのハッシュ値での保存や、パスワード変更、パスワードリセットなどパスワード管理の機能も認証プロバイダにすべて任せることができます。

まとめ

パスワードに対する攻撃の急増を受けて、Webサイト側でとれる対策についてまとめました。
繰り返しますが、パスワード認証の機能設計で、本当に必須の要件は以下だけです。
  • 安全なパスワードをつけられること(例: 8文字のパスワードを設定できる)
  • SQLインジェクションなどの脆弱性をなくすこと(要件以前の当然の内容)
前述のように、8文字英数字のパスワードでも、利用者の工夫次第では十分安全なパスワードをつけることができます。このため、上記以外の要件はすべて「オプション」であり、これらがないから直ちに脆弱性というわけではありません。

しかしながら、「パスワード管理は利用者の責任」というタテマエだけでは、ネットの安全性を保つことはできなくなってきているのが現状です。みなさまのWebサイトの安全性強化のために、本稿で紹介したようなセキュリティ機能の強化をお勧めします。


あわせて読みたい

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エスケープしろということです)

読者