1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Svg ファイルのバリデーション

Last updated at Posted at 2025-02-02

はじめに

本記事では、SVG ファイルに限定して、アップロード前に XSS のリスクがないかを検証し、危険なコンテンツが含まれていればアップロードを拒否する仕組みについて、私が実装した方法を紹介します。

フロントエンドに限定して話して行きます

アップロードファイルのバリデーション

MIME タイプの信頼性について

File API の file.type プロパティで MIME タイプを取得できますが、これはユーザー側で容易に偽装できるため、セキュリティの観点からは信用できません。

file.type プロパティはアップロードされたファイルの拡張子から MIME タイプを推測しています。本当の MIME タイプを知るには マジックナンバー (フォーマット識別子) を見る必要があります。

そこで、実際のファイルバイナリから MIME タイプを判定できる file-type ライブラリを利用しました。

SVG 判定について

file-type では SVG ファイルの判定ができないため、文字列として読み込んだ内容が SVG であるかどうかは is-svg を利用してチェックします。

XSS 攻撃の検出とサニタイズ

SVG ファイル内で XSS 攻撃を引き起こす典型的な手法は、<script> タグの利用です。
しかし、あらゆる攻撃パターンを網羅するのは困難なため、ここでは既存のサニタイズライブラリ DOMPurify の機能を活用します。

DOMPurify はサニタイズ処理時に、削除した要素や属性を removed プロパティに格納します。
これを利用し、「削除対象に危険な要素(例: <script>)や属性(例: onclickonerroronload)が含まれているか」をチェックすることで、問題のある SVG のアップロードをブロックします。

Dompurify の https://github.com/cure53/DOMPurify/blob/main/src/tags.ts の svgDisallowed に脆弱性を引き起こす tag を配列として保持されてあったので、これをありがたく使わせていただきました。

実装コード例

以下は、上記の流れを実現した実装例です。

const validate = async (file: File) => {
  // 1. ファイルの MIME タイプを判定する
  const fileType = await fileTypeFromBlob(file)
  if (!fileType) return file

  // 2. ファイル内容をテキストとして取得し、SVG かどうかチェックする
  const text = await file.text()
  if (!isSvg(text)) return file

  // 3. DOMPurify を使ってサニタイズ処理を実施
  const clean = DOMPurify.sanitize(text)
  const removedElms: string[] = []
  const removedAttrs: string[] = []

  // DOMPurify によって削除された要素や属性を収集する
  DOMPurify.removed.forEach((item) => {
    if ('element' in item) removedElms.push(item.element.nodeName)
    if ('attribute' in item && item.attribute)
      removedAttrs.push(item.attribute.name)
  })

  // 4. 危険と判断するタグおよび属性の一覧を定義する
  const tagVuls = ['script'] // 例: SVG 内で許可されないタグ
  const attrVuls = ['onclick', 'onerror', 'onload'] // 例: SVG 内で許可されない属性

  // 5. 削除された要素・属性に危険なものが含まれているかチェックする
  const isVul =
    removedElms.some((elm) => tagVuls.includes(elm)) ||
    removedAttrs.some((attr) => attrVuls.includes(attr))

  if (isVul) {
    throw Error('SVG エラー')
  }

  // 6. サニタイズ済みの内容で新たな File オブジェクトを生成して返却する
  return new File([clean], file.name, { type: fileType.mime })
}

コード解説

  1. MIME タイプの判定
    fileTypeFromBlob により、実際のバイナリから MIME タイプを判定します。判定できなかった場合は、そのままファイルを返却します。

  2. SVG 判定
    file.text() でファイル内容をテキストとして取得し、isSvg で SVG であるかどうかをチェックします。SVG でなければ、そのままファイルを返します。

  3. サニタイズ処理
    DOMPurify の sanitize 関数を利用して、テキスト中の潜在的な危険なコードを除去します。

  4. 削除対象の収集
    サニタイズの過程で DOMPurify が削除した要素や属性を removed プロパティから収集し、別途リストに格納します。

  5. 危険な要素のチェック
    収集した削除対象の中に、予め定義した危険なタグ(例: <script>)や属性(例: onclick など)が含まれている場合、エラーをスローしてアップロードを拒否します。

  6. 新しいファイルオブジェクトの生成
    サニタイズ済みの内容で新たな File オブジェクトを作成し、問題がなければ返却します。

まとめ

この実装により、アップロード前に XSS 攻撃のリスクを検出し、安全なファイルアップロード機能を実現できます。

本記事が同様の課題に取り組む方々の参考になれば幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?