【テクニカル・上級編】HTTP/2クリアテキスト通信(h2c)の仕様とセキュリティリスク – HTTPプロトコル・通信規格実践ガイド

暗号化の戒めを解き放った代償:h2c(HTTP/2 Cleartext)の深淵と残酷な現実

ネットワークエンジニアの常として、私たちはパケットの「中身」を見通せる瞬間に奇妙な安らぎを覚える。Wiresharkを立ち上げ、暗号化のヴェールに包まれる前の生々しいフレームが流れていく様を眺めるのは、ある種の職人的な快感なのだ。しかし、HTTP/2の登場とともに策定されたクリアテキスト通信仕様「h2c(HTTP/2 Cleartext)」は、その知的な好奇心の裏側で、近代Webセキュリティに対する静かなるパンドラの箱を開けてしまった。

HTTP/2といえば、言わずもがな「マルチプレクシング」「HPACKによるヘッダー圧縮」「ストリーム優先度制御」といった革新的なアプローチで、長年HTTP/1.1の足枷となっていたHead-of-Line(HoL)ブロッキングを打ち破ったプロトコルだ。標準仕様(RFC 7540)において、HTTP/2は暗号化レイヤーであるTLS 1.2以降を必須とする「h2」と、暗号化を伴わない「h2c」の2つのモードを定義している。

なぜ、セキュリティが叫ばれる現代において、あえて暗号化を剥ぎ取った「h2c」という仕様が存在するのか。そして、それを実運用に投入することが、どれほど危険な賭けであるのか。パケットの往来とプロトコルの内部挙動から、その全貌を解き明かそう。

—

1. h2cの二つの顔:プレリクエスト(Direct)とアップグレード(Upgrade)のメカニズム

HTTP/2は、その前身であるHTTP/1.1とは異なり、TCPコネクション上に独自のバイナリフレーミングレイヤーを構築する。そのため、クライアントとサーバーが「今からHTTP/2で喋ろう」と合意するためのハンドシェイクプロセスが不可欠となる。

暗号化を伴う `h2` の場合、これはTLSのALPN(Application-Layer Protocol Negotiation)拡張によって、TLSハンドシェイクの最中に一撃で完了する。往復遅延(RTT)を一切増加させることなく、安全にネゴシエーションが行われるわけだ。

では、TLSが存在しない `h2c` ではどうなるのか。仕様では、以下の2つのアプローチが用意されている。

A. ダイレクト接続(Prior Knowledge)

クライアントが「このサーバーはh2cを話せる」と事前に知っている(あるいは設定されている)場合、最初からTLSなしのTCPコネクションを張り、HTTP/2の接続プレフィックス(Connection Preface)を叩き込む。

PRI HTTP/2.0\r\n\r\nSM\r\n\r\n

この特殊な24バイトの魔術的な文字列を先頭に送信することで、サーバー側は直ちにバイナリフレームの解析モードへと移行する。余計なハンドシェイクのオーバーヘッドがなく、極限までレイテンシを削ぎ落とせるのが魅力だ。

B. HTTP/1.1からのアップグレード(Upgrade Mechanism)

厄介なのがこちらだ。通常のHTTP/1.1のりクエストを発出しながら、途中でHTTP/2へ昇格(Upgrade)を試みる方式である。
クライアントは以下のようなリクエストヘッダーを送信する。

GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA

サーバーがこれを受け入れ、h2cへの移行を許可する場合、HTTP/1.1形式で以下のように応答を返す。

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c

[ここから先はHTTP/2バイナリフレームに切り替わる]

この瞬間、単一のTCPコネクション上で、テキストベースのHTTP/1.1からバイナリベースのHTTP/2へと世界が切り替わる。一見するとスマートだが、ここにセキュリティ上の致命的なアキレス腱が潜んでいる。

—

2. なぜ主要ブラウザは h2c を拒絶するのか?

インフラエンジニアが頭を抱える最大のポイントは、「主要なモダンブラウザ(Google Chrome, Mozilla Firefox, Apple Safariなど)が、ことごとくh2cのサポートを見送っている(あるいは削除した)」という冷徹な事実だ。

ブラウザがh2c(特にUpgrade方式)を実装しない理由は明白である。「中間者攻撃(MitM)に対する無防備さと、HTTPリクエスト・スマグリングの温床になるから」に他ならない。

プロキシサーバーや悪意ある経路上のアウトオブパス/インパスの攻撃者が、クライアントから送信された `Upgrade: h2c` ヘッダーを巧妙に書き換え、あるいは無視することで、通信のダウングレードや予期せぬプロトコル解釈のズレを引き起こすことができる。暗号化されていないため、パケットの改ざんは容易であり、セッションハイジャックやクレデンシャルの窃取がノーガードで行えてしまうのだ。

