2016年11月21日
パスワードリセット・リンクが漏れていませんか?
(2016-10-24)by Derek Prior
本記事は、原著者の許諾のもとに翻訳・掲載しております。
セルフサービス・パスワードリセットは、多くのWebアプリケーションで共通部分になっています。通例、パスワードリセット・リンクはユーザにEメールで送られ、そこには、何らかの方法でユーザを識別する一意のトークンが含まれています。ユーザがリンクをクリックすることによって、そのユーザがそのアカウントに関連するEメールをアクセスできることが証明され、次にユーザは二段階認証を行います。この時点で、ユーザは新しいパスワードを入力するよう求められます。
もしも、ある攻撃者がそのパスワードリセット・リンクにアクセスできたなら、その攻撃者は、そのユーザとして認証を受けて新しいパスワードを入力することができるので、そのユーザのアカウントに自由にアクセスできるようになります。多くの場合、攻撃者はユーザのEメールへのアクセスを手に入れることによってこれを行いますが、最近私は 多くのアプリケーションがうかつにもパスワードリセット・リンクを第三者サイトに渡している ことに気付きました。どうやって渡しているかを理解するには、まず、Webサイトがどうやってリファラデータを収集するのか知る必要があります。
HTTP Referer
ヘッダ
HTTP Referer
ヘッダ(”referrer”のスペル間違いは歴史の不幸な過ちで、このヘッダの話をするたびに、しぶしぶ繰り返し説明することになるのです)がブラウザからサーバに送られて、要求元サイトのURLを特定します。このURLは主にサイト管理者が分析の目的に使用します。リファラデータは、人がサイトを探すためにどんな検索語を使用しているかや、そのサイトの最新のソーシャルメディア・キャンペーンに誘導された訪問者の数がどれぐらいかを知る方法です。ユーザがリンクをクリックしたときか、表示されたドキュメント内で参照された画像、スタイルシート、JavaScriptファイル、動画などのリソースをブラウザがフェッチしたときに、ブラウザはこのヘッダを送信します。
HTTPSを介してロードされたドキュメントからHTTPを介してフェッチしたリソースについては、ブラウザは Referer
を送信しません。さらに、 Referer
で完全リファラが明らかにされる条件をドキュメントの作者がコントロールすることのできるリファラポリシーの仕様があり、この仕様の解釈を遵守するブラウザもあります。しかし残念ながら、この仕様は今のところ汎用ではないので、これだけに依存してセキュリティを考えるべきではありません。
パスワードリセット・リンクの漏洩
ユーザがパスワードリセットを要求すると、 https://example.com/passwords/edit?token=1234abcd
というようなリンクがEメールで送られてきます。ユーザがこのリンクをクリックすると、アプリケーションは通常のサイトレイアウトの中にパスワードリセット・フォームを表示します。このサイトレイアウトには、信用できるコンテンツ配信ネットワーク(CDN)からロードされた資産やSegmentなどのアナリティクスパッケージへの参照が含まれていることがあります。レイアウトには、その会社のソーシャルメディア・プロフィールなどの外部サイトへのリンクが含まれていることもあります。
Eメール内のリンクをユーザがクリックしてからパスワードリセット・フォームの描画までの間の中間リダイレクトを阻み、ブラウザは、参照されている資産やアナリティクスパッケージを要求するときに送信される Referer
の中に、このパスワードリセット・リンクを露呈します。ユーザがパスワードリンクのリセット操作を終了せずに外部リンクをどれかクリックすると、その要求の Referer
を介してパスワードリセット・リンクが漏洩することになります。
漏洩の深刻さは、次のような要因によって緩和されます。
-
ほとんどのパスワードリセット・トークンは、ユーザがパスワードリセット・フォームへの入力を終えると無効になるので、漏洩したパスワードリセット・リンクが使用される時間窓は非常に短いと思われます。
-
ユーザの入力した内容が不適切にパスワードリセット・ページに表示されることを阻止すれば、攻撃者はどのサイトにトークンが漏洩するかコントロールすることはできません。
-
トークンが漏洩する場合、最も可能性の高い漏洩先は、信用できるパートナーと考えられるサイトです。
こういった要因のおかげで、漏洩が簡単に利用される可能性は低いものです。また、信用できるCDNで邪悪な従業員が待ち受けていて、リファラデータを受け取るやいなや悪事を働くということも想像しにくいものです。しかし、そのCDNを標的とする攻撃者が、サーバログの中の、この、もしかしたら貴重かも知れない情報に出くわすと想像するのはそれほど難しくありません。この理由から、直接的な悪用の可能性が低くても、この問題に対処することは重要です。
自分のサイトをテストする
自分のサイトで以下のようなテストをすれば、有効なパスワードリセット・リンクが第三者サイトに漏洩してしまう可能性があるかどうかが分かります。
-
パスワードリセットをリクエストし、自分宛にメールされるリンクをクリックする。
-
開いたページのURLをコピーする。
-
ブラウザのプライベートウィンドウか別のWebブラウザを開き、コピーしたURLをペーストする。
有効なパスワードリセット・フォームが表示された場合、緩和ステップが全くなければこのページで生成された Referer
が有効なパスワードリセット・URLを含むことになると証明できたことになります。
ブラウザの開発者ツールにあるネットワークタブを使って、そのページが生成するリクエスト全てを調査すると、その中にページ表示プロセスでパスワードリセット・リンクを外部に漏洩したものがあるかどうかが分かります。ドキュメント中の全てのHTMLリンクを調査して、その中に外部のHTTPSリソースを参照するものがあると、クリックでパスワードリセット・リンクが漏洩されることになります。
上の例では、Upcaseがパスワードリセット・リンクをCDNであるCloudFrontに漏洩しているのが分かります。
このページがブラウザのプライベートインスタンスでロードされても、外部リソースをフェッチしたり外部リソースにリンクしたりしていなければ、現在のところパスワードリセット・リンクを漏洩していません。しかし、それでもやはりその可能性を完全に取り除く対策は取るべきです。例えば、フッターリンクやロードされた外部リソースなどが後に変更されることがあれば、問題が起こる可能性があるからです。
漏洩を止める
パスワードリセット・リンクをクリックして表示されたページのURLに有効なパスワードリセット・トークンが含まれていないことを確認すれば、漏洩を止めることができます。この問題が報告され、私たちがRailsの認証エンジンとして使っているClearanceで修正されたとき、以下の2つの対策を検討しました。
-
passwords#edit
コントローラアクションを更新して、リクエスト内のパスワードリセット・トークンを即時無効化し、フォームアクションで使われる新しいトークンを生成するようにする。Referer
には、もともとのパスワードリセット・トークンが含まれたままとなるが、そのトークンは無効である。 -
passwords#edit
のコントローラアクションを更新して、URL内にトークンの存在を検知すると、そのトークンをセッションに保存して、トークンを除いたURLと共にpasswords#edit
にリダイレクトするようにする。Referer
には、必要なトークンが含まれていない状態となる。
Clearance 1.15.0でセッションベースのアプローチを採りました。なぜなら、1つ目のアプローチで即座にパスワードリセット・リンクを無効化すると、べき等である get
リクエストのべき等性が失われてしまったように感じたからです。 セッションベースのアプローチ と 即時無効化のアプローチ を考慮して私が提出した両方のプルリクエストを見ると、それぞれのアプローチの影響でコードを修正したりディスカッションの方向が変わったりした様子が分かるでしょう。Clearanceをお使いなら、Clearance 1.15以上にアップデートすると、この問題が修正されます。
この問題の続きやClearance内での修正についての詳細を知りたければ、Web開発についてのポッドキャスト、The Bike Shedのエピソード 81 と 82 で聞くことができます。
謝辞
Clearanceの問題を私に提起してくれた Aditya Prakash と、問題の範囲を定める手助けをしてくれた Jeroen Visser に感謝します。
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- Twitter: @yosuke_furukawa
- Github: yosuke-furukawa