僭越ながら、脆弱性"&'<<>\ Advent Calendar 2016 の6日目の記事です。
端的に言うと、URLにBasic認証を埋め込む方法があるのですが、これをオープンリダイレクトの攻撃に応用できます。
この手法は昨年に発見し、もう1年以上経っているので、既にご存知の方もいるかと思われますが、Web初出でしょうか。日々の健全な脆弱性診断にご活用ください。
●オープンリダイレクトの脆弱性
診断ベンダによっては「オープンリダイレクタ」「外部サイトへ誘導可能」といったタイトルで揺らぎはありますが、本質的には同じです。*1
実はこの脆弱性、OWASP TOP 10(2013)に仲間入りしている悪いヤツです。
Top10-2013-A10: Unvalidated Redirects and Forwards (原文)
日本語版PDFでは、未検証のリダイレクトとフォワード と訳されています。要するに、リダイレクト時に意図せず任意のURLにリダイレクト可能なため、攻撃者によってフィッシングサイトへ誘導、マルウェア配布などに悪用されてしまう、という脆弱性です。リダイレクト時、というのはおおよそ次のような手法があると思われます。
◆HTTP Response HeaderによるLocationフィールド
Location: http://example.com
◆HTMLのmeta要素のhttp-equiv属性によるrefresh攻撃手法
<meta http-equiv="refresh" content="0;URL=http://example.com">
◆JavaScriptによるlocation.href プロパティ
location.href='http://example.com';
◆HTMLのform要素によるsubmit(フォームの自動送信)
<form action="http://example.com" name="auto" method="post"><input type="submit"></form><script>document.auto.submit();</script>
リスクレベルとしては、高または中でしょう。 *2 高リスクである根拠として、ログイン状態に関係なく攻撃を受ける、攻撃難度が容易である、フィッシングサイトへ誘導・マルウェア配布といった比較的被害の大きい攻撃に悪用される、という点です。
具体的な例として、よくある攻撃シナリオは次の通りです。
ログイン処理において「http://example.com/login.php?redirect=https://www.google.co.jp」というURLでPOSTされた時、QueryStringのredirectの値をログイン成功後の遷移先として処理するため、HTTPレスポンスヘッダーに「Location: https://www.google.co.jp」がセットされ、ログイン成功後に外部サイトであるGoogleへリダイレクトされてしまう。
Webアプリケーションの設計において、ログアウト状態ではアクセス出来ないページがあるなど、ログイン成功後に任意のページ遷移をさせる処理を実装することが非常に多くあります。実際、オープンリダイレクトに遭遇する場面のほとんどがログイン画面です。
■参考情報
Top 10 2013-A10-Unvalidated Redirects and Forwards - OWASP
https://www.owasp.org/images/7/79/OWASP_Top_10_2013_JPN.pdf (日本語訳PDF)
JVN#32218514: サイボウズ ガルーンにおけるオープンリダイレクトの脆弱性
●URLにBasic認証を埋め込む方法
昨年の話ですが、とある脆弱性診断でオープンリダイレクトが起きそうでバリデーションによって起こらない微妙なやつがあったので、URLに関するRFCを眺めていると、URLにBasic認証の情報を埋め込むことができる興味深いドキュメントを見つけました。
次のように表記することでBasic認証の情報を埋め込むことが可能です。
http://user:password@example.com/basic
http://<アカウント>:<パスワード>@example.com/basic
RFC 3986 では下記のように明記されています。
authority = [ userinfo "@" ] host [ ":" port ]
こんな方法があったのかという印象ですが、Internet Explorer以外のメジャーなブラウザは、このBasic認証を埋め込んだURLに対応しています。(恐らく意図的に対応させてない)Internet Explorerで開くと、エラーダイアログが出てきます。
ちなみに、Basic認証を要求されていないサイトの場合Firefoxは、セキュリティ面からか確認ダイアログが出てきますが、ChromeとSafariは確認なしでアクセスします。
この方法に関しては、xss.moeのやぎはしゅさんのスライドも合わせてご覧いただくと良いかと思います。
■参考情報
http://こいつの:話@shibuya.xss.moe/ (やぎはしゅさんのスライド)
情報セキュリティ技術動向調査(2009 年下期):IPA 独立行政法人 情報処理推進機構
https://www.ietf.org/rfc/rfc3986.txt (RFC 3986)
●攻撃に応用する
URLにBasic認証を埋め込む方法を詳しくつついていくと、下記の特徴がありました。
・パスワードは必須ではない
・ブラウザの挙動として、@以前の文字列はページ遷移に影響を与えない
これらの特徴から導き出したPoCはこれです。
PoC: ?redirect=example.com@www.google.co.jp/evil
IE以外のブラウザで、次のURLをアドレスバーにコピペでアクセスすることでお手元で確認できます。(はてなの自動リンクでもいけるので、下のリンクをクリックしてください。)
「https://example.com@www.google.co.jp/evil」
URLは https://example.com で始まっていますが、Googleにアクセスされます。実際に攻撃に使う事例として、ChromeやSafariで次のURLにアクセスすると、example.comにも関わらず、FirefoxのWindowsバイナリが確認なしに一発でダウンロードされます。
「https://example.com@download.mozilla.org/?product=firefox-50.0.2-SSL&os=win64&lang=ja」
つまりこれは、オープンリダイレクト対策として、FQDNの完全一致・前方一致でバリデーションチェックしている場合、バイパスされる可能性が大きいということです。
●実証実験
この攻撃手法が生きる場面としては、既にオープンリダイレクトの対策を行っており、使用可能な記号が制限されている場合や、FQDNの正規表現によるバリデーションチェックを行っている場合に、それらのバリデーションチェックをバイパスすることが可能です。
検証用に簡単なPHPのコードを書きました。
<?php
if (preg_match("/^https?\:\/\/localhost/", $_GET['redirect'])) {
header("Location: {$_GET['redirect']}");
} else {
exit();
}
正規表現を用いて、必ず http://localhost もしくは https://localhost で始まる文字列であれば、正常値としてリダイレクトさせるコードです。実際には、こんなお粗末なコードではないと思いますが。
下記に検証結果のスクリーンショットを示します。
★検証結果:攻撃失敗形(バリデーションチェックによって弾かれるパターン)
QueryStringに「?redirect=http://www.google.co.jp/localhost」を与えます。
結果、正規表現にマッチしないため、exit(); に分岐しブランクページが表示されました。これはバリデーションチェック処理として期待された結果です。
★検証結果:攻撃成功形(バリデーションチェックをバイパスするパターン)
QueryStringに「?redirect=https://localhost@www.google.co.jp」を与えます。
本来ここでの期待された結果は、リダイレクトされずに exit(); に分岐すべきですが、入力された文字列が、https://localhost から始まっているため、リダイレクト処理に分岐されてしまいました。なお、ChromeとSafariはこの確認画面は表示されずに、即時リダイレクトされます。
残念ながら、攻撃者が意図した外部サイトへリダイレクトされてしまいました。つまり、バリデーションチェックをバイパスして、オープンリダイレクトの攻撃に成功しました。
●オープンリダイレクトの対策
よくありがちなオープンリダイレクトの対策として、自分自身のFQDNのみ許可、というバリデーションチェックを実装していることが多いでしょう。Basic認証の手法を除けば、基本的に記号の禁止か、FQDNの完全一致・前方一致でおおよそ対策は可能です。が、この手法の場合、FQDNの完全一致・前方一致の安全説が崩壊しています。なぜなら、Basic認証のアカウント情報にFQDNを挿入しているので、URLの見かけ上の前方のFQDNをバリデーションチェックしたところで意味を成してないため、当然バイパスされてしまいます。
https://example.com@www.google.co.jp/evil
上記の下線の箇所をバリデーションチェックしても全く意味がないです。
保険的な対策として今すぐ改修するならば、URLの先頭から「/」を含めたFQDNの完全一致・前方一致が落とし所でしょうか。もちろん、「@」を禁止するという方法も一定の効果が期待できます。
・「example.com/」左記が全一致もしくは前方一致している場合のみ許可
・「@」が含まれる入力値は不許可
・予め想定されたURLの完全一致(完全なホワイトリスト制)
ただし、Webアプリケーションの実装方法によっては、NULLバイト攻撃を組み合わせることで、バイパスされる可能性もゼロではないです。
なお、この手法を発見するに至った脆弱性診断では、素直には刺さらず、NULLバイト文字攻撃と組み合わせて突破しました。
抜本的な対策としては、ユーザ入力値をリダイレクト先URLに用いる実装方法自体を変えることです。実装方法の一例ですが、画面IDとURLの一対一の組みを管理してリダイレクトさせるという、下記のような実装方法が良いかと思われます。
https://example.com/login.php?redirectId=0
Location: https://example.com/top
https://example.com/login.php?redirectId=1
Location: https://example.com/info
画面IDを数値で管理する仕様であれば、1~4文字程度の数字のみ入力を受け付けるバリデーションとなり、かつ、ユーザ入力値をそのまま使用してないので、オープンリダイレクトさせることは極めて難しいと言えるでしょう。
オープンリダイレクトの色々な攻撃を想定しながら開発と平行で対策するくらいなら、初めからこの実装方法にしておけば、はっきり言って無駄な、いたちごっこにコストかけずに済むと思います。開発の要件や設計によって、実装が難しい場合もありますが……
●その他の攻撃手法
Basic認証の手法以外にも、様々なオープンリダイレクトのテクニックがあるので、そのオーソドックスな手法の一部をご紹介します。
基本形:https://example.com/login.php?redirect=example.com
Location: https://example.com
※文字列の出方によってやり方が変わるので、あくまで一例です。
◆アンカーリンク
Location: https://www.google.co.jp#example.com
◆パス区切り
Location: https://www.google.co.jp/example.com
◆QueryString
Location: https://www.google.co.jp?example.com
◆ドメインレベルを操作する *3
Location: https://example.com.www.google.co.jp
◆その他使えそうな手法
Location: //www.google.co.jp/example.com ※プロトコル省略記法
Location: https://www.google.co.jp[NULL]example.com ※NULLバイト攻撃
Location: https://example.com[改行]Location: www.google.co.jp ※HTTPヘッダインジェクション
Webアプリケーションのセキュリティ対策の全てに言えることなのですが、ユーザ入力値をReflectedさせる実装(セッション変数に一時的に保持させる場合も含め)は、それ自体は今はXSSなどの脆弱性がなくとも、潜在的にリスクをはらんでいるという害悪な一面を持っていることを認識した上で、セキュアコーディングをするべきですね。実際問題、利便性とセキュリティはトレードオフの関係であるため、様々な事情から簡単な話ではないですが。