Webアプリケーションのセキュリティを強化する上で、HTTPセキュリティヘッダーの適切な設定は欠かせません。これらのヘッダーは、XSS攻撃、クリックジャッキング、MIMEスニッフィング攻撃などの脅威からユーザーを保護する重要な防御層として機能します。

この記事では、主要なセキュリティヘッダーであるContent-Security-Policy(CSP)、Strict-Transport-Security(HSTS)、X-Frame-Options、X-Content-Type-Optionsについて、それぞれの役割、設定方法、そしてNginx・Apache・Express.jsでの実装例を解説します。

セキュリティヘッダーが必要な理由

セキュリティヘッダーは、HTTPレスポンスに含まれるヘッダーで、ブラウザに対してセキュリティに関する指示を与えます。これにより、アプリケーションコードに脆弱性があった場合でも、ブラウザレベルで攻撃を防ぐことができます。

セキュリティヘッダーの役割

sequenceDiagram
    participant Browser as ブラウザ
    participant Server as サーバー

    Browser->>Server: HTTPリクエスト
    Server-->>Browser: HTTPレスポンス<br>+ セキュリティヘッダー

    Note over Browser: ブラウザがセキュリティポリシーを適用<br>・インラインスクリプトのブロック(CSP)<br>・HTTPS通信の強制(HSTS)<br>・iframe埋め込みの制限(X-Frame-Options)<br>・MIMEタイプスニッフィングの防止

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

ヘッダー名 主な目的
Content-Security-Policy XSS攻撃・データインジェクション攻撃の防止
Strict-Transport-Security HTTPS通信の強制
X-Frame-Options クリックジャッキング攻撃の防止
X-Content-Type-Options MIMEスニッフィング攻撃の防止
Referrer-Policy リファラー情報の漏洩防止
Permissions-Policy ブラウザ機能へのアクセス制御

Content-Security-Policy(CSP)の設定

Content-Security-Policy(CSP)は、Webページで読み込み可能なリソースの出所を制限するセキュリティヘッダーです。XSS攻撃やデータインジェクション攻撃に対する強力な防御機能を提供します。

CSPの基本構文

CSPは1つ以上のディレクティブで構成され、各ディレクティブはリソースの種類と許可する出所を指定します。

Content-Security-Policy: <ディレクティブ> <値>; <ディレクティブ> <値>

主要なCSPディレクティブ

ディレクティブ 説明
default-src 他のディレクティブで指定されていないリソースのデフォルトポリシー
script-src JavaScriptの読み込み元を制限
style-src CSSの読み込み元を制限
img-src 画像の読み込み元を制限
font-src Webフォントの読み込み元を制限
connect-src fetch、XMLHttpRequest、WebSocketの接続先を制限
frame-src iframe内に読み込めるコンテンツを制限
frame-ancestors 当該ページをiframeで埋め込める親ページを制限
form-action フォームの送信先を制限
base-uri <base>要素で指定できるURLを制限
object-src <object><embed><applet>要素のソースを制限

CSPのソース値

ディレクティブには以下のソース値を指定できます。

ソース値 説明
'self' 同一オリジンからのリソースのみ許可
'none' すべてのリソースを拒否
'unsafe-inline' インラインスクリプト・スタイルを許可(非推奨)
'unsafe-eval' eval()などの動的コード実行を許可(非推奨)
'strict-dynamic' nonceまたはhashで信頼されたスクリプトが動的に読み込むスクリプトを許可
'nonce-<値>' 指定されたnonceを持つスクリプト・スタイルのみ許可
'sha256-<ハッシュ>' 指定されたハッシュ値に一致するスクリプト・スタイルのみ許可
https: HTTPS経由のリソースのみ許可
data: dataスキームのリソースを許可
*.example.com 特定ドメインからのリソースを許可

CSPの設定例

以下は、実践的なCSP設定の例です。

基本的なCSP設定

1
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'

この設定では以下のポリシーが適用されます。

  • すべてのリソースは同一オリジンからのみ読み込み可能
  • 画像はdataスキーム(Base64画像)も許可
  • iframeへの埋め込みを禁止
  • プラグインコンテンツを完全に禁止

nonce方式を使用したCSP設定

インラインスクリプトを使用する場合は、nonce方式を採用することで安全に許可できます。

