セキュリティヘッダーは設定自体は数行ですが、
何を防いでいるかを理解していないと、効かない設定のまま安心してしまいます。
CSRF — 意図しない操作をさせられる
ログイン中のユーザーに、別サイトから勝手にリクエストを送らせる攻撃です。
フォームにトークンを入れて防ぎます。
<form method="post">
{% csrf_token %}
</form>
非同期リクエストではヘッダーで送ります。
headers: { "X-CSRFToken": getCookie("csrftoken") }
Cookie の SameSite 属性も併用します。
CSRF_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_SAMESITE = "Lax"
Lax にすると、他サイトからの POST に Cookie が付かなくなります。
トークンと二重で守る形になります。
クリックジャッキング — 透明な枠に重ねられる
自サイトを透明な iframe で重ね、ユーザーに気づかせず操作させる攻撃です。
X_FRAME_OPTIONS = "DENY"
add_header X-Frame-Options "DENY" always;
埋め込みを許可する必要がある場合のみ SAMEORIGIN にします。
より細かく制御するなら CSP の frame-ancestors を使います。
その他の基本ヘッダー
# MIME タイプの推測を止める(.txt を script として解釈させない)
add_header X-Content-Type-Options "nosniff" always;
# リファラで内部URLを漏らさない
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# HTTPS を強制(HSTS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 不要な機能へのアクセスを塞ぐ
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
always を付けないと、エラー応答時にヘッダーが付きません。
404 や 500 のページも攻撃対象になるので、必ず付けます。
HSTS の注意
max-age を長く設定すると、その期間ブラウザが HTTPS を強制します。
証明書が切れると、ユーザーは回避できません。
導入時は短い値から始めて、証明書の自動更新が確実に動いていることを確認してから伸ばします。
max-age=300 → 動作確認
max-age=31536000 → 本番運用
includeSubDomains はサブドメイン全てに適用されるので、
HTTPS化していないサブドメインがあると、そこにアクセスできなくなります。
確認方法
curl -sI https://example.com/ | grep -iE "x-frame|x-content|referrer|strict-transport"
存在しないパスでも同じヘッダーが付くかを確認します。
curl -sI https://example.com/does-not-exist | grep -i x-frame
まとめ
| ヘッダー | 防ぐもの |
|---|---|
| CSRFトークン + SameSite | 意図しない操作の実行 |
| X-Frame-Options | クリックジャッキング |
| X-Content-Type-Options | MIME推測による実行 |
| Referrer-Policy | 内部URLの漏洩 |
| Strict-Transport-Security | HTTPへの降格 |
always を忘れないこと、HSTS は短い値から始めることが実務上の要点です。