本記事はImageMagick関連の記事の3本目です。ImageMagickの既知の脆弱性、システム情報の漏洩などの問題を扱った1つ目の記事、DoSを扱った2つ目の記事も参照ください。最終となる3回目の今回は、XSSとアクセス制御を取り上げます。前提とする環境などは前回・前々回と同じです。記事中では右の略語を使っています。初回の記事で述べたように、デフォルトの状態のCWは公開ディレクトリにアップロードされたファイルを置きます。ファイルの最終的な保存前に一時的に作成されるキャッシュも同様です。これらのファイルはURLを推測できれば誰でも参照可能です。http://host/uploads/tmp/1528992691-1593-0025-7417/thumb_logo.gif最終的な保存用画像のURLパスの構成は、既定値で下記のとおりです。1キャッシュについては、その名前のイメージとは異なり、単に一時的に作成されるファイルです。最終的にユーザが登録を完了し保存用ファイルが作成された段階でキャッシュは削除されます。キャッシュのURLを実際に目にするのは、確認画面を設けたWebアプリです。確認画面のHTMLソースに下記のようなタグが含まれているのが典型的です。/>キャッシュのディレクトリのランダムに見える部分(cache_id)は、下のソースのとおり、時刻、プロセスID、連番4桁、乱数4桁を連結した値です。「一瞬で推測可能」とまでは言えないものの、推測困難性を持たせる設計にはなっていません。CWのcache.rbのソースキャッシュのややこしいところは、ユーザが仕掛中のまま登録を完了しなかった場合だけでなく、画像の変換エラーや画像以外も含めたvalidationエラーなどが発生した場合にも、ファイルが削除されずに残ることです。エラー時にもキャッシュを必要とするWebアプリには便利ですが、これは後述する問題を引き起こすことがあります。前段が長くなりましたが、本題のアクセス制御の問題について見ていきます。あるアクセス制御の問題は、最終的な保存用ファイルにはアクセス制御があるのに、キャッシュには制御がないケースです。・確認画面のhiddenに入っているcache_nameを次の完了処理で使う際のアクセス制御の両方です。これらが欠如していれば一般的には脆弱性とみなされると思います。保存用ファイルとキャッシュの両方にアクセス制御がないケースもあり、悩ましいのはむしろこのケースかもしれません。最終版の保存用ファイルを全公開するのが要件であっても、確認画面の段階やエラー時に作られるキャッシュも同じだとは言い切れないからです。ユーザが秘密の画像を間違ってアップロードしてしまい確認画面で間違いに気付いて一安心したとしても、あるいは単にエラーが発生して途中で登録をやめたとしても、アップロードした画像がキャッシュとして公開ディレクトリに置かれているとしたら、単純に気持ちが悪いです。次のXSSの話の中では、デフォルト状態のまま、つまりアップロードされたファイルがApache+Passengerの公開ディレクトリに置かれる前提で話を進めます。XSS下記は初回記事の図の再掲です。中身がJPEG、拡張子がpngのファイルをアップロードした際のサーバ側の処理を示したものです。Apacheの公開ディレクトリ上にはオリジナル版とサムネイル版の2つが画像が置かれます。この両方がXSSの攻撃対象になりえます。古いブラウザ(IE7)が相手ならばXSSは簡単です。図のようにコメント部分にHTMLとJavaScriptを入れたJPEGの拡張子をpngにしてアップロードし、オリジナル版にアクセスするだけです。一方で現代のブラウザでのXSSにはちょっとした工夫が必要となります。ブラウザのContent-Typeの解釈は昔より厳密ですし、画像変換をパスするために妥当な形式の画像を与える必要があるからです。中身をHTMLにしたり、拡張子をhtmlにしたりしたファイルをアップロードしようとしても、画像変換のエラーによってアップロードはできず攻撃も失敗します(サーバにhtml2psがインストールされていない場合)。ここで攻撃の手助けになるのは、IMが非常に多くの画像形式をサポートしていることです。その中には、Webサーバやブラウザにとっては完全に未知の形式も含まれています。オリジナル版、サムネイル版の両方を狙う一番単純な攻撃についてみてみます。VIFF)という形式をサポートしています。コメント領域にscriptタグを入れた、中身も拡張子もXVの画像をアップロードしてみます。入力がIMにとって妥当な画像であるため、サムネイル作成のための画像変換は成功します。その結果、XV形式のオリジナル版とサムネイル版が「logo.xv」「thumb_logo.xv」というファイル名でApacheの公開ディレクトリに置かれます。両画像へのHTTPリクエストの応答は下図のようになります。見てほしいのは下線のコメントです。デフォルトのIMは画像変換時にコメントを消さないため、オリジナル版だけでなくサムネイル版の方にもコメントが残ります。上の応答でもう一つ重要なのはContent-Typeヘッダが存在しないことです。Apacheは、拡張子とマジックバイトによって、静的ファイルのContent-Typeを決定します。今回のようにいずれも未知の場合はContent-Typeを出力しません。ブラウザにとってはContent-Typeがなく中身も未知の形式であるため、Edgeなどはこの画像をHTMLだと解釈してJavaScriptを実行します。画像によるXSS攻撃が成功します。「logo.html.xv」のような二重の拡張子を持つファイル名を使う攻撃方法もあります。Apacheは二重の拡張子を見てContent-Typeをtext/htmlにするため、全ブラウザでJavaScriptが動作します(二重の拡張子が使えるなら、単純に「logo.html.foobar」のような適当な拡張子を付けたJPEGを送ることでもXSSができる場合があります)。画像のコメントを使うXSSは、画像のコメントが削除される時や、画像出力時のContent-Typeがimage/*などに固定されている時には成功しません。いう時にも機能する可能性があるのは、画像のピクセルデータ(各ピクセルの色データ)やパレットを使う手法です。筆者が使ったことがあるのはRGB形式の画像です。2)の画像であればファイルサイズは12Byteになります。RGB画像にはマジックバイトやヘッダなどの固定部分がないため、先頭から末尾まで完全にファイルの中身を制御できます。一方で中身だけからは形式や縦横のサイズを判定できないため、RGB自体をアップロードしてもエラーになります。このRGBをどう攻撃に使うのか見てみましょう。いくつかの攻撃の可能性があると思いますが、ここではFlashを使う例を紹介します。RGBへの変換後にSWFになるように、ピクセルデータをいじった画像を作成します。画像の形式は対象のWebアプリが受け入れるものならば何でもよいですが、ここではGIFとします(サムネイル作成時に拡大縮小されないサイズにしておきます)。このGIFの拡張子をrgbにして攻撃対象のサイトにアップロードすると、サーバ上での画像変換により、拡張子がrgbで中身がSWFのサムネイルが作成されます。次に、攻撃者はこのSWFを読み込む罠のページを自身のサイト上に作ります。Hijacking)。Flashの場合、SWF出力時のContent-Typeによらずに攻撃が成功するというのがミソです。Dalili氏のブログを参照してください。ところで、このRGBを使った攻撃が成功するのはRMagickが使われている場合だけです。MiniMagickは変換後の画像に対してidentifyコマンドを掛けて画像の情報を取得するようになっており、それに対応できないRGB形式の画像はその時点でエラーとなるからです。いっても、identifyに対応する形式に仕立てたPDFを使う攻撃などもありうるので、MiniMagickならば安全というわけではありません。上の方で「HTMLの拡張子をhtmlにしてアップロードすると、画像変換でエラーになりアップロードは失敗する」と書きましたが、実はその表現は余り正確ではありません。確かに画面上はエラーが表示され、オリジナル/サムネイル版とも作成されず、DBも更新されません。アクセス制御の項で述べたように、実はエラーでも入力と同じ内容のキャッシュファイルがサーバ上に作成されることがあります。http://host/uploads/tmp/1528994228-1593-0029-3952/logo.html普通はエラーが起こると上図のような入力画面に戻ってしまうため、作成されたキャッシュのURLを知る術はありませんが、既に述べたようにこのURLの推測困難性は高くありません。総当たりでURLを突き止められれば、XSSやSWFによる情報窃取などの攻撃の可能性があります。厳密にいうと、エラーの際に必ずキャッシュが作成されるわけではありません。Uploaderクラス内で、オリジナル版に対するリサイズなどの画像処理の設定や、extension_whitelist(拡張子チェック)の設定をしており、それらでエラーになった場合にはキャッシュは作成されません。このような場合はキャッシュを使った攻撃は成立しません。上で説明したアクセス制御とXSSの問題に対する対策を書きます。前々回の記事も併せて参照してください。先に述べたように、CWが作成するファイルには保存用とキャッシュの2種類があるので、これらを分けて考えたいと思います。保存用については、画像にいつ誰のアクセスを許すかは業務要件です。全公開が要件ならばデフォルトのまま公開ディレクトリに置けばよいです。アクセスを制限するのが要件ならば、画像を公開ディレクトリの外に置き、セッションをもとに権限チェックをするプログラムを通じて画像をブラウザに出力します。CWのwikiの説明どおり、保存用のファイルを置くディレクトリはstore_dirの設定などにより変更できます。キャッシュキャッシュについてもアクセス制御は業務要件の範疇です。述べたように、仮に保存用を全公開にするとしても、キャッシュについては登録したユーザのみが参照できるよう一律でアクセス制御するのが自然だというのが筆者の考えです。アクセス制御の具体的な方法は保存用と同じです。全公開するならばデフォルトのままとし、アクセス制御をするならばwikiの方法で公開ディレクトリの外にファイルを置きます。確認画面が無くアプリからキャッシュを明示的に参照しないならば、それ以上やることはありません。アクセス制御が必要で、かつ確認画面を設ける場合は、もう少しやることがあります。確認画面のHTMLソースをもう一度見てみましょう。/>ここで防ぎたいことは下記です。単純に他人のキャッシュを削除して妨害するログイン済みのユーザが使うフォームならば、下のようにセッションを使う対策が一番簡単でしょう。確認画面を表示する処理においてcache_nameをセッション変数に保存する。セッション(1)をもとに権限をチェックしてキャッシュ画像を出力するプログラムを用意する。src=には、プログラム(2)のURLを入れる。次の完了処理では、hiddenではなくセッション(1)のcache_name使用する。目的は、1,2,3は1を防ぐこと、1,4は2を防ぐことです。セッションを使わない場合は様々な対策が考えられます。URIを使う(後述)、hiddenのcache_nameを推測困難な値にする、古いキャッシュをまめに消去する、というような対策を行うと思います。下記は画像を出力するHTTPレスポンスの例(良い例)です。PNGのいずれかであり、Content-Typeヘッダがそれに応じたものであれば問題ありません。その近道は前々回の記事の方法で画像の形式と拡張子などをチェックすることです。Apache上に静的ファイルとして画像を配置する場合、このチェックが済んでいるならば、あとはそのままファイルをディレクトリに置くだけでよいです。HTMLなどと誤って解釈する危険性を減らせます。URIでHTMLに埋め込む方法もあります。URIは下のような形式のURIで、現行のメジャーブラウザはもれなく対応しています(caniuse)。URIの使用は性能に影響を与える可能性がありますが、XSS対策になるだけでなく、アクセス制御が簡単になる可能性があるのはメリットと言えます。一時的にしか使わないキャッシュ画像を確認画面に出力するような用途に向いていると思います。まとめ今回の記事では、アクセス制御の不備とXSSを取り上げました。IMやCWに固有なところは、IMが多くの画像形式をサポートしていることを悪用したXSSくらいです。XSSについては、初回の記事で紹介した方法で画像形式を縛っておけば、あとは然るべきHTTPヘッダを付けて画像をブラウザに返すだけでよいです。アクセス制御について求められるのは、どの画像に誰がアクセスできるかを明確にして、それを実装するという一般的な対策です。IM関連の3本の記事全体では、既知の脆弱性、システム情報の漏洩、DoS、アクセス制御の不備、XSSなどの脅威を取り上げました。対策としては、画像形式の絞り込み、サンドボックスの利用、ソフトウェのアップデート、リソース量の制限、アクセス制御の実装、画像表示時のXSS対策などです。このように、セキュリティのために考慮すべきことは少なくありません。現状では残念ながら、画像のアップロード機能について「セキュリティを何も考慮しなくてもセキュアなものができあがる」ということはまずないのだと思います。他の一般的な画像アップロード関連の脅威としては、スクリプトのアップロードによるコマンド実行もありますが、Railsで見ることはほぼないため今回は取り上げていません。セキュリティというよりはプライバシに関連する、画像の付加情報の扱いなどについても取り上げていません。