GMO Flatt Security株式会社の公式ブログです。
プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

こんにちは。株式会社Flatt Securityセキュリティエンジニアの村上@0x003f です。
本稿では、Webアプリケーション上で実装される「ログイン機能」の実装パターンをいくつか示し、その「仕様の中で起きうる脆弱性」とその対策について解説していきます。
「ログイン機能」はToB、ToC問わず多くのWebアプリケーションで実装されている機能で、XSSやSQL Injection、Session Fixationといったような典型的な脆弱性の観点については、なんらかの解説を見たことのある方も多いと思います。
しかし、「仕様の脆弱性」というのはあまり多く語られていない印象です。今回はそのようなタイプの脆弱性についての解説を行います。なお、IDaaSを用いずに自前でログイン機能を実装しているケースを複数パターン想定しています。
最もシンプルかつよく見るパターンです。以下のようなフォームになります。

ログインに失敗したときに「データベース内のメールアドレスの存在を確認できる」レスポンスが何らかの形で表現されてしまっているものになります。このような仕様は以下の点で注意が必要です。
レスポンスの差分からメールアドレスの存在が確認できるものには以下のようなパターンがあります。
それぞれについて簡単に説明していきます。
比較的わかりやすいものであれば以下のように失敗時にユーザーに再試行を促すためのエラーメッセージが、登録済メールアドレスとそれ以外とで違うというものです。

こちらの図ではわかりやすくGUI上での表現の仕方について表していますが、GUI上では表現されないAPIのレスポンスボディに差がある場合も同様の状態になります。
先程の例と似ていますが、GUIやレスポンスボディではなくレスポンスヘッダ自体に差があるものです。「メールアドレスがデータベース内に存在する場合にはパスワードが間違っていてもステータスコードが200になるが、メールアドレスが存在しない場合は400になる」といった出し分けを行っていると同様に存在を確認できる物となってしまいます。
認証の処理手順によってはメールアドレスが存在する場合とそうでない場合で処理時間に差異が生じることがあります。例えば以下のような処理順です。
このような場合、2では直ちにエラーを返しているのに対して3ではハッシュ値の計算を行っています。このような応答時間の差分を計測することで判別可能になる場合があります。

ユーザーに開示される文言やブラウザの開発者ツールで確認可能な範囲でのレスポンスに差分が出ないように処理を統一してください。
エラーメッセージであれば「メールアドレスかパスワードが間違っています」に統一したり、ログイン失敗時にはレスポンスは常に400を返したりという処理を行ってください。
データベース内にメールアドレスが存在しない場合も入力されたパスワードに対して同等のハッシュ値計算等の計算処理を行ってからレスポンスを生成してください。

フォームから第三者に対して「このメールアドレスがこのサービスで利用されている」という情報が伝わらなければよいので、メールアドレスとは別にログインのためのID等の利用状況が外部に漏れても問題のない情報を別途用意して生成しそれをログイン時に用いるようにすることでも、今回のような問題に対処することが可能です。
ログインの試行回数に制約が無く成功するまで試行できる場合は注意が必要です。このような場合には攻撃者が機械的にログイン成功するまであらゆるメールアドレスとパスワードのパターンを入力し続ける「ブルートフォースアタック」が可能になります。これにより攻撃者がログインに成功してしまった場合、そのサービスにおいて被害者の情報が窃取または改ざんされるなど非常に危険な状態になります。
このような無制限の試行に対する対策を紹介します。これらの対策はどれか1つだけを行うよりも、複合的に導入することでより安全になります。
多要素認証を導入することで、攻撃者が正しいメールアドレスとパスワードの入力に成功してしまった場合でもそれだけでログインすることを防ぎます。
こちらの対策は後述の対策に比べ本質的な対策になりますが、正しく多要素認証を実装・導入するコストや、ユーザーへの設定を任意にした場合のユーザーが導入してくれるかどうかといった課題があります。
reCAPTCHAを導入することで、攻撃者が用意したスクリプト等による機械的なログイン試行を防ぎます。
攻撃の効率が落ちることによりフォームに対しての攻撃を狙いにくくする効果がありますが、昨今では様々な手法によりこのreCAPTCHAを突破するような攻撃者もいるのでこれだけを導入すれば安全とは言えません。
「10分以内に5回失敗したらそのIPアドレスからの入力を受け付けなくする」というような回数制限をIPアドレス単位で行うことで多数のログイン試行自体を防ぎます。このような対策はクラウドサービスによって簡単に多数のIPを入手できるようになった現在ではあまり効果の高い対策と言えないかもしれませんが、複合的な対策の一つとして導入しておくことで攻撃のハードルが上がるのは間違いありません。一般のユーザーがパスワードを試しているうちに上限に到達してしまう可能性もあるので、導入する際にはそのようなユーザビリティへの配慮も行う必要があります。
パターンAのフォームに多要素認証としてのPINコード入力を求めているパターンです。多要素認証を導入しているため一見セキュアに見えますが、このPINコード入力に関してもいくつか注意すべき点が存在します。

