今回解説するWebアプリケーションのバリデーションの欠陥はPHPに限った問題ではありません。多くのプラットフォームのWebアプリケーションで見過ごされているバリデーション仕様の欠陥です。それは文字エンコーディングのチェックです。
文字エンコーディングバリデーションの必要性
筆者の知る限りでは、2004年に相次いで今まで知られていなかったアタックベクタ(攻撃経路)が見つかりました。2004年に多く見つかった新しいアタックベクタとは不正な文字エンコーディングを利用した攻撃です。不正な文字列を利用したJavaScriptインジェクションやSQLインジェクションの攻撃手法が公開されました。
文字エンコーディングを利用した攻撃自体は当時でも新しい攻撃手法ではありませんでした。文字エンコーディングを利用した攻撃は、少なくとも2000年から広く知られていた攻撃手法でした。ブラウザが文字エンコーディングを自動的に識別する仕様を用いたインジェクション攻撃は、2000年初めには、少なくとも一部のハッカーには、割と広く知られていた攻撃手法でした。
意図している通りの正しい文字エンコーディングが利用されているか確認することの重要性は随分昔から「セキュリティ維持に必須」の確認であると認識されているべき問題でした。
データベースシステムの場合
データベースシステムの場合、2005年に文字エンコーディングを利用したSQLインジェクション問題が認識され、その対策としてほとんどのデータベースシステムで文字エンコーディングのバリデーションコードが追加されました。
以来、マルチバイト文字をサポートするDBMSは保存されている文字列が正しい文字エンコーディングであるかチェックした後、文字列データを保存するようになっています。不正な文字エンコーディングの場合、エラーでSQLが実行できないようになっています[1]。
文字エンコーディングバリデーションの重要性に対する無理解
現在でも、文字エンコーディングが正しいエンコーディングであるか確認することがセキュリティ上とても大切なバリデーションである、と正しく認識していない開発者が数多く居ます。日本語のようにマルチバイト文字を使用する国の開発者であっても、文字エンコーディングを入力時にバリデーションする重要性を正しく理解していないことも少なくありません。
シングルバイト圏の開発者に、文字エンコーディングのバリデーションの重要性を理解してもらうのはさらに困難です。にも関わらず多くのWebアプリケーションはシングルバイト圏で開発され、それらのWebアプリケーションを参考にマルチバイト圏の開発者も開発しているため、ほとんどのWebアプリケーションで文字エンコーディングのチェックが行われていません。
文字エンコーディングを正しく扱わないリスク
データベースシステムの場合、正しく文字エンコーディングを扱わないと、SQLインジェクション、XPathインジェクション等が可能となる場合があります。
Webアプリケーションの場合、さらに多くのリスクがあります。
- SQL/XPathインジェクション
- Frame/JavaScript/HTML/CSS/オブジェクトインジェクション
- JavaScriptのバッファーオーバーフロー
- XMLインジェクション
- XML処理系のバッファオーバーフロー(XSLTなど)
- 文字エンコーディング認識を利用したJavaScript実行
- その他、文字列を処理する箇所すべて
これらの攻撃が文字エンコーディングを利用して行われる可能性があります。
不正な文字エンコーディングの利用を許してしまうと、Webアプリケーション内部で問題が発生しなくても、システム全体のどこで問題が発生するか分からなくなります。ブラウザ、外部のXML処理系、バッチ処理など、文字エンコーディングを利用した攻撃のリスクを残すことになります。
システムへ与える影響を考えれば、アプリケーションが文字エンコーディングが正しいがチェックしなければならないことは明らかです。
文字エンコーディングのバリデーション
ユーザからの入力チェックは入力を受け入れるアプリケーションの責任です。出力を受け入れるアプリケーションが正しく処理できるよう、正しい文字エンコーディングで出力することは文字列を出力するアプリケーションの責任です。
ユーザが正しい文字エンコーディングで出力してくれる、などという仮定は絶対にできません。いい加減な文字エンコーディングで出力しても、出力先のアプリケーションで正しく処理してくれる、などと仮定することも絶対にできません[2]。
文字エンコーディングのチェックは、ほかのセキュリティチェックと同様に、ブラックリスト方式でチェックするのではなく、ホワイトリスト方式でチェックする方法が利用できる場合はホワイトリスト方式でチェックします。
チェックは必ずリクエスト処理の最初に行われる入力バリデーション処理で行い、不正な文字エンコーディングを検出した場合にはリクエスト処理をエラーにして終了させます。サニタイズして処理を継続してはいけません。不正な文字エンコーディングの不都合がある部分だけを取り除くと、別の問題が発生する場合があるからです。
PHPの場合、文字エンコーディングが正しい文字エンコーディングであるかチェックするための関数、mb_check_encoding関数を利用して、入力チェック時にすべての文字列の文字エンコーディングが期待している文字エンコーディングとして妥当であるかチェックします。
オプションモジュールのmbstring
今の所、PHP6になっても文字エンコーディングが正しい文字エンコーディングであるかチェックする関数を提供するのはmbstringモジュールです。アプリケーションで文字エンコーディングをチェックすることは、システム全体の安全性にとって非常に重要です。にも関わらず文字エンコーディングチェック関数を提供しているmbstringモジュールはオプションモジュール扱いになっています。
mbstringモジュールがデフォルトモジュールでないので、多くのPHPアプリケーションがセキュリティ維持に必須である文字エンコーディングのバリデーションコードを持たない状態が続くと考えられます。
セキュリティ面以外でも、マルチバイト圏では必須の文字エンコーディングの検出機能など、本格的なマルチバイト対応アプリケーションに必須の機能はmbstringにしかありません。
mbstringはデフォルトモジュールであったほうがWebシステム開発には都合がよいのですが、これからも必要な文字エンコーディングのチェック、特にオープンソースのアプリケーションでは、チェックが行われていないアプリケーションが多数作られると考えられます。
auto_prepend_fileの利用
PHPには幸い便利なauto_prepend_file機能があります。php.iniのauto_prepend_file設定を利用すれば、すべてのリクエストで文字エンコーディングのバリデーション処理を簡単に追加できます。この機能を利用すればアプリケーションの互換性を大きく損ねることなく、Webアプリケーションに文字エンコーディングのバリデーション機能を持たせることが可能です。
PHPの場合、$_GET, $_POST, $_COOKIE, $_SERVER、$_REQUEST、そして$_FILESにユーザから送信された文字列が含まれます。$_FILESのファイルデータには画像などのバイナリデータを含むと考えられるので、テキストファイルのアップロードの場合は、例外としてアップロードスクリプトで個別に処理するとよいでしょう。
$_FILESのファイルデータを除けば送信されてくる文字エンコーディングは概ね予想ができます。HTMLフォームは特に指定がなければ、ページが記述されている文字エンコーディングの文字列を送信する決まりになっています。
問題となるのは直接URLにマルチバイト文字を入力してリクエストを送信してくるユーザです。この場合、ブラウザがどの文字エンコーディングを利用してリクエスト送信してくるか予想できません。アプリケーションによってはこのようなリクエストでも正しく処理したいこともあるでしょう。その場合、送信されてきた文字エンコーディングを自動判定して文字エンコーディングをチェックするか、機械的にアプリケーションが期待する文字エンコーディングに変換してから入力文字列のエンコーディングをチェックすればよいでしょう。
前者の方法では文字エンコーディングの自動検出に成功すれば正しく処理でき、後者の場合は文字化けした文字列を使用して処理を継続できます。前者の方法を利用するメリットは攻撃用の入力を送信してきた場合に、攻撃を検出できるのでより安全性が高いと言えます。
ファイルアップロードと文字エンコーディングの自動変換
mbstringモジュールは入力文字エンコーディングを内部文字エンコーディングに自動変換する機能を持っています。この機能はファイルアップロードを行った場合は動作しないので注意が必要です。ファイルアップロードで送信されるファイルデータはバイナリであることが多いのでこのような仕様になっています。
まとめ
オープンソースのWebアプリケーションのほぼすべてがセキュリティ対策に必須といえる文字エンコーディングのバリデーション処理を行っていません。しかし、利用するアプリケーションに文字エンコーディングのバリデーション処理を追加するにはあまり多くの作業は必要ありません。
今回は文字エンコーディングのバリデーションについて解説しましたが、文字エンコーディングは厳密取り扱うことが重要です。HTTPヘッダで文字エンコーディングを指定する、データベースシステムで利用する文字エンコーディングを正しく設定する、不正な文字エンコーディングを検出した場合は絶対に処理を継続させない、などの対策も不可欠であることに留意してください。