はじめに
本記事では、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>
)や属性(例: onclick
、onerror
、onload
)が含まれているか」をチェックすることで、問題のある 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 })
}
コード解説
-
MIME タイプの判定
fileTypeFromBlob
により、実際のバイナリから MIME タイプを判定します。判定できなかった場合は、そのままファイルを返却します。 -
SVG 判定
file.text()
でファイル内容をテキストとして取得し、isSvg
で SVG であるかどうかをチェックします。SVG でなければ、そのままファイルを返します。 -
サニタイズ処理
DOMPurify のsanitize
関数を利用して、テキスト中の潜在的な危険なコードを除去します。 -
削除対象の収集
サニタイズの過程で DOMPurify が削除した要素や属性をremoved
プロパティから収集し、別途リストに格納します。 -
危険な要素のチェック
収集した削除対象の中に、予め定義した危険なタグ(例:<script>
)や属性(例:onclick
など)が含まれている場合、エラーをスローしてアップロードを拒否します。 -
新しいファイルオブジェクトの生成
サニタイズ済みの内容で新たな File オブジェクトを作成し、問題がなければ返却します。
まとめ
この実装により、アップロード前に XSS 攻撃のリスクを検出し、安全なファイルアップロード機能を実現できます。
本記事が同様の課題に取り組む方々の参考になれば幸いです。