前記事でHTTP/2の話が出たので、ちょっと脱線して「HTTP/2におけるHTTPヘッダインジェクション」に関連して行った実験について書く。
既に何年も前に語られているように(abend氏, yasulib氏)、HTTP/2では「ヘッダの区切り文字」という概念は無くなっているものの、HTTP/2を使うWebアプリケーションでもCR/LFを使ったヘッダインジェクションの可能性はある。
実験
HTTP/2のヘッダに改行を含めた不正な応答をサーバからブラウザに送り、ブラウザ側の挙動を調べる実験を行った。上述のようにHTTP/2では「区切り文字」という概念は無いため、改行等を入れた応答ヘッダを作れる。
今回の実験では、HTTP/2のヘッダの値に加えてヘッダ名についても改行を入れてみた。
結果は下のとおり。
- Chrome/Safari/IE
- エラー画面を表示した(期待通り)。
- Firefox
- ヘッダ値に含まれる改行をスペース(0x20)に変換する。
- ヘッダ名に改行を入れることで、新たなヘッダを作り出せる。
ヘッダ名に改行を入れた例を下に挙げる。
aaa:aaa[0x0D][0x0A]set-cookie:hello=111;path=/;
サーバから上のようなHTTP/2応答のヘッダ名を受け取ると、Firefoxは改行の後のset-cookie
を新たなヘッダとして解釈してしまった。FirefoxはHTTP/2のヘッダをhttp/1.1のテキスト形式にした上でparseをしているが、テキストにする際のチェックが漏れていたのだろう。
ちなみにFirefoxはHttp2Compression.cpp内でヘッダをhttp/1.1のテキストにしているようだ。そんな処理をするのかと意外な感じがしたが、工数を減らすために同様の方式にしている実装は割とあるのかもしれない。
HTTPヘッダインジェクションの文脈で(ヘッダ値ではなく)ヘッダ名をいじれる状況は殆どありえないし、変なヘッダ名をHTTP/2ヘッダにそのまま送り込むようなWebアプリケーション実行環境が存在するか甚だ疑問ではあるが、一応この問題をMozillaに報告したところ、既に別の方が「スペース等がヘッダ名に許されている」というバグを報告していることが分かった。先日リリースされたFirefox 93からはエラー画面を表示するよう変更されている。
なお、Firefoxに関してはこの辺のチェックが緩かったようで、v92にてHTTP/3のヘッダインジェクションも修正されている。
HTTP/2の仕様
HTTP/2の仕様(RFC 7540)はどうなっているかと言うと、10.3等に規定がある。
- ヘッダ値にCR, LF, NULが含まれる場合はmalformedと扱う。
- ヘッダ名についてもhttp/1.1的に不正なものはmalformedと扱う。
当然、http/1.1の仕様はヘッダ名に改行文字を許可していないので、ヘッダ名/値ともに改行が含まれるならば何らかのエラーとしなければならない。
ちなみに「malformedと扱う」のは個々のヘッダではなくて要求/応答メッセージ全体であり、クライアントであればエラー画面を表示し、サーバであればステータス400等を応答するべきなのだろう。
HTTP/2固有の応答ヘッダ
最後に、HTTP/2に特有のヘッダで、ヘッダインジェクションで使えるものがあるか?という点について。
例えば、前記事のAlt-Svc
は、ChromeではHTTP/2でしか動かないようなので、ChromeにとってはHTTP/2に特有のヘッダと言える。他に何かHTTP/2特有のヘッダがあったかなと思ってざっと見直してみた。結論を言うと「今のところ使えそうなものは無さそう」なのだが、一応調べたことを書いておく。
HTTP/2のヘッダとしては以下の2種類がある。
- 通常のヘッダ
- 疑似ヘッダ
まず通常のヘッダをざっと調べたがめぼしい情報は見つからなかった。
次の疑似ヘッダだが、これは先頭に:
が付いたHTTP/2固有のヘッダだ。応答で使える疑似ヘッダは:status
の1つのみだが、これをうまく使えば(CGIのstatus
ヘッダと同じように)応答のステータスコードが変えられるかもと思い、NginxのCGIで試してみた。
ヘッダインジェクションがあるCGIに、以下のパラメータを与える。
redirect=ZZZZZ%0D%0A:status: 200
CGIプログラムはパラメータの値をLocation
ヘッダに出力する。
サーバが出力する応答ヘッダの全体は以下のようになる(HTTP/1的な形式で表現)。
:status: 302 server: nginx/1.20.1 date: Tue, 21 Sep 2021 14:59:34 GMT content-type: text/html; charset=iso-8859-1 location: ZZZZZ :status: 200 ← インジェクトされた部分
CGIなので単一のHTTP/2ヘッダの中に改行が入るのではなく、インジェクトした:status
は独立したヘッダとなる。
意図どおり応答には2つの:status
が含まれるが、全てのメジャーブラウザがこの応答をエラーとした。これはおそらく、HTTP/2仕様(8.1.2.1)の「疑似ヘッダは通常のヘッダより前に位置しなければならない」というチェックをブラウザが行っているためだろう*1。
ちなみにBurp経由にすると、下のようにBurpのEvent logに「Normalized malformed HTTP/2 response」というメッセージが出る。
しかし、Burpが2つ目の:status
に一本化する形で正常なヘッダに書き換えるため、ブラウザに届くのは正常なステータス200の応答になる。要はBurpがある時だけは:status
ヘッダのインジェクションは成功する。
調べていないが、一般のリバースプロキシ等で同じような挙動をするものも、ひょっとしたらあるかもしれない(ただし、使える可能性で言えば、:
無しのstatus
ヘッダの方が大きいだろう)。
診断の観点では、HTTP/2においては「Burpを挟んでいる時だけ動作するHTTPヘッダインジェクションのPoC」というのが存在するので気に留めておく必要があるかもしれない(BurpはHTTP/1でも応答をnormalizeするので、HTTP/2でも/1でも発生しうることではある)。
*1:本来は、ヘッダを出力するWebサーバ側でも、何らかのチェックが必要なのだろう。