【実務・中級編】 ロードバランサー(ALB/NLB)のTLS終端と最新暗号スイートの強制 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

おい、ちょっと手を止めてこっちを向いてくれ。

先日、とある本番環境のペネトレーションテスト(模擬攻撃)結果を見直していたんだが、相変わらず「TLS 1.0/1.1の有効化」や「脆弱なレガシー暗号スイートの野放し」という、平成初期で時が止まったようなミスが散見された。
「いやいや、ウチはクラウドのロードバランサー(ALB)を使っているから大丈夫です」なんて油断しているエンジニアほど、デフォルト設定の罠に足元をすくわれている。

今日は、ロードバランサーのTLS終端における「古いプロトコルの切り捨て」と「Perfect Forward Secrecy(PFS)の強制」について、現場のリアルな泥臭い知見を交えて徹底的に解説する。明日からのインフラ設計や設定見直しに、そのまま役立ててほしい。

—

なぜレガシーなTLSと暗号スイートが今も悪夢を呼ぶのか

攻撃者は、最新の脆弱性だけを狙っているわけではない。むしろ、古くて手垢のついた設定ミスや、互換性のために残された「バックドア」のようなレガシープロトコルを好んでスキャンする。

TLS 1.0やTLS 1.1は、CBCモードの暗号利用モードにおける脆弱性(BEAST攻撃やPOODLE攻撃など)が指摘されて久しい。これらが有効なままであれば、いくらアプリケーション層でWAFを固めようが、通信経路上でトラフィックを傍受・復号されるリスクが常につきまとう。

さらに厄介なのが、暗号スイートの選定ミスだ。
RSA鍵交換をベースにした古い暗号スイート(例: RC4 や DES は論外として、AES128-SHA のような非PFSなもの)が残っているとどうなるか?
もし将来的に、何らかの原因でWebサーバーの秘密鍵が漏洩したとする。その時、もしPFS(完全前方秘匿性)をサポートしない暗号スイートで通信が行われていた場合、過去にキャプチャされたすべての暗号化トラフィックが、その秘密鍵一つで芋づる式に復号されてしまうのだ。

「過去の通信がすべて丸裸になる」——これが、PFSを実装していないシステムが抱える真の恐怖だ。

—

攻撃者の視点:SSL Labsやnmapを使った脆弱性スキャンの現実

攻撃者は、ターゲットのドメインに対して手動でパケットを投げるような泥臭いことはしない。自動化されたスクリプトや、お馴染みの nmap スクリプトを使って一瞬で脆弱性を炙り出す。

例えば、ターゲットサーバーのTLSバージョンや対応している暗号スイートを暴くためには、次のようなコマンドが日常的に使われている。

# nmapを使った脆弱なTLSバージョン(TLS 1.0/1.1)および暗号スイートの検出
nmap --script ssl-enum-ciphers -p 443 target-domain.example.com

このスキャンを実行した際、もし TLSv1.0 や TLSv1.1 が State: VULNERABLE と表示されたり、TLS_RSA_WITH_AES_128_CBC_SHA のような非PFSなスイートが含まれていたりしたら、それは攻撃者にとって「格好の獲物」のサインとなる。

—

対策:AWS ALB/NLBにおける最新セキュリティポリシーの強制

クラウドインフラ(AWSを例に取るが、GCPやAzureでも考え方は同じだ)において、この問題を根本から解決する特効薬が「セキュリティポリシー(TLS Security Policy)」の厳格化である。

デフォルトのポリシーに頼るな。あれは「古いガラケーやレガシーブラウザの接続を切らさないため」の妥協の産物だ。我々が目指すべきは、最新かつ最も堅牢なポリシーの適用である。

AWSのALB/NLBでTLSリスナーを作成・更新する際は、必ず最新のポリシー(例: ELBSecurityPolicy-TLS13-1-2-2021-06 など、TLS 1.3を最優先し、PFSを強制するポリシー)を選択すること。

もし、インフラストラクチャを Terraform などのIaCで管理しているなら、以下のように明示的にコード化してレビューの目をすり抜けられないようにするべきだ。

TerraformによるセキュアなALBリスナー設定のサンプル

