本サイトは、快適にご利用いただくためにクッキー(Cookie)を使用しております。
Cookieの使用に同意いただける場合は「同意する」ボタンを押してください。
なお本サイトのCookie使用については、「個人情報保護方針」をご覧ください。

最新情報

2018.09.07

ImageMagickを使うWebアプリのセキュリティ - 2. DoS

本記事はImageMagick関連の記事の2本目です。ImageMagickの既知の脆弱性、システム情報の漏洩などの問題を扱った1つ目の記事はこちらです。

2回目の今回は、画像のアップロード機能を利用したDoS攻撃を取り上げます。前提とする環境などは前回と同じです。また前記事の方法で画像の形式をJPEG, GIF, PNGの3種類に絞っているとします。

※ 記事中では右の略語を使っています。 IM = ImageMagick、CW = CarrierWave

DoS

画像のアップロードに関連するDoSにはいくつかの種類があります。

1. アップロードしたファイルにより、サーバのストレージをfullにする(OWASP
2. NW帯域を飽和させる、または一般的なファイル処理の負荷を利用する(Imperva
3. 画像処理ツールの既知のDoS脆弱性を利用する
4. 画像処理ツールが行う画像処理による負荷を利用する

1,2についてはIMとの関連が薄く、3は基本的にIMのアップデートにより解決すべき問題であるため、今回は4の画像処理による負荷を利用したDoSのみを取り上げます。

攻撃方法の概要

画像処理はそれなりのコンピュータ資源を消費する重い処理です。画像によるDoSは、これを悪用して、細工した画像によりサーバに大きな負荷をかけさせる攻撃です(この種の攻撃に言及する日本語の文献は少ないのですが、徳丸氏の書籍には関連する記述があります)。

具体的な攻撃方法を含む情報としては、dutchgraa氏が2013年にHackerOneに出した3つのバグレポートがあります。

1. JPEG pixel flood   2. GIF flooding   3. PNG compression

いずれもメジャーな画像形式の、かつ比較的小さいファイルサイズの画像を使った攻撃です。

この中で一番よく知られていると思われるpixel flood攻撃について説明します。

氏のバグレポートに添付のPoCは、ピクセルサイズ部分を64,250 x 64,250に書き換えただけの、5KB弱のJPEGです。この画像をIMでサムネイルの作成などを行っているサイトにアップロードすると、サーバに大きな負荷がかかり応答の遅延が生じます。

このときIMの内部では、画像の各ピクセルの色データを保持するためのピクセルキャッシュと呼ばれる領域がメモリに確保され、これを埋めていく処理が実行されます。この領域の通常のサイズはピクセル当たり8Byteであり、上のJPEGでは全体で約33GB(64,250 * 64,250 * 8)にもなります。メモリに収まらない場合、ピクセルキャッシュはストレージ上に一時ファイルとして保持されます(下図)。

参考まで、検証環境(Core i7-8700)でこのJPEGのサムネイル作成に要した時間は、SSDのストレージにピクセルキャッシュが置かれる場合は10分弱、オンメモリの場合は1-2分でした。同じJPEGを複数アップロードしたところ、I/O、メモリ使用量、CPUのI/O待ち、ブロックされているプロセス数、スワップ量などが増加し、最後にはリブート以外には何もできない状態に陥りました。サーバのスペックにもよりますが、DoS画像がかなりの威力を持つことが分かります。

氏の他の2つのレポートについても触れておきます。

GIF floodingのPoCは数万のフレームを持つアニメーションGIFです。PNG compressionは、圧縮されたメタデータを格納するチャンクに、解凍後に非常に大きくなるデータを入れたPNGです。いずれもIMで処理すると、大量のメモリを消費して、応答が遅延する現象が発生します。

ソフトウェア構成と画像DoSのリスクについて

記事で前提としているPassenger, CW, IMの環境を実際に作って試してみればわかりますが、DoS画像が与える負荷は相当大きく、サーバのスペックによっては、単一の画像による攻撃でもサーバが使用不能になってしまいます。しかし、それはどのような環境でも同じように起こる訳ではなく、環境を構成するソフトウェアに依るところが大きいです。

具体的に言うと、今回の環境のソフトウェア(Passenger, CW, IM)では、画像のピクセル数、アップロードファイルのサイズ、メモリ使用量、実行時間などの上限がデフォルトで無いか、あっても大きな値になっており、これがDoSへの耐性を低くしています。

さらに、PassengerにはworkerプロセスのOOM Killerスコアの補正値を最低値にするバグがあり、これもメモリを大量に使うタイプのDoSの影響を大きくします(Issue 2105, 1631)。バグによりOSがIMをkillできなくなるからです。このバグは補正値(/proc/$PID/oom_score_adj)を書き換えることで回避できます。

対策

上で説明した攻撃の対策を見ていきます。基本編と応用編に分けています。

前回の記事と同じく、IM7とMiniMagickの使用を想定しています。

基本編①

前記事で画像形式を絞る方法としてIMのポリシーファイルを取り上げましたが、ポリシーにはDoS対策を意図したと思われる設定項目もあります。例えば、画像のピクセルサイズや、個々の画像の処理ごとにピクセルキャッシュで消費される、ストレージ量、メモリ量、時間などのリソース量を制限できます。

下記は、縦/横がともに2KPまでの画像をオンメモリ・単一スレッドで処理することを念頭に、筆者が検証環境のIM7に設定したリソース制限の例です。

<!-- 許容するピクセル幅/高さ + 1 -->
<policy domain="resource" name="width" value="2001"/>
<policy domain="resource" name="height" value="2001"/>
<!-- 画像シーケンス数 アニメーションや連結された画像で2以上になる 10まで許可 -->
<policy domain="resource" name="list-length" value="11"/>
<!-- オンメモリのためストレージは使わない -->
<policy domain="resource" name="map" value="0GiB"/>
<policy domain="resource" name="disk" value="0GiB"/>
<!-- メモリの上限とタイムアウト 目分量(環境により異なる) -->
<policy domain="resource" name="memory" value="200MB"/>
<policy domain="resource" name="time" value="10"/>
<!-- 並列処理しない -->
<policy domain="resource" name="thread" value="1"/>

各項目の説明や例は付属のdoc(1, 2)やpolicy.xmlのコメントにありますが、クックパッドのブログが詳細で分かりやすいです。ちなみに、オンメモリ・単一スレッドにしたのは、単一の画像処理が与える負荷の量を分かりやすくするためです。

CW側でできる設定もあります。IMのコマンドにタイムアウトを設定し(MiniMagickはIMのidentify, mogrifyコマンドを実行して画像処理を行います)、ファイルサイズにも上限を設けましょう。

class PictureUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

  # MiniMagick: IMコマンドのタイムアウト(秒)
  MiniMagick.timeout = 10

  # CW: アップロードファイルのサイズを制限
  def size_range
    1..5.megabytes
  end

これらの設定を行いJPEGやGIFのDoS画像をアップロードすると、期待どおり即座にエラーになります。

基本編②

ここから基本編②です。②を作ったのは、①の対策には大まかに言って2つの点で穴があるからです。

1つ目の穴は、IMのポリシーの仕様に起因します。ポリシーで制限できるのはピクセルキャッシュにかかわるリソースのみであるため、一部のDoS画像において、実際のメモリ使用量や処理時間がポリシーの設定値を大きく超える現象が発生します。これを考慮してMiniMagickのタイムアウトも設定しましたが、メモリ量についてはカバーできていません。

2つ目は、基本編①の設定で制限しているのは単発のDoS画像による負荷だけだということです。何らかの制限を加えても画像処理のリソース消費はかなり多いため、(同時接続数の設定によっては)複数同時の攻撃によりサーバのリソースを使いつくしてしまう可能性があります。

次の応用編で書くように、これらの穴を埋める方策は色々とありますが、ここでは画像のDoSにより簡単にサーバが落ちないよう、サーバ全体でIMに大きな枠をはめることにします。

そのツールとして使えるのはcgroupです。cgroupは、任意に定義するグループの単位でリソース消費を制限するLinuxの仕組みです。全てのIMのプロセスを同じグループに入れてやれば、それらが使う合計のリソース量に上限を設定できます。

下記は筆者が検証環境に作ったグループ設定です。

# 全部の画像処理で、物理メモリは1GiB、CPUは2個(コア)まで。
group imagic_grp {
  ...
  memory {
    memory.limit_in_bytes = 1G;
    memory.swappiness = 0;
  }
  cpu {
    cpu.cfs_quota_us = 200000;
    cpu.cfs_period_us = 100000;
  }

このグループの制限をcgrulescgexecを使ってIMに適用してやれば、単一もしくは複数のDoS画像がサーバ全体のCPUやメモリを使いつくすことはなくなります。

もちろん、画像処理を別のマシンで実行することでも、IMによりWebサーバが過負荷に陥ることを避けられます。そのマシンをNW的に隔離すれば、IMの脆弱性の影響を封じ込めることもできます。

応用編

次は応用編です。まずは、これまでの対策で何のリソースがどこまで制限できているか、さらに取りうる手は何なのかを整理したいと思います。

・CPU, メモリ
全体での「枠」をcgroupではめています。少数の画像がこの枠を使い切ることを防ぐには、階層化したcgroupなどを使って、個々の画像処理に対するリソース制限を厳密にします。

・ストレージ(I/Oと使用量)
既に画像の形式やサイズを絞っており、またオンメモリ処理にしたため、ほぼ対策できています。

・Webサーバの接続数
特に制限をしていません。もし画像処理の同時実行数を絞って、Webサーバの同時接続数より少なくすれば、時間が掛かる画像処理が接続数を使いつくすことも防げます。

上記を含めて、さらなる対策の候補となりうるものを下記にまとめます。

・個々の画像処理のリソース制限(例: 階層化したcgroup, nsjail, ulimit)
・画像処理の同時実行数の制限
・IMのチューニングによりリソース消費量のベースを低減(例: crakaC氏のGist
・画像データのチェック強化(例: 圧縮データのサイズ)
・画像処理用のハードウェアの増強、リソース消費量の監視、など

※ 同時実行数の制限と、階層化したcgroupの例は筆者個人のGistにあります。

なお、サーバ全体の画像処理のリソースや処理の同時実行数だけを絞ると、画像アップロード機能だけを使用不能にするのはむしろ容易になる場合があります。この種の設定の目的は、画像処理の負荷によって他の機能やサーバ全体が使用不能になることを防ぐことです。

まとめ

今回は画像処理の負荷を利用するDoSについて書きました。

画像処理はリソースを多く消費するため、アプリケーション層でのボトルネックになりえます。特に今回検証環境としたIM, CW, Passengerの環境は、デフォルト状態では画像処理に関連するリソースの制限をほぼ欠いており、DoSの影響をうけやすいと言えます。

対策は基本編と応用編に分けました。基本編に記述したのは、①IMのポリシーなどの設定と、②cgroupによる、画像のフィルタとリソース消費量の制限です。

これらを含め、複数の対策候補を基本編~応用編に記述しましたが、全てがMUSTだと言っているわけではありません。そもそも完全なDoS対策はありませんし、可用性の要求レベルはサイトにより異なります。IMは突出したボトルネックになりうるものであり、何らかの手当てをするのが望ましいと思いますが、どこまでやるかはサイト全体のDoS対策を検討する中で判断してほしい、という趣旨で書いています。

NW層のDDoSなどと違って、画像DoSの攻撃対象はWebアプリの一部の機能に限られており、また現状で攻撃が多発していることを示す情報も、筆者が知る限りありません。もし、サイトの可用性の要求レベルが低く、かつ画像アップロード機能の重要性が低いのであれば、基本編の対策のみをしておく、または攻撃を受けた際に該当機能だけを一時的に無効化して凌ぐ、などの対処もあるでしょう。

応用編に挙げたように、画像データのチェックを強化するという真正面からの対策もあります。おそらく画像の解析器を自前で作る必要があり、実装のハードルはやや高いですが、既知の種類のDoS画像を処理の早い段階ではじくのに有用ですし、ユーザフレンドリーなエラーメッセージを出すことも簡単です。

3. XSS・アクセス制御編へ



寺田 健 の他のブログ記事を読む

プロフェッショナルサービス事業部
寺田健