🛡️
概念 #セキュリティ #Webアプリ #CSP #HSTS #セキュリティヘッダー #HTTP 📚 Webアプリセキュリティ

セキュリティヘッダー完全ガイド

セキュリティヘッダーとは

HTTP レスポンスヘッダーにセキュリティ指示を含めることで、ブラウザ側の攻撃緩和を実現する仕組み。

コードのバグをなくすことが理想だが、セキュリティヘッダーは XSS・Clickjacking・情報漏洩などを ブラウザレベルで「被害を減らす」第二の防衛線となる。

ヘッダーなしの場合:
  攻撃者が XSS を成功させる → スクリプトが制限なく実行
  iframe で埋め込まれる → Clickjacking が成立
  HTTP で通信を降格される → MITM 攻撃でデータ窃取

ヘッダーありの場合:
  XSS が成功しても → CSP がインラインスクリプトをブロック
  iframe に埋め込もうとしても → X-Frame-Options/CSP が拒否
  HTTP に降格しようとしても → HSTS が HTTPS を強制

Content-Security-Policy(CSP)

ブラウザが読み込み・実行してよいリソースのソースを宣言するヘッダー。XSS 緩和の主力。

ディレクティブ早見表

ディレクティブ対象推奨設定
default-srcすべてのリソースのデフォルト'self'
script-srcJavaScript'self' 'nonce-{random}'
style-srcCSS'self' 'nonce-{random}'
img-src画像'self' data: https:
font-srcフォント'self'
connect-srcfetch/XHR/WebSocket'self' https://api.example.com
frame-ancestorsiframe 埋め込みの許可元'none'(Clickjacking 対策)
base-uri<base> タグの URL'self'
object-srcFlash・プラグイン'none'
form-actionフォームの送信先'self'
upgrade-insecure-requestsHTTP → HTTPS に自動昇格設定推奨

実装例(Flask)

import secrets
from flask import Flask, g, request

app = Flask(__name__)

@app.before_request
def generate_nonce():
    g.csp_nonce = secrets.token_urlsafe(16)   # リクエストごとにランダム生成

@app.after_request
def add_security_headers(response):
    nonce = g.get('csp_nonce', '')

    response.headers['Content-Security-Policy'] = (
        f"default-src 'self'; "
        f"script-src 'self' 'nonce-{nonce}'; "
        f"style-src 'self' 'nonce-{nonce}'; "
        f"img-src 'self' data: https:; "
        f"font-src 'self'; "
        f"connect-src 'self'; "
        f"object-src 'none'; "
        f"base-uri 'self'; "
        f"form-action 'self'; "
        f"frame-ancestors 'none'; "
        f"upgrade-insecure-requests"
    )
    return response
<!-- テンプレートで nonce を使用 -->
<script nonce="{{ g.csp_nonce }}">
  // このスクリプトだけ CSP が許可する
  const userId = {{ current_user.id | tojson }};
</script>

CSP の段階的導入

本番に突然 enforce モードを導入すると既存の JS が壊れる可能性がある。
段階的に導入する:

Step 1: Report-Only で違反を収集
  Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

Step 2: 違反ログを分析して CSP を調整

Step 3: enforce モードに切り替え
  Content-Security-Policy: default-src 'self'; report-uri /csp-report
# CSP 違反レポートの受信エンドポイント
@app.route('/csp-report', methods=['POST'])
def csp_report():
    report = request.get_json(force=True)
    app.logger.warning(f"CSP Violation: {report.get('csp-report', {})}")
    return '', 204

CSP の落とし穴

❌ 'unsafe-inline': インラインスクリプトを許可(XSS を直接許可するのと同じ)
❌ 'unsafe-eval': eval() を許可(動的コード実行)
❌ script-src *: 任意の外部スクリプトを許可
❌ data: を script-src に: data:text/javascript,... で実行可能
❌ JSONP エンドポイントのドメインを許可: そのドメイン経由で任意 JS を実行可能

  例: script-src https://cdnjs.cloudflare.com
      → https://cdnjs.cloudflare.com/angular.min.js を読み込んで
        Angular テンプレートインジェクションで CSP をバイパスできる
        (known JSONP/Angular bypassable ドメインに注意)

