テキストインターフェース処理の基本でもう書いてしまいましたが、今回はHTTPヘッダーインジェクションの解説です。
HTTPヘッダーインジェクションとは?
HTTPヘッダーインジェクションはHTTPリクエストヘッダーとHTTPレスポンスヘッダーの二種類に分けて考えます。
1. 特定のHTTPリクエストヘッダーに攻撃用文字列を挿入
攻撃者は様々なHTTPリクエストヘッダーに任意の文字列を挿入して攻撃が可能です。PHPでは$_SERVER変数に保存されます。
- HTTP_HOST
- HTTP_USER_AGENT
- HTTP_ACCEPT
- HTTP_ACCEPT_LANGUAGE
- HTTP_ACCEPT_ENCODING
- HTTP_REFERER
- HTTP_CONNECTION
- HTTP_* (その他、全て)
最初のHTTP_HOSTはホストヘッダーに攻撃用の文字列を注入する攻撃です。HTTPプロトコルは同じIPアドレスでも、ホストヘッダーでホスト名を指定して仮想ホストを指定できるようになっています。Webサーバー設定で仮想ホスト名を指定している場合、仮想ホスト名が$_SERVER[‘HTTP_HOST’]に保存されますが、IPアドレスでアクセスした場合にデフォルトとなるホストが設定されている場合、HTTP_HOSTには任意の文字列が設定可能です。※ 動的に名前が変えられる仕組みを導入している場合は同じ。
全てのHTTP_*要素は外部入力なので信用できません。カスタムヘッダーも送信でき、これも$_SERVERに保存されます。これらもインジェクション攻撃に利用されます。利用する場合、可能な限り入力バリデーションを行い、出力コンテクストに合わせたエスケープ処理が必要です。
2. HTTPレスポンスヘッダーを途中で強制的に分割する攻撃
※ 現在のPHPは対応済で分割は不可能。RFCの仕様も改訂され複数行にまたがるヘッダーも無効化されています。
HTTPヘッダーレスポンススプリッティングとも呼ばれる攻撃です。
HTTPヘッダーも可変長テキストインターフェースを持ったインターフェースです。HTTPヘッダーにも攻撃可能なインジェクションが可能です。
Location: $redirect_url
(注:ソケットAPIやcURLなどで他のHTTPサーバーへアクセスする場合は、ソケットやcURLのオプションなどで出力可能ですが、PHPではサーバーに接続してきたクライアントに直接HTTPヘッダーを出力できません)
$redirect_urlが変数で任意の文字列が設定できる場合、HTTPヘッダーインジェクションが可能になります。$redirect_urlに
http://example.com\r\nCache-Control: public
が設定された場合、リバースプロキシがある環境でキャッシュすべきでないページがキャッシュされる可能性があります。リバースプロキシがなくてもブラウザのキャッシュが汚染される可能性があります。
HTTPヘッダーのみでなく、$redirect_urlに
http://example.com <html> <head> <script>[EVIL JavaScript HERE]</script> </head> <body> ... </body> </html>
(改行は\r\nであるとして読んで下さい)
が挿入された場合、
Location: http://example.com <html> <head> <script>[EVIL JavaScript HERE]</script> </head> <body> ... </body> </html>
HTTPプロトコルは改行が二回連続で現れた場合、それ以降をボディとして解釈します。$redirect_urlに任意の文字列が設定できる場合、 不正なHTMLやJavaScriptを簡単にインジェクションできます。
この攻撃はHTTPレスポンススプリッティングと呼ばれ、現在のブラウザやプロキシーサーバーには様々な緩和策が導入されています。
HTTPヘッダーインジェクションの防止
HTTPヘッダーインジェクションを行うには改行文字をインジェクションし、ヘッダーを分割する必要があります。つまり、パラメーターとなる変数に改行文字がなければヘッダー分割は行えなくなります。
PHPのHTTPヘッダーを送信するheader関数は文字列中に改行文字を許可しません。
php > var_dump(header("Location: aaa\r\nX-HEADER: bbb")); Warning: Header may not contain more than a single header, new line detected in php shell code on line 1 NULL
PHPはプログラム中からheader関数以外で任意のHTTPヘッダーを送信できません。HTTPヘッダーを送信するsetcookie/setrawcookie関数も改行文字を許可しません。(許可しない文字:\t, \r, \n, 垂直タブ – 013, Shift Out – 014)
従ってPHPの場合、HTTPヘッダーインジェクションに注意を払う必要はほぼありません。
しかし、PHPスクリプトからは直接利用できないPHP内部のHTTPヘッダー送信用関数は任意の文字列が利用できるようになっています。このため、php.ini設定などでHTTPヘッダーが設定できる場合、脆弱になります。
<?php ini_set('default_charset', "Shift_JIS\r\nX-HEADER: abc");
このコードをWebサーバーで実行すると、X-HEADER: abcが設定さている事が確認できます。
まとめ
Location: $redirect_url
このような任意のリダイレクト先が指定できるヘッダー出力は、HTTPヘッダーインジェクションの可能性がある問題以外に「オープンリダイレクト脆弱性」問題があります。HTTPインジェクションが仕組み的に不可能であっても、HTTPヘッダーを出力する場合は基本的に全てバリデーション済みのデータのみを利用すべきです。
PHPでプログラムする場合、HTTPヘッダーインジェクションに注意する必要はほとんどありません。しかし、php.iniのHTTPヘッダー出力となる設定やモジュールの関数(特にサードパーティ製モジュール)がヘッダーを出力する場合は注意が必要です。
追記
現在利用されているPHPではheader関数が不正な改行を受け付けません。このため、header関数を用いた場合、HTTPヘッダーインジェクションは行なえません。古いRFCでは複数行にまたがるヘッダーが利用できましたが現在のRFCでは利用できなくなっています。最近のブラウザは複数行にまたがるHTTPヘッダーを無効とする物もあります。
参考