← Back to Feed CACHED · 2026-10-23 11:18:20 · CACHE_KEY CVE-2026-115560
CVE-2026-115560 · CWE-502 · Disclosed 2026-10-23

PHP session.upload_progress pre-auth RCE via runtime serialize_handler mismatch

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

It's the difference between leaving your front door unlocked and leaving a secret trap door under a rug that you first have to install yourself

CVE-2026-115560 is a PHP session upload progress vulnerability disclosed on 2026-10-23 as an informational advisory by the PHP project. It arises when an application (a) configures session.serialize_handler to php_binary or php_serialize in its .ini/.htaccess/.user.ini and then (b) calls ini_set('session.serialize_handler', 'php') at runtime before session_start(), while session.upload_progress.enabled remains On (the default). Under these conditions, the upload-progress callback writes session data using the config-level serializer, but session_start() reads it back with the runtime-switched php handler. The php handler treats | as a key-value delimiter, while php_serialize and php_binary do not — an attacker-controlled upload filename containing | followed by a serialized PHP object causes the php handler to split at the wrong boundary and call unserialize() on attacker-supplied data. The resulting object injection fires __destruct() / __wakeup(), enabling POP-chain-to-RCE. Affected: all PHP versions from 5.4.0 (when session.upload_progress was introduced) through current supported branches (8.2.x, 8.3.x, 8.4.x, 8.5.x). No patch is released yet; PHP will include the fix in the next regular bugfix release.

There is no vendor CVSS score for this CVE. PHP itself labels it an *unlikely RCE vector* and is handling it through the regular release cycle — not the emergency process used for the related php2shell CVEs (CVE-2026-109618/109619, CVSS 9.2). That framing is honest: the configuration pattern — deliberately switching serialize_handler at runtime before session_start() — is a code anti-pattern that no major framework (Laravel, Symfony, WordPress, Drupal, Magento) performs by default. PHP bug #71101 documented this handler-mismatch risk back in 2015 and was closed as a *documentation problem*. The practical population of vulnerable deployments is vanishingly small. However, the attack is pre-authentication (upload progress fires in SAPI before application code), so where the conditions *do* exist, exploitation is straightforward.

"Rare runtime handler swap turns session progress into RCE — but almost nobody does this."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Identify a handler-mismatch target

The attacker identifies a PHP application that sets session.serialize_handler to php_binary or php_serialize in its configuration but then switches it to php via ini_set() before calling session_start(). This can be found through error-based disclosure, PHP info pages, or source-code access (open-source apps). No tooling specific to CVE-2026-115560 exists; reconnaissance would be manual or via custom Nuclei template against phpinfo() output.
Conditions required:
  • Target PHP application performs runtime serialize_handler switch
  • session.upload_progress.enabled = On (default)
  • Application is reachable over HTTP/HTTPS
Where this breaks in practice:
  • Almost no production applications change serialize_handler at runtime
  • No automated scanner fingerprints this configuration
  • Attacker may need source-code review or phpinfo() leakage to confirm
Detection/coverage: No known scanner or Nuclei template specifically detects the runtime-switching pattern. Manual code audit or grep for ini_set.*session.serialize_handler is required.
STEP 02

Craft a poisoned multipart upload

The attacker sends a multipart POST request to any endpoint on the target app. The PHP_SESSION_UPLOAD_PROGRESS field is set in the POST body, and the uploaded file's filename contains a pipe | followed by a serialized PHP object payload (e.g., filename="garbage|O:8:\"Exploiter\":1:{…}"). Because session.upload_progress fires in the SAPI layer *before* application code, no application-level auth is needed. The session upload progress callback writes this data to the session file using the config-level serializer (php_serialize or php_binary), preserving the pipe and serialized payload verbatim.
Conditions required:
  • A valid session cookie (PHPSESSID) — can be self-issued if session.use_strict_mode=Off (default)
  • Target endpoint accepts multipart POST (most PHP endpoints do)
Where this breaks in practice:
  • session.use_strict_mode=On would require a pre-existing valid session ID
  • Some WAFs strip or reject unusual filenames in multipart bodies
Detection/coverage: ModSecurity CRS rule 942100 (SQL injection) may incidentally flag serialized objects in filenames. Suricata rules for php2shell (SID 2046001–2046004) may partially match.
STEP 03

Trigger deserialization via handler mismatch

