← Back to Feed CACHED · 2026-10-25 12:33:45 · CACHE_KEY CVE-2026-91765
CVE-2026-91765 · CWE-674 · Disclosed 2026-09-25

PHP SOAP XML parser unbounded recursion denial of service via cleanup_xml_node

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

Like a spring-loaded jack-in-the-box that keeps unfolding until it breaks the shelf — except the shelf is your PHP worker's call stack

CVE-2026-91765 is an unbounded-recursion flaw in PHP's built-in SOAP extension. Three functions — cleanup_xml_node() (XML tree cleanup), master_to_zval_int() (value decoder following href chains), and get_node_with_attribute_recursive_ex() (WSDL node search) — all recurse once per XML nesting level with no depth guard. An unauthenticated attacker can POST a SOAP envelope containing tens of thousands of nested elements to any reachable SoapServer endpoint. The C-level recursion eats stack frames until the process hits the OS stack limit and segfaults. Under PHP-FPM the crashed worker is replaced, but a sustained stream of such requests drains the pool and takes the endpoint offline. Affected versions are PHP 8.2 < 8.2.34, 8.3 < 8.3.35, 8.4 < 8.4.26, and 8.5 < 8.5.11. The fix (shipped in those releases) introduces a SOAP_MAX_XML_DEPTH constant capped at 2048, converts the recursive helpers to iterative loops, and adds depth tracking in SOAP globals to break href cycles.

The vendor rates this HIGH 7.5, and the CVSS math is technically correct for the vector (network / no auth / no complexity / availability-only). However, the vendor score overstates the real-world urgency for most enterprises. The impact ceiling is a process crash — there is zero path to code execution, data exfiltration, or lateral movement. The attack surface is limited to the subset of PHP installations that (a) load the SOAP extension *and* (b) expose a SoapServer endpoint reachable by untrusted clients. Many PHP deployments use SOAP only as a *client* (SoapClient), which is not affected. PHP-FPM worker pools recover automatically, so achieving a sustained outage requires sustained attack traffic — something a basic rate limiter or WAF XML-depth rule defeats. EPSS is 0.55% (≈43rd percentile), there is no CISA KEV listing, no confirmed in-the-wild exploitation, and no published proof-of-concept code. noisgate downgrades this to MEDIUM 6.5.

"Unauthenticated DoS via nested XML, but impact is availability-only on SoapServer subset"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Identify a reachable SoapServer endpoint

The attacker scans for PHP applications exposing SOAP endpoints. These are typically at predictable paths like /soap, /api/soap, /ws/, or documented WSDL URLs. Shodan shows ~1.9 million internet-facing PHP hosts, but only a fraction run SoapServer. On shared hosting with cPanel/DirectAdmin/Plesk, the SOAP extension is loaded by default, but the *server* class still has to be instantiated by the application.
Conditions required:
  • Target PHP application loads ext/soap
  • Target instantiates SoapServer (not just SoapClient)
  • SoapServer endpoint reachable from the internet or attacker's network position
Where this breaks in practice:
  • Most PHP apps use frameworks (Laravel, Symfony) that don't use SoapServer — REST/JSON dominates
  • Internal SOAP APIs are often behind VPN or not exposed to the internet
  • Application firewalls may block or limit SOAP traffic by default
Detection/coverage: Web server access logs show POST requests to SOAP endpoints. A Shodan/Censys dork for X-Powered-By: PHP combined with WSDL path probing can identify candidates.
STEP 02

Craft a deeply nested XML SOAP payload

The attacker generates a SOAP envelope with tens of thousands of nested XML elements — e.g., <a><a><a>.... The payload is compact: 50,000 levels of <a> is under 300 KB uncompressed. With gzip transfer encoding (standard for HTTP), the wire size drops to a few KB. No special tooling is needed; a Python one-liner or bash printf loop suffices. No authentication or valid SOAP action is required — the parser processes the XML structure before any application-level validation.
Conditions required:
  • Ability to send HTTP POST to the target endpoint
  • No request-size or XML-depth filtering by upstream WAF
Where this breaks in practice:
  • Enterprise WAFs (F5 ASM, Cloudflare, AWS WAF) with XML-depth inspection rules will block this
  • ModSecurity with the CRS ruleset caps XML body depth by default
  • Content-length limits on reverse proxies may reject very large nesting if uncompressed
Detection/coverage: WAF logs flagging XML depth violations. IDS signatures for anomalous SOAP body depth (Suricata SID pending).
STEP 03

Send the payload and crash the PHP worker

