Content-Security-Policy(CSP)完全ガイド
CSPはXSS攻撃を防ぐHTTPレスポンスヘッダー。どのリソースの読み込みを許可するかをブラウザに指示する。
この記事の対象読者
- XSS対策を強化したいWebエンジニア
- Nginx / Django でCSPを導入したい方
- CSPの違反レポートを活用してセキュリティ運用を行いたい方
- 既存サイトに段階的にCSPを導入したい方
目次
Step 1CSPとは
Content-Security-Policy(CSP)は、Webページがどのリソースを読み込んでよいかをブラウザに指示するHTTPレスポンスヘッダーです。サーバーが「このページでは、自分自身のドメインのスクリプトだけ実行してよい」と宣言することで、攻撃者が注入した不正なスクリプトの実行をブラウザレベルでブロックできます。
XSS(クロスサイトスクリプティング)攻撃は、Webアプリケーションの脆弱性を突いて、ユーザーのブラウザ上で悪意のあるスクリプトを実行する攻撃です。入力値のエスケープやサニタイズが第一の防御策ですが、CSPはその「二重防御」として機能します。
CSPの本質: CSPはアプリケーションの脆弱性を修正するものではありません。脆弱性が悪用された場合の被害を軽減する「緩和策」です。入力値のバリデーションやエスケープ処理と組み合わせて使いましょう。
Step 2主要ディレクティブ一覧
CSPはディレクティブと呼ばれる命令の集合で構成されます。各ディレクティブが特定の種類のリソースに対するポリシーを定義します。
| ディレクティブ | 対象リソース | 設定例 |
|---|---|---|
default-src | フォールバック | 'self' |
script-src | JavaScript | 'self' https://cdn.example.com |
style-src | CSS | 'self' 'unsafe-inline' |
img-src | 画像 | 'self' data: https: |
font-src | フォント | 'self' |
connect-src | XHR, Fetch, WebSocket | 'self' https://api.example.com |
frame-ancestors | iframe埋め込み制御 | 'none' |
base-uri | base タグ | 'self' |
form-action | フォーム送信先 | 'self' |
object-src | object, embed | 'none' |
注意: default-src はフォールバックであり、個別のディレクティブが指定されている場合はそちらが優先されます。default-src 'none' として必要なものだけ個別に許可するのが最も安全です。
Step 3設定方法
Nginx での設定
server {
add_header Content-Security-Policy
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
always;
}Django ミドルウェアで設定
class CSPMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response["Content-Security-Policy"] = (
"default-src 'self'; "
"script-src 'self'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data:; "
"font-src 'self'; "
"connect-src 'self'; "
"frame-ancestors 'none'"
)
return responseHTML メタタグでの設定
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'">メタタグの制限: メタタグでは frame-ancestors、report-uri を使用できません。本番ではHTTPヘッダーを推奨。
Step 4よく使う設定パターン
静的サイト(最も厳格)
default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'SPA(React / Vue)
default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self' https://api.example.com wss://ws.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'Step 5unsafe-inline と unsafe-eval
'unsafe-inline' を script-src に指定すると、ページ内のすべてのインラインスクリプトが実行可能になり、CSPの保護効果が大幅に低下します。
重要: script-src に 'unsafe-inline' を指定すると、CSPによるXSS防御はほぼ無効化されます。可能な限りnonce または hash ベースに切り替えましょう。
代替手段: nonce(推奨)
import secrets
class CSPNonceMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
nonce = secrets.token_urlsafe(32)
request.csp_nonce = nonce
response = self.get_response(request)
response["Content-Security-Policy"] = (
f"default-src 'self'; "
f"script-src 'self' 'nonce-{nonce}' 'strict-dynamic'; "
f"style-src 'self' 'nonce-{nonce}'; "
f"img-src 'self' data:; font-src 'self'; "
f"connect-src 'self'; frame-ancestors 'none'"
)
return response<script nonce="{{ request.csp_nonce }}">
console.log("nonce付きなので実行される");
</script>
<script>
alert("XSS"); // nonceがないのでブロックされる
</script>Step 6違反レポート(report-uri / report-to)
CSPにはポリシー違反時にレポートを送信する機能があります。
Content-Security-Policy: default-src 'self'; script-src 'self'; report-uri /csp-report-endpointimport json, logging
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
logger = logging.getLogger("csp")
@csrf_exempt
def csp_report(request):
try:
report = json.loads(request.body)
logger.warning("CSP違反: %s", report.get("csp-report", {}).get("blocked-uri"))
except json.JSONDecodeError:
pass
return HttpResponse(status=204)Step 7段階的な導入手順
既存サイトにCSPをいきなり適用するとサイトが壊れる可能性があります。以下の手順で段階的に導入しましょう。
add_header Content-Security-Policy-Report-Only
"default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /csp-report-endpoint"
always;最終目標: 理想的なCSPは default-src 'none' をベースに、必要なリソースだけをホワイトリストで許可し、インラインスクリプトはnonceで管理する構成です。段階的に改善していきましょう。