T.Teradaの日記

2007-07-15

[]画像へのPHPコマンド挿入 01:06 画像へのPHPコマンド挿入を含むブックマーク

だいぶ時間がたってしまいましたが、大垣さんの以下のブログにコメントしたことなどをまとめます。

画像ファイルにPHPコードを埋め込む攻撃は既知の問題 | yohgaki's blog

アップロード画像を利用した攻撃についてです。

攻撃の概要

画像ファイルにPHPコマンドを挿入する攻撃は、大きく2種類に分けることができます。

1つは、画像のアップロード機能を持つサイト自身を狙う攻撃です。PHPで開発されており、任意の拡張子のファイルのアップロードを許すサイトでは、拡張子がphpなどのファイルをアップロードされる恐れがあります。

拡張子がphpなどのファイルに仕込まれたPHPコマンドは、そのファイルにHTTP/HTTPSでアクセスされた際に実行されます。攻撃者は、アップロードファイルを通じて、画像が置かれるWebサーバ上で任意のコマンドを実行することできます。

この脆弱性は、アップロード可能なファイルの拡張子を制限したり、画像を置くディレクトリでは「php」拡張子のファイルをPHPで解釈しないようにWebサーバを設定するなどの方法で、簡単に防げます。

もう一つの問題はRFI(リモートファイルインクルード)です。PHPコードを仕込んだ画像ファイルが自分のサイトにアップロードされたとします。拡張子の制限などをしておけば、この画像ファイルがサーバ上でPHPとして実行されることはありません。

しかし、RFI脆弱性を持つよそのPHPサイトから、この画像ファイルがインクルードされてしまう可能性があります。画像に含まれるコードは、インクルードする側のよそのサイト上で実行されるため、自分のサイトに被害が出るわけではありません。

しかし、自分のサイトがホストする悪意のある画像ファイルが、よそのサイトへの攻撃を助けてしまうことになります。

この危険性は、画像のアップロード機能などを持つサイトであれば、PHPをプログラム言語として使用していないサイトでも発生します。例えばPerlで作られている「はてな」が、PHPの攻撃コードをホストするような事態です。

それでは、自分のサイトがPHPの攻撃コードを含む画像ファイルをホストするような事態は、防ぐことができるのでしょうか。

以下で、いくつかの方法について考えてみます。

画像の形式チェック

画像ファイルの形式チェックとは、アップロードされた画像が画像ファイル形式として適正であるかをチェックすることです。画像の形式を、GIF→PNG→GIFに変換するような方法がとられることもあるでしょう。

結論を言うと、画像形式のチェックや形式変換では、PHPの攻撃コードを含む画像を排除できません。画像ファイルの形式として正しいにも関わらず、PHPの攻撃コードを含むような画像は存在するからです。

実際に試して見る

まずはGIFファイルを用意し、GIFのGlobal Color Tableというパレット定義領域に、攻撃コードを仕込むことにしました。

Color Tableをいじるので、画像の色は変わりますが、画像形式としては正常で、ブラウザでのレンダリングも可能です。

この画像を、以下のPHPコードでGIF→PNG→GIFに変換します(内部的には、GDによって画像形式変換が行なわれます)。

<?php
$i1 = imagecreatefromgif('/tmp/xxx.gif');
imagepng($i1, '/tmp/xxx.png');
$i2 = imagecreatefrompng('/tmp/xxx.png');
imagegif($i2, '/tmp/yyy.gif');

結果はというと、攻撃コードは変換後のGIFファイル(yyy.gif)にもそのまま引き継がれていました。

恐らく、GIF→PNG→GIFのような形式変換では、不正なフォーマットの画像を排除できるでしょう。また、形式変換によって、画像ファイル内のコメント領域は削除されるようです。

しかし、PHPの攻撃コードを含む画像が、不正なフォーマットであるとは限りませんし、攻撃コードがコメント領域にあるとも限りません。つまり、形式変換では攻撃コードを削除できない場合があります。

画像がPHPのコードを含むか調べる

