HTTP/2 Bombを確認していたら、HTTP/2とHTTP/3を有効化してIPv6 QUICまで確認することになった

投稿者: | 9月 25, 2026

Botデコイにしている、架空の俳優(一応俳優)サイトは検索エンジンに来てもらうために検索エンジンにも登録した。ネタの架空のテレビ番組がもう思い付かない。手を入れにくくなった。如何せん、架空のネタなので。しかし、設定の手は入れられる。

 

閑話休題

 

HTTP/2を利用したDoS攻撃手法「HTTP/2 Bomb」が話題になっている。
 
少ない通信量からサーバ側に大きなメモリ消費を発生させるタイプの攻撃で、nginx、Apache httpd、Envoyなど、広く使われているHTTP/2実装が確認対象になる。ちょうどOracle Cloud Infrastructure(OCI)上に、インターネットへ公開しているBotデコイにしているnginxサーバがある。そこで、自分のnginxがどういう状態なのか確認してみることにした。調べ始めた時点ではHTTP/2 Bombへの影響を確認するだけのつもりだった。
ところが確認していくと、
HTTP/2 Bomb
↓
nginxのHTTP/2対応状況を確認
↓
HTTP/2が無効だった
↓
Ubuntuの修正状況を確認
↓
HTTP/2を有効化
↓
HTTP/3もビルドされていることに気付く
↓
HTTP/3 / QUICも有効化
↓
OCIでUDP/443を開放
↓
IPv6でQUICの実通信を確認
という、別の実験に発展してしまった。
 

HTTP/2 Bombとは何か

HTTP/2では、HTTPヘッダを毎回そのまま送信するのではなく、HPACKという仕組みを使って圧縮する。HPACKには、一度登録したヘッダ情報をインデックス番号で参照できる仕組みがある。
概念的には、
Client Server
 
大きなHeaderを登録 ──────────→ HPACK Table
│
小さなIndexを大量送信 ─────────→ 展開
│
├─ Header
├─ Header
├─ Header
├─ Header
└─ …
となる。
ネットワーク上を流れるデータ量は小さくても、サーバ側では大量のHTTPヘッダとして展開しなければならない。
つまり、
送信量 小さい
↓
HPACK展開
↓
サーバ処理量 大きい
メモリ消費 大きい
という非対称性が生まれる。
さらに、サーバからの応答を積極的に受け取らず、接続やリソースを長時間保持させる手法と組み合わせることで、少数のクライアントからでもDoSにつながる可能性がある。HTTP/2そのものが危険という話ではない。HTTP/2が持つヘッダ圧縮、多重化、ストリーム管理といった高機能な仕組みの中に、攻撃側から見ると都合のよい増幅ポイントが存在した、という話である。
 

まず自分のnginxを確認する

検証用サーバはUbuntu 26.04.1上で動作している。まずnginxのバージョンを確認した。
nginx -V 2>&1
結果は、
nginx version: nginx/1.28.3 (Ubuntu)
built with OpenSSL 3.5.5 27 Jan 2026
だった。Ubuntuパッケージとしての完全なバージョンは、
nginx 1.28.3-2ubuntu1.11
である。さらにビルドオプションを見ると、
–with-http_v2_module
–with-http_v3_module
が含まれている。つまり、このnginxバイナリは、
HTTP/1.1
HTTP/2
HTTP/3
のすべてを扱える。HTTP/2 Bombの確認という意味では、いかにも関係がありそうである。
しかし、ここで一つ重要なことがある。
 

HTTP/2モジュールがあることとHTTP/2が有効なことは別

