httpsじゃないログイン画面でもセキュリティを保つ方法はあることはある

Webサイトのセキュリティに関するまとめ的な話がまたにわかに多くなってきた (この業界だけだとは思うが)。 筆者的には以下の記事が手っ取り早くまとまってると思う。

セキュリティレベルの高いサイトを構築する 19 カ条 :: Drk7jp

ところで、ふつうのhttp:// な画面でユーザーIDやパスワードを入力させるのは必ずしもけしからんのかというとそうでもない。 たとえば、Yahoo!などのログイン画面では、チャレンジレスポンス認証が実装されている(Yahoo!のログイン画面のソースを見てみましょう)。 技術的な詳細は省くが、これによって、SSL使ってない画面であってもパスワードが平文のままでネットワーク上を流れてしまうことはとりあえず防げている。

もちろん、データの暗号化だけでなくなりすまし防止の意味でSSLを使うべきだとかなんだとかいろいろ議論は尽きないのだが眠いのでこのへんで。

Webアプリの安全装置は簡単につけられる

Webに限らずコンピュータシステム一般に言えることなのだが、 簡単な安全装置をつけるのを怠ることで何が起きるかという事例がこちら。

asahi.com: みずほ証券、誤注文で270億円の損失 (2005/12)
「1株61万円で売り」とすべきところを「1円で61万株を売り」にして しまったとのこと。 すかさず買いを入れて数億円規模の利益を得てしまったデイトレーダーもいるとかいないとか。 うらやまし。(笑)
ネットでテニスコートを不正予約 (nikkansports.com 2005/11)
市区町村などの公営テニスコートのネット予約システムの不備を突いて、 本来は申し込めないはずの1ヶ月以上先の日程について予約を入れた輩がいたらしい。 不審に思った他のユーザーの指摘であっさり発覚。

いずれの場合も、安全装置をつけることは簡単だったはずだ。

株の例で問題となったJCOM社の発行済み株式総数は4万株くらいだったらしい。 だったら60万株もの売買が成立するはずが無い。 「発行済み株式総数を超える数値の売買注文はありえないので却下」 というごく当たり前かつ簡単なロジックを入れることを誰かが怠った。

テニスコートの例も、「予約できるのは1ヶ月先まで」という仕様なのなら、 「現在日時+1ヶ月 以上の日付は却下」というロジックを入れるだけだ。難しくない。

ちょっと話は変わるが、コンビニやスーパーマーケットで ノートPCの「液晶パネルだけ」みたいな不思議な端末を首からぶら下げて、 商品棚の前でパネルをピッピッと押している店員さんを見たことはないだろうか? あれは発注端末である。例えば日付を入れて「牛乳」の欄に「10」を入れると 本部を通じて卸業者に仕入れ注文が飛び、牛乳10本が指定日に届くという仕組み。 昔コンビニでバイトしていた筆者の知人はためしにと思って牛乳1000本という 入力をしてみたら「過剰注文!ほんとによろしいですか?」みたいな 警告メッセージが出たとのこと。 なるほど、何百種類もの商品を日々点検して品切れの無いように発注している 店員さんなのだから、こういう入力ミスはありうることだ。 見事な安全装置である。

でも、別にここまでユーザーフレンドリーな安全装置じゃなくたっていいのだ。

例えば、小売店舗なりECサイトなりのポイントシステムがあるとしよう。 データベースにPostgreSQLを使っているとして、 会員テーブルに会員番号とポイント残高が記録されるとする。

CREATE TABLE kaiin_table (
    kaiin_id int4,
    point    int4
)
貯まったポイントを使って買い物をするときはpointからそのぶんを減算するわけだ。
UPDATE kaiin_table 
    set 
        point = point - 使ったポイント 
    where
        kaiin_id=その人の会員番号
みたいに。

ここでもしも仮に、「自分の持っている以上のポイントを使う」 あるいは極端な話「1億ポイント使う」というどう見てもありえない命令が来てしまったら、 当然、却下しなければならない。