それでは、もう少し直接的なアプローチについて考えてみます。アップロードされる画像ファイルがPHPのコードを含んでいるかを調べ、PHPのコードを含む場合には、アップロードを禁止するという方法が考えられます。

これには、画像ファイルのデータ内にPHPの開始タグが含まれていないか調べる方法と、PHPのParserを使う方法の2つがあります。

PHPの開始タグ

画像データ中に、「<?php」などのPHP開始タグが含まれているかを調べ、その画像のアップロードを禁止する方法です。

PHPの開始タグは、以下の4種類が存在します。

① <?php
② <?
③ <%
④ <script language="php">

①と④は常に有効ですが、②と③はPHPの設定によって使えたり使えなかったりします。

脆弱性があるよそのサイトが、②と③を無効にしているかどうかは判りませんので、確実な対策を期すならば、②と③も禁止することになります。

しかし、②と③ は2文字しかありません。計算してみると、完全にランダムな10KBのファイルがあるとして、そのデータ中に特定の2Byteの文字列が含まれる確率は約15%です。つまり、攻撃の意図が無いファイルを、かなりの確率で排除してしまうことになります。

PHP Parserを使う

これは、大垣さんがコメントでの議論の中で提示された方法です。

PHPは、当然ながらPHPのプログラムソースのParserを持っています。実はそのParserは、PHPプログラムの中から呼び出すこともできます。

これを使うと、PHPプログラムから、アップロードされた画像をPHPコードとしてParseすることができます。Parseした結果、PHPのプログラムのトークンがいくつか検出された場合、PHPコードが含まれている画像だと判断して、アップロードを禁止するような処理をすることができます。

この方法の最大の問題は、PHPをインストールしていないサーバ環境では使用できないということです。上記で「はてな」の例に触れたように、PHPの攻撃コードをホストする可能性があるのは、何もPHPで開発されたサイトには限らないわけです。

もう一つの問題は、RFI脆弱性を持っていて、画像をインクルードしに来るよそのサイトの環境(PHPのバージョンや設定)は予測できないということです。

Parserの動作は、PHPのバージョンや設定に依存します。設定に関しては、short_open_tag、asp_tags、script_encodingなどが、Parserの動作に影響を及ぼすと思われます。

つまり、自分のサイトにアップされた画像ファイルをParseして、PHPのコードを含まないことを確認したとしても、よそのサイトがその画像をインクルードした時に、PHPコードと認識されないとは限りません。

結論らしきもの

上記のように、ユーザにファイルをアップロードを許すサイトをPHPで開発する場合、自身のサイト上で任意のPHPプログラムを実行されないように、アップロード可能なファイルの拡張子を制限するなどの対策が必要です。

一方、自身のサイトにPHPの攻撃コードを含むファイルがアップロードされ、よそのRFI脆弱性を持つサイトへの攻撃に利用されるタイプの事象を防ぐのは、非常に難しいです。

ここで一つ確認する必要があるのは、後者のケースにおいて、攻撃を許す根本原因はRFIの脆弱性を持つよそのサイトの側にあるということです。攻撃コードをホストしている側には根本原因は無く、また攻撃による直接的な被害もありません。

一般論として、何らかの攻撃への対策は、根本原因の部分でなされるべきだと思います。根本原因でないところでの対策は、往々にして不完全なものになったり、変な副作用を持ったり、やたらと複雑になったりしがちです。

この問題もその例にたがいません。基本的に、対策が求められるのは、RFI脆弱性を持つサイトの側で(あるいはPHPの言語仕様で)あって、画像ファイルをホストする側では無いと思います。

それでも画像変換はした方がよい

とはいっても、上記で挙げたような、画像ファイルの形式変換や、画像ファイルデータ内にPHPコードを含むかのチェックなどの対策が、攻撃コードをホストすることを防ぐという観点で、全く無意味だと言っているわけではありません*1