--with-http_v2_module があるからといって、そのWebサイトがHTTP/2で公開されているとは限らない。実際の設定を確認した。
sudo nginx -T 2>&1 | grep -Ei ‘http2|listen .*443’
すると、
listen 443 ssl;
listen [::]:443 ssl ipv6only=on;
となっていた。
http2 on; がない。そこで外部から確認してみる。
curl -sI –http2 https://mitsuki-mouse.masezou.com/ | head
結果は、
HTTP/1.1 200 OK
だった。curl側ではHTTP/2を利用可能なら使うよう要求しているが、実際の通信はHTTP/1.1になっている。
つまり、この時点の構成は、
nginx
│
├─ HTTP/1.1 有効
│
├─ HTTP/2 モジュールあり / 無効
│
└─ HTTP/3 モジュールあり / 無効
だった。HTTP/2 Bombを調べ始めたのに、そもそもHTTP/2を外部公開していなかったのである。
 

ではnginx 1.28.3は脆弱なのか

ここでもう一つ注意しなければならないことがある。脆弱性情報を調べていると、修正が新しいupstream nginxへ導入されたという情報が出てくる。
すると、
自分のnginx
1.28.3
 
upstreamの修正版
もっと新しい
 
↓
 
1.28.3は古いから脆弱なのでは?
と考えたくなる。しかし、UbuntuやDebian系Linuxでは、この判断方法は危険である。
 

Ubuntuではバージョン番号だけを見てはいけない

Ubuntuは、セキュリティ問題が発生するたびに必ずupstreamの最新版へ更新するわけではない。既存バージョンを維持しながら、必要なセキュリティ修正だけをバックポートすることがある。
概念的には、
upstream nginx
│
│ security fix
▼
新しいupstream version
で実装された修正を、
Ubuntu nginx 1.28.3
│
├─ security patch A
├─ security patch B
├─ security patch C
└─ HTTP/2関連修正
のように取り込む。そのため、
nginx/1.28.3
という表示だけを見ても、脆弱性の有無は判断できない。今回の環境では、
1.28.3-2ubuntu1.11
まで見る必要がある。
HTTP/2 Bomb関連の修正は、Ubuntu 26.04向けにはこれより前のパッケージリビジョンでバックポートされている。
つまり今回の環境では、HTTP/2を有効化した場合についても修正済みのパッケージを使用していることになる。
これはnginxだけの話ではない。
Ubuntu Serverを管理するとき、
upstream version
だけを見て、
古いバージョン
↓
脆弱
と判断すると間違えることがある。確認すべきなのはディストリビューション側のパッケージリビジョンとSecurity Advisoryである。
 

それならHTTP/2を有効にしてみる

HTTP/2 Bombの攻撃面は公開されていなかった。さらにUbuntu側の修正も適用済みである。それならHTTP/2を無効のままにしておく積極的な理由もない。
nginx 1.28では、HTTPSのserverブロックへ、
listen 443 ssl;
listen [::]:443 ssl ipv6only=on;
 
http2 on;
を追加した。設定を確認する。
sudo nginx -t
問題がなければ、
sudo systemctl reload nginx
で反映する。再度確認した。
curl -sI –http2 https://mitsuki-mouse.masezou.com/ | head
今度は、
HTTP/2 200
server: nginx/1.28.3 (Ubuntu)
となった。HTTP/2が有効になった。
 

ところでHTTP/3も入っている

ここで最初のnginx -Vの結果を思い出した。
–with-http_v2_module
–with-http_v3_module
HTTP/2だけではなくHTTP/3もビルドされている。せっかくなのでHTTP/3も有効にしてみることにした。HTTP/3は単純に「HTTP/2の上位版」というわけではない。
大きな違いはトランスポートである。
HTTP/1.1 ─ TCP ─ TLS
HTTP/2 ─ TCP ─ TLS
HTTP/3 ─ QUIC / UDP
HTTP/2はHTTPリクエストを多重化できるが、下位では依然として1本のTCP接続を使っている。そのためTCPレベルでパケットロスが発生すると、TCPの順序保証によって複数のHTTP/2ストリームが影響を受ける場合がある。
HTTP/2
 
