AWS Penetration Testing Notes: S3, IAM, and Lambda Misconfigurations
Common AWS findings: exposed S3 buckets, IAM misconfigurations, and Lambda weaknesses, grouped by service.
AWS Penetration Testing Notes: S3, IAM, and Lambda Misconfigurations#
Why this topic matters: most cloud findings are configuration findings. The services themselves hold up; the way teams connect them is where the audits start.
Ethics and scope: every check here assumes a written scope that names the AWS account, the regions, and the services under test. Do not probe accounts you do not own, and do not extract data beyond a proof of read access. For the web-side equivalents, see OWASP Top 10 notes.
S3: The Classic Exposure#
S3 shows up in almost every cloud audit. The patterns are stable.
Public access#
- Bucket policy grants public read.
- ACL grants
READ_ACPto everyone. - Static website hosting exposes directory listings.
- A CDN fronts a bucket and preserves the public policy.
Dangling buckets#
A CNAME that points at a deleted bucket is the same class of problem as a dangling subdomain, with the bucket name as the claimable resource.
cdn.example.com CNAME old-assets.s3.amazonaws.com
If old-assets.s3.amazonaws.com is gone, the name is claimable. That claim under your domain is a finding.
Triage questions#
- Does the bucket allow public reads?
- Does it allow public writes or listing?
- Is it referenced from DNS or docs?
- Is encryption on by default?
- Are access logs enabled?
IAM: The Perimeter#
IAM decides who can do what. The findings cluster around trust and policy breadth.
Over-broad policies#
Action: "*",Resource: "*"on a role used by a workload.AdministratorAccessattached to a human role.- Managed policies copied from tutorials without review.
Trust misconfigurations#
- A role assumable from a broad set of principals.
sts:AssumeRoletrusting a third-party account that changed hands.- Cross-account trusts that were never re-reviewed.
PassRole patterns#
PassRole lets one principal hand a role to another service. It is powerful and common, which is why it appears in escalation notes: a principal that can both pass a powerful role and create the service gets the service's powers.
What to enumerate#
| Asset | Question | Tool shape |
|---|---|---|
| Roles | Who can assume them? | iam list-roles plus trust docs |
| Policies | Any * actions on * resources? | Policy simulator |
| Users | MFA on console users? | Credential report |
| Groups | Direct policies vs inherited | Console or API listing |
| Access keys | Rotation and age | Last-used report |
Lambda: Serverless Attack Surface#
Lambda findings are code and configuration findings, in serverless clothes.
Handler input#
The event JSON is user input. Treat it like any other untrusted body:
- No validation of expected fields.
- Template injection in log lines or SQL built from event fields.
- Path traversal when events carry file names.
Permissions#
- Execution role with
s3:GetObjecton every bucket. - Environment variables holding long-lived secrets.
- VPC placement that grants unexpected network reach.
Supply chain#
- Dependencies pinned loosely.
- Layers from unverified sources.
- Build steps that do not record provenance.
Event shapes to probe#
# API Gateway event to a function {"pathParameters": {"id": "../../etc/passwd"}, "queryStringParameters": {...}} # S3 event to a function {"Records": [{"s3": {"object": {"key": "../../../tmp/x"}}}]}
Common Findings by Service#
| Service | Frequent finding | Fix direction |
|---|---|---|
| S3 | Public read/write bucket | Block public access, audit policies |
| IAM | AdministratorAccess on a role | Narrow to task actions |
| Lambda | Over-broad execution role | Least privilege per function |
| EC2 | Security group open to 0.0.0.0/0 | Restrict ingress, use SSM |
| RDS | Public endpoint enabled | Private subnets, no public IP |
| CloudTrail | Logging gap in a region | Enable in all regions |
| KMS | Over-broad key policies | Scope key use to one principal |
Tooling Notes#
- ScoutSuite: multi-cloud audit with a scored report.
- Pacu: exploitation framework for scoped AWS tests.
- Prowler: CIS benchmark style checks.
- CloudSploit: rule-based misconfiguration scan.
Each tool produces a list. The tester's job is to verify the list, remove false positives, and write what is actually exploitable in the scoped account.
A Scoped Test Flow#
- List the account's resources by region.
- Run the passive audit tool inside the authorized account.
- Verify each candidate with a minimal read, not a write.
- Record the exact resource ARN and the observed access.
- Write the fix, the owner, and the verification date.
Proof of Access, Not Theft#
For every bucket or key you believe is exposed, capture the minimum evidence:
- A directory listing head, not the data.
- A first record of a listing, not the listing.
- A failed and a successful call for the same resource.
Evidence that proves access without copying the contents is what a good report needs.
Limits of These Notes#
- AWS features change; the audit checklist ages.
- Some findings depend on the tenant and region of the account.
- A misconfiguration flagged by a scanner may be intentional.
- This is an audit guide, not an exploitation handbook.
IAM Deep Dive: The Trust Policy#
A role's trust policy says who can assume it. The interesting cases are the broad ones:
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"AWS": "arn:aws:iam::000000000000:root"}, "Action": "sts:AssumeRole" }] }
That document alone is not wrong. The finding is when the account ID, the external ID, or the condition is weaker than the role's power.
S3 Deep Dive: Block Public Access#
The four account-level settings that define the posture:
| Setting | What it blocks |
|---|---|
| Block public ACLs | New public ACLs on buckets and objects |
| Ignore public ACLs | Existing public ACLs |
| Block public bucket policies | Public bucket policies |
| Restrict public buckets | Access to buckets with public policies |
A finding note names which of the four is off and which bucket proves the exposure.
Lambda Deep Dive: Handler Shape#
def handler(event, context): name = event.get("name", "") return {"statusCode": 200, "body": f"hello {name}"}
The vulnerable shape is the same code with the name concatenated into a system call or a SQL string. Every handler deserves the same question: what in this function touches the outside world because of the event?
Reporting Shape#
| Field | Example |
|---|---|
| Resource | arn:aws:s3:::client-exports |
| Observed | Public ACL granting READ |
| Scope | Account 111122223333, region us-east-1 |
| Evidence | Directory listing head, trimmed |
| Fix | Enable Block Public Access, remove ACL |
| Verification | Re-run audit after change |
Triage by Exploitability, Not Volume#
A scanner may return hundreds of findings. Sort them:
- Can an unauthenticated principal reach data?
- Can a low-privilege principal escalate?
- Can a role escape its intended account?
- Everything else is hygiene.
A Small Walkthrough#
Walkthrough 1: bucket audit#
- List buckets in the scoped account.
- For each, read the ACL and the policy.
- Note any public grants.
- Try one anonymous GET on a single object head.
- Record ARN, grant, and observed status.
Walkthrough 2: role audit#
- List roles and their trust documents.
- Flag wildcard trusts or missing external IDs.
- Check attached policies for
*:*. - Note which roles are assumed by which services.
- Record one line per risky pair.
Walkthrough 3: function audit#
- List functions and their execution roles.
- Check environment variables for long-lived secrets.
- Look at the handler for unvalidated event fields.
- Check dependency pins and layer sources.
- Record one line per function with a concern.
False Positive Friends#
- A bucket that is intentionally public for a static site.
- A role with a big policy used only in a sandbox account.
- An environment variable that holds a rotated, short-lived token.
- A security group rule that looks open but is limited by a NACL you did not read.
Each of these needs one verification before it becomes a report.
What a Good Cloud Report Contains#
- The scoped account and region list.
- The exact resource identifier for each finding.
- One proof request or API call per finding.
- A fix that names the console path or the Terraform resource.
- A verification date and the re-run command.
Reading Order Around This Topic#
- Web classes for the API side: OWASP Top 10 notes.
- CNAME and bucket references: dead link detection.
- Access-control patterns the cloud map of: access-matrix testing.
- API authz checks on your cloud APIs: API access control test plan.
- BOLA checks for the services behind them: BOLA test matrix.
- Scoring what you find: security finding scoring.
- Logging for cloud events: security logging design.
- The hidden API problem: shadow API inventory.
- Web-side XSS that may front these: XSS types compared.
- WP hosting on the same account: WordPress attack surface.
- The roadmap context: reading order.
- Proxy work on the API side: Burp Suite basics.
- ZAP for the API side: ZAP review notes.
Triage Cadence#
Re-run the audit after every infrastructure change. The finding list is a living artifact; a check that passed in January tells you nothing about March.
Short Glossary#
- ARN: the AWS resource name, a unique identifier per resource.
- Trust policy: the document saying who may assume a role.
- Execution role: the IAM role a Lambda function runs with.
- Public access block: the four S3 account settings that gate public grants.
- External ID: the extra condition many cross-account trusts require.
Final Notes#
Cloud findings are boring in the best way: they follow a checklist, they have a known fix, and they verify cleanly. The discipline is the same as web testing. Scope first, one minimal proof per finding, and a fix that names the exact setting.
A Note on Evidence#
A curl that lists a bucket head is a proof. A copied customer record is not evidence; it is an incident. Trim every artifact before it leaves the engagement machine. When in doubt, record the ARN and the failing call, and let the client re-run the proof.
Small Print for the Report#
- Record the scanner version and rule set used.
- Record the region list covered.
- Mark findings that were verified manually.
- Note the settings that were changed as a result.
Service Trust Boundary#
internet --> S3 / CDN --> IAM decision --> Lambda / app --> data | | | public policy role trust handler input
Each hop has a configuration check. The classic findings live where two hops disagree: a bucket policy that trusts a CDN, a role that trusts a changed account, a handler that trusts its input.
Detection#
iam AccessAnalyzerfindings for public or cross-account access.- S3 access logs and
Block Public Accessstate. - Lambda env vars containing secrets in plaintext.
- CloudTrail for
s3:GetBucketAcl,iam:PassRole, and AssumeRole patterns.
Limitations#
- A finding in a scanner is configuration evidence, not impact; a public bucket on a test account is not a breach.
- Rate limits and honeypots shape what a read-only probe will reveal; record the limit, do not fight it.
- Rotating a key does not revoke sessions minted before rotation; track session TTLs.
Cikarilar#
- Most cloud findings are configuration findings.
- Public access is a posture, not an attribute of one bucket.
- PassRole plus create-service equals escalation.
- Record the ARN and the failing call, not customer data.
What do you think?
React to show your appreciation