結果として、h2cが活躍する舞台は、インターネットの荒野ではなく、信頼されたクローズドなマイクロサービス間の通信(North-Southの一部、あるいはEast-WestのAPI間通信)に限定されている。

—

3. 実践:Nginxにおけるh2cの構成と、そのリスク管理

どうしても社内ネットワークやサービスメッシュの境界内でh2cを実装する必要がある場合、リバースプロキシの設定には細心の注意が必要となる。以下は、NginxにおいてクリアテキストのHTTP/2(h2c)をハンドリングし、バックエンドへ転送する際の設定例だ。

http {
# HTTP/1.1およびHTTP/2クリアテキスト(h2c)を受け付けるサーバーブロック
server {
listen 80 http2; # Nginx 1.25.1以降では “listen 80” に “http2” パラメータを付与して有効化
server_name internal.service.local;

# クライアントからの接続タイムアウトやバッファのチューニング
client_body_buffer_size 128k;
client_max_body_size 10m;

location / {
# バックエンドのマイクロサービスへHTTP/2クリアテキストでプロキシ
proxy_pass http://backend_cluster;

# HTTP/2接続を維持するためのプロキシヘッダー設定
proxy_http_version 1.1;
proxy_set_header Connection “”;
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 Upgrade $http_upgrade;
}
}
}

この構成を運用する際、インフラエンジニアは以下のカーネルパラメータおよびプロトコルパラメータのチューニングを怠ってはならない。マルチプレクシングの恩恵を最大限に受けるためである。

Linuxカーネルチューニング(`sysctl.conf`)の要点

h2cであれh2であれ、単一のTCPコネクション上で何十もの並行ストリーム(`SETTINGS_MAX_CONCURRENT_STREAMS`)を流し込むため、TCPウィンドウサイズやバッファの枯渇は即座にアプリケーション全体のスループット低下を招く。

TCPウィンドウサイズの自動チューニング範囲を拡大(高スループット・高BDP環境向け)
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

TIME_WAITソケットの再利用と高速化
net.ipv4.tcp_tw_reuse = 1

同時接続数とリスニングキューの最大化
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

暗号化の負荷(CPUのAES-NI命令消費など)がない分、h2cは理論上のスループット限界を突き詰めやすいが、それは同時に「平文のまま膨大なトラフィックを高速に流出させるリスク」と表裏一体であることを忘れてはならない。

—

4. HPACKの魔力:暗号化なき世界でのヘッダー圧縮リスク

h2cのもう一つの重要な要素が「HPACK」によるヘッダー圧縮だ。HTTP/1.1の冗長なテキストヘッダー(`User-Agent`, `Cookie`, `Accept-Encoding`など)を、静的テーブルと動的テーブルを用いて数バイトのインデックスに変換するこの技術は、帯域の節約において神業的な効果を発揮する。

しかし、セキュリティの文脈において、暗号化されていない通信経路上でのHPACKの利用は、「CRIME攻撃」や「BREACH攻撃」の系譜に連なるサイドチャネル攻撃の標的になりやすいという側面を持つ。

平文で流れるパケットのサイズ(バイト数)を攻撃者が観測可能である場合、圧縮アルゴリズムの挙動(動的テーブルに一致した文字列が短縮される特性)を利用して、暗号化されていないクッキーや認証トークンの値を総当たりで推測される危険性が残る。TLSを使っていればパケット長はある程度パディングで隠蔽できるが、純粋なh2c環境では、ペイロードのサイズ変動がそのままネットワーク上の観測者に筒抜けになるのだ。

—

結びにかえて:現代インフラにおけるh2cの正しい処方箋

パケットアナライザの画面を眺めながら、「ハンドシェイクのオーバヘッドを極限まで削り、サービスメッシュ内の通信を極限まで高速化したい」というエンジニアリングの欲望は、痛いほどによくわかる。

しかし、歴史とセキュリティの教訓は私たちにこう語りかけている。「インターネットの外側であっても、ゼロトラストの原則を忘れた平文通信の導入は、時限爆弾を抱えることに等しい」と。

今日、KubernetesのIstioやLinkerdといった先進的なサービスメッシュの多くは、内部通信であっても mTLS(Mutual TLS)をデフォルトで強制しつつ、その上でHTTP/2やHTTP/3(gRPC)のパフォーマンスを引き出すアーキテクチャを採用している。現代のハードウェアアクセラレーション能力の前において、TLSのオーバーヘッドはもはや「セキュリティを諦める理由」にはならない。

h2cの仕様を深く理解し、そのパケットの挙動を把握することは、プロトコルスペシャリストとしての必須教養である。だが、それをプロダクション環境のエンドユーザー向け、あるいは信頼しきれないネットワークセグメントにデプロイすることは、技術的なロマンではなく、単なる「怠慢」にほかならない。

暗号化という最後の防壁を外すときは、その代償として何を差し出すのかを、パケットの先に見据える冷静な眼差しを持ち合わせていたいものだ。

コメント

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