CSPとは?
CSPは、クロスサイトスクリプティング(XSS)などの攻撃を防ぐために、ブラウザがどのオリジン(ドメイン)からリソースを読み込んで良いかを制御するセキュリティ機能です。意図しないドメインからのスクリプトや画像の読み込みをブロックできます。
ちょっと大きめの会社だとセキュリティ要件やコーディング規則に入っていたりするので、「CSPってなんだろう?」と恥をかかないように覚えておきましょう。
基本的な設定方法
設定方法は主に2つあります。
1. HTMLのmetaタグで設定する
手軽に試せるのが、HTMLの<head>
内にmetaタグを記述する方法です。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' https://cdn.example.com;">
<title>CSPの例</title>
</head>
<body>
<script src="/js/app.js"></script>
<script src="https://cdn.example.com/library.js"></script>
<script src="https://evil.com/malicious.js"></script>
<script>
alert('このコードは実行されません');
</script>
</body>
</html>
この例では、default-src 'self'
で基本的に自ドメインからのリソースのみを許可し、script-src
で追加でhttps://cdn.example.com
からのスクリプト実行を許可しています。デフォルトでインラインスクリプトはブロックされます。
2. HTTPヘッダーで設定する
より推奨される方法が、サーバーからのHTTPレスポンスヘッダーでCSPを指定する方法です。Node.js/Expressでhelmet
ミドルウェアを使うと簡単に設定できます。
import express from 'express';
import helmet from 'helmet';
const app = express();
// helmetを使用してCSPを設定
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"], // スタイルはインラインを許可
imgSrc: ["'self'", "data:", "https:"], // 画像はdata:とhttpsも許可
objectSrc: ["'none'"], // objectタグはすべて禁止
upgradeInsecureRequests: [], // HTTPリクエストをHTTPSに
},
})
);
よく使うディレクティブと値
CSPの設定はディレクティブと値の組み合わせで行います。
ディレクティブ | 説明 |
---|---|
default-src |
全てのリソースタイプのデフォルトの読み込み元を指定します。 |
script-src |
JavaScriptファイルの読み込み元を指定します。 |
style-src |
CSSファイルの読み込み元を指定します。 |
img-src |
画像ファイルの読み込み元を指定します。 |
connect-src |
fetch やWebSocket などの接続先を指定します。 |
font-src |
フォントファイルの読み込み元を指定します。 |
object-src |
<object> , <embed> , <applet> の読み込み元を指定します。 |
値 | 説明 |
---|---|
'self' |
同一オリジンからのリソースを許可します。 |
'none' |
すべてのリソースをブロックします。 |
'unsafe-inline' |
インラインの<script> や<style> を許可します。(非推奨) |
'unsafe-eval' |
eval() のような動的コード実行を許可します。(非推奨) |
https: |
HTTPSプロトコル経由のリソースを許可します。 |
data: |
data: URIスキーム(Base64エンコード画像など)を許可します。 |
非推奨のものもあるので注意
設定例(Next.js)
Next.jsのようなフレームワークでは、設定ファイルで一元管理するのが一般的です。next.config.js
に記述することで、全ページにCSPヘッダーを適用できます。
module.exports = {
async headers() {
const cspValue = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.google-analytics.com;
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: https:;
connect-src 'self' https://api.example.com;
font-src 'self';
object-src 'none';
frame-ancestors 'none';
upgrade-insecure-requests;
`.replace(/\s{2,}/g, ' ').trim(); // 改行と余分なスペースを削除
return [
{
source: '/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: cspValue,
},
],
},
];
},
};
この設定は、Google Analyticsのスクリプトを許可しつつ、APIサーバーとの通信も許可する、といった要件を想定したものです。
CSPは設定を間違えるとバグを生みやすく、原因が分かりづらいのでメンテナンス性が落ちる可能性もあります。導入する際は、ブラウザの開発者ツールでブロックされたリソースを確認しながら、少しずつ適用範囲を広げていくのが安全だと感じました。