雑記帳
高木浩光さんの「サニタイズ言うなキャンペーン」 という言葉自体はずいぶん前から存在したのだが、 続・「サニタイズ言うなキャンペーン」とはにて高木さん自身がいくつも誤解の例を挙げているように、 そしてまた最近も 駄目な技術文書の見分け方 その1にて「まだわからんのかね」と言われているように、 「わかりにくい」概念なんだろうとは思う。
そこで、僭越ながら、「サニタイズ言うなキャンペーン」について、 私なりの解釈を書いてみようと思う。 もっともこれが正解であるという保証はないのだが、 間違っていたらどなたかツッコミいただけることを期待しています(_o_)
たとえば住所氏名を登録させるWebアプリケーションは珍しいものではないと思う。 そこで、私が「Taro&Jiro's castle サウスポール」 とかいう恥ずかしい名前のマンション(?)に住んでいたとする。
で、私がネットショッピングか何かで住所を入力した時、 その文字列は、ブラウザ→Webアプリケーション→D/Bの順に流れて登録されるし、 逆に、次にログインして登録した住所を表示させたときには、 D/B→Webアプリケーション→ブラウザの順に流れて表示されることになる。 そして、D/Bに登録する際にはSQLの構文上の都合から シングルクォートはエスケープしなければならないかもしれないし、 ブラウザに表示する際にはHTMLの構文上の都合から「&」 をエスケープしなければならないかもしれない。
ここで重要なのは、シングルクォートをエスケープしなければならないのは あくまで「D/Bの(SQLの)都合」であり、「&」 をエスケープしなければならないのは「HTMLの都合」であるということである。 私が住んでいるマンションは、あくまで「Taro&Jiro's castle サウスポール」 であり、「Taro&Jiro\'s castle サウスポール」でも、 「Taro&Joro's castle サウスポール」でもないのだ。 よって、シングルクォートのエスケープは、(それが必要なら)D/Bに突っ込む直前に行う (またはprepared statement/パラメタライズドSQLを使う)べきであるし、 「&」のエスケープは、表示する直前に行うべきである。
汚い部分はできるだけ狭い範囲に押し込めるのが プログラミングの常道である。
こうすることで、アプリケーションの本体部分では 「Taro&Jiro's castle サウスポール」という「正しい住所」 が流れることになるので、 たとえば特定のマンションからの申し込みだけ監視したいとか、 D/B以外にテキストファイルにも申し込みの履歴を取りたいとかの機能を考えたときにも、 変なことを意識せず扱うことができる。 しかし、実のところこんな具体例を思い浮かべるまでもなく、 こんなのは「普通に考えたら当然そうでしょ」というレベルの話なのであって、 だからこそ、入力の段階でシングルクォートをエスケープしてしまうPHPのmagic quoteや、 HTMLでエスケープしなければならない文字を(デフォルトでは) 入力の段階で弾いてしまうASP.NETは、そもそもその発想からして間違っている、 と私は思う※1。
「汚い部分はできるだけ狭い範囲に押し込める」というのは、 要するに抽象化の層を積み重ねるということであり、 Webアプリケーションに限らず、 プログラミングにおいてたいていの問題は「層を積み重ねる」ことで解決されてきた。
たとえばたいていのプログラミング言語には「変数」があるが、 これは「アドレスを指定してメモリに直接値を書き込む」という汚い(面倒くさい) 作業に皮をかぶせて簡易に扱えるようにしたものだし、 そもそもそのメモリ自体が今時は仮想メモリであり、 メモリが足りなくなったときのスワップアウトやら何やらの汚いことを OSの層で隠蔽している。 Cだとmalloc()で確保したメモリはfree()しなければならないが、 今時のたいていの言語ではそういう汚い作業はGCが行ってくれる。 こうして汚い作業はどんどん下層に押し付けて、 アプリケーションは本来解決すべき問題だけに注力できるようにする、 というのが、プログラム開発における通常の考え方である。
もちろんこれにはコストがかかる。 GCは、プログラマがすべてのメモリを意識して管理するのに比べて遅いかもしれないし、 メモリだって、手作業でオーバーレイするのに比べて仮想記憶は遅いかもしれない。 でも、Javaではメモリを自分でfree()しようったってさせてくれない。 汚いところに手を突っ込んで多少の速度を稼ぐことより、 便利さと安全を優先するほうが、 アプリケーション開発の立場からすれば正しいとされている。
そう考えると、HTML出力やSQLのためのエスケープは、 ライブラリのような「下の層」に押し込むべきだ、ということになる。
ここから、 出力するときは全部htmlspecialcharsを通せとか、 ASPとかJSPとかPHPとかERBとか、逆だったらよかったのに という発想が自然に導かれる。
汚い層を、ライブラリや、全部htmlspecialcharsをかけるというコーディング規約で 隠蔽する(コーディング規約では隠蔽になってない、という議論はもっともであるが)ことで、 それより上位のアプリケーションでは、エスケープなど必要する必要のない、 任意の「文字列」を扱えるようになる。
さて、ここまで書いてきたことは、「SQLやHTMLの都合から逃れ、 いかにアプリケーションをきれいな世界で書くか」ということであり、 セキュリティとは本質的に無関係である。
インジェクション系の脆弱性は、変数の出力時に、文脈を無視した文字列連結のコーディングをしているところに原因があるのであって、セキュリティ以前に、正常系として、任意の入力に対して正しく動作するプログラミングをしていれば、普通に解決するはず(だが、できていない実態がある)というところに問題の本質がある。
高木さんもこう書いているように(強調は前橋)、 セキュリティ云々を考えなくても、通常のアプリケーション設計の発想で 「できるだけきれいな世界にいられるように」書いていけば、 自然に達成できるはずのことである。
続・「サニタイズ言うなキャンペーン」とはでは、 「ブラックリスト方式ではなくホワイトリスト方式にせよという話だと誤読した事例」 が「サニタイズ言うなキャンペーン」への誤解として挙げられている。
たとえば電話番号を入力させるのであれば、 許される文字は数字とハイフンくらいであり、 入力の段階でそれ以外の文字を弾いてしまえば 危ないタグを入れられたりSQL Injectionを突かれたりすることは回避できる。 しかし、前述の方法で任意の「文字列」を扱えるようになっていれば、 「電話番号」というのは結局「文字列の一種」でしかない。 電話番号としての入力チェックは当然行うとしても、 それは「任意の文字列」が扱えることを下層で達成した上で、 もう1階層上で実現すべきことであると思う。
ここで書いてきたことが高木さんの意図に合っているかどうかはわからないが、 一般論として、間違ったことは書いていないと思う。 というか、あまりに当たり前の話なので、高木さんもあえてこんな形では 書こうと思わなかったんじゃないかなあ。
ていうかこんな当たり前のことを改めて書かなくても、 という話だと思うのだが、どうもたとえば「PHPのmagic quoteはおかしい」 とか「ASP.NETのValidateRequestは本質的な対策になってない」とか言っても 通じない人に私はたまに会うし、高木さんに 「プログラミング解説書籍の脆弱性をどうするか」の記事で批判された 後藤さんの反論で、
htmlspecialchars() は、2重にかけるとおかしくなる関数です。リクエスト変数について「かけないこと」ほどは問題にならないものの、「かけすぎ」ればやはりおかしく表示されます。
こういう意見が出てくるのはいかがなものか、と私には思える (件の本は読んでないので、本そのものについて私は何も言えませんが)。
いや、人のことばかりも言っていられない。 私自身、 PHPとMySQLで掲示板を作るにおいて、 このへんで
なんていうか、データベースに突っ込むのにエスケープが必要なら、 そういう処理はその直前にやりゃいいわけで、 最初から取れてくるものが勝手にエスケープされてる、というのは、 私からすると相当乱暴な仕様に思えるんですがねえ。
と言いつつもmagic quoteがONであることを前提としたプログラムを書いているからだ。
変だ変だと思いつつもそうした、というのには、 「PHPの郷に入ったら郷に従うべきなのかなあ」と思ったとか、 いくつか理由はあるのだが、やっぱりまずいと思うので注釈を追加しました。