AWS Penetration Testing Checklist 2026 — 80 Tests Across IAM, S3, EC2, Lambda, EKS, RDS
Published May 19, 2026 · By AxVeil Cloud · 18 min read
This is the field checklist we hand to consultants on day one of an AWS engagement. Eighty discrete tests, grouped by the eight service families that consistently surface material findings in 2026 — IAM, S3, EC2, Lambda, EKS, RDS, CloudTrail / GuardDuty, and KMS / Secrets Manager. Each test ships with the exact command (aws CLI, Prowler, or Pacu), the output you should expect when something is wrong, and the remediation that closes the gap. For the engagement-level walkthrough — scope, recon, the Rhino IAM privesc map, EBS exfil — pair this checklist with our AWS Pentesting Methodology post.
Scope clarification — what AWS allows without pre-authorisation
Since 2018 AWS has not required prior approval for customers to test their own resources within the permitted-services list, and the 2023 refresh of the AWS Penetration Testing Policykept that posture. The currently published list of services you may test without filing the simulated-event form is short and worth memorising: EC2 instances and NAT gateways and Elastic Load Balancers, RDS, CloudFront, Aurora, API Gateways, AWS Lambda and Lambda Edge functions, Lightsail resources, and Elastic Beanstalk environments. Test only resources owned by AWS accounts in your engagement scope.
Prohibited regardless of service: DNS zone walking against Route 53, denial of service or simulated DoS, port flooding, protocol flooding, request flooding, and anything that targets the AWS service itself (the control plane availability) rather than your application running on top. If any of those is part of the test plan — including red-team exercises that involve simulated availability impact — file the simulated event form via AWS Support and obtain written authorisation before any traffic generates. Re-read the policy URL at scoping time; AWS revises the permitted list periodically and a screenshot from a prior engagement is not authority.
For multi-cloud engagements, the same scope discipline applies across GCP and Azure with their own policies. See our cloud pentesting methodology for the cross-cloud scope contract.
Pre-flight — credentials, profile, and a clean run log
Use a dedicated engagement IAM user or assume-role profile, not your everyday console identity. Configure a named AWS CLI profile so every command is attributable in CloudTrail and every artefact is reproducible. Tag every test artefact (snapshots, IAM users created in demonstrations, S3 objects written during testing) with axveil-engagement so cleanup is mechanical.
# Dedicated engagement profile
aws configure --profile axveil-engagement
# Verify identity before doing anything destructive
aws sts get-caller-identity --profile axveil-engagement
# Set the regions Prowler and Pacu should scan
export AWS_REGIONS="us-east-1,us-east-2,eu-west-1,ap-south-1"
# Prowler full sweep -- the baseline you triage from
prowler aws --profile axveil-engagement \
--output-formats html json-asff \
--output-directory ./prowler-out
# Pacu session
pacu
> import_keys axveil-engagement
> set_regions us-east-1 us-east-2 eu-west-1 ap-south-1
> run aws__enum_account1. IAM — root account, access keys, privesc primitives (Tests 1-10)
IAM is the AWS control plane. Every breach in our 2024-2026 retainer data either started in IAM or ended in IAM. Run all ten tests on the production payer account before touching workloads.
Test 1 — Root account MFA enforcement
aws iam get-account-summary --profile axveil-engagement \
--query 'SummaryMap.AccountMFAEnabled'
# Expected (bad): 0
# Expected (good): 1Remediation: enable a hardware MFA device on the root account, lock the device in a safe, and use the root account only for the documented short list of root-only tasks.
Test 2 — Root account access keys present
aws iam get-account-summary --profile axveil-engagement \
--query 'SummaryMap.AccountAccessKeysPresent'
# Expected (bad): 1 (root has access keys)
# Expected (good): 0Remediation: delete root access keys; root should authenticate interactively via MFA only.
Test 3 — IAM Access Analyzer enabled in every region
for r in $(echo $AWS_REGIONS | tr ',' ' '); do
echo "== $r =="
aws accessanalyzer list-analyzers --region $r --profile axveil-engagement \
--query 'analyzers[].name'
done
# Expected (bad): empty list in any region
# Expected (good): one organisation-level analyzer in each regionTest 4 — Unused IAM access keys older than 90 days
aws iam generate-credential-report --profile axveil-engagement
aws iam get-credential-report --profile axveil-engagement \
--query 'Content' --output text | base64 -d | \
awk -F',' 'NR>1 && $11=="true" && $14 < "'$(date -u -d '90 days ago' +%Y-%m-%dT%H:%M:%S)'"'
# Reports rows where access_key_1_active=true and last_used older than 90 days.Remediation: rotate or delete; add a Config rule for access-keys-rotated.
Test 5 — Users with both console and programmatic access
aws iam list-users --profile axveil-engagement --query 'Users[].UserName' \
--output text | xargs -n1 -I{} sh -c \
'echo "{}: $(aws iam get-login-profile --user-name {} 2>/dev/null && \
aws iam list-access-keys --user-name {} --query AccessKeyMetadata[].AccessKeyId --output text)"'Remediation: separate human (SSO + console) from machine (access key) identities.
Test 6 — Pacu privilege-escalation scan
pacu
> run iam__privesc_scan
# Reports each potential escalation primitive ranked by likely success,
# including iam:CreateAccessKey, iam:AttachUserPolicy, iam:PassRole, etc.The full Rhino Security Labs escalation map is documented in our methodology post.
Test 7 — iam:PassRole + service-create primitives
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/foothold-user \
--action-names iam:PassRole ec2:RunInstances lambda:CreateFunction \
ecs:RegisterTaskDefinition glue:CreateJob codebuild:CreateProject \
--profile axveil-engagement \
--query 'EvaluationResults[?EvalDecision==`allowed`].EvalActionName'Remediation: scope iam:PassRole to specific role ARNs only; use Condition on iam:PassedToService.
Test 8 — Wildcard managed policies in use
aws iam list-policies --scope Local --only-attached --profile axveil-engagement \
--query 'Policies[].Arn' --output text | xargs -n1 -I{} sh -c \
'aws iam get-policy-version --policy-arn {} --version-id \
$(aws iam get-policy --policy-arn {} --query Policy.DefaultVersionId --output text) \
--query "PolicyVersion.Document" | grep -E "\"(\*|Resource\".*\"\\*\")" && echo "WILDCARD: {}"'Test 9 — Role trust policies open to Principal: "*"
aws iam list-roles --profile axveil-engagement \
--query 'Roles[?AssumeRolePolicyDocument.Statement[?Principal==`*`||Principal.AWS==`*`]].RoleName'Remediation: tighten trust to specific account IDs; for OIDC federation, scope the sub claim to a specific repo and branch (GitHub Actions) or specific workload identity.
Test 10 — SCP coverage on the organisation
aws organizations list-policies --filter SERVICE_CONTROL_POLICY \
--profile axveil-engagement --query 'Policies[].Name'
# Expected (bad): only "FullAWSAccess"
# Expected (good): deny policies for IMDSv1, snapshot share outside org,
# CloudTrail tamper, root use, region lockdown.2. S3 — public buckets, ACLs, presigned URLs, MFA-delete (Tests 11-20)
S3 misconfigurations are still in the top five global breach causes in 2026 despite Block Public Access being on by default since 2023. Test every account, every region.
Test 11 — Account-level Block Public Access
aws s3control get-public-access-block --account-id 123456789012 \
--profile axveil-engagement
# All four flags should be true; any false is a finding.Test 12 — Bucket-level Block Public Access
aws s3api list-buckets --profile axveil-engagement --query 'Buckets[].Name' \
--output text | xargs -n1 -I{} sh -c \
'aws s3api get-public-access-block --bucket {} 2>&1 | grep -q false && echo "WEAK: {}"'Test 13 — Public bucket policy
aws s3api list-buckets --profile axveil-engagement --query 'Buckets[].Name' \
--output text | xargs -n1 -I{} sh -c \
'aws s3api get-bucket-policy --bucket {} --query Policy --output text 2>/dev/null \
| grep -E "\"Principal\".*\"\\*\"" && echo "PUBLIC POLICY: {}"'Test 14 — AllUsers / AuthenticatedUsers ACL grants
aws s3api list-buckets --profile axveil-engagement --query 'Buckets[].Name' \
--output text | xargs -n1 -I{} sh -c \
'aws s3api get-bucket-acl --bucket {} \
--query "Grants[?Grantee.URI!=null].Grantee.URI" --output text \
| grep -E "(AllUsers|AuthenticatedUsers)" && echo "OPEN ACL: {}"'Test 15 — Server-side encryption configured
aws s3api list-buckets --profile axveil-engagement --query 'Buckets[].Name' \
--output text | xargs -n1 -I{} sh -c \
'aws s3api get-bucket-encryption --bucket {} >/dev/null 2>&1 || echo "NO SSE: {}"'Test 16 — Versioning + MFA-delete on sensitive buckets
aws s3api get-bucket-versioning --bucket axveil-target-prod-backups \
--profile axveil-engagement
# Expected (good): Status: Enabled, MFADelete: Enabled
# Without MFA-delete, an attacker with s3:DeleteBucket and s3:PutBucketVersioning
# can wipe versioned objects irrecoverably.Test 17 — Presigned URL leak surface
Grep application source for generate_presigned_url / getSignedUrl calls and assess whether the URL TTL is short and the surface is logged. Long-TTL presigned URLs pasted into Slack or Jira are a recurring data-loss finding.
Test 18 — Cross-account read in bucket policy
aws s3api get-bucket-policy --bucket axveil-target-prod-backups \
--query Policy --output text --profile axveil-engagement | jq '.Statement[] | \
select(.Effect=="Allow") | .Principal'
# Any external account ID in Principal that is not in the org is a finding.Test 19 — Object Ownership = BucketOwnerEnforced
aws s3api get-bucket-ownership-controls --bucket {} --profile axveil-engagement \
--query 'OwnershipControls.Rules[].ObjectOwnership'
# Expected (good): "BucketOwnerEnforced" (disables ACLs entirely).Test 20 — Access logging enabled, written to a separate logging account
aws s3api get-bucket-logging --bucket {} --profile axveil-engagement
# No LoggingEnabled object => no S3 access logs => post-incident forensics blind spot.3. EC2 — IMDSv2, security groups, SSM, snapshots (Tests 21-30)
Test 21 — IMDSv2 enforced (HttpTokens=required)
aws ec2 describe-instances --profile axveil-engagement \
--query 'Reservations[].Instances[?MetadataOptions.HttpTokens!=`required`].InstanceId'
# Any returned instance still accepts IMDSv1 -- SSRF-to-credential primitive open.Test 22 — IMDS HTTP hop limit = 1
aws ec2 describe-instances --profile axveil-engagement \
--query 'Reservations[].Instances[?MetadataOptions.HttpPutResponseHopLimit>`1`].InstanceId'
# Limit > 1 lets containers and second-hop processes reach IMDS.Test 23 — Security groups open 0.0.0.0/0 on sensitive ports
aws ec2 describe-security-groups --profile axveil-engagement \
--query 'SecurityGroups[?IpPermissions[?IpRanges[?CidrIp==`0.0.0.0/0`] \
&& (FromPort==`22` || FromPort==`3389` || FromPort==`3306` || FromPort==`5432`)]].GroupId'Test 24 — SG with overly broad CIDR (/22 or wider on management ports)
# Any IpRange whose CidrIp prefix length is <= 22 is suspicious for SSH/RDP.
aws ec2 describe-security-groups --profile axveil-engagement \
--query 'SecurityGroups[].IpPermissions[?FromPort==`22`].IpRanges[?contains(CidrIp,`/2`)]'Test 25 — SSM agent reachable / patching enabled
aws ssm describe-instance-information --profile axveil-engagement \
--query 'InstanceInformationList[].InstanceId'
# Compare to ec2 describe-instances; any EC2 missing from SSM = unpatched, unmanaged.Test 26 — Public EC2 instances with attached IAM role
aws ec2 describe-instances --profile axveil-engagement \
--query 'Reservations[].Instances[?PublicIpAddress!=null && IamInstanceProfile!=null].\
[InstanceId, IamInstanceProfile.Arn, PublicIpAddress]' --output tableTest 27 — Snapshots shared publicly
aws ec2 describe-snapshots --owner-ids self --restorable-by-user-ids all \
--profile axveil-engagement --query 'Snapshots[].SnapshotId'
# Any returned ID is publicly restorable -- treat as a data breach pending review.Test 28 — AMIs shared publicly
aws ec2 describe-images --owners self --profile axveil-engagement \
--query 'Images[?Public==`true`].[ImageId,Name]' --output tableTest 29 — EBS volumes unencrypted
aws ec2 describe-volumes --profile axveil-engagement \
--query 'Volumes[?Encrypted==`false`].[VolumeId,Attachments[0].InstanceId]'
# EBS-default-encryption should also be on at the account level:
aws ec2 get-ebs-encryption-by-default --profile axveil-engagementTest 30 — Default VPC in active use
aws ec2 describe-vpcs --filters Name=isDefault,Values=true \
--profile axveil-engagement --query 'Vpcs[].VpcId'
# Any instance in the default VPC is a hygiene finding -- prefer named VPCs.4. Lambda — env vars, role over-permission, DLQ (Tests 31-40)
Test 31 — Secrets in Lambda environment variables
aws lambda list-functions --profile axveil-engagement --query 'Functions[].FunctionName' \
--output text | xargs -n1 -I{} aws lambda get-function-configuration \
--function-name {} --query '{Name:FunctionName, Env:Environment.Variables}' \
| grep -iE "(password|secret|token|api[_-]?key|aws_secret)"Remediation: move to Secrets Manager or SSM Parameter Store with KMS encryption.
Test 32 — Lambda execution role with AdministratorAccess
aws lambda list-functions --profile axveil-engagement --query 'Functions[].Role' \
--output text | tr '\t' '\n' | sort -u | xargs -n1 -I{} \
aws iam list-attached-role-policies --role-name $(basename {}) \
--query 'AttachedPolicies[?PolicyArn==`arn:aws:iam::aws:policy/AdministratorAccess`]'Test 33 — Function URL with AuthType=NONE
aws lambda list-functions --profile axveil-engagement --query 'Functions[].FunctionName' \
--output text | xargs -n1 -I{} aws lambda get-function-url-config \
--function-name {} 2>/dev/null | jq 'select(.AuthType=="NONE")'Test 34 — Resource policy allowing Principal: "*"
aws lambda get-policy --function-name foo --profile axveil-engagement \
--query Policy --output text | jq '.Statement[] | select(.Principal=="*")'Test 35 — Dead-letter queue missing
aws lambda list-functions --profile axveil-engagement \
--query 'Functions[?DeadLetterConfig==null].FunctionName'
# Missing DLQ => silent failure => loss of audit trail on error paths.Test 36 — Code signing not enforced
aws lambda list-functions --profile axveil-engagement \
--query 'Functions[?CodeSigningConfigArn==null].FunctionName'Test 37 — Old runtimes (Python 3.7, Node 14, etc.)
aws lambda list-functions --profile axveil-engagement \
--query 'Functions[?Runtime==`python3.7` || Runtime==`nodejs14.x` \
|| Runtime==`nodejs12.x`].FunctionName'Test 38 — VPC-attached Lambda with overly broad egress SG
aws lambda list-functions --profile axveil-engagement \
--query 'Functions[?VpcConfig.SecurityGroupIds!=null].\
[FunctionName,VpcConfig.SecurityGroupIds]'Test 39 — Lambda layers from third-party publishers
aws lambda list-functions --profile axveil-engagement \
--query 'Functions[?Layers!=null].[FunctionName,Layers[].Arn]'
# Cross-account layer ARNs are a supply-chain finding.Test 40 — X-Ray tracing / CloudWatch logging missing
aws lambda list-functions --profile axveil-engagement \
--query 'Functions[?TracingConfig.Mode!=`Active`].FunctionName'5. EKS — RBAC, hostNetwork, IRSA, public API server (Tests 41-50)
Test 41 — EKS cluster public endpoint exposed
aws eks list-clusters --profile axveil-engagement --query 'clusters' \
--output text | xargs -n1 -I{} aws eks describe-cluster --name {} \
--query 'cluster.resourcesVpcConfig.endpointPublicAccess'
# Expected (good): false, or with publicAccessCidrs restricted to office/VPN.Test 42 — Control plane logging types complete
aws eks describe-cluster --name prod --profile axveil-engagement \
--query 'cluster.logging.clusterLogging'
# All five (api, audit, authenticator, controllerManager, scheduler) should be enabled.Test 43 — aws-auth ConfigMap maps to broad IAM roles
kubectl --context prod -n kube-system get cm aws-auth -o yaml | grep -E "rolearn|groups"
# Look for "system:masters" group bound to anything other than an emergency role.Test 44 — Pods with hostNetwork: true
kubectl --context prod get pods -A -o json | \
jq '.items[] | select(.spec.hostNetwork==true) | .metadata.namespace + "/" + .metadata.name'Test 45 — Pods with hostPID, hostIPC, or privileged: true
kubectl --context prod get pods -A -o json | \
jq '.items[] | select(.spec.hostPID==true or .spec.hostIPC==true or \
(.spec.containers[]?.securityContext.privileged==true)) | \
.metadata.namespace + "/" + .metadata.name'Test 46 — IRSA service accounts bound to over-permissive roles
kubectl --context prod get sa -A -o json | \
jq '.items[] | select(.metadata.annotations["eks.amazonaws.com/role-arn"]) | \
{ns: .metadata.namespace, sa: .metadata.name, \
role: .metadata.annotations["eks.amazonaws.com/role-arn"]}'
# For each, evaluate the IAM role's attached policies:
aws iam list-attached-role-policies --role-name <role> --profile axveil-engagementTest 47 — RBAC over-permission — cluster-admin bindings
kubectl --context prod get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") | \
{name: .metadata.name, subjects: .subjects}'Test 48 — Default service account used by workload pods
kubectl --context prod get pods -A -o json | \
jq '.items[] | select(.spec.serviceAccountName=="default" or .spec.serviceAccount==null) | \
.metadata.namespace + "/" + .metadata.name'Test 49 — Worker node instance role reachable from pods
From a pentest pod, attempt to reach the worker node IMDS. If the EKS launch template did not set HttpPutResponseHopLimit=1, pods can mint node-role credentials.
# Inside a test pod
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/Test 50 — PodSecurity admission set to restricted in user namespaces
kubectl --context prod get ns -o json | \
jq '.items[] | select(.metadata.labels["pod-security.kubernetes.io/enforce"]!="restricted") | \
.metadata.name'6. RDS — encryption, snapshots, IAM auth (Tests 51-60)
Test 51 — RDS instances unencrypted at rest
aws rds describe-db-instances --profile axveil-engagement \
--query 'DBInstances[?StorageEncrypted==`false`].DBInstanceIdentifier'Test 52 — RDS snapshots shared publicly
aws rds describe-db-snapshots --snapshot-type public \
--profile axveil-engagement --query 'DBSnapshots[].DBSnapshotIdentifier'
# Any owned, public snapshot is a likely data breach.Test 53 — RDS instances with PubliclyAccessible=true
aws rds describe-db-instances --profile axveil-engagement \
--query 'DBInstances[?PubliclyAccessible==`true`].[DBInstanceIdentifier,Endpoint.Address]'Test 54 — IAM database authentication enabled
aws rds describe-db-instances --profile axveil-engagement \
--query 'DBInstances[?IAMDatabaseAuthenticationEnabled==`false`].DBInstanceIdentifier'Test 55 — Automated backups disabled or short retention
aws rds describe-db-instances --profile axveil-engagement \
--query 'DBInstances[?BackupRetentionPeriod<`7`].DBInstanceIdentifier'Test 56 — Deletion protection off on production instances
aws rds describe-db-instances --profile axveil-engagement \
--query 'DBInstances[?DeletionProtection==`false`].DBInstanceIdentifier'Test 57 — Aurora clusters with public endpoint
aws rds describe-db-clusters --profile axveil-engagement \
--query 'DBClusters[?PubliclyAccessible==`true`].DBClusterIdentifier'Test 58 — Default master username (admin, postgres, root)
aws rds describe-db-instances --profile axveil-engagement \
--query 'DBInstances[?MasterUsername==`admin` || MasterUsername==`postgres` \
|| MasterUsername==`root`].DBInstanceIdentifier'Test 59 — Enhanced monitoring + Performance Insights
aws rds describe-db-instances --profile axveil-engagement \
--query 'DBInstances[?MonitoringInterval==`0` || PerformanceInsightsEnabled==`false`].\
DBInstanceIdentifier'Test 60 — Security group permits 0.0.0.0/0 to DB port
aws ec2 describe-security-groups --profile axveil-engagement \
--query 'SecurityGroups[?IpPermissions[?IpRanges[?CidrIp==`0.0.0.0/0`] && \
(FromPort==`3306` || FromPort==`5432` || FromPort==`1433` || FromPort==`27017`)]].GroupId'7. CloudTrail and GuardDuty — event coverage and detection gaps (Tests 61-70)
Test 61 — Multi-region CloudTrail enabled
aws cloudtrail describe-trails --profile axveil-engagement \
--query 'trailList[?IsMultiRegionTrail==`true`].Name'
# Expected (good): at least one multi-region trail in the management account.Test 62 — Log file validation enabled
aws cloudtrail describe-trails --profile axveil-engagement \
--query 'trailList[?LogFileValidationEnabled==`false`].Name'Test 63 — CloudTrail S3 destination in separate logging account
aws cloudtrail describe-trails --profile axveil-engagement \
--query 'trailList[].[Name,S3BucketName]'
# Bucket should live in a logging account, not the same account as the trail.Test 64 — Data events (S3, Lambda) recorded for sensitive resources
aws cloudtrail get-event-selectors --trail-name org-trail --profile axveil-engagement
# Look for DataResources entries for production buckets and Lambdas.Test 65 — CloudTrail Insights enabled
aws cloudtrail get-insight-selectors --trail-name org-trail --profile axveil-engagement
# Looks for ApiCallRateInsight selectors -- detects anomalous API spikes.Test 66 — GuardDuty enabled across all regions
for r in $(echo $AWS_REGIONS | tr ',' ' '); do
aws guardduty list-detectors --region $r --profile axveil-engagement --query 'DetectorIds'
doneTest 67 — GuardDuty protection plans active (S3, EKS, RDS, Lambda, Malware)
DETECTOR=$(aws guardduty list-detectors --query 'DetectorIds[0]' --output text)
aws guardduty get-detector --detector-id $DETECTOR --profile axveil-engagement \
--query 'Features[].[Name,Status]' --output tableTest 68 — Security Hub aggregating org findings
aws securityhub describe-hub --profile axveil-engagement
aws securityhub get-enabled-standards --profile axveil-engagement \
--query 'StandardsSubscriptions[].StandardsArn'
# Expected (good): AWS Foundational, CIS, PCI DSS standards enabled.Test 69 — SCP denies CloudTrail tamper
Check the organisation SCPs for an explicit deny on cloudtrail:StopLogging, cloudtrail:DeleteTrail, and cloudtrail:UpdateTrail. Absence is a structural finding.
Test 70 — Custom detection gaps
GuardDuty is necessary but not sufficient. The 2024-2026 MITRE ATT&CK Cloud Matrix lists ten primitives (e.g. T1078.004 Valid Accounts: Cloud Accounts, T1098.001 Account Manipulation: Additional Cloud Credentials) that GuardDuty does not cover end-to-end. Build custom detections in your SIEM for the gaps. NIST SP 800-204D and the MITRE ATT&CK enterprise framework remain the canonical references.
8. KMS and Secrets Manager — key policy, rotation, cross-account (Tests 71-80)
Test 71 — KMS key policy allows broad principals
aws kms list-keys --profile axveil-engagement --query 'Keys[].KeyId' --output text | \
xargs -n1 -I{} sh -c 'aws kms get-key-policy --key-id {} --policy-name default \
--query Policy --output text | jq ".Statement[] | select(.Principal==\"*\" or \
.Principal.AWS==\"*\")"'Test 72 — Customer-managed KMS key rotation enabled
aws kms list-keys --profile axveil-engagement --query 'Keys[].KeyId' --output text | \
xargs -n1 -I{} aws kms get-key-rotation-status --key-id {} \
--query 'KeyRotationEnabled' --output textTest 73 — KMS keys pending deletion
aws kms list-keys --profile axveil-engagement --query 'Keys[].KeyId' --output text | \
xargs -n1 -I{} aws kms describe-key --key-id {} \
--query 'KeyMetadata[?KeyState==`PendingDeletion`].[KeyId,DeletionDate]'Test 74 — Cross-account access in KMS key grants
aws kms list-grants --key-id <id> --profile axveil-engagement \
--query 'Grants[?GranteePrincipal!=null].[GranteePrincipal,Operations]'Test 75 — Secrets Manager automatic rotation disabled
aws secretsmanager list-secrets --profile axveil-engagement \
--query 'SecretList[?RotationEnabled==`false` || RotationEnabled==null].Name'Test 76 — Secrets Manager resource policy with cross-account access
aws secretsmanager list-secrets --profile axveil-engagement --query 'SecretList[].Name' \
--output text | xargs -n1 -I{} aws secretsmanager get-resource-policy --secret-id {} \
--query 'ResourcePolicy' --output text | jq '.Statement[]?.Principal'Test 77 — SSM Parameter Store SecureString without KMS CMK
aws ssm describe-parameters --profile axveil-engagement \
--query 'Parameters[?Type==`SecureString` && KeyId==`alias/aws/ssm`].Name'
# Parameters using the AWS-managed default key cannot enforce per-key policy --
# move sensitive parameters to a customer-managed key.Test 78 — Secrets older than 90 days without rotation
aws secretsmanager list-secrets --profile axveil-engagement \
--query "SecretList[?LastRotatedDate<'$(date -u -d '90 days ago' +%Y-%m-%dT%H:%M:%S)'].Name"Test 79 — KMS keys without alias
aws kms list-aliases --profile axveil-engagement --query 'Aliases[].TargetKeyId' --output text \
| sort > /tmp/aliased
aws kms list-keys --profile axveil-engagement --query 'Keys[].KeyId' --output text \
| tr '\t' '\n' | sort > /tmp/all
comm -23 /tmp/all /tmp/aliased
# Keys without an alias are operational debt -- usually orphaned, sometimes deliberately hidden.Test 80 — CloudHSM cluster posture (where applicable)
aws cloudhsmv2 describe-clusters --profile axveil-engagement \
--query 'Clusters[].[ClusterId,State,SubnetMapping]'
# Confirm the cluster is in PRIVATE subnets and policy enforces FIPS 140-2 / 140-3.Reference research and known misconfiguration patterns
The patterns this checklist hunts for are not invented. The canonical references — useful both as scoping evidence and as report citations — are:
- NIST SP 800-204D— strategies for the integration of software supply chain security in DevSecOps CI/CD pipelines. Useful for the Lambda layer and IAM trust findings.
- NIST SP 800-53 Rev. 5— the master control catalog SOC 2 and PCI examiners trace back to; AC-2, AC-3, AC-6, AU-12, SC-12, SC-13 map directly to the IAM, logging, and KMS tests above.
- MITRE ATT&CK Cloud (IaaS) Matrix— tactic/technique IDs to cite per finding. Tests 6-9 map to T1078.004 and T1098.001/.003; tests 21-22 to T1552.005 (Unsecured Credentials: Cloud Instance Metadata API); tests 27-28 to T1537 (Transfer Data to Cloud Account).
- NVD— for specific CVEs reachable from the cloud workload tests (e.g. container kernel CVEs surfaced when IRSA + privileged pods combine). Never cite a CVE you cannot find in NVD; the audit committee will check.
Mapping findings to SOC 2, PCI DSS, and ISO 27001
Customers paying for the engagement want the audit-language version of every finding. The mapping we use in deliverables:
- IAM tests (1-10) — SOC 2 CC6.1 (logical access), CC6.2 (provisioning), CC6.3 (privilege management); PCI DSS v4.0 7.2 and 8.2; ISO/IEC 27001:2022 Annex A.5.15, A.5.18, A.8.2.
- S3 tests (11-20) — SOC 2 CC6.1, CC6.6(system boundaries); PCI DSS 1.4.2 (untrusted networks), 3.5(storage); ISO 27001 A.8.9 (configuration management), A.8.24 (cryptography).
- EC2 tests (21-30) — SOC 2 CC6.6, CC7.1 (detection of vulnerabilities); PCI DSS 1.2, 1.3, 2.2; ISO 27001 A.8.20, A.8.22, A.8.9.
- Lambda tests (31-40) — SOC 2 CC6.1, CC8.1 (change management); PCI DSS 6.2; ISO 27001 A.8.25, A.8.28.
- EKS tests (41-50) — SOC 2 CC6.1, CC6.6, CC7.2; PCI DSS 1.2, 6.4.2; ISO 27001 A.8.9, A.8.22.
- RDS tests (51-60) — SOC 2 CC6.1, CC6.6; PCI DSS 3.5, 1.2; ISO 27001 A.8.24, A.8.20.
- CloudTrail / GuardDuty (61-70) — SOC 2 CC7.2(monitoring) and CC7.3 (incident response); PCI DSS 10.2, 10.3, 10.6; ISO 27001 A.8.15, A.8.16.
- KMS / Secrets Manager (71-80) — SOC 2 CC6.1, CC6.7 (transmission); PCI DSS 3.6, 3.7; ISO 27001 A.8.24, A.8.10.
For the score-and-prioritise step on each finding, use the CVSS calculator with the cloud-specific environmental metrics (Modified Attack Vector, Modified Scope).
FAQ
Do we still need to file a simulated-event request before pentesting our AWS account in 2026?
For the eight permitted services AWS lists (EC2, RDS, CloudFront, Aurora, API Gateway, Lambda, Lightsail, Elastic Beanstalk) you do not need pre-authorisation as long as you only test resources you own and you stay clear of prohibited activities — DNS zone walking, denial of service, port/protocol/request flooding, and anything that hits the AWS service itself. Anything outside that list, or any simulated availability impact, still requires the simulated event form. The policy page changes; check it before scoping.
How is this checklist different from the AWS pentesting methodology blog?
The methodology post walks the engagement end-to-end — recon, IAM privesc patterns, IMDS, EBS exfiltration. This checklist is the field-card version: 80 discrete tests grouped by service, each with the exact command, expected output, and a fix. Use the methodology to scope, use this checklist during execution.
Can Prowler and Pacu replace a manual pentest?
No. Prowler and ScoutSuite catch about 70% of well-known control-plane misconfigurations on a clean sweep. Pacu automates the privilege-escalation primitives Rhino Security Labs documented. Neither tool reasons about your trust graph, your custom OIDC federation, or the business logic that decides which Lambda gets which secret. Use the tools to clear the obvious findings fast, then manually audit IAM trust, KMS key policy, and cross-account access paths.
Which of these tests is the single highest-impact for a SaaS company?
Enforce IMDSv2 on every EC2 instance and set the hop limit to 1 (test 23 in this checklist). One control closes the entire SSRF-to-IAM-credential chain that caused Capital One, ChannelMix, and dozens of smaller breaches. Pair it with a CloudTrail alert on iam:CreateAccessKey from any role other than your approved automation principals.
How do these 80 findings map back to our audit framework?
Each finding category maps to common controls — IAM and access tests to SOC 2 CC6.1 and PCI DSS 7.x and 8.x, network exposure tests to PCI 1.2 and ISO/IEC 27001:2022 Annex A.8.20, encryption tests to A.8.24 and PCI 3.x, logging tests to CC7.2 and PCI 10.x. The closing section of this post lists the mappings. Your auditor will still want narrative evidence, not just findings — keep the Pacu and aws CLI output as engagement artefacts.
Further reading
- AWS Pentesting Methodology — End-to-End Playbook
- Cloud Pentesting Methodology
- Cloud Misconfiguration Top 10 — 2026
- AxVeil VAPT service
- AxVeil for SaaS
- CVSS calculator
Run all 80 tests with AxVeil.
Authenticated AWS configuration review plus exploitation testing across IAM, S3, EC2, Lambda, EKS, RDS, CloudTrail, and KMS — one engagement, audit-ready deliverable.
Talk to us about scoping →