画像への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、JavaScriptJSON(P)など、動的に生成するデータも同じです。

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

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

その他の話

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

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