blog.dopana

Back

銀行に2つの入り口があると想像してみてください。1つは警備員がいない鍵のない通常のドア(HTTP)、もう1つは強固な防犯ドア(HTTPS)です。

多くの人は無意識に通常のドアに向かって歩き出します。中に入ると受付のスタッフが「安全な防犯ドア側へ回ってください」と案内します(HTTP 301/302リダイレクト)。

しかし、もしその通常のドアの前に悪意のある人物が待ち伏せていたらどうなるでしょうか? これこそが従来のHTTPリダイレクトが抱える重大なセキュリティの穴であり、この危険なドアを完全に封鎖するために生まれた仕組みが HSTS です。

問題点:なぜHTTPSリダイレクトだけでは不十分なのか?#

Webサイトを訪れる際、多くのユーザーはURL欄にわざわざ https:// を入力せず、単に example.com と打ち込みます。

標準的な動作:

  1. ブラウザはまず暗号化されていないHTTP(ポート80)で最初のリクエストを送信します。
  2. サーバーは 301 Moved Permanently を返し、https://...(ポート443)へリダイレクトさせます。
  3. ブラウザが安全なHTTPS接続を確立します。

このステップ1の隙間を突いて行われるのが、SSL Stripping(SSLダウングレード攻撃) です。

sequenceDiagram
    autonumber
    actor User as 👤 ユーザー
    participant Attacker as 🦹 攻撃者 (MitM / 公共Wi-Fi)
    participant Server as 🏢 Webサーバー

    User->>Attacker: 1. http://example.com にアクセス(平文通信)
    Note over Attacker: 最初のHTTPリクエストを傍受
    Attacker->>Server: 2. 攻撃者がサーバーとHTTPS接続を確立
    Server-->>Attacker: 3. 暗号化されたコンテンツを受信
    Attacker-->>User: 4. SSLを解除し、平文HTTPでユーザーへ偽装送信
    Note over User,Attacker: パスワードやセッションCookieが平文で盗まれる!

[!WARNING] カフェや空港などのフリーWi-Fi環境では、攻撃者が最初のHTTPリクエストを横取りし、自身とサーバー間のみでHTTPS通信を行います。ユーザー側のブラウザには警告が出ず、正常な平文HTTPとしてページが表示されてしまいます。

解決策:HSTS (HTTP Strict Transport Security) とは?#

HSTS はWebサーバーがブラウザに対して送信するレスポンスヘッダーの1つです。

「今後、ユーザーが http:// と入力したりHTTPリンクをクリックしても、ネットワークに通信を流す前に、ブラウザ内部で即座に https:// に書き換えなさい(Internal Redirect 307)!」

HSTSが有効になると:

  • ブラウザが端末内部でリクエストを自動的にHTTPSへと昇格させます。
  • 暗号化されていないHTTPパケットはネットワーク上に一切送出されません。
  • SSL証明書の期限切れや無効エラーが発生した場合、ブラウザは警告を無視して進むボタンを非表示にし、接続を完全にブロックします。
sequenceDiagram
    autonumber
    actor User as 👤 ユーザー
    participant Browser as 🌐 ブラウザ (HSTS有効)
    participant Attacker as 🦹 攻撃者 (MitM)
    participant Server as 🏢 Webサーバー

    User->>Browser: 「http://example.com」と入力
    Note over Browser: HSTSキャッシュを確認:HTTPSを強制!
    Browser->>Browser: 内部で直接「https://example.com」へ置換 (307)
    Browser->>Server: 最初からセキュアなHTTPSで通信 (End-to-End暗号化)
    Note over Attacker: 通信の盗聴・改ざんは一切不可能!
    Server-->>Browser: 安全にレスポンスを返却

HSTSヘッダーの構成要素#

構文は非常にシンプルです:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
http

各ディレクティブの解説:

ディレクティブ概要設定例
max-age=<seconds>ブラウザがこのHTTPS強制ルールを記憶する秒数。max-age=31536000 (1年間)
includeSubDomains(任意) すべてのサブドメイン(api.domain.com 等)にも適用。Preload登録に必須
preload(任意) 各種ブラウザのプリロードリストへの登録に同意する。初回アクセスの隙間を排除