どういうチェックロジックを入れたらいいだろう? 「ポイント残高 > 使用ポイント数」が一般的だが、 いちいちポイント残高をDBから引っ張ってこなければならない。面倒だ。 「1億 > 使用ポイント数」とか? いやそれだとしきい値を1億にするか1万にするか考えなければならないし、 そもそもポイントシステム用のチェックロジックにしてはあいまいだ。

筆者だったらこうする。

create table kaiin_table (
    kaiin_id int4,
    point    int4,
    constraint pointcheck check(point >= 0)
)
たったこれだけ。DB定義の段階で1行追加するだけである。 「ポイント残高はゼロ以上であって、ゼロ未満=負の値になることはありえない」 というごく常識的な仕様を、データベースにきちんと定義しているだけのことだ。 たったこれだけで、上のUPDATE文で使用ポイント数がいくらであっても結果としてポイント残高が負の値になってしまうようなら却下される。自分の持つポイント数以上のポイントを使用して買い物されてしまうことを防ぐ効果がある (注文処理とポイント減算処理とでちゃんとトランザクションとっていればの話であることは当然として)。 買い物での使用以外にもポイントをとりあつかうほかの処理(他社のポイントとの交換とか)があっても、 同様に安全装置として働いてくれるだろう。

もちろんこれではユーザーフレンドリーとはならない。 アプリケーションレベルの安全装置じゃないからだ。 これだけだと、上で書いたコンビニの発注端末みたいなエラーメッセージを出すことはできないので、エラー処理は別途書く必要がある。書かなければ、 なんだか意味不明なエラーをユーザーに見せることになるだろう。 しかし実際に1億ポイントをご利用のご注文が成立してしまうよりは百倍マシである。

さして複雑でもない常識レベルの仕様を(できればシステム上の低レベル層で)たった1行書くだけで、入力ミスであれ悪意の操作であれ多くのの想定外な現象について水際で防ぐことができる。それがフェイルセーフなシステムへの第一歩だ。 こんなところで力説するまでもないことだと思う。しかしそれを怠ったがための事件が起きている。

追記:
こんな記事があった。

東証には、異常な売買注文がきても、成立前にチェックする仕組みがない。「銘柄ごとに発行株式数を確認するとシステムに負荷がかかり過ぎる」との理由からだ。
東証、誤発注のジェイコム株売買停止に 事態収拾見えず (asahi.com 2005/12)
システムに負荷がかかりすぎる??そぉかあ? 上場企業全部あわせても1万社にも満たない。 そんなテーブルを検索するのにかかる負荷なんてビビたるものだ。 発行済み株式の総数なんてそういつもいつも変わるもんじゃないからキャッシュしておいて性能を上げることだってできるはずだ。 「はじめからそんなチェックロジックは考えてなかった」が実際のところなんじゃないだろうか。

2006/1/21 追記
みずほ証券が発表した今後の対策に次のようにあった。

・・・人為的な入力ミス等による誤発注に対するシステム的ガードを強化いたします。・・・
(1) 発注不可となる上限金額及び上限数量を変更
(2) 発行済み株式総数を基準とした上限数量の設定
・・・
こうした基本的チェックロジックの必要性を知るために使った勉強料が数百億。高くついたものだ。

Y!J-BSC Yahooの新型クローラー現る。

Webサーバのアクセスログに見慣れないロボットがいた。

Y!J-BSC/1.0 (http://help.yahoo.co.jp/help/jp/search/indexing/indexing-15.html)
他にもY!J-DSC というのがあるらしい。 ちなみにRSS情報上にあるURLに集中的にアクセスしてきていた。

pingの結果を見せて「御社にはセキュリティホールが!」と押しかけるとほほな詐欺師がいるらしい

くわしくはこちら。

不倒城: 今思い返すと、あれはつまり詐欺師か何かだったのだろうか