On the next request (or even the same request, after the upload callback completes), the application calls ini_set('session.serialize_handler', 'php') and then session_start(). The php handler reads the session file and splits it at the first | character. Everything after | is passed to unserialize(). The attacker's serialized object is instantiated, and PHP calls __wakeup() at creation time and __destruct() at request shutdown — even if the overall decode returns FAILURE.
Conditions required:
  • Application must call ini_set() to switch handler to php before session_start()
  • A usable POP chain must exist in loaded classes (framework, CMS, or application code)
Where this breaks in practice:
  • The runtime ini_set switch is the critical and extremely rare precondition
  • POP chain availability depends on what libraries are loaded; vanilla PHP has limited gadgets
  • PHP 8.x disabled many __wakeup() gadgets via ReadOnly properties
STEP 04

RCE via POP chain

The deserialized object's __destruct() method chains through available class gadgets to achieve arbitrary code execution — typically via system(), exec(), file_put_contents(), or Monolog/Guzzle/SwiftMailer POP chains documented in PHPGGC. The attacker now has code execution as the PHP-FPM worker user (typically www-data or nobody). From here, standard post-exploitation applies: credential harvesting, lateral movement, web shell deployment.
Conditions required:
  • POP gadget chain present in application dependencies
  • No disable_functions restriction blocking the chain's terminal call
Where this breaks in practice:
  • Modern hardened PHP deployments use disable_functions to block system/exec
  • Containerized PHP (Docker/K8s) limits lateral movement
  • SELinux/AppArmor may block file writes outside the webroot
Detection/coverage: EDR/HIDS should detect anomalous child process spawning from PHP-FPM workers. Sysmon for Linux or Falco rules for shell execution from www-data are effective.
03 · Compensating Control

1
MEDIUM 5.3→IGNORE 0.0
SEVERITY REDUCED
Set session.upload_progress.enabled=Off in php.ini and restart PHP-FPM/Apache workers — This completely disables the code path that writes attacker-controlled data to the session file during multipart uploads. It is the single most effective control and was already recommended for CVE-2026-109618/109619 two days prior. No noisgate mitigation SLA applies for MEDIUM severity — go straight to the 365-day remediation window, but given the overlap with php2shell mitigations this should already be deployed.
2
MEDIUM 5.3→IGNORE 0.0
SEVERITY REDUCED
Audit application code for runtime serialize_handler changes — Run grep -rn "ini_set.*session.serialize_handler" /var/www/ across all PHP codebases. If no matches are found, the application is not vulnerable regardless of other settings. If matches exist, refactor to use a consistent handler configured at the ini level. This eliminates the root cause. Deploy within the 365-day noisgate remediation SLA.
3
MEDIUM 5.3→LOW 2.5
SEVERITY REDUCED
Deploy WAF rules to block serialized PHP objects in multipart filenames — Add ModSecurity or cloud WAF rules that reject multipart form-data where the filename field contains PHP serialization markers (O:, a:, s: followed by length-prefixed strings). This blocks the payload delivery mechanism. Residual risk remains if an attacker finds an alternative injection point.
4
MEDIUM 5.3→MEDIUM 4.5
Enable session.use_strict_mode=On — When strict mode is enabled, PHP rejects session IDs not generated by the server. This prevents the attacker from self-issuing a PHPSESSID cookie, adding friction to step 2 of the attack path. It does not fully mitigate — an attacker who can obtain a valid session ID (e.g., via another endpoint) can still proceed.
What doesn't work
  • session.use_only_cookies=On — This was proven insufficient by CVE-2026-109618 research; the upload progress callback bypasses cookie-only restrictions because it writes to the session file directly in the SAPI layer, not through $_SESSION.
  • disable_functions for system/exec — While this blocks many POP chain terminal calls, sophisticated chains can use file_put_contents to drop a webshell or mail() with -X to write files, bypassing disable_functions entirely.
  • Upgrading to patched PHP versions (8.2.35 / 8.3.36 / 8.4.27 / 8.5.12) — These versions patch CVE-2026-109618/109619 but do NOT yet contain the fix for CVE-2026-115560. The fix will ship in the *next* regular bugfix release.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNone observed. Not listed in CISA KEV. No reports from GreyNoise, Shadowserver, or vendor telemetry. All active exploitation telemetry (Amazon MadPot, Cloudflare, watchTowr) pertains to the sibling CVE-2026-109618/109619, not this variant.
