なぜPHPアプリにセキュリティホールが多いのか?

第20回 文字エンコーディングとセキュリティ(2)

この記事を読むのに必要な時間:およそ 3.5 分

前回に引き続き,今回も文字エンコーディングとセキュリティをテーマに解説します。前回は文字エンコーディングを利用した攻撃で,JavaScriptインジェクションやSQLインジェクションなどが可能であることを紹介しました。今回はなぜ,文字エンコーディングを利用して攻撃できるのか,簡単に紹介します。

文字エンコーディングを利用した攻撃の原理

文字エンコーディングを利用した攻撃には3種類の方法があります。

  • 不正な文字エンコーディングを利用する方法
  • 文字エンコーディングを誤認識させる(誤認識を利用する)方法
  • 文字エンコーディングのエスケープ方式を利用する方法

この連載は攻撃方法を詳しく解説する事が目的ではありません。具体的なの攻撃方法の解説はできる限り控え,なぜこの3つ手法が攻撃に利用できるのか解説します。

前回も触れましたが,パス遷移攻撃には文字エンコーディングを利用した攻撃方法もあります。

  • 文字列の正規化を利用する方法

この方法はJavaを利用した環境でよく発生する脆弱性です。今回の解説を行う3つの攻撃方法とは,内容と対策が異なります。

文字の正規化以外にも,文字エンコーディング以外のエンコーディングも攻撃対象となる場合があります。

  • URLエンコーディングを利用する方法

URLエンコーディングは文字エンコーディングといえませんが,エンコーディング全般のリスクを紹介するために紹介します。

文字エンコーディングについて

文字エンコーディングを利用した攻撃手法を解説する前に,簡単に文字エンコーディングについて解説します。

文字エンコーディングは「文字コード」と呼ばれることがあります。しかし,厳密には文字エンコーディングと文字コードは同じではありません。文字コード(文字符号)とは文字をコンピュータで処理可能にするため,文字に番号を与えたコード体系です。文字エンコーディングは文字コードをコンピュータが取り扱いやすい形に符号化(エンコーディング)したデータ形式といえます。同じ文字コードであっても,複数の文字エンコーディングが存在します。最も有名な例は,UnicodeとUTF(UCS Trasformation Format)エンコーディングでしょう。

Unicodeは文字コードですが,通常はUnicodeのままでは利用しません。最もよく利用されているUnicodeのエンコーディング形式はUTF-8とUTF-16です。UTF-8はASCIIコードとの互換性が考慮されたエンコーディングで,1バイトですべての文字を表せる欧米のアプリケーションであれば,ASCIIコードの範囲内で文字列を利用している限り,修正なくプログラムが動作します。このため,非常に多くのアプリケーションがUTF-8エンコーディングを採用しています。

UTF-8エンコーディングの場合,1文字を表現するために1バイトから4バイトを利用します。日本語の場合1文字を格納するために通常3バイトを必要とします。また,文字データのバイト数でなく文字数を知りたい場合,マルチバイト文字なのかシングルバイト文字なのか常に判別しなければならないので処理が面倒です。そこで,UTF-16を内部文字エンコーディングとして利用しているシステムが多くあります。JavaやWindowsは文字列を内部的にはUTF-16として扱っています※1)。

注1
UTF-16の場合も,1文字が2バイトとは限りません。UCS-4のコード範囲は4バイトで表現します。
UTFエンコーディングの種類
UTF-7, UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE

日本語の文字エンコーディングにはUTF以外にJIS(ISO-2022-JP),Shift-JIS,EUCがありますが,基本となっている文字コードはおおよそ一緒です。利用している文字コードはASCII,JIS X 0201,JIS X 0208,JIS X 0212,JIS X 213などです。

  • 参考:文字コード研究 (ラトルズ) ISBN4-89977-051-0

不正な文字エンコーディングを利用した攻撃

日本のWebサイトで利用されている文字エンコーディングは,すべてマルチバイト文字です。すべてのマルチバイト文字エンコーディングは,不正な文字エンコーディングによる攻撃のリスクがあると考えて構いません。

不正な文字エンコーディングを利用した攻撃には,本来必要なバイト数未満のデータを送り,特殊文字を無効化する方法が利用されます。文字列の終端を表す「"」(ダブルクオート),「'」(シングルクオート)を無効化し,JavaScriptインジェクション/HTMLインジェクション/XMLインジェクション/SQLインジェクションを行います。

