素人が居丈高にセキュリティーを語る2006年04月12日 00時59分02秒

ことの詳細はこの辺参照。CSRF 対策やる以上、IE の CSSXSS 脆弱性を考慮するべきなんじゃねーの? という議論がこの辺のサイトを中心に沸きあがっていたわけだわな。まぁ、この辺のスレッドなんかを辿ってみたりすると分かるとおり、ワンタイムトークンを使用することが CSSXSS 脆弱性による脅威を回避することになるという理由が説明できていなかったりして、微妙に香ばしかったりするわけなのであるが。

個人的にはこの件については、大岩氏によるこの辺の記事の通り、Microsoft が早急にこのバグを修正すべきなんであって、Web 開発者がどうにかする問題ではないと思っているし、実際に MS に問い合わせている hoshikuzu star_dust さまはヱラい!と思うわけですよ。

んで、高木さまの今回の記事なのですが、出だしで異様なほどご丁寧な GUI 設計に関する議論から始まっている (しかも図付きでw) ので、いったいなんのつもりだ? とか思いながら読んでいたわけですが、「ワンタイムトークン方式って何?」辺りまで読み進んだところでその理由が明快に分かりましたよ。なるほど高木さんはそういうレベルの人たち相手に分かるように説明してあげないといけないと思ってその記事を書いていらっさるわけですね。お忙しい中、本当に本当にご愁傷様です (T-T)/

。。。あのさぁ。つくづく思うんだけどさぁ。セキュリティーに関する議論に素人が首を突っ込むな、とまでは言わんからさぁ、せめて足手まといになるようなことだけは避けてくれまいか? とか思うわけですよ。確かに、いろんな物事に対して疑いを持つというのは、大切なことですよ。でもね。モノを疑うにも、優先順位というものがあるのですよ。

例えば、プログラムを書いていて、すぐには原因を理解できないバグに遭遇したときに、その原因をすぐに OS や開発環境に求めるのは現実的ではありません。たいていの場合、バグの原因は自分や、共同開発者が書いたコードにあるものです。まず疑うべきは手元のコードであるべきであり、設計上でも理論上でも API の仕様においても間違った書き方をしていないというのであれば、その結果浮き彫りになった予測される OS などのバグの存在を立証するコードを別途書いてみる、という手はずで進めるべきなのです。

今回の一件の場合、情報セキュリティー研究の権威が (個人のブログ記事とはいえ) 書いていることを取り上げて、その中の「なんとなく危なっかしそうな気がする箇所」に異論を唱え、「本当に正しい対策はこうなんだ!」という妄信の元に記事を執筆してみたものの、あっちゃこっちゃから逆に突っ込みのコメントを寄せられ、それでも考えを改められずに意固地になっている、というパターンなワケですが。だから異論を唱えてみる前に、何で自分はそれが危なっかしいと思ってしまうのかもう一度考え直してみたらどうなのかと。

確かに、IE の脆弱性を回避する方法について考えてみるというのは、場合によってはまったく無益ではないのかもしれないけど*1、セッション ID が hidden フィールドに書き込まれることに対する危険性の証明ができていないのに、わざわざ複雑でその分メンテナンス性が低下する実装方法を推奨するのは、問題の切り分けを余計に難しくする分むしろ有害だと断ぜられるべきです。

*1 : おいらの個人的見解としてはまったく以って無益だと思っているけどね。「フォーム画面を表示するリクエストを POST メソッドでしか受け付けないようにすればいい」みたいに書いてあるけど、IE の脆弱性の影響範囲を知るのはあくまで IE を記述するソースコードだけで、それがプロプライエタリなものである以上、ユーザーであるおれらに確証を得る手立てはないのだから。

よい子のための hidden 講座2006年04月12日 09時05分49秒

hiddenって、何?

(X)HTML におけるフォーム要素の一種です。ブラウザ上には表示させずに、ページに任意の値を埋め込むことができます。例えば以下のように使います。

<form action="diary.cgi" method="post">
  タイトル:
  <input type="text" name="subject" size="40"><br>
  日付:
  <input type="text" name="date-y" value="2000" size="8" maxlength="4">年
  <input type="text" name="date-m" value="4" size="4" maxlength="2">月
  <input type="text" name="date-d" value="12" size="4" maxlength="2">日<br>
  本文:<br>
  <textarea cols="60" rows="10" wrap="soft"></textarea><br>
  
  <input type="hidden" name="real-date" value="1144802196">
  <input type="hidden" name="session"
  value="89f016e7e6e59426e68c0eea9bc353f9">
  
  <input type="submit" value="書き込む">
</form>

表示上では以下のように表示されますが、

タイトル:
日付:
本文:

実際には、こうして表示される項目に加えて、以下の名前と値の組み合わせが、このページの中に埋め込まれているわけです。

名前意味
real-date 1144802196 このフォームを表示するときのサーバーの時刻を表す値。日本時間で 2006/4/12 09:36:36
session 89f016e7e6e59426e68c0eea9bc353f9 セッション ID。通常は予測困難な値にするだろう、っちゅーことで、このサンプルでは適当な文字列を MD5 してみた。

どんなときに使うもの?