HSTS Preload:初回アクセス問題(TOFU)の克服#

HSTSが設定されていても、Trust on First Use (TOFU) と呼ばれる弱点が残ります。

  • ユーザーが新しいPCや初期化されたブラウザで初めてアクセスする瞬間、ブラウザはまだHSTSヘッダーを知りません。
  • 初回接続のみHTTPになり、攻撃を受けるリスクが残ります。

HSTS Preloadリストの仕組み#

この問題を解決するため、主要ブラウザ(Chrome, Firefox, Safari, Edge)は HSTS Preload List を共有・管理しています。

  1. 必要なディレクティブを設定したHSTSヘッダーを配信します。
  2. ドメインを hstspreload.org に申請します。
  3. 審査完了後、ドメインがブラウザのバイナリに直接ハードコードされます。
  4. ユーザーが初めてアクセスする瞬間から、一切の例外なくHTTPS接続が行われます。
graph TD
    A[ユーザーが example.com と入力] --> B{ブラウザのPreloadリストにあるか?}
    B -->|ある - ブラウザに内蔵済み| C[初回から即座にHTTPSで直接通信]
    B -->|なし| D{ブラウザのHSTSローカルキャッシュにあるか?}
    D -->|ある - 過去に訪問済み| C
    D -->|なし - 初回アクセス| E[平文HTTPリクエストを送信]
    E --> F[サーバーがHTTPSとHSTSヘッダーを返却]
    F --> G[次回以降のためにローカル保存]

[!TIP] .dev.app.page などのTLDは、ドメインレジストリ全体でHSTS Preloadが最初からデフォルトで組み込まれています。

主要WebサーバーでのHSTS設定例#

1. Nginx#

HTTPS(443ポート)の server ブロック内に記述します:

nginx.conf
server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    # SSL証明書設定...

    # HSTSヘッダーの追加 (有効期間1年、サブドメイン適用、Preload対応)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
nginx

2. Apache#

mod_headers モジュールを有効にして設定します:

<VirtualHost *:443>
    ServerName example.com
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</VirtualHost>
apache

3. Caddy Server#

Caddyfile
example.com {
    header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    reverse_proxy localhost:3000
}
plaintext

4. Cloudflare#

Cloudflareのダッシュボードを使用する場合:

  1. SSL/TLS -> Edge Certificates を開きます。
  2. HTTP Strict Transport Security (HSTS) の設定を見つけ、Enable HSTS をクリックします。
  3. Max-Age の期間と Include SubdomainsPreload を有効化します。

導入時の注意点と安全な移行ステップ#

[!CAUTION] 一度設定すると即時の取り消しが困難です! includeSubDomains を有効にして max-age=1年 を設定した場合、社内用サブドメイン(例: internal.example.com)でSSL証明書が用意されていないと、1年間アクセス不能になります。

推奨される段階的導入ステップ:

  1. ステップ1:短期テスト

    Strict-Transport-Security: max-age=300
    http

    (5分間設定し、問題なくアクセスできるか確認)

  2. ステップ2:サブドメイン確認と期間延長

    Strict-Transport-Security: max-age=86400; includeSubDomains
    http

    (1日間運用し、すべてのサブドメインで正常動作を確認)

  3. ステップ3:本番運用とPreload登録

    Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
    http

    (1年間に設定し、hstspreload.org に申請)

まとめ#

比較項目通常のHTTPリダイレクトHSTS有効時
初動リクエスト暗号化されていないHTTP(盗聴可能)ブラウザ内部で即座にHTTPSへ昇格
SSL Stripping対策❌ 防げない✅ 完全に防御
SSLエラー時の挙動ユーザーによる無視・続行が可能接続を完全に遮断し安全を確保
表示速度リダイレクト往復(301/302)が発生内部リダイレクト(307)のため高速

HSTSは簡単なヘッダー設定でありながら、Webサイトとユーザーを守る強力な防御策です。ぜひ導入を検討してください。

参考文献#