During a recent engagement I found an SSRF vulnerability that looked
boring on the surface — internal HTTP request, filtered output, no
obvious pivot path. Forty minutes later I had IAM credentials with
iam:PassRole. Here’s exactly how it unfolded.
the initial find
The target was a SaaS platform with a “fetch URL for preview” feature. Classic SSRF surface. The first thing I tried was the AWS metadata endpoint:
No filtering. The iam/ path was right there.
extracting credentials
# Step the path down
GET /preview?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# → ec2-instance-role
GET /preview?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-instance-role
# → { AccessKeyId, SecretAccessKey, Token, Expiration }
The role had iam:PassRole attached to it, which meant I could
escalate by creating a Lambda with a more privileged role and executing
it. Full account takeover from a “low severity” SSRF.
impact chain
- SSRF → metadata service (no IMDSv2 enforced)
- Metadata service → temporary IAM credentials
- Credentials →
iam:PassRole+lambda:CreateFunction - Lambda → assume admin role via pass-role escalation
remediation
- Enforce IMDSv2 on all EC2 instances (
HttpTokens: required) - Filter SSRF targets server-side with an allowlist, not a blocklist
- Scope instance profiles to least-privilege —
iam:PassRoleshould almost never be on an instance profile
The fix here is IMDSv2. One config change and the entire chain collapses at step 1.