Strict-Transport-Security(HSTS)

ブラウザに「このドメインは常に HTTPS で接続せよ」と指示するヘッダー。MITM 攻撃・プロトコルダウングレード攻撃を防ぐ。

HTTP でアクセス → 301 Redirect → HTTPS

HSTS があると:
  2回目以降のアクセスはブラウザが自動で HTTPS にアップグレード
  → HTTP リクエスト自体が送られない(傍受できない)
@app.after_request
def add_hsts(response):
    # max-age: 1年(秒)推奨
    # includeSubDomains: サブドメインにも適用
    # preload: ブラウザの HSTS プリロードリストに登録申請可能にする
    response.headers['Strict-Transport-Security'] = (
        'max-age=31536000; includeSubDomains; preload'
    )
    return response
注意事項:
  ・HTTPS が正しく設定されてから有効にする(HTTP しか使えない状態で設定するとサイトが開けなくなる)
  ・includeSubDomains を設定する前に全サブドメインが HTTPS 対応していることを確認
  ・HSTS プリロード: hstspreload.org に登録するとブラウザのハードコードリストに入る
    → ブラウザが初回から HTTP リクエストを送らなくなる(最強の設定)

X-Content-Type-Options

ブラウザが Content-Type を無視してコンテンツを推測する「MIME スニッフィング」を禁止する。

MIME スニッフィング攻撃:
  攻撃者がテキストファイルに偽装した JavaScript をアップロード
  サーバーが Content-Type: text/plain で返す
  ブラウザがファイルの中身から「これは JS だ」と推測して実行してしまう

対策:
  X-Content-Type-Options: nosniff
  → ブラウザは Content-Type を絶対に信頼し、推測しない
response.headers['X-Content-Type-Options'] = 'nosniff'

X-Frame-Options(Legacy)

このページを iframe で埋め込むことを制御する。Clickjacking 対策。

# DENY:       すべての iframe 埋め込みを禁止(推奨)
# SAMEORIGIN: 同一オリジンの iframe のみ許可
response.headers['X-Frame-Options'] = 'DENY'
X-Frame-Options は古いヘッダー。
現代の推奨: CSP の frame-ancestors ディレクティブを使う。
  ・より細かい制御が可能(複数オリジンの許可など)
  ・両方設定しておくと後方互換性も担保できる

Referrer-Policy

リンクを踏んだときに Referer ヘッダーに何を含めるかを制御する。

デフォルト動作:
  https://example.com/secret-page → https://external.com/
  Referer: https://example.com/secret-page  ← URL が漏れる

対策: パス情報を隠す
動作
no-referrerReferer ヘッダーを送らない
no-referrer-when-downgradeHTTPS→HTTP のとき送らない(デフォルト)
same-origin同一オリジンへのリクエストのみ送る
strict-originオリジン(スキーム+ドメイン)のみ送る
strict-origin-when-cross-origin同一オリジン時は full URL、クロスオリジン時はオリジンのみ(推奨)
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'

Permissions-Policy(旧 Feature-Policy)

ブラウザの機能(カメラ・マイク・位置情報等)へのアクセスを制御する。

response.headers['Permissions-Policy'] = (
    'camera=(), '          # カメラ: 全て禁止
    'microphone=(), '      # マイク: 全て禁止
    'geolocation=(), '     # 位置情報: 全て禁止
    'payment=self, '       # 決済: 同一オリジンのみ
    'fullscreen=self'      # フルスクリーン: 同一オリジンのみ
)
XSS が成功した場合でも、Permissions-Policy があればカメラ・マイクへのアクセスを防げる。
サードパーティ iframe に対しても機能を制限できる。

Cache-Control(機密ページのキャッシュ制御)

# 機密情報を含むページ・API レスポンスはキャッシュしない
@app.route('/api/v1/users/me')
@jwt_required
def get_profile():
    response = jsonify(current_user.to_dict())
    response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate'
    response.headers['Pragma'] = 'no-cache'   # HTTP/1.0 互換
    return response