Proof-of-ConceptNo dedicated PoC for CVE-2026-115560. No repos found on GitHub or pocindex.io for this specific CVE ID. However, the underlying technique — serialize_handler mismatch via session.upload_progress — is thoroughly documented in PHPGGC and multiple CTF writeups since 2016. The php2shell PoC repos (e.g., mahumadad/php2shell-analysis) demonstrate the serializer-mismatch → POP chain vector that overlaps conceptually.
EPSS ScoreNot yet scored. CVE-2026-115560 was disclosed on 2026-10-23; EPSS data typically lags 24–72 hours after NVD publication. Given the narrow preconditions, expect a low EPSS percentile (<5th).
KEV StatusNot listed. No CISA KEV entry as of 2026-10-23.
CVSS VectorNo vendor score published. Estimated CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H → 8.1 base (AC:H due to the rare runtime config requirement). The high AC pulls the realistic score down significantly.
Affected VersionsPHP 5.4.0 through current: 8.2.x (≤ 8.2.35), 8.3.x (≤ 8.3.36), 8.4.x (≤ 8.4.27), 8.5.x (≤ 8.5.12). All branches where session.upload_progress exists. PHP 8.1 and older are EOL and will not receive a fix.
Fixed VersionNot yet released. PHP will include the fix in the next regular bugfix release (expected within ~4 weeks of 2026-10-23). The session.upload_progress.enabled=0 workaround fully mitigates in the interim.
Scanning / ExposureShodan and Censys show ~5.8M internet-facing PHP hosts as of October 2026, but the fraction performing runtime serialize_handler switching is estimated at <0.1% — no mass-exposure signal exists. GreyNoise shows no scanning activity targeting this specific variant.
Disclosure Date2026-10-23 — published as an informational advisory on the php.webmaster mailing list, two days after the php2shell emergency patches.
Credit / OriginDiscovered during the php2shell investigation. The PHP security team identified it as a leftover variant of the session upload progress attack surface. No individual researcher credited. PHP bug #71101 (filed 2015) first documented the handler-mismatch risk.

Sources.

  1. PHP Informational Advisory (php.webmaster mailing list)
  2. GHSA-6wmf-4rv5-p3rp — PHP RCE via stale global state (sibling CVE-2026-109619)
  3. PHP Bug #71101 — serialize_handler must not be switched for existing sessions (2015)
  4. Sansec — PHP2Shell: PHP RCE Research
  5. Wiz — PHP2Shell: AI uncovers critical RCE vulnerabilities
  6. CERT-EU Security Advisory 2026-018 — Critical Vulnerabilities in PHP
  7. UW IT — Critical PHP vulnerabilities (Oct 2026)
05 · The Call

Final Verdict
= UNCHANGED to MEDIUM (5.3/10)

Why this verdict

  • Pre-auth RCE is real but gated by an extreme precondition. The attack fires before application code runs (in the SAPI rfc1867 callback), so no authentication is needed — but *only* if the application performs a runtime ini_set('session.serialize_handler', 'php') switch. This is a deliberate code pattern that no major PHP framework (Laravel, Symfony, WordPress, Drupal, Magento, Joomla) uses by default. PHP's own security team calls it "unlikely in practice."
  • Attack Complexity is HIGH under CVSS 3.1 semantics. The attacker cannot create the vulnerable condition — it must pre-exist in the target application's code. There is no way to force the runtime handler switch remotely. This is a true AC:H scenario: the attacker needs a specific, rare application-level behavior they cannot influence.
  • No PoC, no exploitation, no scanning. As of 2026-10-23, there are zero public PoCs targeting this specific CVE, zero KEV entries, zero GreyNoise tags, and zero incident reports. The broader php2shell exploitation wave targets CVE-2026-109618/109619, not this variant.
  • Mitigation overlap with php2shell. Any organization that applied the recommended session.upload_progress.enabled=Off mitigation for the CVSS 9.2 php2shell CVEs (two days earlier) is *already fully protected* against CVE-2026-115560. The incremental risk from this CVE for teams that responded to php2shell is zero.
  • Role multiplier: PHP powers ~77% of web servers, spanning every deployment role from dev sandboxes to production e-commerce and healthcare portals. In high-value roles (Magento storefronts, SaaS backends, healthcare FHIR endpoints), a successful chain yields RCE as www-data — host-level compromise with potential for lateral movement. However, the chain only succeeds if the application code performs the runtime handler switch, which is not correlated with deployment role. No known high-value application performs this pattern. The estimated fraction of PHP installs where this chain succeeds is <0.1%, well below the 1% floor threshold. The blast radius *per successful exploitation* is HIGH (host-level RCE), but the reachable population is negligible, keeping the aggregate severity at MEDIUM.