これらの対策は、攻撃者の手間を(少しですが確実に)増やします。攻撃者は、これらの対策がとられていない、攻撃の手間の少ないサイトに、PHPコードを含む画像をアップするかもしれません。

また、自分のサイトに攻撃コードを含む画像が置かれて、よそのサイトへの攻撃に利用されたとしても、画像形式の変換などをしていれば「いちおう我々も最低限の対策はしています」と言い訳できるでしょうから。

画像ファイル以外も問題になるかも

ここまでは、アップロード画像にPHPコードを仕込む攻撃について記述しました。

しかし危険なのは画像ファイルだけではありません。アップロードされたOfficeファイル、テキスト、PDFなどのファイルも、PHPコードを含む可能性があります。

アップロードファイルだけではなく、HTML、JavaScript、JSON(P)など、動的に生成するデータも同じです。

これら全てが攻撃に利用される可能性がある訳ですが、いちいち「このデータがよそのPHPのサイトからインクルードされたら、どうなるのか?」などと考えるのは、相当ナンセンスな気がします。

私は、PHPの攻撃コードをホストしてしまう問題については、余り深刻に考える必要は無いと思っています。例えば、具体的に自サイトのコンテンツが悪用されるような事態になったら、そのコンテンツを削除するなどのレベルの対処で十分では無いかと思います。

その他の話

この日記で書いているのは、アップロード画像ファイルによって生じうる問題の一部です。この他に、画像によるXSSやウィルス付き画像などの問題もあります。これらは、RFIとは対策に共通する部分もありますが、根本的には別の話です。XSSについては、そのうち日記で取り上げたいと思います。

*1:大垣さんの日記へのコメントでは、勢いで「無意味」みたいなことを書いたかもしれませんが・・・

ockeghemockeghem 2007/07/16 12:40 こんにちは。有益な情報ありがとうございます。
T.Teradaさんの言われるように、画像がPHPコードとして実行されるのは、アップロードの問題などよりも、むしろ、拡張子の設定やRFIの脆弱性の方がそもそも問題であって、画像の中身をチェックしたり変換したりというのは本質的でないような気がします。さらに、カラーパレットの部分には任意のバイトが書けるわけで、画像の内容を変更しない限り、形式変換などでは対応できない可能性が高いでしょうね。
むしろ、この種の問題で対策が必要なのは、IEにおけるXSS対策のほうでしょうね。ユーザ側で脆弱性対策していても防げない可能性が高いので。カラーパレットだけで3*256=768バイト保持できますので、かなりの量のJavaScriptが記述できることになります。

teraccteracc 2007/07/16 20:32 こんばんは。コメントありがとうございます。おっしゃるとおり、IEのXSSは頭の痛い問題だと思います。これについては別途書きたいなと思っているところです。

#蛇足ですが、768Byteで足りなければ<script src=”...”>を挿入して、どこかに置いたJSファイルを呼び出す手も使えます。一つ突破口を作られると、何でもアリですね。

NameLess-1NameLess-1 2007/07/16 23:55 お邪魔します。はてなブックマークから来た、ただの通りすがりなのですが、一つ気になった点が。
ふと気になったのですが、画像変換って相当コストのかかる処理なのではないでしょうか?「やっといた方がいいけど、実用的ではない」では意味が無い。もしくは誰も賛同しないのではないかと。

ockeghemockeghem 2007/07/17 09:13 返答ありがとうございます。IEのXSSについての解説、期待して待ってます。ところで、
> Color Tableをいじるので、画像の色は変わりますが、画像形式としては正常で、ブラウザでのレンダリングも可能です。
色が変わると言うのは、この実験では変わったという意味でしょうが、もう少し工夫すると見た目正常な画像であっても攻撃コードを埋め込むことは難しくないと思います。特殊なツールは不要で、画像ツールなどてできると思います。

teraccteracc 2007/07/17 09:51 コメントありがとうございます。

>画像変換って相当コストのかかる処理(NameLess-1さん)

