Hatena::ブログ(Diary)

岩本隆史の日記帳

2009-09-10

不正な文字列をどこでチェックすべきか

大垣さんの書かれた:

不正な文字エンコーディングを利用した攻撃は、文字エンコーディングを厳格に取り扱い、文字エンコーディングをバリデーションすれば無くなります。これを怠ると、システムのどこで問題が発生するか予想できなくなります。つまり、いい加減に文字エンコーディングを取り扱うと安全なシステムは作れないのです。

何故かあたり前にならない文字エンコーディングバリデーション | yohgaki's blog

という警告には同意なのですが、対策の一番手に「全ての入力文字列の文字エンコーディングをバリデーションする」が挙げられているのに違和感を覚えます。入力時のバリデーションは、あくまで保険的な対策でしかないのではないでしょうか。

この文脈において、XSSが起きるのは、不正なバイト列や冗長なUTF-8表現がHTML中に出力されるためでしょう。そうならば、不正な文字列がHTMLに出力されそうになった段階で例外を吐くのが根本的な解決策だと思います。

SQLインジェクションについても同様で、不正な文字列を含むSQLが実行されそうになった段階で例外を吐くのが根本的な解決策だと思います。特にSQLの場合、実行エラーとなることも想定されるわけで、セキュリティとは無関係に捕捉されるべきでしょう。

高木浩光さんは『「サニタイズ言うなキャンペーン」とは何か』という記事で:

元々、HTMLを出力するときは、その出力全体に対して「<」「>」「&」のエスケープ処理の検討が要求されているのであって、CGI入力に依存しているかどうかは無関係である。

と書かれています。文字エンコーディングについても同じことがいえるはずです。不正な文字列が必ずCGI入力からもたらされるとは限らないからです。

というわけで、同じく高木さんの『要約版:「サニタイズ言うなキャンペーン」とは』に書かれているとおり:

入力段階ではなく「出力段階」で。「出力」というのは(出力というより)、問題を起こす場所。

での対策を講じるのが、少なくとも新規開発の場面では当然なのではないか、と私は考えています。

追記(2009-09-11)

SQLインジェクションの不正な文字列って何?各出力先に併せてフォーマットする事と入力のチェックを混同されているかと。規定する文字コードとして不正なバイト列が入力された場合には入力時にエラーにするべき

はてなブックマーク - 2009-09-10 - 岩本隆史の日記帳

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

SQLインジェクションについては、大垣さんの元記事で触れられていたため、深い考えなしに記しました。おそらく大垣さんは「[Think IT] 第2回:PHPのSQLインジェクションを実体験 (1/3)」という記事に書かれた内容を想定されているのだと思います。

私は、XSSやSQLインジェクションを予防する目的での「入力のチェック」は不要と考えています。「各出力先に併せたフォーマット」を完璧に実行すれば防げるはずだからです(フォーマットできない不正なバイト列があれば例外を吐く)。

とはいえ、かくいう私も「規定する文字コードとして不正なバイト列が入力された場合には入力時にエラーにする」かもしれません。が、それはXSSやSQLインジェクションを予防するためではありません。「結局エラーを返すのならば早めに返したい」というコストの観点からです。

追記(2009-09-12)

  • 「全ての入力文字列の文字エンコーディングをバリデーションする」という対策案を読み、私は「スクリプトの先頭で、HTTPリクエストに含まれる文字列のバリデーションを行う」というような実装を想起しました。そんなもの、保険的対策とよぶのもためらわれるほどで、たとえばPHPなら file_get_contents() の戻り値にだって不正な文字列が含まれる可能性があるわけです。HTTPリクエスト以外の「入力文字列」にはどのようなものがあるのか、そこを明確にしてあげないと、ザルのような実装で慢心してしまう開発者が量産されないとも限りません。
  • 私は、当記事へのブックマークコメントで保険的対策が推奨されているのを不思議に感じています。「保険的対策は保険的でしかないのだから、少なくとも新規開発の場面では根本的解決策を推奨すべきだ」というのが当記事の主旨なわけです。なぜ根本的解決策(文字列の出所を問わず「問題を起こす場所」でバリデーションする)ではいけないのか、そこを明確にしていただかないと、納得できるはずがありません。徳丸さんは「不正な文字エンコーディングの影響はプログラム全体に及ぶ」と書かれていて、根本的解決策について実装が困難だとみなされているように読み取れます。ただ、本当に困難なのでしょうか? 「問題を起こす場所」ごとにラッパなりモジュールなりを作ればすむ話だと私には思えるのですが。