no-store:         キャッシュに保存しない(最も強力)
no-cache:         再検証なしでキャッシュを使わない
must-revalidate:  期限切れキャッシュを再検証なしで使わない

機密ページに no-store を設定しないと:
  ・ブラウザの「戻る」ボタンでログアウト後も内容を見られる
  ・共有端末でキャッシュから個人情報が漏れる

Cross-Origin ヘッダー群(CORP・COEP・COOP)

高度なブラウザ分離機能。SharedArrayBuffer・Spectre 対策として必要。

# Cross-Origin Resource Policy: 他オリジンからの読み込みを制御
response.headers['Cross-Origin-Resource-Policy'] = 'same-origin'
# same-origin: 同一オリジンのみ読み込み可
# same-site:   同一サイト(サブドメイン含む)まで許可
# cross-origin: 全て許可(デフォルト)

# Cross-Origin Embedder Policy: 全サブリソースに CORP または CORS を要求
response.headers['Cross-Origin-Embedder-Policy'] = 'require-corp'

# Cross-Origin Opener Policy: クロスオリジンとのブラウジングコンテキスト共有を制限
response.headers['Cross-Origin-Opener-Policy'] = 'same-origin'

# この3つを設定すると SharedArrayBuffer を使えるようになる(Spectre 対策済み環境として)

Nginx / Caddy での一括設定

# Nginx: セキュリティヘッダーをまとめて設定
server {
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    # CSP はアプリ側で nonce を生成するため、ここでは基本設定のみ
    # add_header Content-Security-Policy "default-src 'self'; object-src 'none';" always;
}
# Caddy: セキュリティヘッダー(Caddyfile)
example.com {
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "camera=(), microphone=(), geolocation=()"
        -Server   # Server ヘッダーを削除(バージョン情報の非開示)
    }
}

ヘッダーのスコアリングと評価ツール

securityheaders.com: URL を入力するとヘッダーをスキャンして A〜F のグレードで評価

各ヘッダーの優先度:
  Critical(必須):
    ✅ Content-Security-Policy
    ✅ Strict-Transport-Security
    ✅ X-Content-Type-Options

  Important(強く推奨):
    ✅ X-Frame-Options(または CSP frame-ancestors)
    ✅ Referrer-Policy

  Recommended(推奨):
    ✅ Permissions-Policy
    ✅ Cross-Origin-Resource-Policy

セキュリティヘッダー チェックリスト

□ CSP を設定し 'unsafe-inline' / 'unsafe-eval' を使っていない
□ CSP に nonce を使いリクエストごとにランダム生成している
□ CSP の Report-Only で違反を収集・モニタリングしている
□ Strict-Transport-Security を max-age=31536000; includeSubDomains で設定している
□ X-Content-Type-Options: nosniff を設定している
□ X-Frame-Options: DENY または CSP frame-ancestors 'none' を設定している
□ Referrer-Policy を strict-origin-when-cross-origin に設定している
□ Permissions-Policy でカメラ・マイク・位置情報を制限している
□ 機密 API レスポンスに Cache-Control: no-store を設定している
□ Server / X-Powered-By ヘッダーを削除してバージョン情報を非開示にしている
□ securityheaders.com でスキャンして A グレード以上を確認している

参考文献

  • MDN Web Docs『HTTP headers』— 各ヘッダーの仕様・ブラウザサポート表(developer.mozilla.org)
  • OWASP Secure Headers Project(owasp.org/www-project-secure-headers)— 全ヘッダーの推奨値と設定例
  • W3C CSP Level 3 仕様(w3.org)— Content-Security-Policy の正式仕様
  • RFC 6797『HTTP Strict Transport Security(HSTS)』— HSTS の設計と実装仕様
  • securityheaders.com — ヘッダー設定をスキャンして評価するオンラインツール

出典: MDN Web Docs Security / OWASP Secure Headers Project / securityheaders.com / RFC 6797(HSTS)/ W3C CSP Level 3