XSSの脆弱性を限りなくなくす方法

このエントリーをはてなブックマークに追加

XSSがはやっているので便乗しておきます。

私がよく使う方法なんですが、この方法を利用するとXSSの脆弱性を限りなくなくすことが出来ます。

対応方法は、PHPファイルの最初に以下のコードを挿入するだけ。

foreach($_GET as $key => $value){
	$_GET[$key] = htmlspecialchars(htmlspecialchars_decode($value,ENT_QUOTES),ENT_QUOTES);
}
foreach($_POST as $key => $value){
	$_POST[$key] = htmlspecialchars(htmlspecialchars_decode($value,ENT_QUOTES),ENT_QUOTES);
}

これで自動的にエスケープの処理を行ってくれます。

通常のXSS対策がエスケープしたくない場所以外はhtmlspecialcharsを利用してでエスケープを行うのに対して、このコードを入れることによりエスケープしたくない場所にhtmlspecialchars_decodeを利用するといった全く逆のアプローチになります。

これでうっかりXSSスクリプティング対策忘れというのを防ぐことができます。

htmlspecialchars_decodeはPHP5から利用できる関数ですので、PHP4ではhtml_entity_decodeを利用すること大体同じような効果が得られます。

サンプルコードで一度デコードを行ってからエスケープを行っているのは実体参照化された文字列に更に実体参照化していくと「&」とえらい事になるからです。

GETやPOSTで受け取る値の性質が変わるので、すべてのプログラムで冒頭に入れれば動作するという魔法のコードではないですが、導入すれば楽してXSS対策ができるかなと思います。

また、すべてのXSSの脆弱性に対応できるわけでもないのであしからず。

追記

いくつかの反応をみていると入力時ではなく、サニタイズせず出力時にエスケーブすべきとの反応が見受けられます。それに関しては全くその通りで否定はしないんだけども、XSS脆弱性の大半が「対策漏れ」から来るものだと思っていて、「対策漏れ」に関してはXSS対策の正しい知識があるかどうかとは別の次元の話だとおもう(だから一流のプログラマが参加しているようなシステムでもXSS脆弱性が発生してるんだよね)。あくまで「対策漏れ」をなくす一つのアプローチだと思っていただけたらよいかと。

ベストの対策はアプリの性質に合わせたエスケープなんだけども、これって完全になくすにはかなりの几帳面さが必要になるんじゃないかなと思います。(規模が大きくなればなるほど)

また、出力時に、エスケープするのはフレームワークなり何なり利用すればいいんだけども、それはシステムの出力アプローチによって色々変わるのでケースバーイケースで対応していく必要があるのではないかと。

当然「限りなくなくす」なんて書いている通り「完全になくす」方法ではないです。script要素内やstyle要素内、イベントハンドラ内で利用したらXSS脆弱性になるし、$_REQUESTを利用している場合もエスケープされない、また途中のコードによってはアンエスケープされたものを出力してしまうなど、完全になくそうと思えばやっぱり正しい知識が必要なるります。

別に出力時のエスケープと併用しても問題ないかなと思いますし。

さらに追記

「&lt;」も「<」も「&lt;」として表示されるので入力データを生で利用したい場合などには向きません。

生データを利用したい場合は$_REQUESTというのもありだけど、「&lt;」と「<」を使い分けるような処理が必要な場合には、めんどくさくなるので私も使いません。

なんども書きますが「魔法のコード」ではありませんので。

さらにさらに追記

私が伝えたいことが全く伝わらないエントリーのようなので、全部delしました。

有効な局面でのみ利用すれば良いだけの話なんですけどね。。。

関連エントリー

WEBデザイナーの為のXSS(クロスサイトスクリプティング)入門
PHPサイバーテロの技法―攻撃と防御の実際
re:キケンなSQLインジェクション
SQLインジェクションについて
絶対に公開してはいけないPHPプログラミング

スポンサードリンク

«Yahoo!とアフィリエイト | メイン | [書評]検索にガンガンヒットさせるSEOの教科書»

このエントリーのトラックバックURL
http://blog.webcreativepark.net/cgi/mt/mt-bt.cgi/1004
コメント

それはサニタイズという方法でやってはいけないXSS対策です。
サニタイズは非常に忌み嫌われています。
下記URLで一連の記事を読む事をお勧めします。

投稿者:通りすがり | 2008年10月25日 00:16

リクエストに配列がきたらワーニングになりますよね。
htmlspecialchars_decode(array())
@を使うとか
そしたら本来の概念とずれますね、、、

それならフォームに配列を使わないってことに、、、

投稿者:(ai) | 2008年10月29日 14:09

はじめまして。なんとなくたどり着いて気になるエントリでしたのでコメントさせて頂きました。

いくつかの勘違いをなさっていらっしゃるようですので薮蛇かも知れませんがアドバイスさせて下さい。