1
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123'; style-src 'self' 'nonce-abc123'

HTML側では、許可するスクリプトにnonce属性を付与します。

1
2
3
4
5
6
7
8
9
<script nonce="abc123">
  // このスクリプトは実行される
  console.log('許可されたスクリプト');
</script>

<script>
  // このスクリプトはブロックされる
  console.log('ブロックされるスクリプト');
</script>

nonce値はリクエストごとに一意で予測不可能な値を生成する必要があります。

Report-Onlyモードでのテスト

CSPの導入時には、まずReport-Onlyモードで動作を確認することを推奨します。

1
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-uri /csp-report; report-to csp-endpoint

このモードでは、ポリシー違反があってもリソースはブロックされず、レポートのみが送信されます。

CSP違反レポートの設定

CSP違反が発生した際にレポートを受け取る設定を追加できます。

1
Content-Security-Policy: default-src 'self'; report-uri /csp-violation-report-endpoint

レポートはJSON形式で以下のような情報が含まれます。

1
2
3
4
5
6
7
8
9
{
  "csp-report": {
    "document-uri": "https://example.com/page.html",
    "violated-directive": "script-src 'self'",
    "blocked-uri": "https://evil.com/malicious.js",
    "source-file": "https://example.com/page.html",
    "line-number": 10
  }
}

Strict-Transport-Security(HSTS)の設定

Strict-Transport-Security(HSTS)は、ブラウザに対してHTTPS通信のみを使用するよう指示するセキュリティヘッダーです。中間者攻撃(MITM攻撃)やSSLストリッピング攻撃を防止します。

HSTSの基本構文

Strict-Transport-Security: max-age=<秒数>; includeSubDomains; preload

HSTSのディレクティブ

ディレクティブ 説明
max-age ブラウザがHTTPS接続を記憶する期間(秒単位)
includeSubDomains サブドメインにもHSTSポリシーを適用
preload ブラウザのプリロードリストへの登録を希望

HSTSの動作

sequenceDiagram
    participant Browser as ブラウザ
    participant Server as サーバー
    
    Note over Browser,Server: 1. 初回アクセス(HTTPでアクセスした場合)
    Browser->>Server: http://example.com
    Server-->>Browser: 301 → https://...
    
    Note over Browser,Server: 2. HTTPSでの接続
    Browser->>Server: https://example.com
    Server-->>Browser: + HSTS ヘッダー
    Note over Browser: ブラウザがHSTSを記憶
    
    Note over Browser,Server: 3. 以降のアクセス(HTTPを指定しても自動でHTTPSに)
    Note over Browser: http://example.com<br>→ 内部変換 →<br>https://example.com
    Browser->>Server: https://example.com

HSTSの設定例

推奨される設定

OWASPが推奨する設定では、max-ageを2年(63072000秒)に設定します。

1
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

段階的な導入

HSTSを初めて導入する場合は、短いmax-ageから始めて徐々に延長することを推奨します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# ステップ1: 5分間のテスト
Strict-Transport-Security: max-age=300

# ステップ2: 1週間
Strict-Transport-Security: max-age=604800

# ステップ3: 1年
Strict-Transport-Security: max-age=31536000; includeSubDomains

# ステップ4: 2年 + preload
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

HSTSプリロードリスト

HSTSプリロードリストは、ブラウザに事前に登録されたHSTS対応サイトのリストです。これに登録されると、ユーザーが初めてサイトにアクセスする前からHTTPS接続が強制されます。

プリロードリストへの登録はhstspreload.orgで申請できます。登録には以下の要件を満たす必要があります。

  • 有効なSSL/TLS証明書を持っていること
  • すべてのサブドメインでHTTPSが有効であること
  • max-ageが31536000秒(1年)以上であること
  • includeSubDomainspreloadディレクティブが含まれていること

HSTSの注意点

HSTSを有効にする前に、以下の点を確認してください。

  1. すべてのサブドメインがHTTPS対応していること(includeSubDomains使用時)
  2. SSL/TLS証明書の有効期限切れに注意すること
  3. 一度設定するとmax-age期間中はHTTP通信に戻せないこと
  4. プリロードリストからの削除には時間がかかること

X-Frame-Optionsの設定

