セキュリティヘッダーとは?
セキュリティヘッダーとは、Webサーバーがブラウザに送信するHTTPレスポンスヘッダーの中で、Webサイトのセキュリティを向上させる目的で設定されるヘッダーのことです。
これらのヘッダーを適切に設定することで、以下のような攻撃から Webサイトを保護できます:
- XSS(クロスサイトスクリプティング)攻撃
- クリックジャッキング攻撃
- 中間者攻撃
- コンテンツタイプ詐称攻撃
- リファラー情報漏洩
- HTTPSダウングレード攻撃
セキュリティヘッダーの大きな利点は、サーバーの設定だけで有効化でき、アプリケーションコードを変更する必要がないことです。正しく設定するだけで、Webサイトのセキュリティレベルを大幅に向上させることができます。
主要なセキュリティヘッダー一覧
1. Content-Security-Policy (CSP)
最も重要なセキュリティヘッダーの一つで、XSS攻撃を防ぐために使用されます。
基本的な設定例:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
段階的なCSP実装:
# ステップ1: レポートのみモード(本番影響なし)
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
# ステップ2: 基本的な制限
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
# ステップ3: より厳格な制限
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' https:; connect-src 'self'
より安全な nonce ベースのCSP:
<!-- サーバーサイドで一意のnonceを生成 -->
<script nonce="random-nonce-value">
// 信頼できるスクリプト
console.log('This script is allowed');
</script>
Content-Security-Policy: script-src 'nonce-random-nonce-value'
2. X-Frame-Options
クリックジャッキング攻撃を防ぐためのヘッダーです。
# 完全にフレーム埋め込みを禁止
X-Frame-Options: DENY
# 同一オリジンのみ許可
X-Frame-Options: SAMEORIGIN
# 特定のサイトのみ許可(廃止予定)
X-Frame-Options: ALLOW-FROM https://trusted-site.com
現代的なアプローチ(CSP frame-ancestors):
Content-Security-Policy: frame-ancestors 'none'
Content-Security-Policy: frame-ancestors 'self'
Content-Security-Policy: frame-ancestors https://trusted-site.com
3. Strict-Transport-Security (HSTS)
HTTPS接続を強制し、HTTPSダウングレード攻撃を防ぎます。
# 基本設定(1年間)
Strict-Transport-Security: max-age=31536000
# サブドメインも含める
Strict-Transport-Security: max-age=31536000; includeSubDomains
# ブラウザのプリロードリストに追加
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
HSTSプリロード申請:
# プリロード申請の条件
1. 有効なSSL証明書
2. HTTPからHTTPSへのリダイレクト
3. preload ディレクティブ付きのHSTSヘッダー
4. https://hstspreload.org/ での申請
4. X-Content-Type-Options
ブラウザのMIMEタイプ推測機能を無効化し、コンテンツタイプ詐称攻撃を防ぎます。
X-Content-Type-Options: nosniff
攻撃例と防御:
<!-- 攻撃者がアップロードした「画像」ファイル -->
<!-- 実際はJavaScriptコードが含まれている -->
<img src="malicious-file.jpg">
<!-- X-Content-Type-Options: nosniff があると -->
<!-- ブラウザはContent-Typeを厳密にチェックし、実行を阻止 -->
5. Referrer-Policy
リファラー情報の送信を制御し、プライバシーを保護します。
# リファラー情報を送信しない
Referrer-Policy: no-referrer
# 同一オリジンの場合のみ送信
Referrer-Policy: same-origin
# HTTPS→HTTPの場合はリファラーを送信しない
Referrer-Policy: no-referrer-when-downgrade
# オリジン部分のみ送信
Referrer-Policy: origin
# 厳格なオリジンポリシー
Referrer-Policy: strict-origin-when-cross-origin
6. Permissions-Policy
ブラウザの機能(カメラ、マイク、位置情報など)へのアクセスを制御します。
# カメラとマイクを完全に無効化
Permissions-Policy: camera=(), microphone=()
# 同一オリジンのみ許可
Permissions-Policy: geolocation=(self)
# 特定のサイトのみ許可
Permissions-Policy: camera=(self "https://trusted-video-service.com")
# 複数の機能を制御
Permissions-Policy: geolocation=(), camera=(), microphone=(), payment=(self)
7. X-XSS-Protection
古いブラウザ向けのXSS保護機能(現在は非推奨、CSPを使用推奨)。
# XSS保護を有効化(古いブラウザ用)
X-XSS-Protection: 1; mode=block
8. Cache-Control
キャッシュ制御によるセキュリティ向上:
# 機密情報はキャッシュしない
Cache-Control: no-store, no-cache, must-revalidate, private
# 静的リソースは適切にキャッシュ
Cache-Control: public, max-age=31536000, immutable
Webサーバー別の設定方法
Apache (.htaccess)
# セキュリティヘッダーの設定
# CSP
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:;"
# X-Frame-Options
Header always set X-Frame-Options "SAMEORIGIN"
# HSTS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# X-Content-Type-Options
Header always set X-Content-Type-Options "nosniff"
# Referrer Policy
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Permissions Policy
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"
# X-XSS-Protection(古いブラウザ用)
Header always set X-XSS-Protection "1; mode=block"
# HTTPSリダイレクト
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Nginx
# セキュリティヘッダーの設定
server {
listen 443 ssl http2;
server_name example.com;
# SSL設定
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# セキュリティヘッダー
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:;" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
add_header X-XSS-Protection "1; mode=block" always;
# HTTPからHTTPSへのリダイレクト
if ($scheme != "https") {
return 301 https://$server_name$request_uri;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}
IIS (web.config)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Content-Security-Policy" value="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:;" />
<add name="X-Frame-Options" value="SAMEORIGIN" />
<add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains; preload" />
<add name="X-Content-Type-Options" value="nosniff" />
<add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
<add name="Permissions-Policy" value="geolocation=(), camera=(), microphone=()" />
<add name="X-XSS-Protection" value="1; mode=block" />
</customHeaders>
</httpProtocol>
<rewrite>
<rules>
<rule name="Redirect to HTTPS" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}"
redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
アプリケーション別の実装
Express.js (Node.js)
const express = require('express');
const helmet = require('helmet');
const app = express();
// Helmet を使用した包括的なセキュリティヘッダー設定
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "https:", "data:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
frameguard: {
action: 'sameorigin'
},
noSniff: true,
xssFilter: true,
referrerPolicy: {
policy: "strict-origin-when-cross-origin"
},
permissionsPolicy: {
geolocation: [],
camera: [],
microphone: []
}
}));
// カスタムヘッダーの追加
app.use((req, res, next) => {
// 開発環境でのみ緩い設定
if (process.env.NODE_ENV === 'development') {
res.setHeader('Content-Security-Policy',
"default-src 'self' 'unsafe-inline' 'unsafe-eval'; script-src 'self' 'unsafe-inline' 'unsafe-eval';");
}
next();
});
app.listen(3000, () => {
console.log('Server running with security headers');
});
Laravel (PHP)
<?php
// app/Http/Middleware/SecurityHeaders.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class SecurityHeaders
{
public function handle(Request $request, Closure $next)
{
$response = $next($request);
// セキュリティヘッダーの設定
$response->headers->set('Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:;");
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Permissions-Policy',
'geolocation=(), camera=(), microphone=()');
$response->headers->set('X-XSS-Protection', '1; mode=block');
return $response;
}
}
// app/Http/Kernel.php で登録
protected $middleware = [
// 他のミドルウェア...
\App\Http\Middleware\SecurityHeaders::class,
];
// またはconfig/secure-headers.phpを作成
return [
'content-security-policy' => "default-src 'self'; script-src 'self' 'unsafe-inline';",
'x-frame-options' => 'SAMEORIGIN',
'strict-transport-security' => 'max-age=31536000; includeSubDomains; preload',
'x-content-type-options' => 'nosniff',
'referrer-policy' => 'strict-origin-when-cross-origin',
'permissions-policy' => 'geolocation=(), camera=(), microphone=()',
];
?>
Django (Python)
# settings.py
import os
# セキュリティヘッダーの設定
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'SAMEORIGIN'
# HSTS設定
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# SSL設定
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# CSP設定
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "'unsafe-inline'")
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
CSP_IMG_SRC = ("'self'", "https:", "data:")
# django-csp パッケージを使用
MIDDLEWARE = [
'csp.middleware.CSPMiddleware',
# 他のミドルウェア...
]
# カスタムミドルウェア
# middleware/security_headers.py
class SecurityHeadersMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# カスタムヘッダーの追加
response['Referrer-Policy'] = 'strict-origin-when-cross-origin'
response['Permissions-Policy'] = 'geolocation=(), camera=(), microphone=()'
return response
セキュリティヘッダーのテストと検証
1. オンラインツールでの検証
# Security Headers チェックサイト