まず、入力時にデータの内容をエスケープしてしまうのは間違いではありませんがメリットのない行為です。なぜならばリクエストデータはそもそもクライアントが送りつけてきた予測のできない値が含まれています。
予測ができないのでプログラムで決められた評価方法に基づいてリクエストデータを評価します。
これはどんな小さなプログラムでも共通して言える事です。プログラム言語の種類も関係ありません。どんな言語であれ「リクエストデータの評価」はウェブプログラムに必須の実装です。

リクエストデータの評価が必須である限り、リクエストデータをいきなりエスケープしてしまってはエスケープする前の入力内容をエスケープ後のデータから抽出しなければいけません。要するにエスケープしたデータを元に戻して評価しない限りそれは正しい評価だとは言えません。

おそらく単純に script タグなどをエスケープされる事を想定されているかもしれませんが、提示された「入力時のエスケープ」ではプログラムの多様性が失われメリットはありません。

また、出力時のエスケープと併用しても問題ないと書かれていますが、これも勘違いです。
2回 htmlspecialchars() にデータを渡してみて下さい。アンパサンドが二重にエスケープされてしまうはずです。エスケープは出力する際に出力先で想定している形式のデータとして出力するための措置であって、プログラム内部で扱うデータを変換する方法ではありません。
だからこそエスケープは出力する直前で行うべきだと言われるわけです。

HTML に限定してしまうとイメージしにくいかもしれませんが、リクエストデータをデータベースに流した後に html に出力する事を想定してみて下さい。
最初にエスケープしてしまうとデータベースに流されるデータもエスケープされたものになりますので、プリペアドステートメントを使っている場合エスケープされたデータが欠損する可能性があります。

生涯一つのプログラムしか作らないと決めているのであれば良いのですが、たくさんのプログラムも生み出していかれるのであれば「シーンに合わせて対策を考える」のではなく、「なるべく多くのシーンで流用できる対策を考える」方が生産的です。
おそらくプログラムが好きな人はすべからくこう思っているはずです。面倒くさがりですから。
使えるシーンがたった一つしかないからこそ、ブックマークのコメントなどで「これはないだろ」と言われているのだと思います。

「このプログラムだけに限定した対策です」と前置きとプログラムが提示されていればきっとどなたも否定はしないと思います。
間違いではありませんから。
だけど今回のように限定的ではない提示のされ方をしていると逆説的に考えて汎用的だと捉えられますので、即ち「これだと汎用的に使えないよね」となるわけです。

> ベストの対策はアプリの性質に合わせたエスケープなんだけども、これって
> 完全になくすにはかなりの几帳面さが必要になるんじゃないかなと思いま
> す。(規模が大きくなればなるほど)

確かにぱっと見た感じでは複雑に思えるかもしれませんが、出力先の形式に合わせてエスケープすれば良いだけですので、HTML に出力するのであれば " /> などとすれば良いだけですので、実際にはとても単純です。


だいぶ端折って書かせていただきましたが、インターネットを検索して頂ければ多くの先人がとてもためになる記事を公開されています。

繰り返し言いますがご提示されている方法は間違いではありません。間違いではありませんがデメリットだけでメリットのない実装です。
模索するのもプログラマの楽しみだと僕は考えていますので、これ以上偉そうに講釈たれるのは控えておきます。

僕自身最初はエスケープなんて概念すら知りませんでした。
あなたのアウトプットするバイタリティには尊敬を覚えています。
僕の書き方に失礼があってご気分を害されましたら大変申し訳なく思います。
アドバイスなどと上から目線で長文失礼いたしました。
今後ともご健勝下さい。

投稿者:nic | 2008年11月 1日 18:05

>nicさん

コメントありがとうございます。

エントリー内で「生データを利用したい場合には向いていない」書いているのですが、伝わりにくいエントリーですみません。

投稿者:西畑一馬 | 2008年11月 4日 00:42

レスありがとうございます。

> エントリー内で「生データを利用したい場合には向いていない」書いているのですが、伝わりにくいエントリーですみません。

はい、承知しています。
今回提示されている、いわゆるサニタイズは生のデータを扱うかどうかに依存せずメリットのない実装です。
繰り返しになりますが文字列のエスケープ処理自体には(配列を扱わなければ)何も問題はありません。ただ単に処理をする順番の問題です。

> 対応方法は、PHPファイルの最初に以下のコードを挿入するだけ。

と冒頭で仰られていますが、最初に挿入するのはあらゆる面から見てメリットがないと言うだけの事です。

また、データを生で扱うかどうかを考慮してコードをいちいち書き換えるのはプログラムの本質的なところで矛盾を感じずにはいられません。
そしてプログラムの最初に挿入する事で色々な制限が出る実装ならば、例示という概念上参考にするのは難しいと思います。

だからこそわざわざ制限が増えるだけの「最初にエスケープする」という例示をされているのは、消去法で考えて「どこかで勘違いをなさっているのでは」と思いコメントをさせて頂いた次第です。

ご提示されたエスケープ処理が、最後(HTML に出力する時点)に行われるものだとするだけで誰からも批判は浴びなくなると思います。