TCP connection
├─ Stream A ────────
├─ Stream B ────────
└─ Stream C ────────
X packet loss
│
└→ TCP再送待ち
他のStreamにも影響
HTTP/3ではQUICを利用する。
HTTP/3
 
QUIC connection
├─ Stream A ────────
├─ Stream B ── X ── 再送
└─ Stream C ─────────────→
HTTP/2で残っていたTCPレベルのHead-of-Line Blockingを回避できる。Wi-Fiやモバイル回線、RTTの大きなネットワークなどでは特に意味がある。
 

HTTP/3はTCP/443ではない

HTTP/3を設定する際に重要なのがここである。
通常のHTTPSは、
TCP/443
だが、HTTP/3のQUICは、
UDP/443
を使用する。
したがってHTTP/3を追加すると、サーバ構成は、
nginx
│
┌───────────┴───────────┐
│ │
TCP/443 UDP/443
│ │
┌─────┴─────┐ │
HTTP/1.1 HTTP/2 HTTP/3
となる。
HTTP/3はHTTP/2を置き換えるものではない。HTTP/1.1、HTTP/2、HTTP/3を同時に公開し、クライアント側に利用可能なプロトコルを選択させればよい。
 

nginxでHTTP/3を有効化

設定を次のようにした。
listen 443 ssl; # managed by Certbot
listen [::]:443 ssl ipv6only=on; # managed by Certbot
 
http2 on;
 
# HTTP/3 / QUIC
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
 
# Advertise HTTP/3 to clients
add_header Alt-Svc ‘h3=”:443″; ma=86400’ always;
 
ssl_certificate /etc/letsencrypt/live/mitsuki-mouse.masezou.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mitsuki-mouse.masezou.com/privkey.pem;
ここで、
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
がQUIC用UDP/443のlistenerである。そして、
add_header Alt-Svc ‘h3=”:443″; ma=86400’ always;
によって、HTTP/1.1やHTTP/2で接続してきたクライアントへ、
このサーバはHTTP/3も使える
UDP/443へ来てよい
と通知する。設定確認後、
sudo nginx -t
sudo systemctl reload nginx
を実行した。
 

UDP/443が本当に開いたか

確認する。
sudo ss -lntup | grep ‘:443’
結果は、
udp UNCONN … 0.0.0.0:443
udp UNCONN … [::]:443
tcp LISTEN … 0.0.0.0:443
tcp LISTEN … [::]:443
となった。つまり、
IPv4 TCP/443 OK
IPv6 TCP/443 OK
IPv4 UDP/443 OK
IPv6 UDP/443 OK
である。nginx側のHTTP/3 listenerは正常に起動した。
 

HTTP/2からもHTTP/3が見える

再びcurlで確認する。
curl -sI –http2 https://mitsuki-mouse.masezou.com/ | head
結果は、
HTTP/2 200
server: nginx/1.28.3 (Ubuntu)
content-type: text/html
alt-svc: h3=”:443″; ma=86400
となった。HTTP/2通信そのものが成功しているだけでなく、
alt-svc: h3=”:443″; ma=86400
も返っている。つまり、
Client
│
│ TCP/443
▼
HTTP/2
│
│ Alt-Svc: h3=:443
▼
「次からHTTP/3も使える」
という広告までは正常に動いている。
 

OCI側でもUDP/443を開ける

ただし、nginxがUDP/443をlistenしているだけではInternetから到達できない。OCI側のIngress RuleにもUDP/443を追加する必要がある。
IPv4は、
Source CIDR: 0.0.0.0/0
Protocol: UDP
Source Port: All
Destination Port: 443
Stateless: OFF
IPv6は、
Source CIDR: ::/0
Protocol: UDP
Source Port: All
Destination Port: 443
Stateless: OFF
とした。
ここでステートレスにはしない。OCIのステートフルなルールなら、受信したQUIC通信に対する戻りトラフィックをConnection Trackingで扱える。HTTP/3を公開するだけなら、その方が自然である。
 
 