Why not higher?

Upgrading to HIGH would require either (a) evidence that ≥1% of PHP installations perform the runtime serialize_handler switch, or (b) a specific widely-deployed application/framework that does this by default, or (c) active exploitation / public PoC lowering the attacker's cost. None of these conditions are met. The PHP team's own decision to defer this to the regular release cycle — rather than the emergency process used for CVE-2026-109618/109619 — is strong signal that the vendor's own threat model considers the practical risk low.

Why not lower?

Downgrading to LOW would understate the impact when the preconditions *are* met: this is unauthenticated, pre-auth RCE with no user interaction required. The underlying deserialization technique is well-understood, PHPGGC provides ready-made POP chains for dozens of frameworks, and the fix is not yet available. Any custom PHP application that *does* switch handlers at runtime is fully exposed with no patch to deploy. The attack surface is narrow but the impact is maximum severity.

06 · Verification

Crowdsourced verification payload.

Run this script on each PHP application host as root (or any user with read access to php.ini and the webroot). Invoke with: sudo bash check_cve_2026_115560.sh /var/www/html where the argument is the path to your PHP application code. The script checks (1) whether session.upload_progress is enabled, (2) whether any PHP files perform a runtime serialize_handler switch, and outputs VULNERABLE / PATCHED / UNKNOWN.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_115560.sh — CVE-2026-115560 exposure checker
# Usage: bash check_cve_2026_115560.sh [/path/to/webroot]
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

set -euo pipefail

WEBROOT="${1:-/var/www/html}"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'

# Step 1: Check if PHP is installed
if ! command -v php &>/dev/null; then
  echo -e "${YELLOW}[UNKNOWN]${NC} PHP is not installed on this host."
  exit 2
fi

PHP_VERSION=$(php -r 'echo PHP_VERSION;' 2>/dev/null || echo "unknown")
echo "[*] PHP version: $PHP_VERSION"

# Step 2: Check session.upload_progress.enabled
UPLOAD_PROGRESS=$(php -r 'echo ini_get("session.upload_progress.enabled") ? "1" : "0";' 2>/dev/null || echo "unknown")

if [ "$UPLOAD_PROGRESS" = "0" ]; then
  echo -e "${GREEN}[PATCHED]${NC} session.upload_progress.enabled is Off — CVE-2026-115560 is fully mitigated."
  exit 0
fi

if [ "$UPLOAD_PROGRESS" = "unknown" ]; then
  echo -e "${YELLOW}[UNKNOWN]${NC} Could not determine session.upload_progress.enabled setting."
  exit 2
fi

echo "[!] session.upload_progress.enabled is On (default)."

# Step 3: Scan webroot for runtime serialize_handler switching
if [ ! -d "$WEBROOT" ]; then
  echo -e "${YELLOW}[UNKNOWN]${NC} Webroot '$WEBROOT' does not exist. Cannot scan for handler switching."
  echo "    session.upload_progress.enabled is On — apply mitigation if handler switching exists in your code."
  exit 2
fi

echo "[*] Scanning $WEBROOT for ini_set('session.serialize_handler',...) calls..."

MATCHES=$(grep -rlnE "ini_set\s*\(\s*['\"]session\.serialize_handler['\"]" "$WEBROOT" --include='*.php' 2>/dev/null || true)

if [ -z "$MATCHES" ]; then
  echo -e "${GREEN}[PATCHED]${NC} No runtime serialize_handler switching found in $WEBROOT."
  echo "    session.upload_progress.enabled is On, but the app does not switch handlers."
  echo "    CVE-2026-115560 preconditions are NOT met."
  exit 0
fi

echo -e "${RED}[VULNERABLE]${NC} Runtime serialize_handler switching detected AND session.upload_progress.enabled is On."
echo "    Matching files:"
echo "$MATCHES" | while read -r f; do
  LINE=$(grep -nE "ini_set\s*\(\s*['\"]session\.serialize_handler['\"]" "$f" | head -3)
  echo "      $f: $LINE"
done
echo ""
echo "    REMEDIATION: Set session.upload_progress.enabled=Off in php.ini and restart PHP-FPM,"
echo "    or refactor the application to use a consistent serialize_handler."
exit 1