The attacker POSTs the crafted envelope to the SoapServer endpoint. PHP's libxml parses the document, then cleanup_xml_node() begins its recursive walk. With ~50K nesting levels, the function consumes ~400 bytes of stack per frame (platform-dependent), exhausting the default 8 MB stack in under 20,000 frames. The process receives SIGSEGV and dies. The request takes milliseconds to deliver but the crash is instantaneous once parsing begins.
Conditions required:
  • Payload reaches PHP's SOAP parser without depth truncation
Where this breaks in practice:
  • PHP-FPM's pm.max_children limits concurrency — killing one worker doesn't kill the pool
  • systemd/cgroup OOM-killer or stack-size ulimits may vary the crash threshold
  • Process managers restart workers automatically
Detection/coverage: PHP-FPM error logs show children ... exited on signal 11 (SIGSEGV). Monitoring for abnormal worker churn (e.g., Prometheus phpfpm_slow_requests or process-restart counters) catches this quickly.
STEP 04

Sustain the attack to exhaust the worker pool

To achieve a full denial of service, the attacker must send requests faster than PHP-FPM can respawn workers. With a typical pm.max_children = 50 and each crash taking ~100ms round-trip, the attacker needs roughly 500 requests/second to keep the pool drained. This is a modest volumetric requirement. However, any rate-limiting, connection throttling, or IP-based blocking at the load balancer or WAF layer will break the attack loop. The DoS persists only while attack traffic flows — there is no persistence mechanism.
Conditions required:
  • Sustained network access to the endpoint
  • No rate limiting or IP blocking at the network edge
Where this breaks in practice:
  • CDN/WAF rate limiting is standard for internet-facing applications
  • Geo-blocking or IP reputation lists reduce attacker pool
  • Auto-scaling infrastructure (k8s HPA, cloud ASG) can absorb worker churn
  • Incident response team can null-route attacker IPs within minutes
Detection/coverage: Spike in 5xx errors, worker restart rate, and inbound POST volume to SOAP paths. SIEM correlation rule: phpfpm_process_restarts > 10/min AND http_path contains 'soap'.
03 · Compensating Control