ところがUbuntu標準curlはHTTP/3非対応だった

ではcurlで直接HTTP/3を確認しよう。
curl -I –http3-only https://mitsuki-mouse.masezou.com/
ところが、
curl: option –http3-only: the installed libcurl version does not support this
となった。curlのバージョンを確認する。
curl -V
結果は、
curl 8.18.0
…
Features:
…
HTTP2
…
HTTP3がない。curl自体のバージョンは新しい。
しかし、HTTP/3対応はcurlのバージョン番号だけで決まるわけではない。HTTP/3対応としてビルドされたlibcurlが必要であり、今回のUbuntu標準curlはHTTP/2対応だがHTTP/3対応ではなかった。
これも、
新しいcurl
↓
HTTP/3が使える
とは限らないという話である。
 

それならtcpdumpで見てみる

HTTP/3対応ブラウザからアクセスしながら、OCI VM側で、
sudo tcpdump -ni any ‘udp port 443’
を実行した。
すると、
IP6 240d:…59872 > 2603:c023:…949e.443: UDP, length 1230
 
IP6 2603:c023:…949e.443 > 240d:…59872: UDP, length 1200
 
IP6 2603:c023:…949e.443 > 240d:…59872: UDP, length 1200
 
IP6 240d:…59872 > 2603:c023:…949e.443: UDP, length 51
という通信が大量に流れ始めた。
しかもIPv4ではない。IPv6である。
クライアント側は、
240d:…
OCI側は、
2603:c023:…
であり、
Client IPv6
│
│ UDP/443
│ QUIC
│
▼
OCI IPv6
│
└─ nginx HTTP/3
という通信になっている。受信だけではなく、
Client → Server
Server → Client
の双方向でUDP/443が流れている。QUICの実通信が成立している。
 

IPv6 end-to-endでHTTP/3が動いた

今回の環境では、これがなかなか面白い。
最終的に、
自宅クライアント
IPv6 240d:…
│
│
│ IPv6
│ UDP/443
│ QUIC
│ HTTP/3
│
▼
OCI
IPv6 2603:c023:…
│
▼
nginx 1.28.3
という経路になった。
IPv4 NATを経由するのではなく、IPv6 end-to-endでQUICが飛んでいる。HTTP/3はQUICのConnection IDを利用するため、TCPとは接続管理の考え方そのものが異なる。HTTP/2を有効化しただけのつもりが、結果としてIPv6ネイティブなQUIC通信まで確認することになった。
 

HTTP/2とHTTP/3ではヘッダ圧縮も違う

今回の発端はHTTP/2 Bombだった。HTTP/2ではHPACKを使用する。
一方HTTP/3ではQPACKを使用する。
HTTP/2
│
├─ TCP
└─ HPACK
 
HTTP/3
│
├─ QUIC / UDP
└─ QPACK
HTTP/3は単純にHTTP/2をUDPへ載せ替えたものではない。QUIC上でHTTPを効率的に動作させるため、ヘッダ圧縮の仕組みもHTTP/3向けに設計されている。
このあたりまで見ると、
HTTP/1.1 → HTTP/2 → HTTP/3
は単純な「バージョンアップ」というより、
HTTP/1.1
TCP上でHTTP
 
↓
 
HTTP/2
TCPは維持
HTTPレイヤを多重化
HPACK
 
↓
 
HTTP/3
TCPそのものをやめる
QUICへ移行
QPACK
という変化だと分かる。
 
 
IPv4 + HTTP/1.1
IPv4 + HTTP/2
IPv4 + HTTP/3
IPv6 + HTTP/1.1
IPv6 + HTTP/2
IPv6 + HTTP/3
を扱える公開Webサーバになった。特にIPv6 + HTTP/3については、実際にUDP/443の双方向通信まで確認できた。
 

コメントを残す