X-Frame-Optionsは、ページをiframe、frame、embed、objectタグ内で表示することを制御するセキュリティヘッダーです。クリックジャッキング攻撃を防止します。

クリックジャッキング攻撃とは

flowchart TB
    subgraph AttackerPage["攻撃者のページ"]
        subgraph TransparentIframe["透明なiframe(被害者サイト)"]
            RealButton["送金実行ボタン<br>← 実際のクリック対象"]
        end
        FakeButton["景品をGET!<br>← ユーザーに見えるボタン"]
    end

    User((ユーザー)) -->|クリック| FakeButton
    FakeButton -.->|実際にクリックされる| RealButton

    Note["ユーザーは『景品をGET!』をクリックしたつもりが、<br>実際には透明なiframe内の『送金実行』をクリックしている"]

X-Frame-Optionsの値

説明
DENY すべてのiframe埋め込みを禁止
SAMEORIGIN 同一オリジンからの埋め込みのみ許可

X-Frame-Optionsの設定例

1
2
3
4
5
# すべてのiframe埋め込みを禁止(推奨)
X-Frame-Options: DENY

# 同一オリジンからの埋め込みのみ許可
X-Frame-Options: SAMEORIGIN

CSPのframe-ancestorsとの関係

CSPのframe-ancestorsディレクティブは、X-Frame-Optionsより柔軟な制御が可能です。CSPをサポートするブラウザではframe-ancestorsが優先されます。

1
2
3
4
5
6
7
8
# X-Frame-Options: DENY と同等
Content-Security-Policy: frame-ancestors 'none'

# X-Frame-Options: SAMEORIGIN と同等
Content-Security-Policy: frame-ancestors 'self'

# 特定のドメインからの埋め込みを許可(X-Frame-Optionsでは不可能)
Content-Security-Policy: frame-ancestors 'self' https://trusted.example.com

両方のヘッダーを併用することで、古いブラウザと新しいブラウザの両方に対応できます。

1
2
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'

X-Content-Type-Optionsの設定

X-Content-Type-Optionsは、ブラウザのMIMEタイプスニッフィングを無効にするセキュリティヘッダーです。

MIMEスニッフィング攻撃とは

ブラウザは、サーバーから送信されたContent-Typeヘッダーを無視して、コンテンツの内容からMIMEタイプを推測する機能(MIMEスニッフィング)を持っています。この機能が悪用されると、テキストファイルとしてアップロードされた悪意のあるスクリプトがHTMLやJavaScriptとして実行される可能性があります。

flowchart TD
    A["1. 攻撃者がファイルをアップロード<br>ファイル名: image.jpg<br>内容: &lt;script&gt;alert('XSS')&lt;/script&gt;"] --> B["2. サーバーはContent-Typeを返す<br>Content-Type: image/jpeg"]
    B --> C["3. ブラウザがコンテンツをスニッフィング<br>『このファイルはHTMLっぽい...』"]
    C --> D["HTMLとして解釈・実行"]
    D --> E["XSS攻撃成功"]

    style A fill:#ffcccc
    style E fill:#ff6666

X-Content-Type-Optionsの設定

設定は非常にシンプルです。

1
X-Content-Type-Options: nosniff

この設定により、ブラウザはContent-Typeヘッダーで指定されたMIMEタイプのみを信頼し、スニッフィングを行いません。

その他の重要なセキュリティヘッダー

Referrer-Policy

リファラー情報の送信を制御するヘッダーです。

1
2
# クロスオリジンリクエストではオリジンのみを送信
Referrer-Policy: strict-origin-when-cross-origin
説明
no-referrer リファラーを送信しない
no-referrer-when-downgrade HTTPSからHTTPへのリクエスト時はリファラーを送信しない
origin オリジンのみを送信
origin-when-cross-origin 同一オリジンにはフルURL、クロスオリジンにはオリジンのみを送信
same-origin 同一オリジンにのみリファラーを送信
strict-origin HTTPS→HTTPでは送信しない、それ以外はオリジンのみを送信
strict-origin-when-cross-origin 推奨設定。HTTPS→HTTPでは送信しない、クロスオリジンにはオリジンのみを送信
unsafe-url 常にフルURLを送信(非推奨)

Permissions-Policy

ブラウザのAPI機能へのアクセスを制御するヘッダーです。

