課題:利用しているCDNを本当に信頼していますか?
多くの開発者は、jQuery、Bootstrap、FontAwesomeなどのライブラリを、Cloudflare、Google、cdnjsといった大手CDN経由で読み込む習慣があります。そのメリットは明確です。読み込み速度の向上、サーバー帯域の節約、そしてユーザーのブラウザキャッシュの活用です。
しかし、多くのプロジェクトでセキュリティ監査を行った結果、致命的な脆弱性に気づきました。ほとんどの開発者はスクリプトのリンクをコピー&ペーストするだけで、ファイルの内容を検証することを忘れているのです。
これが単なる杞憂ではないことは、歴史が証明しています。最近の Polyfill.io への攻撃を思い出してください。CDNドメインの所有者が変わったことで、10万以上のウェブサイトが意図せずマルウェアを読み込むことになりました。CDNサーバーが乗っ取られると、ハッカーは正規の jquery.min.js を、クレジットカード情報やクッキーを盗み出すコードを仕込んだ「改ざん版」に差し替えることができます。ブラウザはCDNのドメインを完全に信頼しているため、このコードを実行してしまいます。これが、極めて危険な Supply Chain Attack(サプライチェーン攻撃)の手法です。
このリスクを遮断するために、Subresource Integrity (SRI) はウェブサイトを守る最後の盾となります。
Subresource Integrity (SRI) の仕組み
SRIを利用すると、ブラウザはハッシュ値(Hash)を使用して、ダウンロードしたファイルが不正に改ざんされていないかを確認できるようになります。
ハッシュ値を付与してファイルを読み込む際、ブラウザは以下の4つのステップを実行します。
- CDNからファイルを一時メモリにダウンロードする。
- 指定されたアルゴリズム(通常はSHA-384)を使用して、そのファイルのハッシュ値を自ら計算する。
- 計算したハッシュ値を、
integrity属性に記述されたハッシュ値と比較する。 - 一致すればファイルを実行する。カンマ一つでも異なれば、ブラウザはそのファイルをブロックし、コンソールに真っ赤なエラーを表示します。
この仕組みのおかげで、万が一CDNサーバーがハッキングされたとしても、マルウェアがユーザーのブラウザ上で実行される機会は失われます。
実プロジェクトへのSRI導入方法
1. 手動でハッシュ値を生成する
数個のライブラリしか使用しない小規模なプロジェクトでは、ターミナルから素早くハッシュ値を生成できます。例えば、app.js ファイルのSHA-384ハッシュ値を生成する場合を考えます。
LinuxまたはmacOSでは、以下のコマンドを使用します:
cat app.js | openssl dgst -sha384 -binary | openssl base64 -A
結果として長いBase64文字列が出力されます。コマンドを打つのが面倒な場合は、srihash.org を利用してください。CDNのリンクを貼り付けるだけで、完成したHTMLコードを自動生成してくれます。
2. HTMLの integrity 属性を設定する
ハッシュ値を取得したら、それを <script> または <link> タグに追加します。この際、crossorigin="anonymous" 属性を忘れないでください。これがないと、ブラウザはクロスドメインのセキュリティ上の理由からSRIのチェックをスキップしてしまいます。
Bootstrapを適切に読み込む例:
<!-- CSSの読み込み -->
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
crossorigin="anonymous">
<!-- JSの読み込み -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
crossorigin="anonymous"></script>
3. Webpackによる自動化
更新のたびに数十個のファイルに対して手動でハッシュ値を生成するのは現実的ではありません。Webpackを使用している場合は、webpack-subresource-integrity プラグインに任せましょう。
プラグインのインストール:
npm install webpack-subresource-integrity --save-dev
webpack.config.js での設定:

