Описание
SOAP: Unbounded Recursion in Server-Side cleanup_xml_node
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():
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
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.
Пакеты
php
>=8.2.0, <8.2.34
8.2.34
php
>=8.3.0, <8.3.35
8.3.35
php
>=8.4.0, <8.4.26
8.4.26
php
>=8.5.0, <8.5.11
8.5.11
Связанные уязвимости
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.
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.
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.
cleanup_xml_node() in the SOAP XML parser recurses once per XML nestin ...