そうですね、リソースを食う処理です。「やった方がよい」と書きましたが、言い過ぎだったかと思っています。ただ、全く意味がない訳でもないので、コストとのトレードオフで、やるかやらないか決めることになるんでしょうね。

>色が変わると言うのは、この実験では変わったという意味でしょうが(ockeghemさん)

既存の画像に対して、Color Tableをいじる処理を行なったので、元の画像と比べて色が変わったという意味です。ご指摘のように、人が見ても自然な画像にすることは可能だと思います。

ockeghemockeghem 2007/07/17 13:51 リプライありがとうございます。ところで、カラーパレットの内容をきれいにするだけであれば、GIF→PNG→GIFではなく、いったん24ビットカラーにして256色(あるいは16色)に戻すという方法もありますね。GIF画像は24ビットカラーをサポートしていませんが、画像ライブラリのメモリ上の処理なら可能です。といっても、カラーパレットだけの話ですし、アドホックな対応であることには変わりませんね。

kuippakuippa 2007/07/18 00:15 こんにちは。大変参考になりました。
拡張子、ヘッダ供に偽装したPHPコードやhtmlがアップロードされたことがあり、何に使うのかと思っていたのですがRFIなんですね。
確かにRFIにしておけば管理がずさんなところの既存ファイルなりを改ざんしても管理側からは発覚しにくくなるので効果があるかもしれませんね。
自分は画像サイズの変更で対応しましたがパレット領域とまでなると頭が痛いです。
ん〜……。

NameLess-1NameLess-1 2007/07/18 02:16 レスありがとうございます。入力データのチェックはいつも困らされている者で、毎回関数の頭にif(〜)return;if(〜)return;if(〜)return;と頭の悪い処理をやっています。いや、素人はここらで撤収しますよ。失礼いたしました。

teraccteracc 2007/07/20 22:16 ockeghemさん:確かに、減色するなどの方法でパレットをいじれば、パレットにPHPやJSコードを入れるのは難しくなると思います。ただ、そこまでしなければならないのか? というところで、引っ掛かってしまいます(思考実験だとは理解していますが)。ところで、残念ながら私はSANSに行けませんでした。というわけで、日記などでのご紹介を楽しみにしています。

kuippaさん:画像サイズの変更だと、パレット内のコードは温存されるかもしれませんね(試していませんけども)。ですので、PHPコードの問題への対策としてはともかく、HTMLやJSを画像に挿入される攻撃への対策としては、少々心もとない感じもします。

NameLess-1さん:私もそんなコードを書いてましたよ。今はフレームワークなどで、ある程度はスマートにできるんでしょうけども。

ockeghemockeghem 2007/07/22 10:36 T.Teradaさん、コメントどうも。ちょっと今忙しくて書けそうもないのですが、そのうち書きたいと思います。Ajax/JSON(P)もまとめたいと思っているのですが・・・・ところで、
>画像サイズの変更だと、パレット内のコードは温存されるかもしれませんね
これはサイズ変更のアルゴリズムによります。最近はサイズ変更時にアンチエイリアス処理を書ける場合が多く、内部的には256色→フルカラーにしてからサイズ変更するのではないでしょうか。その場合ですと、パレット情報は変更される(場合が多い)ので、まず大丈夫かと。・・・このように処理の細かいところまで指定しないと、厳密なことがいえない上に、カラーパレットの場合はこれでいけるが、他の場所だと、手法も別のものを追加しないといけないなど、確かに面倒ですね。

teraccteracc 2007/07/24 00:33 ockeghemさん。コメントありがとうございます。

サイズ変更によってパレット情報が変更されるとしても、変更のアルゴリズムが既知ならば、変更後に攻撃コードが出現するようなオリジナルの画像を作れる可能性を、原理的に否定できないんじゃないかと思います(残念ながら、私にはそんな画像を作ることは無理そうですけども)。元々、サイズ変更や形式変換などの処理は、攻撃コードを除去する事を意図したものでは無いので、基本的に信頼(コード除去についての)してはならないもののような気がしています(少々極端な意見かもしれませんが)。