ユーザーが CGI に対して送信しなければいけない値のうち、ユーザー自身に手入力させたくない値で、なおかつ表示する必要のない値を、あらかじめフォームに含めておきたいときに使います。先ほどの例の場合、表示に使われる日記の日付はユーザーが自由に設定できるよう、入力欄を設けていますが、実際にその日記を書き始めた日時 (これは CGI プログラムが記事を整理したり、日記を書くのにかかった時間を把握する必要がある場合などに使う値という想定です) はユーザーに書き換えられても嬉しくないし (困る、というほどのものではない)、わざわざ表示するような情報でもないので、hidden として埋め込んでいます。

使ってはいけない場面って?

hidden は (X)HTML のフォームの一部に過ぎません。ブラウザ上では表示されませんが、ソースを閲覧することは容易です。また、フォームはユーザーが手元で自作することもできるため、意図しない値に書き換えられる可能性が十分にあります。

1. ユーザーに見られたくない値

例えば、hidden フィールドにパスワードを入れておき、入力されたパスワードと、hidden の値を比較して認証を行う、という使い方は NG です。

<form action="autholi.cgi" method="post">
  パスワード:
  <input type="password" name="subject" size="40"><br>
  
  <input type="hidden" name="pwd" value="oppaidaisuki">
  
  <input type="submit" value="部屋に入る">
</form>

「ソースを表示」でパスワードを確認。誰でも入れる認証ページ。これぞ開かれたインターネット。すばらしい。

信じられないかもしれないけど、昔はこーいう間抜けなインターネット、意外と多かったのよw。

2. 書き換えられると非常に困る値

ショッピングサイトとかで安易に hidden 使いまくっちゃうと、ユーザーにちゃっかり決算を安く見積もられちゃうかもしれません。

<table frame="border">
  <tr>
    <th>商品名</th>
    <th>単価</th>
    <th>数量</th>
    <th>価格</th>
  </tr>
  <tr>
    <td>テレビ</td>
    <td>200,000</td>
    <td>1</td>
    <td>200,000</td>
  </tr>
  <tr>
    <td>テレビ台</td>
    <td>50,000</td>
    <td>1</td>
    <td>50,000</td>
  </tr>
  <tr>
    <td>HDD/DVD レコーダー</td>
    <td>100,000</td>
    <td>1</td>
    <td>100,000</td>
  </tr>
  <tr>
    <td>DVD-R 10枚パック</td>
    <td>10,000</td>
    <td>2</td>
    <td>20,000</td>
  </tr>
  <tr>
    <th>合計</th>
    <td colspan="3">370,000 円</td>
  </tr>
</table>

<form action="kessan.cgi" method="post">
  この内容でよろしければ、「確認」を押してください。<br>
  <input type="submit" value="確認">
  
  <input type="hidden" name="transaction"
  value="e355bcacf56d8edf3735ab8f18f371cc">
  <input type="hidden" name="itemlist"
  value="tv 1 kagu 1 hdddvdrec 1 media-dvdr10 2">
  <input type="hidden" name="price" value="370000">
</form>

上記のコード例では、hidden にトランザクション ID (いわゆるワンタイム・トークン)、商品リスト、そして合計金額を埋め込んでいます。

トランザクション ID は、おそらくこの場面では必要なものでしょう。商品リストはトランザクション ID に関連付けられてサーバー側で管理されているべきなので、本来ならば不要ですが、それでも一応許容範囲内です。なぜならこれを書き換えられても、購入したい商品が変更になるだけで、どちらかが損をすることはないからです。

しかし合計金額を hidden に埋め込むのは NG です。ユーザーはこのフォームの HTML を保存し、テキストエディタでこの hidden の値を書き換え、それをブラウザで表示して、「確認」ボタンを押すかもしれません。その場合、これらの商品がたったの 1円で決算されてしまうかもしれないのです。

3. 経路上を流れてはまずい情報

HTTP による通信は、通常平文で行われます。平文で流れる情報は、通信経路となるホストを管理する人間に閲覧される可能性があります。SSL によって通信自体を暗号化してしまえば、閲覧される可能性はほとんどなくなりますが、SSL を使用できない環境の場合、閲覧されてはまずい情報自体を通信に乗せないようにする必要があります。

では、その場合、どうするべきなのでしょうか?

答えは、「閲覧されたくない情報は、どのような手段であれ、通信に流してはいけない」です。これは、hidden フィールドはもちろんのこと、クッキーだろうが基本認証だろうが結局は同じことです。

SSL による暗号化が使用できない環境では、すべての通信内容が常に第三者に傍受される可能性があります。これは防ぎようのないことなのです。セッション ID の漏洩は困るかもしれませんが、hidden に置こうがクッキーに置こうが、漏れるときは漏れるんです。セッション ID を再度暗号化したところで同じです。結局「一意の値」であることに変わりはないのです。

hidden フィールドが漏洩することって、あるの?

Web アプリを使用するユーザーや場面に応じて変化する値が、想定しないユーザーに勝手に閲覧される可能性については、Web アプリ自体やブラウザなどに特別な欠陥でもない限り、起こり得ないと想定すべきです (経路上での傍受の可能性については考慮しないものとして)。少なくとも、「クッキーよりも危険」ということはありません。