投稿者:nic | 2008年11月 4日 14:00

うーん。この件は他の方に全く伝わらないようなで、私自身もうめんどくさくなってるのですが、私が利用するケースをお伝えします。

管理画面など入力がメインの場合、生データを扱わないというのは考えづらく利用するのは困難です。

ただ、表示のみを想定した場合どうでしょう?

GETでパラメータを引きますなどの処理の場合、生データを意識せずともこの手法で十分にエスケープが可能です。(生データを利用したいい場合はこの手法を使わなければいいだけです。)

何度も書きますが、使えないケースがあるのは十分承知してます。魔法のコードのつもりでは書いていません。

nicさんにとってメリットがないかもしれませんが、私にとってはメリットがある手法です。そこらへんは個人のスタイルなので議論するつもりはありません。

「プログラムの本質的」などを語る目的のエントリーではないので、ちょっとそこらへんの意見はスルーさせてください。

投稿者:西畑一馬 | 2008年11月 4日 15:07

この記事が誰でも読める以上僕としては西畑さんだけではなくこの記事を読んだ方にもお伝えしておきたい事でもありますので、最後にもうひとつだけ書かせて下さい。

> ただ、表示のみを想定した場合どうでしょう?

先のコメントで書いたとおり、表示のみを想定した場合は問題ありません。
そうではなく西畑さんが提示された方法論はほとんどのケースで「メリットのない実装」であるだけです。
拡張性もメンテナンス性も悪いためプログラムを書いて飯を食ってる人は使わないです。
これは常識とかセオリーとかそういうレイヤーではなく、事実として「行うべきではない方法」です。

質問に質問を返すのは失礼でありますが、失礼を承知でお聞かせ下さい。
エスケープ処理を出力直前に行うようにするだけで「表示のみを想定する」必要がなくなる点はご理解されていますか?
ご理解されているようでしたら、なぜ使うシーンを限定してまでシーンを限定しないと有用性が立証できない方法を対策としてご提示されているか、お聞かせ頂けないでしょうか。

おそらく僕の説明が長ったらしくてわかりにくかったのだと思います。
徳丸さんがまとめられている記事を URL に指定しておきますのでご一読下さい。


凄く粘着なやつだと思われているでしょうが、西畑さんのように影響力の強い方が前提や考え方をすっ飛ばして危険なコードを対策として挙げられている事に危惧を覚えたためしつこく書かせて頂いています。
ご迷惑かと思いますがご容赦下さい。

投稿者:nic | 2008年11月 4日 16:18

「使うシーンを限定する」方法を提示するのはダメ、といわれると何も返す言葉がありません。

参考に挙げられているURLに書かれている通り、XSS対策は出力のバリエーションに合わせて多様に変化します。

「使うシーンを限定しない」方法などないと思うのですがいかがでしょうか?

あるようでしたら是非ご教授ください。

コメント欄はhtmlを許可しておりませんので、nishihata[at]to-r.netまでご連絡いただけたらと。

一般論でしたら
http://blog.webcreativepark.net/2007/02/03-013141.html
で書きましたが、やはりすべてのケースで対応できるものではありません。webアプリの性質によってはホワイトリストやブラックリストを利用してチェックを行う必要も出てきます。

ベストな方法はアプリによって施行を変更することのはずです。

投稿者:西畑一馬 | 2008年11月 4日 17:05

がると申します。
えと…私のBlog( http://d.hatena.ne.jp/gallu/20081028/p1 )でも、少々手厳しい内容で書かせていただいたのですが。

この手の脆弱性をどうにかする本質は「出力する時に適切な手法で"出力先に合わせた"エスケープ処理をする」ことに尽きると思うのですが如何でしょうか?
で、出力時のフックが難しい場合の「やむを得ない逃げ手の一つとしての」入力部分での「危険な文字の排除」であり。それでもなお「入力時のフックは副作用と危険とを伴う」事を十二分に認識すべき、だと思うのですが如何でしょうか?

これが、例えば書き出しが「一つの場当たり的な対処として」という形であるのであれば、ここまであちこちでなにか言われるものでもないと思うのですが。
「この方法を利用するとXSSの脆弱性を限りなくなくすことが出来ます。」という文言から見ると、正直「入力時のフックで問題ない」と思われているように感じられる部分があり。その場合、やはりセキュリティを真摯に考える人たちからすると、非常に奇異に映るのではないかと思います。

システム開発をお仕事になさっているのであれば、そのあたりの「原理原則」はやはりきちんと学び、習得しておいたほうがよいと思われるのですが如何でしょうか?

投稿者:がる | 2008年11月12日 12:18

>がるさん

>「この方法を利用するとXSSの脆弱性を限りなくなくすことが出来ます。」

はい、かなり誤解をまねきやすいエントリーですので全文del要素で取り消しています。

投稿者:西畑一馬 | 2008年11月12日 13:10
コメントを投稿