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

セキュリティヘッダーは、ブラウザに指示を与えてセキュリティを強化するHTTPレスポンスヘッダーです。

なぜ必要か

  • 🔒 XSS攻撃の防止
  • 🛡️ クリックジャッキングの防止
  • 📡 中間者攻撃の防止
  • 🚫 情報漏洩の防止
  • ⚡ 信頼性の向上

主要なセキュリティヘッダー

1. Strict-Transport-Security (HSTS)

# HTTPSを強制する
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

max-age: HTTPS強制期間(秒)
includeSubDomains: サブドメインも含む
preload: HSTSプリロードリストに登録可能

メリット:
✅ HTTP→HTTPS自動リダイレクト
✅ SSL/TLS ストリッピング攻撃を防ぐ
✅ パフォーマンス向上(リダイレクト不要)

2. Content-Security-Policy (CSP)

# XSS攻撃を防ぐ
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';

ディレクティブ:
default-src: デフォルトポリシー
script-src: JavaScriptの読み込み元
style-src: CSSの読み込み元
img-src: 画像の読み込み元
connect-src: Ajax、WebSocketの接続先
font-src: フォントの読み込み元

例:
Content-Security-Policy: 
  default-src 'self'; 
  script-src 'self' https://cdn.jsdelivr.net; 
  style-src 'self' 'unsafe-inline'; 
  img-src 'self' data: https:; 
  font-src 'self' https://fonts.gstatic.com;

3. X-Frame-Options

# クリックジャッキング防止
X-Frame-Options: DENY

DENY: すべてのフレーム埋め込みを禁止
SAMEORIGIN: 同一オリジンのみ許可
ALLOW-FROM https://example.com: 特定のURLのみ許可(非推奨)

用途:
✅ iframeでの悪用防止
✅ UI redressing攻撃防止

4. X-Content-Type-Options

# MIMEタイプスニッフィング防止
X-Content-Type-Options: nosniff

メリット:
✅ ブラウザがContent-Typeを尊重
✅ XSS攻撃のリスク軽減

5. Referrer-Policy

# リファラー情報の制御
Referrer-Policy: strict-origin-when-cross-origin

no-referrer: リファラーを送信しない
no-referrer-when-downgrade: HTTPS→HTTPで送信しない
same-origin: 同一オリジンのみ
strict-origin: オリジンのみ送信
strict-origin-when-cross-origin: 推奨(デフォルト)

6. Permissions-Policy

# ブラウザ機能の制御(旧Feature-Policy)
Permissions-Policy: geolocation=(), microphone=(), camera=()

制御できる機能:
- geolocation: 位置情報
- microphone: マイク
- camera: カメラ
- payment: 支払いAPI
- usb: USB

例:
Permissions-Policy: 
  geolocation=(self), 
  microphone=(), 
  camera=(self "https://trusted-site.com")

Nginxでの設定

基本設定

# /etc/nginx/conf.d/security-headers.conf

server {
    listen 443 ssl http2;
    server_name example.com;
    
    # SSL証明書
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    
    # CSP
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;
    
    # X-Frame-Options
    add_header X-Frame-Options "SAMEORIGIN" always;
    
    # X-Content-Type-Options
    add_header X-Content-Type-Options "nosniff" always;
    
    # Referrer-Policy
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    
    # Permissions-Policy
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
    
    location / {
        root /var/www/html;
        index index.html;
    }
}

共通設定ファイル

# /etc/nginx/snippets/security-headers.conf
# 複数のサーバーで再利用

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self';" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

# 使用方法
server {
    listen 443 ssl http2;
    server_name example.com;
    
    include snippets/security-headers.conf;
    
    location / {
        root /var/www/html;
    }
}

Content-Security-Policyの詳細設定

段階的な導入

# Step 1: レポートモード(違反を検出するが適用しない)
Content-Security-Policy-Report-Only: 
  default-src 'self'; 
  report-uri /csp-violation-report;

# Step 2: 違反レポートを確認
# ログを分析して必要なソースを追加

# Step 3: 本番適用
Content-Security-Policy: 
  default-src 'self'; 
  script-src 'self' https://cdn.example.com; 
  report-uri /csp-violation-report;

