安全なログイントークン管理、フロントエンドの必須戦略
2024年11月22日
序文
現代の技術環境において、ユーザー認証方式は日々進化しており、それに伴ってセキュリティ方式も絶え間なく進化しています。高い拡張性と柔軟性を持つトークンベースの認証方式は急速に標準化され、特にセキュリティと効率性の理由から、従来のセッションベースの認証方式に取って代わるケースが増加しています。QueryPieもセキュリティ向上のために、製品にトークンベースの認証方式を導入しました。
フロントエンドにおける新たな挑戦
ウェブフロントエンドにおける認証の実装歴はそれほど長くありません。従来のセッションベースの認証方式では、サーバーがユーザーセッションを管理していました。フロントエンドでの認証とは、フォームを適切に作成し、サーバーが指定したエンドポイントにIDとパスワードを正しく送信することに終始していました。しかし、ここ数年、フロントエンド領域は多くの技術的な変化に直面しています。
AJAXの登場とSingle Page Application(SPA)構造により、フロントエンド開発者はバックエンドとJSONベースのAPI通信でデータをやり取りするようになりました。
Web Storage APIの登場により、フロントエンドは永続性(Persistence)を容易に処理できるようになりました。
NextJSのようなSSR(サーバーサイドレンダリング)サーバーの登場により、以前サーバーで行われていた役割がフロントエンドに移行しました。
これらの変化は、トークンベースの認証方式と連携して、フロントエンドに安全なトークンの送受信と永続性管理という新たな課題を投げかけました。
安全なトークン、そうでないユーザー環境
トークンベースの認証は、開発の利便性だけでなく、トークンの改ざんがほぼ不可能であるため、セキュリティの面でも優れています。しかし、トークン自体が盗まれてしまった場合、話は変わります。
脅威の種類
トークンを盗まれる主な経路は、ユーザーのブラウザです。ユーザーが古いブラウザを使用している場合、そのブラウザ自体の脆弱性を突かれる可能性があります。最近では、Chromeのように常に最新バージョンを使用させるエバーグリーンブラウザの普及により、ブラウザの脆弱性は迅速に修正されています。
むしろ脆弱な部分は、ブラウザが提供するさまざまなセキュリティレベルを満たしていないJavaScriptコードやサーバー設定にある可能性が高いです。このような状況を悪用し、攻撃者はクロスサイトスクリプティング(Cross Site Scripting、XSS)、クロスサイトリクエストフォージェリ(Cross Site Request Forgery、CSRF)、セッション ハイジャッキング(Session Hijacking)などの攻撃を通じてトークンを盗む、またはそれに準じた攻撃を行うことができます。
クロスサイトスクリプティング(XSS)
https://owasp.org/www-community/attacks/xss/
クロスサイトスクリプティング(XSS)攻撃は、悪意のあるスクリプトが無害で信頼されているウェブサイトに注入されるタイプのインジェクション攻撃です。XSS 攻撃は、攻撃者がウェブアプリケーションを利用して、一般的にブラウザ側のスクリプトの形式で悪意のあるコードを別のエンドユーザーに送信する際に発生します。この攻撃が成功する原因となる脆弱性は広範囲にわたり、ウェブアプリケーションがユーザーからの入力を検証やエンコードなしで出力に含める場所で発生します。XSS 攻撃では、通常、攻撃者がユーザーのブラウザで実行されるスクリプトを悪用し、ユーザーのセッションを乗っ取ったり、個人情報を盗んだり、悪意のある行動を実行したりすることができます。
XSS(クロスサイトスクリプティング)攻撃は、ユーザーのブラウザに悪意のあるスクリプトを実行させる方法で、伝統的なコードインジェクション攻撃です。様々な攻撃方法がありますが、最も一般的な例を以下に示します。
攻撃者は、
vulnerable-site.com/search?query=${query}のような形式で接続し、queryパラメーターがそのまま結果ページに出力される脆弱性を確認します。攻撃者は次に、
vulnerable-site.com/search?query=<script>...悪意のあるコード...</script>と入力し、結果ページに悪意のあるコードが送信されるリンクを作成します。その後、攻撃者はメッセンジャーやコミュニティなどを利用して、悪意のあるコードが送信されるリンクに他のユーザーがアクセスするように誘導します。
もし、そのサイトの利用者のアクセス・トークンがJavaScriptを通じて取得できる状態であれば、トークンの窃取のリスクにさらされることになります。
クロスサイトリクエストフォージェリ(CSRF)
https://owasp.org/www-community/attacks/csrf
クロスサイトリクエストフォージェリ(CSRF)は、エンドユーザーが現在認証されているウェブアプリケーションで望ましくない操作を強制的に実行させる攻撃です。ソーシャルエンジニアリング(例えば、メールやチャットでリンクを送信するなど)の助けを少し借りて、攻撃者はウェブアプリケーションのユーザーを騙して、自分が選んだ操作を実行させることができます。
もし被害者が通常のユーザーであれば、成功した CSRF 攻撃は、ユーザーに資金の移動、メールアドレスの変更など、状態を変更するリクエストを実行させることができます。もし被害者が管理者アカウントであれば、CSRF攻撃はウェブアプリケーション全体を危険にさらす可能性があります。
CSRF攻撃自体はトークンを盗む脅威ではありませんが、XSS攻撃と同様に、攻撃者はウェブサイトの設計上の隙間とユーザーの認証状態を利用して、ユーザーに害を及ぼす行為を強制することができます。これは、トークンの盗難と同じくらい重大な脆弱性です。
攻撃者は、ユーザーにフィッシングメールを送信し、
vulnerable-shop.comのサイトのように見せかけたscam-shop.comのリンクをクリックさせようとします。vulerable-shop.comにログインしていたユーザーがそのリンクをクリックします。scam-shop.comのページには、以下のようなスクリプトが含まれており、ページが読み込まれると自動的に実行されます:
<form id="form" action="https://vulerable-shop.com/api/purchase" method="POST"> <input type="hidden" name="item_id" value="$expensive_item"> <input type="hidden" name="address" value="$attackes_house"> <input type="hidden" name="amount" value="10000"> <button type="submit">Purchase</button></form><script> document.getElementById('form').submit();</script>ユーザーがすでにログインしており、適切なセキュリティ設定がなされていない場合、ユーザーは知らぬ間に攻撃者によって高額な商品を 10,000個も購入して送信させられることになります。
CSRFは、クッキーの特性を利用した攻撃です。クッキーは以下のように動作します:
クッキーはユーザーのブラウザに保存されます。
クッキーは、そのクッキーに設定されたドメインに対するリクエスト時に常に含まれます。
フロントエンド認証セキュリティ: QueryPieのREDチームと共に行うベストプラクティスと脅威診断
認証の実装は非常に難易度が高いです。特に一般的な機能開発だけでなく、セキュリティもしっかりと考慮しなければなりません。QueryPie はセキュリティソリューションを作成する過程で、フロントエンドチームが認証機能にしっかりとセキュリティを組み込むために多大な努力とリサーチを行いました。認証を実装する過程で、QueryPieのREDチームも重要な役割を果たしました。REDチームは自社のプロダクトを対象にホワイトハッキングを行い、潜在的な脆弱性を発見し、それを改善するために大きな貢献をしました。そのおかげで、より安全な認証システムを構築することができました。
文書の初めに述べたように、フロントエンドでの認証処理はまだ十分に成熟した分野ではありません。インターネット上には認証実装方法に関する様々なガイドがありますが、実際にはセキュリティ的に脆弱なものが多いため、正しい情報を選別するのは簡単ではありません。
QueryPieで実際にフロントエンド開発者が書いたコードにどのような脅威があるのか、いくつかの例を通じてフロントエンドコードにおける脅威を診断し、ベストプラクティスを提案します。この文書を読んだ後は、もうフロントエンドでの認証処理について悩む必要はなくなるでしょう。
例 - 初心者フロントエンド開発者によるSPA認証の実装
以下は、Single Page Application(SPA)でよく見られるログインと認証が必要な API リクエストのコードです。
async function login(id, pw) { const res = await fetch('<cross_origin_api_url>/api/auth', { method: 'POST', body: encrypt({id, pw}), mode: 'cors' }); const token = await res.json(); localStorage.setItem('accessToken', token.accessToken);} async function getProtectedResource(id) { const accessToken = localStorage.getItem('accessToken'); const res = fetch(`<cross_origin_api_url>/api/protected-resource/${id}`, { headers: { Authorization: `Bearer ${accessToken}` }, mode: 'cors' });}このコードには以下のような履歴があります:
バックエンドとのAPI規約:
Authorizationヘッダーにトークンを含める。Access-Control-Allow-Originヘッダーは開発の便宜上、*に設定されている。
トークンの永続性の実装:
localStorageAPIを使用してトークンを保存。
上記のコードと履歴を見た場合、露呈する脅威は以下の通りです。