暗号化の「賞味期限」はとっくに切れている:TLS 1.0/1.1を今すぐ殺すべき理由
現場でインシデント対応をしていると、いまだに「互換性のために」という甘い言葉でTLS 1.0や1.1を有効にしているサーバーに出くわす。これがどれほど危険なことか。攻撃者にとって、TLS 1.0/1.1が有効なサーバーは「通り魔を待つ無防備な路地裏」と同じだ。
今日は、なぜ古い暗号スイートが現代の脅威に対して無力なのか、そしてどうやって「鉄壁」の構成に書き換えるのかを、現場の視点で叩き込む。
なぜ「古い暗号」は攻撃者に愛されるのか
TLS 1.0/1.1には、もはやパッチで防げない「設計上の欠陥」が存在する。代表的なのが BEAST や POODLE といった攻撃手法だ。
特に恐ろしいのは、これらが中間者攻撃(MITM)を前提としていることだ。攻撃者は、ユーザーとサーバーの間に割って入り、暗号化された通信をリアルタイムで解読・改ざんする。通信を「ただの通過点」と考えているエンジニアは甘い。パブリックWi-Fiや脆弱なルーターを介した通信において、あなたのアプリのセッションクッキーが盗まれるのは一瞬だ。
さらに、RC4 や 3DES といったアルゴリズムは、計算機パワーが向上した現代では数時間から数日で解読される「オモチャ」に過ぎない。これらを使っている時点で、暗号化は「気休め」にしかなっていないと自覚してほしい。
攻撃の現場:MITMによるセッションハイジャック
想像してほしい。攻撃者は無料のWi-Fiスポットでパケットをキャプチャし、サーバーが古いプロトコルを許容していることを検知する。次に、通信をTLS 1.0に強制ダウングレードさせ、暗号化の脆弱性を突いてセッションIDを抽出する。
結果、攻撃者は正規ユーザーになりすまして管理画面にログインする。サーバー側には「正規のIPアドレス」からのアクセスとして記録されるため、ログだけでは不正アクセスだと気づくことすら難しい。これが、古い暗号スイートを放置する代償だ。
「鉄壁」のNginx設定:ここをコピペせよ
インフラエンジニア諸君、設定ファイルに迷う時間は無駄だ。以下の設定は、現代のベストプラクティスに基づいた「TLS 1.2/1.3のみ許可」「PFS(Perfect Forward Secrecy)強制」「脆弱な暗号スイート排除」を網羅した構成だ。
# /etc/nginx/conf.d/ssl.conf などのサーバー設定ブロックに記述
# TLS 1.0/1.1は無効化し、1.2/1.3のみを許可
ssl_protocols TLSv1.2 TLSv1.3;
# 脆弱な暗号スイートを排除し、PFSを優先する
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
# サーバー側で暗号スイートの優先順位を強制する(これがないとクライアント任せになる)
ssl_prefer_server_ciphers on;
# セッションチケットの無効化(PFSを完璧にするため)
ssl_session_tickets off;
アプリケーション開発者が気をつけるべきこと
サーバー側だけを固めても、アプリケーション側の通信がザルでは意味がない。例えば、バックエンドから外部APIを叩く際、cURL や Requests ライブラリでプロトコルの指定を忘れていないだろうか。
Pythonの requests ライブラリでAPIを叩く場合も、明示的にTLSバージョンを指定する癖をつけよう。
import requests
import ssl
from requests.adapters import HTTPAdapter
# TLS 1.2以上を強制するためのアダプター
class TLSAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
context = ssl.create_default_context()
context.set_ciphers('ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256')
context.minimum_version = ssl.TLSVersion.TLSv1_2
kwargs['ssl_context'] = context
return super(TLSAdapter, self).init_poolmanager(*args, **kwargs)
# セッションに適用
session = requests.Session()
session.mount('https://api.vulnerable-server.com', TLSAdapter())
まとめ:セキュリティは「削る」ことから始まる
セキュリティ対策というと、何か新しいツールを導入することばかり考えがちだが、最も強力な対策は「不要なものを削る(無効化する)」ことだ。
TLS 1.0/1.1を殺すことで、「古い端末が使えなくなる」という苦情が来るかもしれない。しかし、その苦情は「サイバー攻撃からユーザーを守った勲章」だと思ってほしい。
まずは明日、自社のサーバーが nmap --script ssl-enum-ciphers -p 443 <target> を叩いたときに何と答えるか確認してみてくれ。もしそこにTLS 1.0の文字が見えたら、それはもう火事と同じだ。今すぐ消火活動(設定変更)に取り掛かってほしい。
コメント