net/http: add Server.MaxHeaderValueCount
要約
概要
net/http.Server に MaxHeaderValueCount フィールドを追加し、HTTPリクエストが持てるヘッダー値の最大数を制限できるようにするproposal。多数の小さなヘッダーを送りつけるDoS攻撃への対策として、既存の MaxHeaderBytes だけでは対処できないケースを補完する。
ステータス変更
(なし) → likely accept
Proposal Review Committeeでの議論で、Go 1.27へのfreeze exceptionを適用して早期リリースする方向が浮上した。そのタイミングでaclements(@aclements)がlikely acceptを宣言。GODEBUGによる暫定対応(#80020)と並行して検討が進んでいる。
技術的背景
現状の問題点
net/http.Server にはヘッダーの合計バイト数を制限する MaxHeaderBytes(デフォルト1MB)が存在する。しかしこれは、多数の小さなヘッダーを使ったDoS攻撃に対して不十分な場合がある。
攻撃の核心は**メモリ増幅(Memory Amplification)**にある。ヘッダーは内部で map[string][]string として表現され、各エントリにはGoランタイムのmap管理オーバーヘッドが乗る。PoC検証では200,000個のヘッダーを送った際、入力2.4MBに対してヒープ割り当てが32.4MB(17.5倍)に膨れ上がることが確認されている(#79363)。
特にHTTP/2では、HPACKの動的テーブルを利用した攻撃(通称「HTTP/2 bomb」)が注目されており、攻撃者は1バイトのワイヤーデータからサーバー側で数十〜数千バイトのアロケーションを引き起こせる。
さらに、SSOやOIDCのような正当な用途では大きなCookieヘッダーが必要になるため、MaxHeaderBytes を小さく絞ることができない運用もある。こういったユーザーは、DoS耐性と正当リクエストの許容を同時に満たす手段を持っていなかった。
提案された解決策
package http
// DefaultMaxHeaderValueCount はHTTPリクエストで
// 許可されるヘッダー値の最大数のデフォルト値。
const DefaultMaxHeaderValueCount = 500
type Server struct {
// MaxHeaderValueCount はサーバーがリクエストから
// パースするヘッダー値の最大数を制御する。
// ゼロの場合、DefaultMaxHeaderValueCount が使われる。
MaxHeaderValueCount int
}
バイト数ではなく値の個数を制限することで、攻撃者が「小さなヘッダーを大量に送る」手法を封じる。ヘッダーの合計サイズは大きくても、個数を500に絞れば内部のmap管理コストを抑制できる。
これによって何ができるようになるか
大きなヘッダーを許容しつつDoS耐性も確保する、という今まで両立できなかった要件を同時に満たせるようになる。
ユースケース1: SSO/OIDC環境
認証トークンが巨大なCookieとして送られるアーキテクチャでも、MaxHeaderBytes を大きくしたまま MaxHeaderValueCount=50 に絞ることで、小ヘッダー大量送付への防御が可能になる。
ユースケース2: APIゲートウェイ
プロキシ経由で多数のヘッダーが付与される環境でも、現実的な上限(例: 100)を設定することでヘッダー膨張攻撃を防ぎつつ正常なリクエストを通せる。
ユースケース3: デフォルトのセキュリティ向上
DefaultMaxHeaderValueCount = 500 をデフォルト適用することで、設定を何もしていないサーバーのセキュリティベースラインが自動的に上がる。
コード例
// Before: MaxHeaderBytes だけでは小ヘッダー多数の攻撃を防げない
// 大きな Cookie を許可するために MaxHeaderBytes を大きくすると
// 攻撃面が広がってしまう
srv := &http.Server{
Addr: ":8080",
MaxHeaderBytes: 1 << 20, // 1MB: 大量の小ヘッダーを許容してしまう
}
// After: MaxHeaderValueCount で個数を制限できる
srv := &http.Server{
Addr: ":8080",
MaxHeaderBytes: 1 << 20, // 大きなCookieのために広めに確保
MaxHeaderValueCount: 50, // ただし個数は50に絞る
}
議論のハイライト
- neild(@neild)のコメント: Proposal Committeeでは
Server.MaxHeaderValueCountをfreeze exceptionとしてGo 1.27に入れる方向が検討された。GODEBUGによる暫定対応(GODEBUG=httpmaxheadervalues=N)は不要になる可能性があり、もし残す場合はフィールド名に合わせたhttpdefaultmaxheadercount=Nが適切とされた。 - aclements(@aclements)の疑問: ヘッダーのバイト数制限では補えないリソースとは何か、という本質的な質問がなされた。ヘッダー数を制限することでどのCPUコストが削減されるかを具体的に聞いた。
- 提案者(@nicholashusin)の回答: ①攻撃者が大きなヘッダーを1MBを埋めるのに実際に1MB送らないといけなくなる(非対称性の解消)、②map entryの canonicalize・GCの反復コストは1つの大きなヘッダーより多数の小ヘッダーで格段に高くなる、という二点を説明した。
- 既存の先行議論(#62298)との関係: 2023年にも同様の
MaxHeaderCountを追加するproposalが出ていたが、当時のレビューでは「MaxHeaderBytesで代替可能」として棄却されていた。今回のproposalはHTTP/2 bomb攻撃の実例が注目を集めたことで再浮上した。 MaxHeaderValueCountvsMaxHeaderNameCount: ヘッダー名の数ではなく値の数を制限する設計が選ばれた理由は、攻撃者が1つのヘッダー名に大量の空値を付与するパターンを想定しているため。
関連リンク
- Proposal Issue github.com/golang/go
- Review Comment proposal review meeting
- Proposal Issue #79936
- Review Minutes (aclements comment)
- 関連Issue #62298: proposal: net/http: add option to limit header count in Server
- 関連Issue #80020: net/http: add GODEBUG=httpmaxheadervalues=N (freeze exception)
- 関連Issue #79363: net/http, net/textproto: memory amplification in ReadMIMEHeader