はじめに
本記事では、Webアプリケーションの脆弱性の1つであるオープンリダイレクトの概要とその対策方法を記載します。
特に、自身のアプリケーション実装のみでは対応できないパターンとして、Azure Active Directory B2C(以降、AADB2C)のログアウトURLに対するオープンリダイレクト対策をお伝えします。
対象読者
- オープンリダイレクトの概要を知りたい方
- AADB2Cで認証しているWebアプリケーションのオープンリダイレクト対策を知りたい方
オープンリダイレクトとは
オープンリダイレクトは、Webアプリケーションがユーザーを任意の外部サイトにリダイレクトできてしまう脆弱性です。
例えば以下のようなURLがあるとします。
https://example.com/logout?url=https://example.com/home
これはログアウト後の遷移先URLをurlクエリパラメータで指定しているイメージです。このurlですが、何も対策が施されていないと任意のURLに改ざんすることができてしまいます。したがって、以下のような流れでフィッシングに悪用される可能性があります。
- 攻撃者はあらかじめ
https://exanple.comのような、正規サイトと誤認しやすい罠サイトを用意しておく - 攻撃者は
https://example.com/logout?redirect=https://exanple.comにアクセスさせる罠ページやリンクを用意し、メールやSNS等で誘導する - 被害者が罠ページやリンクにアクセスすると、
https://exanple.comにリダイレクトされてしまう - 被害者がリダイレクト先で認証情報等を入力してしまい、情報流出
その他、リダイレクト先でマルウェアをダウンロードさせられてしまう、正規サイトのドメインから不正サイトに飛ばされることによるブランドの信頼失墜など、様々な危険性を孕む脆弱性がオープンリダイレクトです。
このオープンリダイレクトですが、自身のアプリケーションで対応できる場合とできない場合があります。
自身のアプリケーションで対応可能な場合
自身のアプリケーション実装でリダイレクト処理を制御可能な場合は、以下のような対処を行います。
-
リダイレクト先が固定の場合、リダイレクト先URLを固定値にする
リダイレクト先URLが外部入力に依存しなくなるため、改ざんの余地がなくなります。 -
リダイレクト先が自ドメイン内(ローカル)のみで良い場合、ローカルのみに限定する
例えばリダイレクト先を自ドメインの相対パスのみに制限することで、外部サイトへのリダイレクトを防止します。
また、ASP.NET Coreの場合、LocalRedirectメソッドが用意されています。LocalRedirectはリダイレクト先にローカルではないURLを指定すると例外をスローします。
https://learn.microsoft.com/ja-jp/aspnet/core/security/preventing-open-redirects?view=aspnetcore-9.0&source=recommendations -
リダイレクト先のURLを直接指定せず、ページ番号を指定する
ページ番号とURLの対応をデータベース等で管理しておき、外部入力はページ番号を指定するようにします。実際のリダイレクト時は入力されたページ番号からリダイレクト先URLを取得する形式になるため、任意のURLに飛ばされることがなくなり、オープンリダイレクト攻撃を回避できます。 -
リダイレクト先URLを検証する
アプリケーション側でリダイレクト先URLをチェックし、不正な入力を弾くロジックを実装します。しかしながら、この対処は落とし穴が多く、書籍「体系的に学ぶ 安全なWebアプリケーションの作り方」でも他の方法が推奨されています。そのため、本記事では検証ロジックの具体的な実装は割愛します。
自身のアプリケーションのみでは対応できない場合
上記は自身のアプリケーションの設計や実装でオープンリダイレクトを回避できるパターンでした。一方で、自身のアプリケーションのみでは完全にコントロールできない場合があります。
例えば認証にAADB2Cを利用している場合、ログアウト時のURLは以下のような形式になります。このURLはAADB2Cの仕様で規定されるものであり、自身のアプリケーションで制御することはできません。
https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/<policy_name>/oauth2/v2.0/logout?post_logout_redirect_uri=<redirect_uri>
ログアウト先の遷移先は post_logout_redirect_uri パラメータで指定します。
post_logout_redirect_uriは任意の値を指定することができるため、改ざんによってオープンリダイレクト攻撃の対象となってしまいます。
この対処策は以下の公式ドキュメントに記載があります。
Azure Active Directory B2C でセッションの動作を構成する
ログアウト後、ユーザーは、アプリケーションに対して指定されている応答 URL に関係なく、post_logout_redirect_uri パラメーターに指定された URI にリダイレクトされます。 ただし、有効な id_token_hint が渡され、 [ログアウト要求に ID トークンが必要] が有効になっている場合、Azure AD B2C では、リダイレクトの実行前に、post_logout_redirect_uri の値がいずれかのアプリケーションの構成済みのリダイレクト URI と一致するかどうかが検証されます。 一致する応答 URL がアプリケーションで構成されていない場合は、エラー メッセージが表示され、ユーザーはリダイレクトされません。
つまり、ログアウト時にIDトークンを付与することで、AADB2Cの「アプリの登録」-「認証」の「リダイレクトURI」に設定したURIとpost_logout_redirect_uriを突合できるようになります。これにより、「リダイレクトURI」に設定されていない不正なURLを指定してもエラーとなり、オープンリダイレクト攻撃を無効化できます。
以下では、カスタムポリシーの場合を例として具体的な設定を紹介します。
設定例
カスタムポリシー設定
設定したい対象のカスタムポリシーのxmlファイルに対し、 EnforceIdTokenHintOnLogout="true" の設定を記述します。これにより、ログアウト時にid_token_hintパラメータの指定が必須になります。
<UserJourneyBehaviors> <SingleSignOn Scope="Tenant" EnforceIdTokenHintOnLogout="true"/> </UserJourneyBehaviors>
リダイレクトURI設定
リダイレクトURIの指定はAADB2Cの「アプリの登録」で登録済みのアプリを選択し、「認証」メニューの「リダイレクトURI」にログアウト先URLを設定します。
以下の画面はシングルページアプリケーションのアプリでの設定画面です。ここでは開発環境を想定してhttp://localhost:8080 を登録しています。
IDトークン設定
クライアント側でログアウトURLにid_token_hint を渡すようにします。
ここではMSAL.js(Microsoft Authentication Library for JavaScript)を利用したログアウト処理のコードを例示します。
import * as msal from '@azure/msal-browser'
const msalConfig = {
auth: {
clientId: "<アプリのクライアントID>",
authority: "https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/<policy_name>",
redirectUri: "http://localhost:8080",
},
};
const msalInstance = new msal.PublicClientApplication(msalConfig);
function logout() {
// idTokenを取得
const idTokenHint = msalInstance.getAllAccounts()[0].idToken;
const postLogoutRedirectUri = msalConfig.auth.postLogoutRedirectUri;
// ログアウト処理にidTokenHintを付与
msalInstance.logoutRedirect({
idTokenHint: idTokenHint,
postLogoutRedirectUri: postLogoutRedirectUri
})
}
検証
上記により、ログアウト時にid_token_hintを付与することでpost_logout_redirect_uriとAADB2Cで登録したリダイレクトURIが突合され、マッチしない場合はエラーとなるよう設定されました。
実際の挙動を確認してみましょう。
post_logout_redirect_uri に正しいURLを指定した場合
まずは正常系の動作を確認してみます。ログアウト処理実行時のURLは以下です。
id_token_hint が渡されていることが分かります。
https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/<policy_name>/oauth2/v2.0/logout
?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A8080
&client-request-id=<client-request-id>
&id_token_hint=<id_token>
ログアウト処理の結果、正常にhttp://localhost:8080 にリダイレクトされること確認できます(このアプリケーションは未認証でhttp://localhost:8080 にアクセスするとAADB2Cの認証画面へ遷移する仕様になっています)。
post_logout_redirect_uri に不正なURLを指定した場合
では、オープンリダイレクト攻撃を想定してpost_logout_redirect_uriに不正なURL(ここでは例としてhttps://www.google.co.jp)を渡してみましょう。
検証のため、上記JavaScriptのログアウト処理にブレイクポイントを設定し、post_logout_redirect_uri を書き換えています。
この状態でログアウト処理を進めると、以下のエラー画面となります。リダイレクトURIが登録されていない旨のエラーメッセージが表示されており、期待通りリダイレクトに失敗したことが分かります。
id_token_hintが不正の場合
id_token_hint が不正な場合はどうでしょうか。
例えば id_token_hint が改ざんされて、ログインユーザーのIDトークンとは異なる文字列が指定されたことを想定します。
post_logout_redirect_uri と同様、ログアウト処理にブレイクポイントを設定して id_token_hint を書き換えています。
ログアウト処理を進めると、以下のエラー画面になります。
id_token_hintのバリデーションに失敗し、こちらも期待通りリダイレクトエラーになることが確認できました。
ちなみに、id_token_hintがnullの場合は以下のエラーになります。
カスタムフローのEnforceIdTokenHintOnLogout="true"の設定どおり、id_token_hintが必須になっていることが確認できます。
まとめ
オープンリダイレクトの概要とその対策方法を紹介しました。オープンリダイレクトはフィッシング等に悪用されるため対策が必要です。AADB2Cの場合、既定のまま利用するとオープンリダイレクトの危険性があるため注意する必要があります。本記事で紹介した設定方法が参考になると幸いです。
参考
We Are Hiring!







