Back to blog
AWSCloud SecurityRecon

How I map cloud attack surfaces before an adversary does

June 1, 2025 8 min read

Before any real assessment begins, I spend time mapping the environment the same way an attacker would — methodically, without triggering alerts, and looking for the things that defenders usually miss because they're buried in noise.

This isn't a tool tutorial. It's how I actually think through AWS attack surface enumeration.

Start with what's publicly visible

The first question is always: what can I see without credentials?

S3 is the usual starting point. Misconfigured buckets still show up regularly — either with public ACLs or bucket policies that grant s3:GetObject to *. A simple subdomain enumeration pass combined with known naming patterns (company name, environment suffix, service name) surfaces these faster than most people expect.

Beyond S3:

  • Route53 public zones — zone walking isn't possible on Route53, but you can enumerate subdomains via certificate transparency logs (crt.sh, Censys). These often point to forgotten staging environments or internal tools exposed accidentally.
  • EC2 metadata exposure — if there's a WAF bypass or SSRF surface anywhere in the app, the IMDSv1 endpoint at 169.254.169.254 hands over IAM credentials. This still works on a surprising number of older deployments that haven't migrated to IMDSv2.
  • GitHub dorking — access keys committed to public repos, .env files, Terraform state files with embedded secrets. This is embarrassingly common and embarrassingly effective.

Enumerate IAM without triggering GuardDuty

Once you have a credential — whether from metadata, a leaked key, or a test account — the instinct is to run aws iam list-users and aws iam list-roles. Don't. GuardDuty has detections for that exact pattern.

Instead, lean on what the credential can actually do:

# What identity am I?
aws sts get-caller-identity

# What can this role do? (reads the inline and attached policies)
aws iam get-role --role-name <role>
aws iam list-attached-role-policies --role-name <role>
aws iam list-role-policies --role-name <role>

# Check for wildcard permissions
aws iam get-role-policy --role-name <role> --policy-name <policy>

The goal is to understand the blast radius of the current credential before moving laterally. A credential with iam:PassRole and ec2:RunInstances is effectively admin — even if it doesn't look like it at first.

What I look for in IAM policies

A few patterns that consistently show up:

Wildcard resources on sensitive actions:

{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:PutObject"],
  "Resource": "*"
}

This grants access to every S3 bucket in the account. It's almost always an accident.

iam:PassRole without conditions: Any principal that can pass roles to services (Lambda, EC2, ECS) can escalate to whatever role they can pass. If there's an admin role in the account, this is often a direct path to it.

Trust policies that are too broad:

"Principal": {"AWS": "arn:aws:iam::*:root"}

This trusts every AWS account. I see this on cross-account roles that were set up quickly and never tightened.

Automate the boring parts

For the enumeration work that doesn't require judgment — scanning for public resources, checking encryption at rest, auditing security groups — I use a combination of AWS Config rules and custom scripts.

The scripts I've published in the Vijenex GitHub repos cover:

  • Enumerating all IAM roles with iam:PassRole and identifying what they can pass to
  • Finding EC2 instances with IMDSv1 still enabled
  • Listing S3 buckets and checking their effective public access configuration
  • Identifying Lambda functions with overly permissive execution roles

They're not polished products. They're the scripts I actually run during assessments, shared so others don't have to write them from scratch.

The attack path is rarely obvious

The most dangerous findings aren't single misconfigurations. They're chains.

An example I hit recently:

  1. Public S3 bucket contains a Terraform state file
  2. State file includes an IAM access key for a deployment role
  3. Deployment role has iam:CreateAccessKey on *
  4. New key created for the admin user — full account takeover

Each individual finding looked like a medium-severity issue. The chain was critical.

When mapping attack surfaces, I always trace paths, not just findings. The question isn't "is this misconfigured?" — it's "what can someone do with this?"

All posts