PINの入力桁数が少ないため無作為に入力をした場合に成功してしまう確率が高いというものです。例えば4桁ですと0000から9999で1万通りしかないので、でたらめに入力しても単純に1/10000の確率で成功してしまいます。何度も入力できるフォームの場合は回数の分だけ成功しやすくなってしまいます。
単純に桁数を増やしましょう。6桁や8桁の入力を採用するサービスが多いです。
対策1が完了したとしても、無制限に試行ができるフォームであれば効果が低くなってしまいます。簡単なスクリプトで100万回の試行をされれば6桁の入力でも確実に突破されてしまいます。
「30秒以内かつ1度発行したPINに対しては5回まで」のような制限を入れることで総当りを物理的に困難にします。
PIN入力についてサーバー側で入力の成否状態を保持しておらずフロントエンドに一致の成否を返しているだけの実装になっている場合には以下のような手順でPIN入力を突破することが可能になってしまいます。

このような状態はPIN入力の処理時にサーバーでPIN入力が成功しているかどうかの状態を保存しておらず、以下のように成功か失敗かのみをクライアントに渡し、それを持って次の処理であるセッション発行のリクエストを処理してしまうフローになっているために起こります。

これを、以下のようにEmail/PWと2段階認証の成否をサーバー側で管理していて、両者が成功しているとサーバー側で確認できたときに初めてログインセッションやトークンをクライアントに対して発行し、それを受け取ることでクライアントがその後の処理をすすめるような実装に変更する必要があります。

もしくは以下のように単純にPIN入力成功時のレスポンスにセッション発行を組み込むフローにしても問題ありません。

このような処理に変更することで、攻撃者が自身の持つレスポンスで置き換えた場合でも被害者のユーザーとしてログインされることはなくなります。
最近いくつかのWebサービスで見られるパターンです。こちらにもリンクの実装で注意すべき点があります。

第三者が容易に閲覧不可能な経路で送信したにも関わらず、あるユーザーに対して生成したリンクが容易に推測可能であれば意味がありません。
例えば以下のようなリンクが発行されていると容易に推測可能であることがわかります。
https://example.com/login_link?email=userA@example.com&time=1642421877
こちらの例ではクエリストリングのemailにログインしようとしているユーザーのメールアドレスを、timeにはログイン試行した時間をunixtimeで入力して試せば成功しそうなことが見て取れます。
なんらかの外部から推測と再現が困難な識別子を用いてリンクを生成しましょう。「サーバー内で疑似乱数をキーにして発行したSHA-256」や「UUID等の標準化された一意な値」をストレージに保存し、その値をクエリストリングに付与してユーザーに通知し、アクセスされた際にその値を照合するというような方法があります。
「ログインリンクが何度でも利用できる」や「発行されたリンクの有効期限が無制限もしくは非常に長い」といった場合、リンクを攻撃者が何らかのタイミングで取得した際に何度でも活用できてしまうというリスクが存在します。
「一度使用したログインリンクを無効化する」「ログインリンクの有効期限をできるだけ短くする」という対策を行いましょう。
攻撃者がリンクを用意して被害者にアクセスさせることで、強制的に攻撃者のアカウントとしてログインさせることが可能です。
以下のような手順でリンクにアクセスしてきたユーザーが本来想定していたユーザーかどうかを確認することで対策が可能です。
ただし、この対策を導入した場合には正規のユーザーであれば「すでに送信前に入力したメールアドレスを再び入力する」ことになります。このような挙動はユーザの観点では不自然です。これを解消するために以下の処理を行うことで体験を損ねずに同等の対策を行えます。

本稿では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の説明において、前提とすべき情報の記述が不十分であったため前提となる実装についての記述を追加し、シーケンス図もそれにあわせて修正を行いました。
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。