1
MEDIUM 6.5→LOW 3.0
SEVERITY REDUCED
Deploy WAF XML-depth inspection rule — Configure your WAF (F5 ASM, Cloudflare, AWS WAF, ModSecurity) to reject SOAP/XML request bodies with nesting depth exceeding 256 levels. ModSecurity CRS rule 200007 (SecRule XML:/* "@gt 256") provides this out of the box. This breaks the attack at step 2 — the payload never reaches PHP. No noisgate mitigation SLA applies for MEDIUM, so go straight to the 365-day remediation window, but this control is quick to deploy and highly effective.
2
MEDIUM 6.5→LOW 3.5
SEVERITY REDUCED
Rate-limit POST requests to SOAP endpoints — Apply per-IP rate limiting (e.g., 10 req/s) on any URL path serving SoapServer. This prevents the attacker from sustaining enough crash-inducing requests to exhaust the PHP-FPM worker pool. Implement at the load balancer or CDN edge (nginx limit_req, HAProxy stick-tables, Cloudflare rate rules). Breaks the attack at step 4.
3
MEDIUM 6.5→IGNORE 0.0
SEVERITY REDUCED
Disable ext/soap on hosts that don't use SoapServer — If the application only uses SoapClient (outbound SOAP calls) or doesn't use SOAP at all, remove the extension entirely: comment out extension=soap in php.ini or apt remove php-soap. This eliminates the vulnerable code path completely. Audit with php -m | grep -i soap across the fleet. On shared hosting, request the provider disable it if unused.
4
MEDIUM 6.5→LOW 2.5
SEVERITY REDUCED
Restrict SoapServer endpoint access to trusted networks — If the SOAP endpoint serves only known partners or internal systems, bind it behind a VPN, IP allowlist, or network segmentation rule. This removes the 'unauthenticated remote attacker' precondition entirely. Breaks the attack at step 1.
5
MEDIUM 6.5→IGNORE 0.0
SEVERITY REDUCED
Patch PHP to fixed version — Upgrade to PHP 8.2.34, 8.3.35, 8.4.26, or 8.5.11 (or distro-backported equivalents). This is the definitive fix — it introduces SOAP_MAX_XML_DEPTH=2048 and converts recursive functions to iterative loops. Per the noisgate remediation SLA for MEDIUM, complete within 365 days. Coordinate with your next PHP minor-version maintenance window.
What doesn't work
  • PHP memory_limit (ini_set) — does not help. The crash is caused by *stack* exhaustion (SIGSEGV), not *heap* exhaustion (OOM). PHP's memory_limit governs heap allocations only; the C-level call stack is bounded by the OS ulimit -s, which PHP cannot control at runtime.
  • max_input_time / max_execution_time — does not help. The crash occurs during XML parsing *before* the PHP script's execution timer starts. The process segfaults in libxml/C land, not in userspace PHP.
  • libxml_disable_entity_loader() — irrelevant. This CVE is about recursion depth in tree traversal, not XXE or entity expansion. Disabling external entities has no effect on nesting depth.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNone confirmed. Not listed in CISA KEV. No campaigns or threat-actor usage documented as of 2026-10-25.
Proof-of-ConceptNo public PoC published. Mallory.ai and TheHackerWire both report filtering out candidate repos as fakes or README-only. However, the exploit is *trivially constructable* — a loop printing nested <a> tags is sufficient. Expect weaponized PoCs within weeks.
EPSS0.55% probability of exploitation in the next 30 days, approximately 43rd–45th percentile. Low exploitation likelihood relative to the broader CVE population.
CISA KEVNot listed. No federal mandate for emergency patching under BOD 22-01.
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H — Network-accessible, no auth, no complexity, availability-only impact. The vector correctly reflects the technical attack surface but overstates operational risk because it cannot model the SoapServer-subset constraint or PHP-FPM recovery.
Affected VersionsPHP 8.2.0–8.2.33, 8.3.0–8.3.34, 8.4.0–8.4.25, 8.5.0–8.5.10. Older EOL branches (8.1 and below) are likely affected but will not receive patches.
Fixed VersionsPHP 8.2.34, 8.3.35, 8.4.26, 8.5.11. Distro backports: Debian bookworm 8.2.35-1~deb12u1 (DLA-4819-1), Debian trixie 8.4.27-1~deb13u1 (DSA-6514-1), Fedora 43 8.4.27, Amazon Linux 2 ALAS2PHP8.2-2026-014. RHEL status under investigation.
Scanning / ExposureShodan reports ~1.9 million internet-facing PHP hosts. SOAP extension is loaded by default on cPanel/DirectAdmin/Plesk shared hosting. However, only a fraction of these instantiate SoapServer — most modern PHP apps use REST/JSON. No GreyNoise tags or mass-scanning activity observed for this CVE.
Disclosure & CreditDisclosed 2026-09-25. Reported by ExPatch-LLC. Fix developed by alexandre-daubois (php-src contributor). GitHub advisory GHSA-rgrp-mwpx-f6rm.
Related SOAP CVEs (2026)CVE-2026-6722 (SOAP RCE via use-after-free, CVSS 9.5), CVE-2026-7261 (SOAP use-after-free in session persistence), CVE-2026-7262 (SOAP NULL-ptr DoS). If you run SoapServer, patch the entire batch together.

Sources.

  1. PHP Security Advisory (GHSA-rgrp-mwpx-f6rm)
  2. Red Hat CVE Page
  3. Debian Security Tracker
  4. Amazon Linux 2 Advisory (ALAS2PHP8.2-2026-014)
  5. TheHackerWire — PoC & Exploit Status
  6. Mallory.ai Vulnerability Detail
  7. OpenCVE Entry
  8. VulDB Entry
05 · The Call

Final Verdict
↓ DOWNGRADED to MEDIUM (6.5/10)

Why this verdict

  • DoS-only impact ceiling: The vulnerability's maximum outcome is a process crash and transient service disruption. There is zero path to code execution, memory disclosure, data exfiltration, or lateral movement. The CVSS Confidentiality and Integrity sub-scores are both None. This fundamentally caps the operational severity below what a HIGH rating implies to a patch-prioritization audience.
  • Attack surface narrowing: The flaw requires a running SoapServer instance reachable by the attacker — not merely a PHP server with ext/soap loaded. Most modern PHP applications use REST/JSON frameworks (Laravel, Symfony, WordPress REST API) and never instantiate SoapServer. The reachable population is a small subset of the ~1.9M internet-facing PHP hosts, concentrated in legacy enterprise integrations and payment gateways.
  • PHP-FPM automatic recovery: Crashed workers are respawned by the process manager within milliseconds. Achieving sustained denial of service requires *continuous* attack traffic at ~500 req/s for a typical 50-worker pool. This is not a 'fire and forget' crash — it is a volumetric DoS that any rate limiter, WAF, or CDN throttle defeats.
  • No exploitation evidence: EPSS is 0.55% (43rd percentile), no CISA KEV listing, no confirmed in-the-wild activity, no published PoC. The trivial nature of the exploit means this could change, but current threat intelligence does not support emergency treatment.
  • Role multiplier: PHP runs across workstations (dev), typical web app servers, and high-value roles (e-commerce payment gateways, ERP SOAP connectors, healthcare HL7/SOAP bridges). On high-value roles, the chain succeeds if SoapServer is exposed and unprotected. However, the blast radius even on a payment gateway is *host-level availability disruption* — not domain takeover, fleet compromise, or data exfiltration. A sustained outage of a payment SOAP endpoint is operationally painful but does not cross the HIGH floor threshold (which requires domain/fleet/supply-chain/safety-scale outcomes). Enterprise SOAP endpoints in high-value roles are typically behind WAFs and rate limiters, further reducing realistic impact.

Why not higher?

Upgrading to HIGH would require either a path beyond availability impact (RCE, data leak, lateral movement) or evidence of active exploitation making the theoretical risk concrete. Neither condition is met. The DoS impact on even high-value PHP SOAP endpoints caps at service disruption — painful but recoverable, and trivially mitigable with a WAF XML-depth rule or rate limiter. No threat actor has weaponized this, and EPSS confirms low exploitation probability.

Why not lower?

Dropping to LOW would understate the risk for the minority of enterprises that *do* expose SoapServer endpoints to untrusted networks. The attack is unauthenticated, requires no user interaction, has no complexity barrier, and the exploit is trivially constructable even without a published PoC. A single HTTP POST can crash a worker instantly. For organizations running legacy SOAP integrations on internet-facing PHP, this is a genuine operational risk that warrants patching within a year, not indefinite deferral.

06 · Verification

Crowdsourced verification payload.

Run this script on each target PHP host (web server or container) as any user with permission to execute php. No root required. Invoke: bash check_cve_2026_91765.sh — it checks the PHP version, whether ext/soap is loaded, and whether the fix constant SOAP_MAX_XML_DEPTH exists.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_91765.sh — Detect CVE-2026-91765 (PHP SOAP unbounded recursion)
# Run on the target PHP host. No root needed.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

set -euo pipefail

PHP_BIN="${1:-php}"

# Check PHP binary exists
if ! command -v "$PHP_BIN" &>/dev/null; then
  echo "UNKNOWN — '$PHP_BIN' not found in PATH"
  exit 2
fi

# Check if SOAP extension is loaded
SOAP_LOADED=$( "$PHP_BIN" -m 2>/dev/null | grep -ci '^soap$' || true )
if [ "$SOAP_LOADED" -eq 0 ]; then
  echo "PATCHED — ext/soap is not loaded; CVE-2026-91765 is not reachable on this host"
  exit 0
fi

# Get PHP version
PHP_VERSION=$( "$PHP_BIN" -r 'echo PHP_VERSION;' 2>/dev/null )
if [ -z "$PHP_VERSION" ]; then
  echo "UNKNOWN — could not determine PHP version"
  exit 2
fi

echo "Detected PHP $PHP_VERSION with ext/soap loaded"

# Check for the fix constant SOAP_MAX_XML_DEPTH (introduced in the patch)
HAS_FIX=$( "$PHP_BIN" -r 'echo defined("SOAP_MAX_XML_DEPTH") ? "yes" : "no";' 2>/dev/null )
if [ "$HAS_FIX" = "yes" ]; then
  echo "PATCHED — SOAP_MAX_XML_DEPTH constant present (fix applied)"
  exit 0
fi

# Fallback: version comparison for known-fixed versions
# Fixed: 8.2.34, 8.3.35, 8.4.26, 8.5.11
version_gte() {
  # Returns 0 if $1 >= $2
  printf '%s\n%s' "$2" "$1" | sort -V -C
}

MAJOR_MINOR=$( echo "$PHP_VERSION" | grep -oP '^\d+\.\d+' )
case "$MAJOR_MINOR" in
  8.2) FIXED="8.2.34" ;;
  8.3) FIXED="8.3.35" ;;
  8.4) FIXED="8.4.26" ;;
  8.5) FIXED="8.5.11" ;;
  *)
    # PHP 8.1 and below are EOL and likely vulnerable but won't get patches
    if version_gte "$PHP_VERSION" "9.0.0"; then
      echo "UNKNOWN — PHP $PHP_VERSION is newer than known affected range"
      exit 2
    fi
    echo "VULNERABLE — PHP $PHP_VERSION (EOL branch) with ext/soap loaded; no patch available"
    exit 1
    ;;
esac

if version_gte "$PHP_VERSION" "$FIXED"; then
  echo "PATCHED — PHP $PHP_VERSION >= $FIXED"
  exit 0
else
  echo "VULNERABLE — PHP $PHP_VERSION < $FIXED with ext/soap loaded"
  exit 1
fi