追記(2009-09-13)

Wassrでのやりとりを通じて、nihenさんのお考えは理解できました。私なりにまとめると:

  • 入力時のバリデーションは保険的対策ではなく、根本的解決策である
  • 入力バイト列(HTTPリクエストのみとは限らない)をUTF-8文字列として扱いはじめるときにチェックすべし

となります。これはこれで筋が通っていて納得できます。

それでも私はチェック漏れが気になるわけですが、「問題が起きる場所」でのチェックだって漏れうるのであってみれば、結局はアプリ製作者の注意力次第ということになるのかもしれません。あるいは、すべての「入力」について不正バイト列を想定したテストを書け、という指針を採用すべきなのかも。

追記(2009-09-14)

徳丸さんにトラックバックをいただきました。あとは皆様のご判断におまかせします。

私自身は結局、宗旨替えすることはありませんでした。「問題を起こす場所」の「直前」、たとえば preg_match() の直前で、mb_check_encoding() しないと不安で不安で…。「preg_match() に渡される変数は入口でバリデーションされてるはずだからチェックしなくてOK」などと腹は括れません。面倒だろうって? だからRubyを使ってるんです。

追記(2009-09-15)

しつこくてすみません。XSSについていうと、私は下記のような論理で考えています。

  1. 悪意のあるHTTPリクエストによるXSSは、HTTPリクエストをバリデーションすれば防げる
  2. しかしXSSは、悪意のあるHTTPリクエストによってのみ起きるとは限らない(サーバインストール型のフィードリーダーが悪意のあるフィードに攻撃される例をご想像ください)
  3. フィードの例を防ぐためには、外部サイトのフィード参照時にバリデーション(あるいはサニタイズ)する必要がある
  4. 敷衍すれば、文字列として扱いたい入力バイト列は、すべてバリデーション(あるいはサニタイズ)する必要がある
  5. すべての入力バイト列がバリデーションされたかどうか、何が担保してくれるのか?(特に動的型言語において)
  6. その担保がないならば、HTML構築時にバリデーションせざるをえないのではないか?

5の疑問が解決できるなら、入口でのバリデーションに衷心から納得できるのですが…。

ockeghemockeghem 2009/09/15 07:21 追記(2009-09-15)に関してですが、
私がトラックバックで書いたのは、必要条件として、入口でのバリデートが必要であって、それは言語処理系などプラットフォーム側でなされることがのぞましいということです。
通常、入口でのバリデーションが(言語機能などで)保証されていれば、その後の文字エンコーディングのバリデーションは必要ないと思います。しかし、トラックバックでも示したように、*絶対に* 壊れた文字エンコーディングが混入しないという保証もないので、特殊なケースでは、入口以外でのバリデートが必要になる場合もあるでしょう。また、XSS対策ライブラリを作るなどの場合では、念のため文字エンコーディングの再チェックをライブラリ側ですることもありえるでしょうし、Javaや.NETなどUTF-16を内部で使う言語では、文字エンコーディングが原因で起こる問題が限定されるので、そこまではしない、という実装もありそうです。
ですので、この追記に関しては、「必要な場合もある」というくらいしか言えません。PHPなら(文字エンコーディングのチェックが自動的に掛からないので)ぜひやるべきだし、Javaや.NETならたぶんやらない、Rubyも正規表現でチェックが掛かるのでやらない、Perlは??? みたいな個別の議論になると思います。

IwamotoTakashiIwamotoTakashi 2009/09/15 09:02 なるほど、もろもろ了解です。ありがとうございます。PHPでは「ぜひやるべき」ということが明らかになれば、私は満足です。

というわけで、PHPerのみなさんは、XSS対策を含むテンプレートエンジンとか出力フィルタとかを使うといいんじゃないでしょうか。