オープンリダイレクト検査:Locationヘッダ編

オープンリダイレクタを脆弱性とみなすべきかは議論が分かれるところです。Google等の一部のサイトは、自サイトのオープンリダイレクタを脆弱性としてはみていません。一方で、脆弱性検査の現場では、見つかれば脆弱性として報告することが多いと思います。

その辺の議論はおいておいて、オープンリダイレクタの検査は、ブラウザの特性もからんで意外とバリエーションが多くて面白いので、本日の日記で取り上げてみたいと思います。

大まかにいうと、リダイレクトは、302応答のLocationヘッダ、Refresh(HTTPヘッダ、METAタグ)、JavaScriptによるものがありますが、本日は302応答のLocationヘッダのリダイレクタについて取り上げます。

パターン1:サブドメイン部分に値が入る場合

以下のように、サブドメインの箇所が動的なケースです。

Location: http://{$u}.hatena.ne.jp/hoge

このリダイレクタ("redir.cgi" とします)を悪用して、"example.com" ドメインにリダイレクトさせることを目指します。なお、$uの正常な値は "www" で、LF(%0A U+000A)は使えないとします。

まずは、一番基本的な検査文字列3つです。

■1A: redir.cgi?u=example.com/www
 → Location: http://example.com/www.hatena.ne.jp/hoge

■1B: redir.cgi?u=example.com?www
 → Location: http://example.com?www.hatena.ne.jp/hoge

■1C: redir.cgi?u=example.com%23www  (%23 => #)
 → Location: http://example.com#www.hatena.ne.jp/hoge

上の1A、1B、1Cでは、URL内で区切りとして使用される "/", "?", "#" を使っています。

アプリがブラックリストでパラメータuの値チェックをしているとしても、上の3つの記号については対策されていることは割とあります。その場合は、以下の1D〜1Fを試します。

■1D: redir.cgi?u=example.com;www
 → Location: http://example.com;www.hatena.ne.jp/hoge

■1E: redir.cgi?u=example.com:80www
 → Location: http://example.com:80www.hatena.ne.jp/hoge

■1F: redir.cgi?u=example.com\www
 → Location: http://example.com\www.hatena.ne.jp/hoge

1D, 1Eは ";" と ":" を使用しています。いずれも、FirefoxOperaでホスト名の終端として認識され、"example.com" にリダイレクトします。":" を使う場合は、その直後に妥当なポート番号を付けて ":80" のような値にしなければリダイレクトしません。

1Fは "/" の代わりに "\" を使っています。IEChromeで動作します。

次からは制御文字を使う検査文字列です。

■1G: redir.cgi?u=example.com%00www
 → ① Location: http://example.com
 → ② Location: http://example.com[0x00]www.hatena.ne.jp/hoge

1GはNULL(%00 U+0000)を使っています。応答のLocationヘッダを2パターン書いていますが、Apache上のCGIPHPで試してみると、NULL以降が消えてなくなった①のLocationヘッダが返されます。その場合は、当然 "example.com" にリダイレクトします。

仮に、②のような応答が返っても、たいていのブラウザはHTTPヘッダのNULL以降を無視するので、"example.com" にリダイレクトします(Safariだけは違う)。

もう一つ制御文字を使う検査文字列です。

■1H: redir.cgi?u=example.com%0Dwww
 → Location: http://example.com[0x0D]www.hatena.ne.jp/hoge

IEOperaChromeでは、CR(%0D U+000D)以降が無視されて、"example.com" にリダイレクトします。正確に言うと、これらのブラウザはCRをヘッダ行の区切りとして認識しています。つまり、この検査文字列は、HTTP Header Injectionを利用しています。

パターン2:スラッシュが先頭に付けられる場合

余り見ませんが、こんなケースです。

Location: /{$u}

$uの正常な値は "foo/bar.cgi" で、LF(%0A U+000A)は使えないとします。

こんなケースでの基本的な検査文字列は以下です。

■2A: redir.cgi?u=/example.com/foo/bar.cgi
 → Location: //example.com/foo/bar.cgi

URLの先頭の、"http:" や "https:" は省略可能なので、2Aは "example.com" にリダイレクトします。

たいていのアプリは上の2Aで詰むのですが、先頭に "/" が使えない場合は以下のような検査文字列があります。

■2B: redir.cgi?u=\example.com/foo/bar.cgi
 → Location: /\example.com/foo/bar.cgi

■2C: redir.cgi?u=%09/example.com/foo/bar.cgi
 → Location: /[0x09]/example.com/foo/bar.cgi

2Bでは、"/" の代わりに "\" を使っています。IEChromeで動作します。2Cはタブ(%09 U+0009)を使っています。IEChromeは、URLに含まれるタブ等の文字を無視するため、2Aと同様に動作します。

パターン3:Locationヘッダの先頭に値が入る場合

こんなケースです。一番良く見るパターンです。

Location: {$u}

$uの正常な値は "http://www.hatena.ne.jp/foo/" で、LF(%0A U+000A)は使えないとします。

外部にリダイレクトしないように、パラメータuの値を何らかの方法でチェックしていても、それが不完全ならば下の3A、3Bが通ってしまうかもしれません(このような値が通ることは少なくありません)。

■3A: redir.cgi?u=http://www.hatena.ne.jp.example.com/foo/
 → Location: http://www.hatena.ne.jp.example.com/foo/

■3B: redir.cgi?u=http://example.com/http://www.hatena.ne.jp/foo/
 → Location: http://example.com/http://www.hatena.ne.jp/foo/

元のURL(http://www.hatena.ne.jp/)のサブドメイン(www)の箇所のチェックが不完全ならば、下の3Cが通ってしまうかもしれません。

■3C: redir.cgi?u=http://example.com?.hatena.ne.jp/foo/
 → Location: http://example.com?.hatena.ne.jp/foo/

「パターン1:サブドメイン部分に値が入る場合」で見たように、上の3Cの "?" の箇所を、";", "#", ":80", "\", NULL, CR などに変えるバリエーションがあります。

先頭が "/" で始まる値が許容される場合は、下の3Dのような検査パターンが通るかもしれません。

■3D: redir.cgi?u=//example.com/foo/
 → Location: //example.com/foo/

「パターン2:スラッシュが先頭に付けられる場合」でみたように、上の3Dの "/" を "\" にするなどのバリエーションがあります。

テストしたブラウザ

IE8、Firefox3.6、Opera10、Safari5、Chrome9(いずれもWindows Vista版)。