XSS (cross site scripting)は、きわめて一般的でありながら、チェーンによって極めて強力な攻撃に転じうる、怖い脆弱性と言えます。
特に怖いのが、JavaScriptのXMLHttpRequestを利用する攻撃で、同一ホストのどこか一箇所にXSSがあるだけで、クッキー流出やCSRF対策の無効化がなされます。ホストさえ同一なら、どこでもOKというのが怖いところで、クッキーのパスや、CSRF対策のチケットやリファラーチェックなども、ほとんど意味をなさなくなってしまいます。
(IEのバグで、Ajaxの技術を応用すれば、別ホストでも上記と同様の攻撃が可能、というのはここでは言及しません。明らかにIE側で修正すべき穴であり、Webアプリケーション側では手も足も出ません。)
とにかく、XSSを体系的に潰すことが何より重要だというのが、結論です。
本来、XSSというのは、正しいリクエスト処理・表示処理をしていれば防げるものではありますが、人間とは、ポカミスをしでかす生き物です。テンプレートシステムなどの、自動的にhtmlspecialchars()をかけるブラックボックス化、というのも手ではありますが、そういうブラックボックスを用意しても、HTMLを直に出力したい場面が必ず存在する以上、ブラックボックスを経由しない部分でポロっとXSSをだす可能性は大だと私は考えています。
テンプレートシステムなどではなく、Webアプリケーション全体を守ってくれる「大きな傘」のようなもの。これこそが、ほとんどのサイトに求められるXSS対策ではないでしょうか。
これだけ書くと、mod_securityのような「入口」でのチェックを思い浮かべたかもしれません。それも有効な手段ではあるでしょう。しかし、mod_securityでリクエスト制限を行った場合、面倒な問題が発生します。
例えば、"<script" という文字列を含むリクエストを禁止してしまう場合、JavaScriptの開発フォーラムが事実上機能しなくなってしまうでしょう。
かといって、obフィルターなどの「出口」側で "<script" を禁止してしまうと、今度は、本来機能して欲しいJavaScriptが死んでしまいます。
私自身、ここでしばらく悩んでいたのですが、ふと、ベストな解法が思い浮かびました。
「入口」で怪しいリクエストを見つけたらそれを覚えておき、「出口」でそのまま出力されていないかをチェックする
これこそが、「大きな傘」たり得る、XSS対策です!
通常、"<script" などのXSS攻撃に特徴的な文字列がリクエストされることは極めて稀なので、そういう特殊な状況でのみ、ob_start()で「出口」チェックを登録する、という方法で、パフォーマンスへの影響も避けられます。
実際のコーディングは(2)に続きます。
「INと比較すべきは、LEFT JOIN(外部結合)ではなく、INNER JOIN(内部結合)だ」という指摘を、Marijuanaさんから受けました。
確かにその通りでしたので、あらためてベンチマークを行ってみました。
その結果、NULL処理が増えた場合のLEFT JOINが極端に遅いだけで、INNER JOINとINとの比較では、ほぼ同じくらいの速度性能であることが判明しました。しかも、これは極めてリーズナブルな結果と言えるでしょう。
ただ、速度的なメリットがなかったとしても、権限テーブルが複雑になればなるほど、それを途中で分解できることでコーディングしやすくなるメリットは十分にあるはずで、INを使う方が良い、という結論には違いがないと思います。
MySQLの特徴の一つに、「単純なクエリでは圧倒的に速いが、複雑なクエリだとそうでもない」が挙げられます。
そのため、LAMPアプリケーションの設計では、クエリの回数を抑えることよりも、各クエリをバラバラにして、なるべく単純なクエリにすることが、速度向上の近道のようです。
例えば、「カテゴリー対グループという権限設定のついたアイテムを検索する」なんてありがちなケースを考えます。
CREATE TABLE cat (
`cid` int NOT NULL default 0,
`name` varchar(255) NOT NULL default '',
PRIMARY KEY (cid)
) TYPE=MyISAM ;
CREATE TABLE cat_perm (
`cid` int NOT NULL default 0,
`gid` int NOT NULL default 0,
PRIMARY KEY (cid,gid)
) TYPE=MyISAM ;
CREATE TABLE item (
`id` int NOT NULL default 0,
`cid` int NOT NULL default 0,
`name` varchar(255) NOT NULL default '',
PRIMARY KEY (id),
KEY (cid)
) TYPE=MyISAM ;
SELECT item.* FROM item LEFT JOIN cat_perm ON cat_perm.cid=item.cid WHERE cat_perm.gid=1;
SELECT cid FROM cat_perm WHERE gid=1;
SELECT * FROM item WHERE cid IN (cid一覧);
XUGJにおいてPukiWiki-Modでマニュアルを書いていたときに、チケットのタイムアウトを喰らいました。しかもこれ、拙作のgticket(もしくはその派生クラス)です。
この時の投稿内容は相当長かったのですが、それをうまく救い出すことができず、本当に悲しみが雪のように積もりました。それがきっかけで、再POST機能を実装したgticket2をリリースすることになるのですが、それは以下のニュースをご覧ください。
http://www.peak.ne.jp/xoops/md/news/article.php?storyid=95
一見すると、PukiWiki-Modにこのgticket2をインストールすれば良いだけ、と思われがちですが、ここの話題はそれではありません。
「CSRF対策」という名の元に行われてきた、数々のユーザの利便性無視こそが問題なのです。
「CSRF対策」が絶対的に必要なのは、「不可逆な処理」に対してです。可逆な処理であれば、たいていの場合、致命的な状況にはなりません。(その遷移状態からチェーンできる攻撃があれば別ですが)
果たして、Wikiの編集操作は不可逆ですか? 誰かが悪意ある編集をしても、別の誰かがすぐにロールバックできる。それがWikiの売りでしょう? もし誰かがCSRF攻撃を喰らって、あるエントリを変に書き換えてしまったとしても、簡単に元に戻せるのです。
つまり、Wikiの編集操作にチケットチェックを入れることは無意味です。むしろ、CSRFとは何であるかが判っていない証拠ともなってしまいます。
ただ、ここで面白いのは、それくらいWikiの基本思想が優れているという事実です。「CSRF対策」はどうしてもユーザの利便を損なう形になってしまいますが、Webアプリケーションの設計において、操作の可逆性を担保すれば、それこそが最高のCSRF対策になるのです。
チケットエラーが出た時に特にガッカリするのは、「投稿」「編集」といったアクションです。ですから、これらについて可逆性を用意して、いわゆる「CSRF対策」に頼らないことは、これからのWebプログラマーに必要とされる標準スキルと言えるかもしれません。
最近、ELFさんとSQLにおける文字列エスケープ方法について議論をかわしたのですが、それが面白かったのでここにメモしておきます。
私自身はaddslashes()派です。その理由は以下のようなものです。
(A) 速度
(B) magic_quotes_gpc 環境との親和性
(C) 逆変換関数が存在する
(D) コネクションの有無に依らず利用可能
※とはいいつつ、コネクションがなければ利用できないのって、mysql_real_escape_string()くらいか?
※※ DBエンジンをMySQLとPostgreSQLで交換してもソースコードがそのまま流用できる、というメリットはあえて外してあります。
ELFさんはこれに対して、*_escape_string()派です。その理由はこちら。
(1) やろうとしていることが一目でわかる.マニュアルも引いても関数を使う意図がわかる
(2) grepでエスケープしているところを調べられる.関数名が変わってもsedですぐに置換できる
(3) 将来エスケープが変更されるのではなく拡張されることがあってもあわせてくれることをそれに期待できる
さらに、(B)への反論として、magic_quotes_gpc環境なんて自分が管理するサーバではあり得ないし、他者が管理するサーバであれば、magic_quotes_sybase=on環境だってあり得る、というのもありました。
さすがはELFさん。(1)〜(3)とも理にかなっています。最初、(1)や(2)については、私から「addslashes()だってSQL用の文字列エスケープ以外に用途なんてないでしょ」とか反論しましたが、ELFさんに、PHPソースコードを吐き出す場合などに、addslashes()を使う可能性を指摘され、納得しました。
ちなみに、(B)については、バックグラウンドの違いかな、という気はします。私自身、magic_quotes_gpc環境なんて駄目駄目だとは思いますが、想像以上に、デフォルトがmagic_quotes_gpc=onとなっているホスティングサービスは多いのです。(逆に、デフォルトでmagic_quotes_sybase=on環境のホスティングサービスは聞いたことありません)