Analyse your HTTP response headersQuickly and easily assess the security of your HTTP response headers
# Mozilla Observatory

HTTP Header Security Test - HTTP Observatory | MDNTest your site’s HTTP headers, including CSP and HSTS, to find security problems and get actionable recommendations to m...
# CSP Evaluator

CSP EvaluatorCSP Evaluator
2. ブラウザ開発者ツールでの確認
// ブラウザコンソールでのヘッダー確認
fetch('/')
.then(response => {
console.log('CSP:', response.headers.get('Content-Security-Policy'));
console.log('X-Frame-Options:', response.headers.get('X-Frame-Options'));
console.log('HSTS:', response.headers.get('Strict-Transport-Security'));
});
3. curl コマンドでの確認
# ヘッダー情報を確認
curl -I https://example.com
# 特定のヘッダーのみ確認
curl -I https://example.com | grep -i "content-security-policy"
curl -I https://example.com | grep -i "x-frame-options"
curl -I https://example.com | grep -i "strict-transport-security"
# HTTPからHTTPSへのリダイレクト確認
curl -I http://example.com
4. 自動テストの実装
// Jest でのセキュリティヘッダーテスト
describe('Security Headers', () => {
test('CSP header should be present', async () => {
const response = await request(app).get('/');
expect(response.headers['content-security-policy']).toBeDefined();
expect(response.headers['content-security-policy']).toContain("default-src 'self'");
});
test('X-Frame-Options should be SAMEORIGIN', async () => {
const response = await request(app).get('/');
expect(response.headers['x-frame-options']).toBe('SAMEORIGIN');
});
test('HSTS header should be present', async () => {
const response = await request(app).get('/');
expect(response.headers['strict-transport-security']).toContain('max-age=31536000');
});
});
# Python でのセキュリティヘッダーテスト
import requests
import unittest
class SecurityHeadersTest(unittest.TestCase):
def setUp(self):
self.base_url = 'https://example.com'
def test_csp_header(self):
response = requests.get(self.base_url)
self.assertIn('Content-Security-Policy', response.headers)
self.assertIn("default-src 'self'", response.headers['Content-Security-Policy'])
def test_x_frame_options(self):
response = requests.get(self.base_url)
self.assertEqual(response.headers.get('X-Frame-Options'), 'SAMEORIGIN')
def test_hsts_header(self):
response = requests.get(self.base_url)
hsts = response.headers.get('Strict-Transport-Security')
self.assertIsNotNone(hsts)
self.assertIn('max-age=31536000', hsts)
if __name__ == '__main__':
unittest.main()
よくあるCSPエラーと対処法
1. インラインスクリプトエラー
// エラーが発生するパターン
<script>
console.log('This will be blocked by CSP');
</script>
// 解決方法1: nonce を使用
<script nonce="abc123">
console.log('This is allowed with nonce');
</script>
// 解決方法2: 外部ファイルに移動
<script src="/js/app.js"></script>
// 解決方法3: CSPでunsafe-inlineを許可(非推奨)
Content-Security-Policy: script-src 'self' 'unsafe-inline'
2. 外部リソース読み込みエラー
# エラー: Google Fonts が読み込めない
Content-Security-Policy: default-src 'self'
# 解決: font-srcとstyle-srcを追加
Content-Security-Policy: default-src 'self'; font-src https://fonts.gstatic.com; style-src 'self' https://fonts.googleapis.com
3. Ajax/Fetch エラー
# エラー: API呼び出しが失敗
Content-Security-Policy: default-src 'self'
# 解決: connect-srcを追加
Content-Security-Policy: default-src 'self'; connect-src 'self' https://api.example.com
セキュリティヘッダーのベストプラクティス
1. 段階的な実装
# フェーズ1: レポートモードで問題を特定
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
# フェーズ2: 基本的な制限を適用
Content-Security-Policy: default-src 'self' 'unsafe-inline'
# フェーズ3: より厳格な制限
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'
2. 環境別の設定
// 開発環境では緩い設定
if (process.env.NODE_ENV === 'development') {
app.use(helmet({
contentSecurityPolicy: false // 開発時はCSPを無効
}));
} else {
// 本番環境では厳格な設定
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
// 厳格な設定
}
}
}));
}
3. CSPレポートの監視
// CSPレポート受信エンドポイント
app.post('/csp-report', express.json(), (req, res) => {
const report = req.body;
// ログに記録
console.warn('CSP Violation:', {
timestamp: new Date().toISOString(),
violatedDirective: report['violated-directive'],
blockedURI: report['blocked-uri'],
documentURI: report['document-uri'],
userAgent: req.get('User-Agent')
});
// アラート送信(Slack、メールなど)
sendCSPAlert(report);
res.status(204).send();
});
まとめ
セキュリティヘッダーは、簡単に実装できて効果が高いセキュリティ対策です。
重要なポイント:
- Content-Security-Policy:XSS攻撃の最強の防御手段
- Strict-Transport-Security:HTTPS接続の強制
- X-Frame-Options:クリックジャッキング攻撃の防御
- X-Content-Type-Options:MIMEタイプ詐称攻撃の防御
- 段階的な実装:Report-Onlyモードから始める
- 定期的な監視:CSPレポートの確認とヘッダーテスト
特に重要なのは、CSPの適切な実装です。最初は緩い設定から始めて、徐々に厳格にしていくことで、既存サイトに影響を与えずに導入できます。
セキュリティヘッダーは「設定するだけ」でWebサイトのセキュリティレベルを大幅に向上させることができる、コストパフォーマンスの高い対策です。今すぐ実装して、より安全なWebサイトを構築しましょう!
コメント