# セキュアなTLSリスナーの設定例(TLS 1.2および1.3のみ許可、PFSを強制)
resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.main.arn
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06" # 最新の推奨セキュリティポリシーを指定
  certificate_arn   = aws_acm_certificate.cert.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

# 補足:
# "ELBSecurityPolicy-TLS13-1-2-2021-06" を指定することで、
# 1. 古い TLS 1.0 および TLS 1.1 は完全に無効化されます。
# 2. 暗号スイートはすべて Forward Secrecy (PFS) をサポートするものに限定されます。
# 3. 安全性の低い RC4, 3DES, CBCモードの一部などが排除されます。

—

アプリケーション側(Nginx等)で直接TLS終端を行う場合の要件

もしロードバランサーを挟まず、直接EC2やオンプレミスのNginxサーバーでTLS終端を行っている場合は、Nginxの設定ファイル(nginx.conf)を直接叩き直す必要がある。

以下の設定スニペットは、現時点で最高峰のセキュリティ水準を満たすTLS設定だ。これをそのままコピーして適用してほしい。

NginxにおけるセキュアなTLS・暗号スイート設定ファイル

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;

    # --- ここからが要塞化の核心 ---

    # 古い、脆弱なプロトコルを完全に排除し、TLS 1.2 と TLS 1.3 のみに限定
    ssl_protocols TLSv1.2 TLSv1.3;

    # PFS (Perfect Forward Secrecy) を強制するモダンな暗号スイートの指定
    # 注: TLS 1.3 の暗号スイートはプロトコル仕様上、自動的にPFSが強制されます。
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';

    # サーバー側の暗号スイートの優先順位を強制(クライート側の言いなりにならない)
    ssl_prefer_server_ciphers on;

    # セッションキャッシュの設定(パフォーマンス劣化を防ぎつつ安全性を維持)
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # HSTS (HTTP Strict Transport Security) の強制(中間者攻撃の防止)
    # ※ 本番環境で適用する際は、サブドメインを含めるか、プレロード申請を行うかを慎重に検討してください
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # --- ここまで ---

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

この設定のポイントは、ssl_protocols で TLSv1 と TLSv1.1 を完全にシャットアウトしている点、そして ssl_ciphers で ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)や DHE を冠するPFS対応のスイートのみを許可している点だ。さらに ssl_prefer_server_ciphers on; を記述することで、古いブラウザや悪意あるクライアントが脆弱な暗号スイートを強制しようとしても、サーバー側でそれを拒否できるようにしている。

—

導入後の検証:本当にセキュアになったか?

設定を変更したら、「これでよし」と画面を閉じるな。セキュリティの基本は「検証(Verify)」だ。

手元の端末から、以下のコマンドでOpenSSLを叩いて、意図したプロトコルと暗号スイートでしかハンドシェイクが成功しないことを必ず確認しろ。

# TLS 1.1での接続テスト(失敗=拒否されるべき)
openssl s_client -connect example.com:443 -tls1_1

# TLS 1.3での接続テスト(成功=セキュアに接続できるべき)
openssl s_client -connect example.com:443 -tls1_3

もしTLS 1.1での接続テストが「成功(Connection established)」してしまったら、設定のどこかに穴がある証拠だ。すぐにNginxを再起動したか、ALBのポリシーが正しく反映されているかを確認し直してほしい。

—

チーフからのメッセージ

セキュリティ対策において「これくらいで十分だろう」という妥協は、そのまま脆弱性という名の借金になる。
今回紹介したTLSのバージョンアップとPFSの強制は、アプリケーションのコードを一文字も書き換えることなく、インフラ層のパラメータ調整だけでシステム全体を一段上のステージへ引き上げる強力な施策だ。

「古いクライアントが切り捨てられるから困る」というビジネス側の声があがることもあるかもしれない。だが、情報漏洩を起こして会社全体の信頼を失うリスクに比べれば、サポート外のレガシー環境など真っ先に切り捨てるべきだ。

プロのエンジニアなら、理路整然とビジネス側を説得し、強固なインフラを組み上げてみせろ。期待しているぞ。

コメント

タイトルとURLをコピーしました