Google Analytics対応

Content-Security-Policy: 
  default-src 'self'; 
  script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com; 
  img-src 'self' https://www.google-analytics.com; 
  connect-src 'self' https://www.google-analytics.com;

Google AdSense対応

Content-Security-Policy: 
  default-src 'self'; 
  script-src 'self' 'unsafe-inline' https://pagead2.googlesyndication.com https://adservice.google.com; 
  img-src 'self' data: https:; 
  frame-src https://googleads.g.doubleclick.net; 
  style-src 'self' 'unsafe-inline';

CSP違反レポートの受信

# Nginx設定
location /csp-violation-report {
    access_log /var/log/nginx/csp-violations.log;
    return 204;
}

# ログの確認
sudo tail -f /var/log/nginx/csp-violations.log

# 違反レポートの例
{
  "csp-report": {
    "document-uri": "https://example.com/page",
    "violated-directive": "script-src",
    "blocked-uri": "https://evil.com/malicious.js",
    "original-policy": "default-src 'self';"
  }
}

HSTSプリロード

プリロードリストへの登録

要件:
1. 有効なHTTPS証明書
2. すべてのサブドメインでHTTPS
3. HSTSヘッダーの設定:
   - max-age >= 31536000(1年)
   - includeSubDomains
   - preload

Nginx設定:
add_header Strict-Transport-Security 
  "max-age=31536000; includeSubDomains; preload" always;

登録:
https://hstspreload.org/ にアクセス
ドメインを入力して送信

効果:
✅ ブラウザが最初から自動的にHTTPS接続
✅ HTTP接続を一切行わない

セキュリティヘッダーのテスト

オンラインツール

1. SecurityHeaders.com
https://securityheaders.com/
→ セキュリティスコア(A+が最高)

2. Mozilla Observatory
https://observatory.mozilla.org/
→ 詳細な分析とアドバイス

3. SSL Labs
https://www.ssllabs.com/ssltest/
→ SSL/TLS設定の評価

curlでの確認

# すべてのヘッダー確認
curl -I https://example.com

# 特定のヘッダー確認
curl -I https://example.com | grep -i strict-transport

# 詳細表示
curl -v https://example.com 2>&1 | grep -i "< "

期待される出力:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self';
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff

docker-composeでの設定

Nginxコンテナの設定

# docker-compose.yml
version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./security-headers.conf:/etc/nginx/snippets/security-headers.conf:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    restart: unless-stopped

# nginx.conf
http {
    include /etc/nginx/snippets/security-headers.conf;
    
    server {
        listen 80;
        server_name example.com;
        return 301 https://$server_name$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        server_name example.com;
        
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        
        location / {
            proxy_pass http://web:5000;
            proxy_set_header Host $host;
        }
    }
}

実用的な設定例

静的サイト(ブログ)

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com;" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

Webアプリケーション

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always;

API サーバー

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'none'; frame-ancestors 'none';" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
add_header X-Frame-Options "DENY" always;

トラブルシューティング

CSPでサイトが壊れる

問題: スタイルやスクリプトが読み込まれない

解決:
1. ブラウザの開発者ツールでCSP違反を確認
2. 違反しているリソースを特定
3. CSPに追加

# Chrome DevTools > Console
Refused to load the script 'https://cdn.example.com/script.js' 
because it violates the following Content Security Policy directive: 
"script-src 'self'".

→ script-src に https://cdn.example.com を追加

HSTSで開発環境にアクセスできない

問題: localhost でHTTPSを強制される

解決:
# Chrome
chrome://net-internals/#hsts
ドメインを入力 > Delete

# Firefox
about:preferences#privacy
Cookiesとサイトデータ > データを管理
ドメインを削除

予防:
開発環境ではHSTSを無効化

まとめ

セキュリティヘッダーを正しく設定してWebサイトを保護しましょう。

必須ヘッダー

  • Strict-Transport-Security(HSTS)
  • Content-Security-Policy(CSP)
  • X-Frame-Options
  • X-Content-Type-Options
  • Referrer-Policy

ベストプラクティス

  • CSPはレポートモードから始める
  • 段階的に厳格化
  • 定期的にセキュリティスキャン
  • HSTSプリロードリストに登録