| « 画像ファイルにJavaScriptを隠す | 日本語ドメインは覚えやすい、分かりやすい、そしてだましやすい » |
画像ファイルにPHPコードを埋め込む攻撃は既知の問題
国内外のメディアで「画像ファイルに攻撃用のPHPコードが含まれていた」と比較的大きく取り上げられています。しかし、この攻撃手法は古くから知られていた方法です。条件は多少厳しくなりますがPerl, Ruby, Pythonでも同様の攻撃は考えられます。PHPの場合は言語仕様的に他の言語に比べ攻撃が容易です。
典型的な攻撃のシナリオは次の通りです。
1.アバダなどの画像ファイルをアップロードできるサイトを探す
2.ローカルファイルインクルードバグを探す
2.画像ファイルにサイトが利用している言語のコードを埋め込む
3.攻撃コードを含んだファイルを画像ファイルとしてアップロードする
4.ローカルファイルインクルードバグを利用して攻撃コードを実行する
PHPの場合、リモートインクルードバグを攻撃するための攻撃用コードをホストさせる為に不正な画像ファイルをアップロードするケースも考えられます。自分のサイトにファイルインクルードバグが無いからといって対策を怠ってはいけません。
よくあるバグはアップロードされたファイルの妥当性チェックです。以下は全て不十分な対策です。
- ファイルの拡張子をチェックする
- イメージ関数等を利用して画像の種類を判別する
- ファイルヘッダを確認する
- ファイル種別を判別するコマンドを利用する
よく見かけるコードは拡張子とイメージ関数などを利用してファイル種別を判別し、一致している場合に許可するコードになっています。
完全な対策単純な攻撃コードを除去する簡単な対策はこれらのチェックをした後、画像ファイルを別型式に変換し、元の形式に戻す方法です。例えば、GIFファイルのアップロードならGIF->PNG->GIFと変換すると攻撃コードは削除されます。(ライブラリが自動的に元のファイルに含まれたデータを変換後に再現させるような動作をしなければ普通は削除できます。削除と言うより変換エラーにより問題が検出でき、攻撃コードを含んだテキスト部分も除去できます。PHPの場合、変換後もColor Tableを利用してコードとして実行できる部分を作ることは可能です)
PHPのケースだけ考えるのであれば、正規表現関数で"<?php"を検索するのも有効です。(<script language="php"> 、ASP, ショートタグを有効にしている場合はこれらのタグも)ただし、正規表現には注意が必要です。(現実的にはあまり短いパターンだとデータ部分にもマッチする可能性が高いです。この部分は蛇足です。PHPのコードが含まれているかチェックする最も簡単な方法はtokenizerを使ってトークンを数える方法です。)
次のコードは完全にチェックできます。
mb_regex_encoding('ASCII');
if (mb_eregi('<\\?php', $image)) {
die('Attack detected');
}
mb_regex_encoding('ASCII');
if (mb_eregi('^.*<\\?php.*$', $image)) {
die('Attack detected');
}
if (preg_match('/<\\?php./i', $image)) {
die('Attack detected');
}
次のコードは不十分です。
if (preg_match('/^.*<\\?php.*$/i', $image)) {
die('Attack detected');
}
なぜ後者が不十分かは常識ですよね?
追記:コメントをいろいろ頂いてバージョンアップしています。皆さん、ありがとうございます。
?をエスケープし忘れていたので追加、行単位比較時のマッチ漏れ示そうと思っていたのが間違った正規表現だったので修正しました。ところで<?や<%がスクリプト開始タグとして利用できる場合はこれらもチェックしなければなりません。<script language="php">は何時でも使えるのでこれは常にチェックが必要です。
このエントリと関連しているエントリにPerlアプリのYaBB脆弱性に関するエントリを書いています。
http://blog.ohgaki.net/index.php/yohgaki/2007/06/21/perla_ca_a_o_yabb_a_sa_sa_fa_la_la_a_ia_
YaBBはアバダなども登録できるBBSアプリのようです。上記のエントリでもローカルファイルインクルードバグを利用した攻撃に脆弱である可能性が高いと指摘しています。
このエントリを書いた後にha.ckersでJavaScriptを画像ファイルに隠す方法が紹介されていることにも気が付きました。
http://blog.ohgaki.net/index.php/yohgaki/2007/06/24/c_ra_a_a_ia_ca_la_ljavascripta_e_na
画像ファイルに限らずスクリプトファイル以外へのスクリプトの埋め込む攻撃手法は随分前から知られている手法です。例えば通常のバイナリファイルなどは格好のターゲットになりえます。この様な攻撃から守る方法の一つにファイルを圧縮してしまう方法があります。圧縮するとそのままでは利用できないのでファイルをアップロード・ダウンロードできるようなサービスを提供する場合、サイトで再圧縮したファイルのみダウンロード可能にする、などの方法が考えられます。圧縮を2重にかけると2回解凍しなければなりません。この方法が利用者にとって不便であれば、画像と同じように解凍->圧縮する方法も考えられます。アーカイバに脆弱性が発見されることもしばしばあるのでサイトが攻撃されるリスクが増加することも知っておく必要があります。
14 comments
どういう場合に不十分なんでしょうか? 以前、preg_matchでは改行以降は無視すると記事に書かれていましたが、それと関連した話でしょうか? しかし、少なくとも私の環境では、改行以降を無視する現象は再現しません(Stefan Esser氏が以前書いていたものは私も知っていますし、再現もします)。
>自分のサイトにファイルインクルードバグが無いからといって対策を怠ってはいけません。
Webサイトの開発者が守るべきは、基本的に自分のサイトだけだと思います。自分のサイトを守るだけならば、アップロードファイルの拡張子の制限や、Webサーバの設定だけでよいわけです(画像にJavaScriptコードを挿入される攻撃への対処は、別途必要ですが)。
もし他サイトのリモートインクルージョン欠陥にも責任を持たなければならないならば、全てのWebサイトでこの問題への対処が必要になります。全てというのは、PHP以外の言語を使用するサイトや、ファイルアップロード機能を持たないサイトも含みます。
他サイトについては「気になる人は対処したら?」というレベルの話じゃないでしょうか。
>変換の過程で攻撃コードは削除されます。
変換が「完全な対策」と言えるのでしょうか。変換によって、フォーマットが不正な画像は排除できるのでしょうが、変換後のファイルが攻撃コードを含まないことは、保証されないと思います。
私の環境の問題でなければ、mb_eregi() で完全にチェックできるとは言えないのではないでしょうか?
$image = "test\n\x81
mb_regex_encoding( "SJIS" );
if (mb_eregi('<\\?php', $image)) {
die('Attack detected');
}
また、preg_match() を使用したコードが不十分であるとのことですが、もう少し詳しく教えていただけませんでしょうか。
最近 PHP をあまり使っていないからかもしれませんが、常識になっているとは知りませんでした。
実際には、画像形式を変換して攻撃コードを排除する方法が安全なように思います。
> けならば、アップロードファイルの拡張子の制限や、Webサーバの設定だけでよいわけです(画像
> にJavaScriptコードを挿入される攻撃への対処は、別途必要ですが)。
画像ファイルにPHPコードが含まれているか?絶対にチェックしなければならないとは思いません。
しかし、自分のサイトだけ守れば問題なし、という考え方には賛成できません。ウィルスを含んだファイルを公開していても問題なしと言えないと思います。
画像処理ライブラリには度々脆弱性が見つかっています。対策済みのシステムでは画像が表示されないだけで済みますが、サイトの画像を表示しただけで未対策のシステムにスパイウェアがインストールされても問題ない、例えばFlickerを見ていたらスパイウェアがインストールされても気にしない、と思う方はいないと思います。
いづれにせよ画像ファイルの形式を変換して、その際のエラーをチェックすればほとんどの攻撃用のコードは無効化できると思います。
> 変換が「完全な対策」と言えるのでしょうか。変換によって、フォーマットが不正な画像は排除できる
> のでしょうが、変換後のファイルが攻撃コードを含まないことは、保証されないと思います。
例えば、GIFからPNGへ変換した場合、普通のライブラリはGIFのヘッダを解析、画像イメージをデコード、ビットマップイメージに近いデータを生成、新しい画像形式のヘッダを生成、画像データをエンコード、のような過程になっていると思います。
GIF、PNGはロスレス圧縮を利用していますがバイナリ部分は圧縮されているのでこの部分にデータがコードだとすると解凍に失敗します。ヘッダがおかしくても変換に失敗します。仮にフォーマットに大きなパディングがありそこにコードが埋め込める場合でも普通のコードであれば変換するとこの部分のコードも削除されるはずです。GDライブラリを利用している場合、GDネイティブのデータ形式に変換してからイメージを再生成すれば同じ効果を期待できます。ブログでは変換コマンドを利用しているアプリも考慮して別形式に変換、再生成する手順としてGIF、PNG、GIFと変換する手順を紹介しています。ビットマップ型のファイル形式同士だと形式を変換しても攻撃コードが削除できないライブラリがあるかも知れませんが確認していません。あったら是非教えてください。
JPEGのexif拡張を利用している場合、コマンドやライブラリによってはJPEGからJPEGに変換するとテキストデータをそのままコピーしてしまう可能性も高いのでJPEGの圧縮率を変更するだけでは、ここで記載している問題に対処できない可能性があるので注意が必要です。exifを使うと攻撃用のPHPコードをホスティングさせられる事を防ぐには”<?”を”< ?"とスペースを強制的に入れるなどの対策が必要です。
エンコーディングを利用した攻撃が可能なマルチバイト文字を取り扱っている場合、利用している正規表現関数とその設定に注意が必要です。これはよくあるマルチバイト文字を利用して特殊文字を無効化させる方法ですよね。マルチバイト文字を取り扱っている場合、正規表現でバイナリマッチを行うにはASCIIにしてからマッチさせないとならないです。このことを書いていないのは不親切・不十分と言えるので追加します。
mb_regex_encoding( "ASCII" );
> また、preg_match() を使用したコードが不十分であるとのことですが、もう少し詳しく教えていただけませんでしょうか。
正規表現関数は元々ライン志向の処理系です。ですからPCRE(preg_matchが利用しているPerl Compatible Regex Library)は行単位で処理します。意図した通りの動作をさせるにはMultilineモディファイア(m)か$をデータの末尾にマッチさせるDモディファイア(PHPのみのオプション)を使用しなければならないケースを紹介したかったのですが、例が悪かったです。
mb_eregi('<\\?php', $image) -> mb_ereg('^.*<\\?php.*$', $image)
preg_match('/<\\?php/im', $image) -> preg_match('/^.*<\\?php.*$/im', $image)
に修正しておきます。ありがとうございます。
蛇足ですが、デフォルトのmbregexは^->\A, $->\zへの書き換え、正確には\Zも\zと取り扱われます。(つまり「^.*hoge.*$」でデータ全体がマッチ対象となる)mbstringが書き換えるのではなくonigurumaのオプションが設定される。oniguruma内部で^,$の取り扱いが変わる、を行うオプションが有効になっているのでpreg_*と違う動作になります。設定はmb_regex_set_options()で取得・設定できます。デフォルトは"pr"で、シングルラインおよびマルチラインオプションを有効にしたRubyスタイルの正規表現になります。
正規表現は便利ですが微妙な動作の違いなどで困った事になったり、あまりよく考えていないとチェックできないケースを紹介しようとして今回のように間違った正規表現になってします... 最近はバリデーション用ばかり書いているので行に関連するメタ文字を使わないとデータ全体にマッチするの失念していました。最初は^$を書いていたのですが、長くて分かりづらいと思って消してしました。正規表現は簡単な物でもテストしないと... 特に半分寝ながら書いているときは...
ところでこの画像内のテキスト情報を使った場合に発生する脆弱性はexifのテキスト情報をWebで参照できるようにした場合にJavaScriptインジェクションを行う手法として紹介されたと思います。PHPの攻撃用コードをホスティングさせる方法として紹介された例や攻撃例は知りません。ここまでテキストにスクリプトが含まれているかチェックする必要があるか意見が分れるところだと思います。最新のPHP5系ならリモートスクリプトのインクルードはデフォルト無効に設定されているので、チェックなしで良しとするべきかも知れません。
GIFヘッダのGlobal Color Mapなどに攻撃コードを仕込む方法があります。そのようにして作られた画像は、画像形式として正当です。ですので、PHP(GD)でGIF→PNG→GIF変換しても変換エラーも出ず、攻撃コードが削除されることもありませんでした。
画像形式変換が、攻撃コードを削除することがあるとしても、それはたまたま削除されたくらいに思った方がよいと思います。形式変換は、攻撃コードを排除するために作られたものではないですから。
>しかし、自分のサイトだけ守れば問題なし、という考え方には賛成できません。ウィルスを含んだファイルを公開していても問題なしと言えないと思います。
脆弱性のある他のサイトまでは守る必要はなく、また多くの場合は守ることも出来ない、という意味で書きました(ウィルスの話は、リモートインクルードとは別の話で、長くなりそうなので、書くのは控えます)。
>exifを使うと攻撃用のPHPコードをホスティングさせられる事を防ぐには”<?”を”< ?"とスペースを強制的に入れるなどの対策が必要です。
テキスト部分以外にも <? は含まれる可能性があります(攻撃の意図の有無に関わらず)。10KBのランダムなバイナリファイルであれば、約15%の確率で <? が含まれます。画像ファイルを壊すのがこわいです。
>ところで<?や<%がスクリプト開始タグとして利用できる場合はこれらもチェックしなければなりません。
<script language="php">というパターンもありましたね。使っている人を見たことないですが。
なるほど。参考になります。Global Color MapとはGlobal Color Tableのことですね。そのまま渡されるようなデータ領域がある場合、バリデーションに使うには無理があるようですね。GDとモジュールのコードを時間があるときに読んでみます。元々バリデーション用に作られた物でなくても今ではバリデーションに使われている物は多くあります。GDがバリデーションに使えるようになると助かるのですが、もう開発が終わっているライブラリなので無理でしょうね。
GIFの定義では
19. Global Color Table.
a. Description. This block contains a color table, which is a sequence of
bytes representing red-green-blue color triplets. The Global Color Table
is used by images without a Local Color Table and by Plain Text
Extensions. Its presence is marked by the Global Color Table Flag being
set to 1 in the Logical Screen Descriptor; if present, it immediately
follows the Logical Screen Descriptor and contains a number of bytes
equal to
3 x 2^(Size of Global Color Table+1).
This block is OPTIONAL; at most one Global Color Table may be present
per Data Stream.
とあるので削除してしまうオプションを付ければ問題がなくなるのかな?しかし、GDもわざわざ(?)情報を保持してPNGまで持って行っているのですね。ざっとPNGの定義も見てみましたが、どこに保存されるのかいま一つ分かりませんでした。コードを見た方が早いかも知れません。
> <script language="php">というパターンもありましたね。使っている人を見たことないですが。
知ってはいますが私もよく忘れられるものです。このパターンはいつでも使えますからチェックするなら必須ですね... 元々イメージファイルのバリデーションに正規表現は無理があるので、PHP専用でなくもっと一般的に使えるバリデーション方法がないと困りますね。
> テキスト部分以外にも <? は含まれる可能性があります(攻撃の意図の有無に関わらず)。10KBのランダムなバイナリファイルであれば、約15%の確率で <? が含まれます。画像ファイルを壊すのがこわいです。
さすがにバイナリ部分に<?を探すのはちょっと無理がありますね... ショートタグは無効にして<?phpと<script language="php">を探さないとダメですね。GIFもPNGもいろいろテキストを入れることができるので、PHPの言語仕様的に他のサイトを守るのは難しい(攻撃の踏み台にされることを防ぐのは難しい)ですね。ただ、これらのテキスト情報はチェックする気になればできない事もないです。他のサイトの為でなくてもローカルインクルードバグに備えてチェックしておいても良いかも知れません。
# 攻撃目的以外で<?phpとか、<script>とか、exif, png等のテキ
# スト情報に入れるとは思えないので、検出したら「ファイル形式
# に問題があります」とエラーにしてしまっても良いと思います。
# XSSをブラックリストで完全に対処するのは無理なので、攻撃
# されかけた事を記録する為だけに検出すべきで、出力する場合
# は適切にエスケープしてから出力しなければならないです。
結局は自分が使っているグラフィックスライブラリの癖や一時的に変換するフォーマットなどで変換してもバリデーションできない場合がある、と言うことですね。こういう時は最も単純なBMPとかに変換して戻すと大丈夫かもしれません。ざっと仕様を見たところ、さすがにBMP形式はテキスト情報は無いようです。
と言う事でGIF->BMP->GIFを勧めることにします。いろいろ指摘頂いて助かります。この場合にも問題がある時には教えて頂けると助かります。
# 自分のコードもBMP経由に変更しないと
しかし、この方法はBBSのアバダ程度なら問題ないですがアルバムアプリで採用するにはかなり問題があります。普通のデジカメはexif情報を付けて保存していますが、これが全部なくなるとユーザは耐えられないでしょうね。圧縮率を微調整するとExif情報はそのままでバイナリデータ部分に含まれた攻撃用コードを除去できるかも知れません。
いづれにせよ「ちょっと書いてみる問題」を超えてきちんとリサーチしないとならない問題ですね... 毎度の事ですが気軽にさらっと書いたエントリに爆発的にアクセスがあったりするのでブログ書きは気が抜けない...
ググるとすぐに
http://hul.harvard.edu/jhove/
が見つかりました。GIF,JPEG,PDF,TIFF,UTF-8などのフォーマットに対応しているそうです。攻撃用コードの埋め込み対策にどの程度使えるものなのか調べてみる価値がありそうです。GIFのGlobal Color Tableを利用した攻撃は検出できないように思えます。JPEGが妥当なフォーマットかチェックするには十分使えるかも知れません。
BMP変換でも、攻撃コードは削除されないと思います。BMPでもColor Table相当のパレット情報を使っているので、普通に変換する場合は、GIF→BMP→GIFでそのままColor Table情報は引き継がれると思います。
ありがとうございます。後で調べてみます。(GIF→PNG→GIFでNGなケースも試す時間がない..)やはり、ソースコードとデータを照らし合わせて引き継がれるテキスト情報がどうなっているか確認する必要がありますね。GIF→PNG→GIF変換の場合、Global Color Tableのテキスト情報(攻撃コード)はそのまま変換後のGIFに残ってしまったようですが、どのようなファイルでテストしたかは想像できますが、テストに使ったファイルのURLかメールで送っていただけませんか? 思っている形式と違うファイルでチェックしても時間ばかり必要なのでお願いします。
まとめると画像ファイルをバリデーションする目的には
0.画像ファイルとして妥当なファイルであるかチェックする
1.スクリプト系言語の攻撃コードが含まれていないかチェックする
2.不正な画像ファイルにより画像ライブラリの脆弱性を攻撃するコードが含まれていないかチェックする
があると思います。
0.は拡張子、ファイルマジックをチェックした後、画像別の形式に変換してみて問題なければ概ね問題なしと考えられると思います。しかし、普通画像ファイルのデコーダは回復可能なエラーなら無視してデコードを続けるので変換ができるだけでは不正なデータが含まれている可能性は排除できません。
1.はどのレベルまでするか?は議論の余地がある部分ですが、圧縮をサポートする画像ファイルで0のチェックが終わっていれば、デコーダにより正しくデータがデコードできるれば少なくとも圧縮データ部分にはスクリプト系言語の攻撃コードは含まれないことが保障できます。(デコード後に攻撃コードが作れても意味がない。デコーダは致命的なエラー以外はエラーを無視するか警告するだけで処理を中止しないと考えてよいのでオーバーフロー攻撃は無いとはこの時点では言えない)
残りはテキスト系のデータですが、PHPやJavaScriptの場合、作者などのテキスト情報を攻撃コードとして利用可能です。この外にColor Table等をデータをテキスト形式で保存している画像ファイルの場合、これらもチェックするか削除する必要があります。
PHPのGDネイティブの画像形式の構造体を見ると特にテキスト情報を保持する要素が無い(pixelとアンチエイリアス以外は整数型)が無いのでどこか別の場所でテキスト情報を保持しているのでしょう。斜め読みしただけだとGDネイティブの形式すればテキスト情報は保存されないように見えます。テキスト情報が無いファイル形式に変換後に元に戻すとテキストは無くなっているのでスクリプト系言語の攻撃コードが無いことを保証できるようになります。
2. は普通のデコーダはエラーは無視するので攻撃コードが含まれていても変換してエラーが無いことを確認してもあまり意味がない。別形式に変換したデータを元に戻すことにより多くの攻撃コードは削除できると考えられるが画像ファイルのパラメータである整数値が妥当な範囲に変換されてるか、などはライブラリ次第なので完全な安全性保障はできない。ただし、そのままアップロードされたファイルをダウンロードさせるよりははるかに安全といえます。
画像ファイルの攻撃にはデコーダのバグを攻撃する方法が多いので、バグが無いデコーダでデコードしてエラーを除いたファイルを作ればかなり安全性が向上すると考えられます。ただし、利用しているデコードコードにバグが無いとも限らないのでより安全にバリデーションするには専用サーバを用意する方が良いです。
GDについてくるGDネイティブ形式のコードをざっと見た限りではこの形式でイメージを作くれば0から2の要件を満たすように見えます。
その画像をWindows環境のGIMPでGIF→BMP→GIF変換を掛けてみましたが、攻撃コードは消えませんでした。
Color Tableは、テキスト情報ではなく、バイナリ情報です。GIFは最大で255色まで使うことができますが、そのそれぞれの色を3Byte(RGBで1Byteずつ=1677万色)で表したもので、最大255*3=765Byteのサイズを持ちます。GIF、PNG、BMPも同じような情報を持っていて、画像データとしては欠かせない情報です。
#なお、GIFのGlobal Color TableがOPTIONALなのは、Local Color Tableで代用できるからだと思われます。Color Table自体は、画像を表現するために必要なものです。
Color Tableに仕込まれた攻撃コードは、意図的にColor Tableの並びをシャッフルするなどしないと無効化できません。通常の画像変換ソフトは、わざわざシャッフルするようなことはしないと思います。
Color Tableをシャッフルしても、それ以外のところに、巧妙な形で攻撃コード埋め込むことは可能でしょう。例えば、画像サイズはGIFではX方向=2Byte、Y方向=2Byteの合計4Byteで表現されますが、4Byteあれば「<?/*」のようなコードを埋め込むこともできます(16188 x 10799 というかなりデカイ画像になりますが)。
もちろん、サイズ以外の部分にも、例えば本体の画像データの部分に、圧縮後に「<?php」が現われるようなデータを作ることは、画像データ構造と圧縮アルゴリズムの知識が少しあれば可能でしょう。
結局のところ、画像形式としての妥当性を確認し、コメント部分などの付加的なテキスト情報を削除するだけでは、原理的にPHPコードの埋め込みを避けることはできないわけです。かといって、画像ファイル中に、「<?」「<?php」などのパターンを持つものを禁止する方法だと、攻撃の意図がない正常な画像が誤って排除される場合が出てきます。
最初の話に戻ると、PHPのリモートインクルードの脆弱性は、脆弱性のあるサイトで直すべきです。もしも、攻撃コードのホストを簡単かつ完全に防げるのであれば、ホストする側での対策を薦めるのは妥当だと思いますが、簡単で完全な方法はありません。
ですので、自身のサイトにリモートインクルードの脆弱性を持たないようにすることだけが、Webサイト開発者に必要なことだと思います(XSSの問題は置いておくと)。そのために開発者がすべきことは、アップロードされるファイルの拡張子を管理する(あるいは適宜サーバの設定をする)だけです。
GDのコードを斜め読みすると攻撃に利用できそうなデータはピクセルデータのみが受け渡されている部分までは確認しました。つまり、PNGの作者やJPEG, TIFFのexif, GIFのGlobal Color Table等のテキストデータは引き継いでいません。画像に含まれたテキストデータはプログラマが意図的に再現させなければ全て除去されます。(GIF規格によるとGlobal Color Tableはテキストデータとなっているのでそう仮定しています。実際にどう利用されているか理解するまでGIFに詳しく無いです)
バイナリのColor Tableの事は画像ファイルの基礎知識の一つなので知っています。以下になぜ私がバイナリのColor Tableが変換により攻撃に使えなくなる、と考えているか書きます。teracci2002さんには必要ないと思いますが、グラフィックス画像の知識がない方でも分かるように書きます。
攻撃コードを埋め込むの単純にテキストを埋め込むのは簡単です。例えば、2^24色のうち2^16色が使えるとしてこれを必要な色だけColor Tableに入れるとして簡単に2^16文字のテキストデータを埋め込むことがでます。これだけのコードを入れられれば有用な攻撃コードを埋め込むのは簡単です。
ファイル形式の変換による防御に対しても攻撃を行うには、ピクセルデータから攻撃コードを再生成できるようにピクセルデータを構成しなければなりません。これはteracci2002さんのコメントにも書いてありますが、これは非常に難しいです。まず各Color Tableの値は、2^24色なら3バイト、2^32色だとしても4バイトのアライメント単位でユニークになっていなければなりません。ユニークでないとピクセルデータからColor Tableを再生成する場合に攻撃コードが再生成さません。この制限からピクセルデータに長い攻撃用文字列を埋め込むのは現実的ではありません。
コメントに記載されているように非常に短い文字を再現させる、たとえば<? /*, <?php /*、であれば可能と考えていました。(Colorコードもユニークにできるので可能)この点に異論は無いです。もしカラーコードがユニークでなければならない制約の上で、有用な攻撃ができる程度のテキストを再現できてしまうのであればこの時点で変換による対策は完全でないといえます。
少し話がそれますが、ここまでで重要なことは画像形式に関わらず変換を行えば、PHP以外の言語なら攻撃は不可能になります。PHPのような特殊な仕様を持たない言語にとっては変換は完全な対策と言っても構わないでしょう。(PHPはもともと埋め込み型言語なのでスクリプトの開始に開始タグが必要だが、他の言語はスクリプト全体がコードであるため、最初のブログ本文にも記載していましたが、PHPの言語仕様が特殊なのでPHPは攻撃し易くなります)
これも既にコメントとして記載されている通り、PHPの場合、「<?php /*」等をColor Tableに再現させることにより、それ以後「*/」が現れるまでコードのコメントすることができます。Color Table以降のデータに有用な攻撃コードが書ければフォーマット変換によって「完全に防御できない」ことになります。
ここが意見の異なる部分ですが、GIF、PNGはデータ部分に圧縮をかけているのでデータ部分に有用な攻撃コードを再現させるのはまず不可能だと考えれると思います。まず、圧縮されたデータは言語として必要なトークン単位に分割されたテキストを再現できません。もしそのような事が意図的に可能な圧縮アルゴリズムだとするとかなり劣悪な圧縮アルゴリズムだと言えます。LZWもそこまで劣悪ではないと仮定しています。
このことから、GIF→他の形式→GIFのように変換を行えばPHPにとっても「完全な対策」と言っても差支えない程度の安全性が確保可能と考えています。(ブルートフォースでセッションID盗むことは論理的には可能かも知れませんが現実的には不可能と言う意味で「セッションIDによるセッション管理は安全である」とするのと同じような考え方です)
> 最初の話に戻ると、PHPのリモートインクルードの脆弱性は、脆弱性のあるサイトで直すべきです。もしも、攻撃コードのホストを簡単かつ完全に防げるのであれば、ホストする側での対策を薦めるのは妥当だと思いますが、簡単で完全な方法はありません。
この意見には大賛成です。「他のサイトを守る」と書いたのがいけなかったです。「攻撃の踏み台にされない」と書いておけば議論になるようなことは無かったと思います。
> 開発者がすべきことは、アップロードされるファイルの拡張子を管理する(あるいは適宜サーバの設定をする)だけです。
ここは賛成できません。フォーマット変換により、Webサーバのスクリプティング言語で記述された攻撃コードのみでなく、ウィルス付画像ファイル、JavaScript付き画像ファイルなども排除できるようになります。拡張子を管理するだけでは非常に簡単に踏み台として利用可能になります。ブランドを大切にする企業であれば自分のサイトを攻撃の踏み台にはされたくないはずです。
100歩譲ってフォーマット変換による対策が不十分なケースがいくつかあるとしても、変換という単純かつ簡単な方法で飛躍的にセキュリティが向上するのであれば利用すべきではないでしょうか。
GIFのGlobal Color Tableはテキストデータでは無いです。GIF規格にもテキストだというような記述は見当たりませんでした。
>この制限からピクセルデータに長い攻撃用文字列を埋め込むのは現実的ではありません。
攻撃コードは、<?php passthru($_POST['a']); ?> のようなもので十分じゃないですか?(お考えのものとは、毛色が異なるかもしれませんが)。
>もしそのような事が意図的に可能な圧縮アルゴリズムだとするとかなり劣悪な圧縮アルゴリズムだと言えます。
これは、圧縮効率の面で劣悪ということでしょうか?
>ここは賛成できません。フォーマット変換により、Webサーバのスクリプティング言語で記述された攻撃コードのみでなく、ウィルス付画像ファイル、JavaScript付き画像ファイルなども排除できるようになります。
>100歩譲ってフォーマット変換による対策が不十分なケースがいくつかあるとしても、変換という単純かつ簡単な方法で飛躍的にセキュリティが向上するのであれば利用すべきではないでしょうか。
そういうことを否定している訳ではないです。私が言っているのは、
1.リモートインクルードの攻撃コードのホストを防止する対策は必須のものではない
2.上記の対策においては、画像ファイルの形式変換は不十分だ
ということだけです。画像形式変換が、各種のセキュリティ対策として意味が無いとは思っていないです。
上記の2については、ご使用のグラフィックライブラリで、送付した画像の変換をしてみて頂くのが早いかと思います(お仕事の合間にでもお試しを)。
確かに必須としなくても良いとは思います。実際、私も特定多数のユーザが利用するサイト(業務で社員だけが利用するサイト)の場合、アップロードされたファイルの検証は不特定多数のユーザが利用するサイトよりもずいぶん甘い仕様する事が多いです。
> 2.上記の対策においては、画像ファイルの形式変換は不十分だ
自分でもカラーテーブルを使って有用な攻撃方法を考えてみました。sytem("rm -rf")などのDoSやphpinfo()だけ実行させるなどの比較的単純なものであれば、24ビットだと何も考えずに書くだけで再現できますね。画像の作り方も簡単です。防御する側は攻撃する側の立場になって考えるべきなのですが、それが足りなかったです。
> 上記の2については、ご使用のグラフィックライブラリで、送付した画像の変換をしてみて頂くのが早いかと思います(お仕事の合間にでもお試しを)。
確かLZWは行方向に圧縮するので単純なイメージを使えば簡単にエンコード後に文字を作る事が可能かも知れません。どのような攻撃コードになっているか楽しみです :)
ところで、不正なコードを画像から除去するほぼ(?)完全な対策は結構面倒になって(PHPの場合)
1. 拡張子とファイルマジックを確認する
2. 変換してエラーが無いか確認する
3. データをtokenizerで解析する
4. tokenの数を数えてPHPのトークンが3つ以上か判定し、3つ以上だとPHPコードを含むと仮判定する
5. lintモードでコードを実行しエラーがなければPHPの攻撃コードを含むと判定する
と言う感じになると思います。さらに攻撃を難しくするには画像にすかしを埋め込んで、ヘッダとデータ両方に含まれた攻撃コードを無効化する方法も考えられます。この方法でもどのように「すかし」を入れているか分かれば攻略方法も見つけられるかも知れませんが。
4.の「3つ以上でエラー」は「<?php phpinfo() >」は最小限のPHPコード(終了タグ意外に/*を使っても同じ)で3つのトークンが含まれているので3つ以上でPHPコードの可能性があるとしています。偶然PHPのトークンが3つ以上現れ、コードの妥当性をチェックもしているので、ほぼこれで十分でしょう。(追記:開始、終了、コメント、空白、文字列以外のトークンが1つ以上あるか?と言いった感じに判定にした方が精度が良いと思います)
PHP以外のケースを考えるとカラーテーブルにシェルコードを埋め込むなど、いろいろな攻撃方法が考えられます。デコード後のピクセルデータをコードやデータとして利用して攻撃する方法も考えられます。
全てのケースで完全な方法を考えるとなると無理があるので本文を「完全な対策」から「単純な攻撃コードを除去する簡単な対策」に変更しておきます。予想以上に画像ファイルの妥当性チェックには落とし穴があるので非常に参考になりました。
他のサイトの踏み台にされる可能性があるのはリモートからスクリプトがロードできるPHPとJavaScriptです。PHPは上記の方法でほぼ検出できると思います。残りは攻撃用のJavaScriptコードの検出ですがこれはが画像フォーマットも調べてどのように攻撃が可能か調査してからですね。
最近はいたずら目的の攻撃は減少傾向にありますが、例えば有名なアルバムサイトの画像に埋め込んだPHPコードを使って「rm -rf /」を実行させる、などのような攻撃が行われると被害は少数でも「○○サイトの画像を使ってサイトが攻撃させる」のような報道になると思います。愉快犯(もしくは○○サイトを狙った本当のサイバー攻撃?!)にはこれでも十分かと思います。
ところで、一般論として画像ファイルの拡張子だけのチェックだとチェックが甘すぎだと思います。拡張子、マジック、フォーマットのチェックくらいは必須条件として考えた方が良いと思います。拡張子チェックだけだと以前にWikiの脆弱性(データにダイレクトにアクセスできてしまう)が問題になったことがありますが、これと同じ脆弱性も持つことになります。