不正な文字エンコーディングを利用した攻撃では,本来の長さより短いデータや,正しいエンコーディングでは有り得ないデータを送信させ,そのデータを受け取ったプログラムを誤作動させる攻撃です。Webアプリケーションではブラウザやプラグインを誤作動させたり,Webアプリケーションがサーバ側で利用しているSQLデータベース,XMLデータベースなどを誤作動させます。結果として,不正なJavaScriptの実行や不正なSQL文の実行を可能にします。

PHPのXML関連モジュールに利用されているlibxml2も壊れた文字エンコーディングに対する脆弱性が何度も発見されています。壊れた文字エンコーディングがあるとXMLインジェクションが可能となったり,サービス不能攻撃が可能になるケースがありました。PHPのHTMLエスケープ関数であるhtmlentities/htmlspecialcharsも壊れた文字エンコーディングに対する複数の脆弱性が発見されています(PHP 4.4.9には脆弱性が一部残されています)。Webアプリケーションが壊れた文字エンコーディングを利用した攻撃に対して脆弱でなくても,出力先のWebブラウザが脆弱では意味が有りません。壊れた文字エンコーディングを利用した攻撃は大きな脅威と言えます。

対策:
入力時のバリデーション処理で,すべての文字列に対してエンコーディングが正しいかチェックする。

PHPで文字エンコーディングが正しいかチェックする場合,mb_check_encoding関数を利用します。mb_check_encoding関数は文字列型のみチェックするので,$_GET,$_POST,$_COOKIE,$_SERVERなどをチェックする場合,こられらは配列も含むのでarray_walk_recursive関数と一緒に使います。

文字エンコーディングのチェック例

function check_encoding($value, $key) {
  if (!mb_check_encoding($value, 'UTF-8')) {
    die('Invalid charactor encoding detected');
  }
}

array_walk_recursive($arr, 'check_encoding');

この攻撃に対する対策は非常に簡単ですが,ほとんどのアプリケーションは未対策です。特に海外製のPHPアプリケーションはほぼすべて未対策である,と言ってよいくらい,文字エンコーディングが正しいかチェックしていません。

壊れた文字エンコーディングを検出していれば,htmlentities/htmlspecialchars関数のバグを利用したエスケープ処理を回避した攻撃も行えませんでした。libxml2の脆弱性にも,文字エンコーディングをチェックしていれば回避できた問題も多くありました。壊れた文字エンコーディングをチェックしていれば,不正な文字エンコーディングを利用たSQLインジェクションも回避できました。

不正な文字エンコーディングを利用したセキュリティ上の問題はどこで発生してもおかしくありません。文字エンコーディングが正しい文字エンコーディングであるかチェックするのは,Webアプリケーションのみでなく,すべてのアプリケーションに必須のセキュリティ対策だといえます。

文字エンコーディングをチェックしていないWebアプリケーションをそのまま利用するのはリスクが高いので,php.ini設定のauto_prepend_file設定などを利用し,文字エンコーディングをチェックするコードを追加して利用したほうが安全です。ただし,URLエンコードでバイナリデータを受け渡ししているアプリケーションは,auto_prepend_file設定を利用してすべての入力をチェックすると正常に動作しないので,除外リストを作成してバイナリデータは除外するようにします。

著者プロフィール

大垣靖男(おおがきやすお)

University of Denver卒。同校にてコンピュータサイエンスとビジネスを学ぶ。株式会社シーエーシーを経て,エレクトロニック・サービス・イニシアチブ有限会社を設立。
オープンソース製品は比較的古くから利用し,Linuxは0.9xのころから利用している。オープンソースシステム開発への参加はエレクトロニック・サービス・イニシアチブ設立後から。PHPプロジェクトでは,PostgreSQLモジュールのメンテナンスを担当している。

URLhttp://blog.ohgaki.net/

著書

コメント

  • Re:

    ご指摘ありがとうございます。大変失礼いたしました。
    修正いたしましたのでご確認ください。

    Commented : #2  gihyo.jp編集部 (2010/10/28, 12:29)

  • check_encoding 関数に関して

    いつもとても勉強になる連載をありがとうございます.

    文字エンコーディングのチェック例に載っている check_encoding 関数を試してみたところ,引数が意図したものと逆になっているかと思います.
    細かいところですが,そのままコピーして検証もせずに利用する人もいるかと思うので修正お願いします.

    Commented : #1  a_bicky (2010/10/28, 01:38)

コメントの記入