外部CDNから読み込んでいるJavaScript、いま全部把握できているでしょうか。
2024年に発覚したpolyfill.io事件は、「自社サーバーは無傷なのに、訪問者が攻撃される」というサプライチェーン攻撃の典型例でした。本記事では事件の構造を整理したうえで、外部スクリプトの 棚卸し → 削除 → 自前ホスト → SRI → CSP という実務手順を、コマンド・設定例つきでまとめます。
事件の概要
- 2024年2月: polyfill.io のドメインと関連GitHubアカウントが中国系企業 Funnull に売却される。polyfill の元開発者は直後から「もはや信頼できない。直ちにタグを外すように」と警告
- 2024年6月: 配信されるスクリプトへの悪意あるコードの混入が発覚。モバイル端末の訪問者を不正サイトへリダイレクトする挙動などが確認され、ドメインはレジストラにより停止。Cloudflare や Fastly が安全な代替エンドポイントを公開
- polyfill.io は古いブラウザ向けに JavaScript の新機能を補完するスクリプトを配信する無料サービスで、10万以上のサイトで利用されていたと報じられています
重要なのは、利用していたサイトのサーバーは一度も侵害されていない点です。
<!-- サイト側はこのタグを1文字も変えていない -->
<script src="https://cdn.polyfill.io/v3/polyfill.min.js"></script>
タグは同じまま、配信元の所有者が変わったことで「届く中身」だけが悪意化しました。読み込み先ドメインを暗黙に信頼し続ける Web の構造そのものが突かれた形です。元開発者の警告から発覚まで4ヶ月ありましたが、多くのサイトはタグを外していませんでした。消し忘れたタグによる同種の被害はその後も報告が続いており、過去の事件として片付けられる状況ではありません。
以下、サイト運営側の対策を実務手順として整理します。
手順1: 外部読み込みの棚卸し
静的な HTML / テンプレートなら grep で一次洗い出しができます。
grep -rnoE '<(script|link)[^>]+(src|href)="https?://[^"]+"' \
--include='*.html' --include='*.php' ./public
ただし、タグマネージャや CMS プラグインが動的に挿入するスクリプトは grep に出てきません。実際の読み込みは次のどちらかで確認します。
- ブラウザ DevTools の Network タブで JS をフィルタし、Domain 列を確認する
- CSP の Report-Only モードを「棚卸しツール」として使う
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
Report-Only はブロックせず違反レポートだけを送るため、本番に入れても表示は壊れません。数週間流せば「自サイトが実際にどこから何を読み込んでいるか」が網羅的に集まります。制作会社に作ってもらったきりで中身を誰も把握していないサイトでは、特に有効です。
手順2: 不要なものは削除する
棚卸しの結果 polyfill 系のタグが見つかったら、まず「本当に必要か」を疑ってください。polyfill はそもそも古いブラウザの機能差を埋めるためのもので、現行の Chrome / Edge / Firefox / Safari を対象とする限りほぼ不要です。サポート対象ブラウザを確認したうえで、不要なら削除が最も確実な対策です。
外部スクリプト全般についても同様で、「使っていないのに残っている」タグは攻撃面を広げるだけです。
手順3: 必要な部品は自前ホスト化する
削除できない部品は、CDN 参照をやめて自社サーバー(またはビルド成果物)から配信します。
# 特定バージョンを取得して自サイトに同梱する例
curl -fsSL -o public/js/vendor/example-lib.min.js \
https://cdn.example.com/example-lib/1.2.3/example-lib.min.js
npm で管理しているプロジェクトなら、CDN の script タグではなく依存としてインストールし、バンドルに含めるのが筋です。
注意点:
- 更新が自分の責任になります。同梱したライブラリの脆弱性情報のウォッチ(
npm auditや Dependabot など)をセットで運用に入れてください - ライセンス上の表示義務(ライセンス文の同梱など)を確認してください
手順4: 残す外部参照には SRI を付ける
どうしても外部 CDN 参照を残す場合は、SRI(Subresource Integrity)でファイルのハッシュを固定します。配信元で中身がすり替わると、ブラウザが読み込み自体を拒否します。
ハッシュの算出:
curl -fsSL https://cdn.example.com/example-lib/1.2.3/example-lib.min.js \
| openssl dgst -sha384 -binary | openssl base64 -A
HTML への記述(ハッシュ値は上で算出したものに置き換えてください):
<script src="https://cdn.example.com/example-lib/1.2.3/example-lib.min.js"
integrity="sha384-(算出したハッシュ値)"
crossorigin="anonymous"></script>
SRI のつまずきどころ
-
crossorigin="anonymous"が必須。クロスオリジンのリソースに integrity を付ける場合、CORS で取得しないと検証できず読み込みが失敗します。CDN 側がAccess-Control-Allow-Originを返すことも前提です -
バージョン固定 URL でのみ使う。
/latest/のような同一 URL で中身が更新されるエンドポイントに付けると、正規の更新でも読み込みが止まります - polyfill.io にはそもそも SRI が適用できなかった。User-Agent ごとに返す内容を変える動的配信だったため、ハッシュを固定できません。逆に言えば「SRI を付けられない外部スクリプト」は構造的にリスクが一段高い、という判断材料になります
- SRI が検証するのはそのファイル自体だけです。読み込まれたスクリプトがさらに動的に fetch する先までは検証しません
手順5: CSP で読み込み先を制限する
最後に、許可した配信元以外からスクリプトを読み込めないよう CSP(Content Security Policy)で制限します。
nginx の例:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'; base-uri 'self'" always;
Apache の例:
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'; base-uri 'self'"
CSP のつまずきどころ
-
いきなり enforce にしない。
Content-Security-Policy-Report-Onlyで数週間観測し、正規の読み込みが違反として出ないことを確認してから本番ヘッダに切り替えます -
インラインスクリプトが最初の壁になります。既存サイトは
<script>直書きや onclick 属性が多いことがほとんどで、'unsafe-inline'で逃げると効果が大きく削がれます。nonce か hash への移行を計画的に進めてください - タグマネージャ(GTM 等)経由の配信は、GTM が読み込む先のドメインまで許可が必要になり、許可リストが事実上「GTM で配信しうるすべて」に広がります。CSP の効果と運用のバランスを決めてから導入してください
なお CSP は、許可済みドメインの配信元自体が悪意化するケース(今回のような買収型)は防げません。SRI との併用が前提です。
運用への組み込み
ここまでの対策は一度やって終わりではなく、運用に載せて初めて機能します。
- 外部スクリプトの新規追加はコードレビューの確認項目にする(追加理由・SRI 可否・代替手段)
- 棚卸しを定期実行する(四半期に一度など。CSP レポートを常時収集していれば差分確認だけで済みます)
- 利用している配信元の「買収・移管・メンテナ交代」はリスクイベントとして扱う。polyfill.io は売却の時点で元開発者が警告していました
まとめ
| 手順 | 内容 |
|---|---|
| 1. 棚卸し | grep + DevTools + CSP Report-Only で外部読み込みを網羅 |
| 2. 削除 | polyfill 含め不要な外部スクリプトは消す |
| 3. 自前ホスト | 必要な部品は自社配信・脆弱性ウォッチとセット |
| 4. SRI | 残す外部参照はハッシュ固定(バージョン固定 URL + crossorigin) |
| 5. CSP | Report-Only で観測してから enforce |
「自社サーバーが無事ならサイトは安全」という前提は、外部スクリプトを1本でも読み込んだ時点で成り立ちません。まずは手順1の棚卸しからどうぞ。
株式会社ブレインディレクションは、ITソリューション/生成AI活用/クラウド・セキュリティ支援を行うISMS取得企業です: https://www.brain-d.jp