1
Permissions-Policy: geolocation=(), camera=(), microphone=()

この設定では、位置情報、カメラ、マイクへのアクセスをすべて無効にしています。

X-DNS-Prefetch-Control

DNSプリフェッチを制御するヘッダーです。

1
X-DNS-Prefetch-Control: off

外部ドメインへの不必要なDNSクエリを防ぎ、プライバシーを保護します。

主要フレームワークでのセキュリティヘッダー実装例

Nginx

Nginxの設定ファイル(nginx.confまたは個別のサーバー設定)にヘッダーを追加します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
server {
    listen 443 ssl http2;
    server_name example.com;

    # SSL設定(省略)

    # セキュリティヘッダー
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'" always;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options "DENY" 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;

    # その他の設定...
}

alwaysパラメータを付けることで、エラーレスポンス時にもヘッダーが送信されます。

Apache

Apacheでは.htaccessファイルまたは仮想ホスト設定でヘッダーを追加します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<IfModule mod_headers.c>
    # Content-Security-Policy
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'"

    # HSTS
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

    # X-Frame-Options
    Header always set X-Frame-Options "DENY"

    # 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=()"
</IfModule>

mod_headersモジュールが有効になっている必要があります。

Express.js(Node.js)

Express.jsでは、helmetミドルウェアを使用してセキュリティヘッダーを簡単に設定できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const express = require('express');
const helmet = require('helmet');

const app = express();

// helmetを使用した基本設定
app.use(helmet());

// または個別にカスタマイズ
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'"],
        styleSrc: ["'self'"],
        imgSrc: ["'self'", 'data:'],
        fontSrc: ["'self'"],
        connectSrc: ["'self'"],
        frameAncestors: ["'none'"],
        formAction: ["'self'"],
        baseUri: ["'self'"],
        objectSrc: ["'none'"],
      },
    },
    strictTransportSecurity: {
      maxAge: 63072000,
      includeSubDomains: true,
      preload: true,
    },
    frameguard: { action: 'deny' },
    noSniff: true,
    referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
    permissionsPolicy: {
      features: {
        geolocation: [],
        camera: [],
        microphone: [],
      },
    },
  })
);

app.listen(3000);

helmetをインストールするには以下のコマンドを実行します。

1
npm install helmet

Spring Boot(Java)

Spring Bootでは、Spring Securityを使用してセキュリティヘッダーを設定できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.writers.StaticHeadersWriter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .headers(headers -> headers
                .contentSecurityPolicy(csp -> csp
                    .policyDirectives("default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'")
                )
                .httpStrictTransportSecurity(hsts -> hsts
                    .maxAgeInSeconds(63072000)
                    .includeSubDomains(true)
                    .preload(true)
                )
                .frameOptions(frame -> frame.deny())
                .contentTypeOptions(content -> {})
                .referrerPolicy(referrer -> referrer
                    .policy(org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
                )
                .permissionsPolicy(permissions -> permissions
                    .policy("geolocation=(), camera=(), microphone=()")
                )
            );

        return http.build();
    }
}

セキュリティヘッダーの検証方法

設定したセキュリティヘッダーが正しく動作しているか検証することが重要です。

ブラウザの開発者ツールで確認

ChromeやFirefoxの開発者ツール(F12)でネットワークタブを開き、レスポンスヘッダーを確認できます。

curlコマンドで確認

1
curl -I https://example.com

レスポンスヘッダーが表示されます。

オンラインツールで確認

まとめ

セキュリティヘッダーは、Webアプリケーションのセキュリティを強化するための重要な防御層です。

最低限設定すべきヘッダー

  1. Content-Security-Policy:XSS攻撃対策
  2. Strict-Transport-Security:HTTPS通信の強制
  3. X-Frame-Options:クリックジャッキング対策
  4. X-Content-Type-Options:MIMEスニッフィング対策

導入時の注意点

  • CSPはReport-Onlyモードでテストしてから本番適用する
  • HSTSは短いmax-ageから始めて段階的に延長する
  • 既存機能への影響を十分にテストする
  • 定期的にセキュリティヘッダーの設定を見直す

これらのセキュリティヘッダーを適切に設定することで、Webアプリケーションの防御力を大幅に向上させることができます。

参考リンク