はじめに
こんにちは。株式会社Flatt Securityセキュリティエンジニアの村上 @0x003f です。
本稿では、Webアプリケーション上で実装される「ログイン機能」の実装パターンをいくつか示し、その「仕様の中で起きうる脆弱性」とその対策について解説していきます。
「ログイン機能」はToB、ToC問わず多くのWebアプリケーションで実装されている機能で、XSSやSQL Injection、Session Fixationといったような典型的な脆弱性の観点については、なんらかの解説を見たことのある方も多いと思います。
しかし、「仕様の脆弱性」というのはあまり多く語られていない印象です。今回はそのようなタイプの脆弱性についての解説を行います。なお、IDaaSを用いずに自前でログイン機能を実装しているケースを複数パターン想定しています。
- はじめに
- ログイン機能の仕様パターンとセキュリティ観点
- 終わりに
- 更新履歴
ログイン機能の仕様パターンとセキュリティ観点
パターンA: シンプルなメールアドレスとパスワードのみのログインフォーム
最もシンプルかつよく見るパターンです。 以下のようなフォームになります。
観点1: レスポンスの差分からメールアドレスの存在が確認可能
ログインに失敗したときに「データベース内のメールアドレスの存在を確認できる」レスポンスが何らかの形で表現されてしまっているものになります。このような仕様は以下の点で注意が必要です。
- 「当該メールアドレスを持つユーザーがサービスに登録している」という情報が漏洩する
- リスト型攻撃の手がかりとして利用される
レスポンスの差分からメールアドレスの存在が確認できるものには以下のようなパターンがあります。
- エラーメッセージに差異がある
- レスポンスヘッダに差異がある
- 処理時間に差異がある
それぞれについて簡単に説明していきます。
エラーメッセージに差異がある
比較的わかりやすいものであれば以下のように失敗時にユーザーに再試行を促すためのエラーメッセージが、登録済メールアドレスとそれ以外とで違うというものです。
こちらの図ではわかりやすくGUI上での表現の仕方について表していますが、GUI上では表現されないAPIのレスポンスボディに差がある場合も同様の状態になります。
レスポンスヘッダに差異がある
先程の例と似ていますが、GUIやレスポンスボディではなくレスポンスヘッダ自体に差があるものです。「メールアドレスがデータベース内に存在する場合にはパスワードが間違っていてもステータスコードが200になるが、メールアドレスが存在しない場合は400になる」といった出し分けを行っていると同様に存在を確認できる物となってしまいます。
処理時間に差異がある
認証の処理手順によってはメールアドレスが存在する場合とそうでない場合で処理時間に差異が生じることがあります。例えば以下のような処理順です。
- 入力されたメールアドレスをデータベースから取得する
- メールアドレスが存在しない場合、エラーを表示する
- メールアドレスが存在する場合、入力されたパスワードのハッシュ値を計算して該当のメールアドレスと紐付けられたパスワードのハッシュ値と比較し、合っていればログイン処理を続行する
このような場合、2では直ちにエラーを返しているのに対して3ではハッシュ値の計算を行っています。このような応答時間の差分を計測することで判別可能になる場合があります。
対策1-1: 場合によらずレスポンスの出し分けを行わない
エラーメッセージ、レスポンスヘッダの対策
ユーザーに開示される文言やブラウザの開発者ツールで確認可能な範囲でのレスポンスに差分が出ないように処理を統一してください。
エラーメッセージであれば「メールアドレスかパスワードが間違っています」に統一したり、ログイン失敗時にはレスポンスは常に400を返したりという処理を行ってください。
処理時間の対策
データベース内にメールアドレスが存在しない場合も入力されたパスワードに対して同等のハッシュ値計算等の計算処理を行ってからレスポンスを生成してください。
対策1-2: メールアドレスをログインIDとして使用しない
フォームから第三者に対して「このメールアドレスがこのサービスで利用されている」という情報が伝わらなければよいので、メールアドレスとは別にログインのためのID等の利用状況が外部に漏れても問題のない情報を別途用意して生成しそれをログイン時に用いるようにすることでも、今回のような問題に対処することが可能です。
観点2: フォームに試行回数制限がなく、ブルートフォースアタックが可能
ログインの試行回数に制約が無く成功するまで試行できる場合は注意が必要です。このような場合には攻撃者が機械的にログイン成功するまであらゆるメールアドレスとパスワードのパターンを入力し続ける「ブルートフォースアタック」が可能になります。これにより攻撃者がログインに成功してしまった場合、そのサービスにおいて被害者の情報が窃取または改ざんされるなど非常に危険な状態になります。
対策2: 攻撃に対する緩和の仕組みを導入する
このような無制限の試行に対する対策を紹介します。これらの対策はどれか1つだけを行うよりも、複合的に導入することでより安全になります。
多要素認証を導入する
多要素認証を導入することで、攻撃者が正しいメールアドレスとパスワードの入力に成功してしまった場合でもそれだけでログインすることを防ぎます。
こちらの対策は後述の対策に比べ本質的な対策になりますが、正しく多要素認証を実装・導入するコストや、ユーザーへの設定を任意にした場合のユーザーが導入してくれるかどうかといった課題があります。
reCAPTCHAを導入する
reCAPTCHAを導入することで、攻撃者が用意したスクリプト等による機械的なログイン試行を防ぎます。
攻撃の効率が落ちることによりフォームに対しての攻撃を狙いにくくする効果がありますが、昨今では様々な手法によりこのreCAPTCHAを突破するような攻撃者もいるのでこれだけを導入すれば安全とは言えません。
試行回数上限を定めたIPアドレス単位のアクセス制限を導入する
「10分以内に5回失敗したらそのIPアドレスからの入力を受け付けなくする」というような回数制限をIPアドレス単位で行うことで多数のログイン試行自体を防ぎます。 このような対策はクラウドサービスによって簡単に多数のIPを入手できるようになった現在ではあまり効果の高い対策と言えないかもしれませんが、複合的な対策の一つとして導入しておくことで攻撃のハードルが上がるのは間違いありません。 一般のユーザーがパスワードを試しているうちに上限に到達してしまう可能性もあるので、導入する際にはそのようなユーザビリティへの配慮も行う必要があります。
パターンB: メールアドレスとパスワードの入力後に、PINコード入力画面に遷移し入力してもらう
パターンAのフォームに多要素認証としてのPINコード入力を求めているパターンです。 多要素認証を導入しているため一見セキュアに見えますが、このPINコード入力に関してもいくつか注意すべき点が存在します。
観点1: PINの桁数が少ないため確率的に突破できる可能性が高まってしまう
PINの入力桁数が少ないため無作為に入力をした場合に成功してしまう確率が高いというものです。例えば4桁ですと0000から9999で1万通りしかないので、でたらめに入力しても単純に1/10000の確率で成功してしまいます。何度も入力できるフォームの場合は回数の分だけ成功しやすくなってしまいます。
対策1: PINの桁数を小さくしすぎない
単純に桁数を増やしましょう。6桁や8桁の入力を採用するサービスが多いです。
観点2: PINの入力回数に制限がないので総当りで回避できてしまう
対策1が完了したとしても、無制限に試行ができるフォームであれば効果が低くなってしまいます。簡単なスクリプトで100万回の試行をされれば6桁の入力でも確実に突破されてしまいます。
対策2: PINの入力にも回数制限を入れる
「30秒以内かつ1度発行したPINに対しては5回まで」のような制限を入れることで総当りを物理的に困難にします。
観点3: PIN入力の成否をサーバー側で管理していないことによりPIN入力をバイパスできてしまう
PIN入力についてサーバー側で入力の成否状態を保持しておらずフロントエンドに一致の成否を返しているだけの実装になっている場合には以下のような手順でPIN入力を突破することが可能になってしまいます。
- 攻撃者が「自分で用意した正規のアカウント」でログインし、PINコードも正しく入力してログインを行います。
- 1のPINコード入力時のHTTPレスポンスをMITM Proxyツール等に保存しておきます。
- 被害者のアカウントでログイン試行を行い、PIN入力のフォームにでたらめな値を入力します。
- 3のPIN入力時にサーバーから失敗のHTTPレスポンスが返却されますが、ここであらかじめ2で保存しておいた成功のレスポンスに置き換えて処理を実行します。
- 偽装されたHTTPレスポンスを受け取ったクライアントが認証が成功したとしてログイン処理を進めてしまい、ログインできてしまいます。
対策3: PINの入力の成功を確認してからログインセッションやトークンをクライアントに対して発行する
このような状態はPIN入力の処理時にサーバーでPIN入力が成功しているかどうかの状態を保存しておらず、以下のように成功か失敗かのみをクライアントに渡し、それを持って次の処理であるセッション発行のリクエストを処理してしまうフローになっているために起こります。
これを、以下のようにEmail/PWと2段階認証の成否をサーバー側で管理していて、両者が成功しているとサーバー側で確認できたときに初めてログインセッションやトークンをクライアントに対して発行し、それを受け取ることでクライアントがその後の処理をすすめるような実装に変更する必要があります。
もしくは以下のように単純にPIN入力成功時のレスポンスにセッション発行を組み込むフローにしても問題ありません。
このような処理に変更することで、攻撃者が自身の持つレスポンスで置き換えた場合でも被害者のユーザーとしてログインされることはなくなります。
パターンC: メールアドレスの入力後にログイン用リンクが登録メールアドレスに送信され、そのリンク先にアクセスすることでログインが可能になる
最近いくつかのWebサービスで見られるパターンです。こちらにもリンクの実装で注意すべき点があります。
観点1: ログイン用リンクが推測可能
第三者が容易に閲覧不可能な経路で送信したにも関わらず、あるユーザーに対して生成したリンクが容易に推測可能であれば意味がありません。
例えば以下のようなリンクが発行されていると容易に推測可能であることがわかります。
https://example.com/login_link?email=userA@example.com&time=1642421877
こちらの例ではクエリストリングのemailにログインしようとしているユーザーのメールアドレスを、timeにはログイン試行した時間をunixtimeで入力して試せば成功しそうなことが見て取れます。
対策1: 推測されないような生成方法を用いる
なんらかの外部から推測と再現が困難な識別子を用いてリンクを生成しましょう。 「サーバー内で疑似乱数をキーにして発行したSHA-256」や「UUID等の標準化された一意な値」をストレージに保存し、その値をクエリストリングに付与してユーザーに通知し、アクセスされた際にその値を照合するというような方法があります。
観点2: 発行されたログインリンクに利用期限が設定されていない
「ログインリンクが何度でも利用できる」や「発行されたリンクの有効期限が無制限もしくは非常に長い」といった場合、リンクを攻撃者が何らかのタイミングで取得した際に何度でも活用できてしまうというリスクが存在します。
対策2: ログインリンクの利用可能期間の制限を設ける
「一度使用したログインリンクを無効化する」「ログインリンクの有効期限をできるだけ短くする」という対策を行いましょう。
観点3: 攻撃者の用意したリンクによる強制ログイン
攻撃者がリンクを用意して被害者にアクセスさせることで、強制的に攻撃者のアカウントとしてログインさせることが可能です。
対策3: 送信先メールアドレスの再確認を求める
以下のような手順でリンクにアクセスしてきたユーザーが本来想定していたユーザーかどうかを確認することで対策が可能です。
- 予めログインリンクを発行する際に、フォームに入力されたメールアドレスをデータベース等のストレージに発行したログインリンクと結びつけて保存しておきます。
- リンクにアクセスしてきたユーザーに「どのメールアドレスに対して送信したリンクか」という入力を求め、その値を送信します。
- 送信されたメールアドレスがデータベース上のメールアドレスと一致していればログイン処理を継続します。そうでない場合はエラーとします。
ただし、この対策を導入した場合には正規のユーザーであれば「すでに送信前に入力したメールアドレスを再び入力する」ことになります。このような挙動はユーザの観点では不自然です。これを解消するために以下の処理を行うことで体験を損ねずに同等の対策を行えます。
- 送信前にフォームに入力されたメールアドレスをブラウザのローカルストレージに保存しておく
- ログインリンクにユーザーがアクセスした際、ローカルストレージをチェックし、メールアドレスが存在するならばそれをサーバーに送信する値としてセットする
- サーバーにメールアドレスを送信し、前述の対策と同等のチェックを行う
終わりに
本稿ではWebアプリケーション上で実装される「ログイン機能」の仕様におけるセキュリティ上の観点とその対策を紹介しました。
なお、記事の構成は筆者の元同僚の @shioshiota の記事 「Webサービスによくある各機能の仕様とセキュリティ観点(ユーザ登録機能) 」 を参考にしています。ありがとうございました!
Flatt Securityでは、今回ご紹介したような、ツールだけの診断では見つけられない仕様起因の脆弱性も洗い出すため、セキュリティエンジニアの手動検査とツールを組み合わせたセキュリティ診断サービスを提供しています。
過去に診断を実施したが不安や課題がある、予算やスケジュールに制約がありどのように診断を進めるべきか悩んでいる等、お困り事にあわせて対応策をご提案いたしますので、まずはお気軽にお問い合わせください。
お問い合わせは下記リンクよりどうぞ。
https://flatt.tech/assessment/contact
Flatt Securityはセキュリティに関する様々な発信を行っています。 最新情報を見逃さないよう、公式Twitterのフォローをぜひお願いします!
ここまでお読みいただきありがとうございました!
更新履歴
2022年1月31日
一般的にSession Fixationは攻撃者のアカウントへのログインを強制させることを意味せず、誤解を招く表現であったので、「観点3: 攻撃者の用意したリンクによる強制ログイン」の節においてSession Fixationという表記を削除しました。
2022年8月10日
パターンB観点3の説明において、前提とすべき情報の記述が不十分であったため前提となる実装についての記述を追加し、シーケンス図もそれにあわせて修正を行いました。