Safeguard
Supply Chain Security•

PHP CVE-2026-115560: unpatched session upload progress RCE explained

CVE-2026-115560 is an unpatched PHP session RCE that needs a runtime serializer switch. Set session.upload_progress.enabled=0 and restart PHP workers.

Safeguard Security Team
35 min read

CVE-2026-115560 is a new, unpatched remote code execution risk in PHP's session handling, announced on 23 October 2026. It only affects applications that switch session.serialize_handler to php at runtime while session upload progress is on. PHP 8.5.12 and its siblings do not fix it. Set session.upload_progress.enabled=0 in server configuration and restart every worker.

PHP itself calls this "an unlikely RCE vector" and is handling the fix in the regular release cycle rather than with another emergency release. That is a reasonable call for most sites, but unlikely is a statement about the average PHP application, not about yours. The only way to know whether you are in the small group that is exposed is to look at your configuration and your code, including the code you pulled in through Composer.

This post explains the three PHP session serializers, why switching between them has been a known hazard since 2015, which settings and code patterns put you in scope, and how to audit and verify on bare metal, PHP-FPM, Apache, containers and Kubernetes. If you have not patched PHP2Shell yet, read our PHP2Shell explainer first. That is the urgent job.

What happened

On Friday 23 October 2026, two days after the emergency PHP2Shell release, the PHP project published a short informational advisory on php.net titled "Session upload progress". It asks administrators to set session.upload_progress.enabled=0 until the next regular bugfix release, assigns the issue CVE-2026-115560, and promises a full advisory with that release. The PHP Foundation posted the same message on Bluesky.

The advisory makes three points that matter for defenders:

  1. The setting it recommends is the same one PHP already listed on 21 October as a mitigation for CVE-2026-109619, one half of PHP2Shell, in GitHub advisory GHSA-6wmf-4rv5-p3rp. If you applied it then, PHP says to keep it in place and do nothing more.
  2. The bug can only be reached through an unusual mix of configuration and application code, described in detail below.
  3. Applications that never change the serialize handler at runtime can keep upload progress enabled.

BleepingComputer and The Hacker News both covered the advisory on 23 October. Neither reports exploitation of CVE-2026-115560, and The Hacker News says no exploitation has been reported. Neither article, and nothing PHP has published so far, gives a CVSS score, a list of affected versions, a fixed version or the name of whoever reported it.

Timeline

All times UTC. Where a source gives only a date, the time column says so.

Date and time (UTC)EventSource
12 Dec 2015, 13:09Bug #71101, "serialize_handler must not be switched for existing sessions", is filed as a security issue. It describes how upload progress lets a client influence a key in $_SESSION and how a mid-script handler switch makes PHP parse session data with the wrong format.bugs.php.net
1 Jan 2016Bug #71101 is reclassified from security issue to ordinary bug.bugs.php.net
24 Sep 2021Bug #71101 is reclassified as a documentation problem; maintainers decide to document the behaviour rather than change it.bugs.php.net
18 Oct 2026, 15:26:39Commit 3584a98bda0f2c0d367255b046798b0d3a221a19, "Avoid state corruption via bailout", is authored. It fixes CVE-2026-109618 and CVE-2026-109619 and changes ext/session/session.c along with two new upload progress tests.php-src on GitHub
20 Oct 2026, about 15:00CVE IDs for PHP2Shell leak online with a wrong release time and a wrong CVSS score.PHP Foundation
20 Oct 2026, 19:30PHP moves its pre-announcement forward.PHP Foundation
21 Oct 2026, about 14:00PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 ship. The php-src merge commits land between 14:12 and 14:34.php.net, php-src on GitHub
21 Oct 2026 (date only)GHSA-6wmf-4rv5-p3rp for CVE-2026-109619 is published. It lists session.upload_progress.enabled=0 as a mitigation and suggests keeping it off after patching if the feature is not needed.GitHub advisory
23 Oct 2026, 10:36:41The PHP Foundation's Bluesky account posts the new advice and names CVE-2026-115560.Bluesky public API
23 Oct 2026 (date only)php.net publishes "Informational advisory: Session upload progress".php.net
23 Oct 2026 (date only)BleepingComputer and The Hacker News report the advisory.BleepingComputer, The Hacker News
23 Oct 2026, 17:22When we checked, the NVD API returned no record for CVE-2026-115560, the CVE.org API returned CVE_RECORD_DNE, and OSV returned not found.NVD, CVE.org, OSV APIs

The last row is worth a moment. A CVE ID that has been assigned but not yet published will not show up in vulnerability databases, so any scanner that depends on those databases will report nothing for CVE-2026-115560 today. That is normal for an ID reserved ahead of a full advisory, and it means you cannot use an empty scan as evidence that you are safe.

Who is affected

PHP's advisory sets out three conditions. All three have to be true at once.

ConditionWhat it means in practiceHow common it is
session.serialize_handler is set to php_binary or php_serialize in configurationphp.ini, a conf.d file, an FPM pool, an Apache vhost, .htaccess, .user.ini or a FastCGI PHP_VALUE sets a non-default handlerUncommon. The default in both php.ini-production and the manual is php.
The application switches it to php with ini_set() before session_start()Some code path, in your code or a library, calls ini_set('session.serialize_handler', 'php') at runtimeRare. PHP calls the combination unlikely in practice.
Session upload progress is still enabledsession.upload_progress.enabled is on, which is the defaultVery common. It is on unless someone turned it off.

Note the direction. The risky case is configuration that says php_binary or php_serialize and code that flips it back to php. A site that uses the default php handler everywhere and never touches it is outside the stated conditions. So is a site that sets php_serialize in configuration and leaves it alone.

On versions, the honest answer is that PHP has not said. The advisory names no affected or fixed versions. The Hacker News reports that affected versions, fixed versions, severity and whether the bug shares a root cause with CVE-2026-109619 are not yet stated. What we can say from the sources:

PHP lineStatus for CVE-2026-115560Source
8.5.12, 8.4.27, 8.3.36, 8.2.35 (the PHP2Shell releases)Not fixed. PHP asks for the setting until the next regular bugfix release, and The Hacker News reports the new CVE is not fixed by those releases.php.net, The Hacker News
Earlier 8.x releasesNo statement. Treat as in scope until the full advisory says otherwise, and patch PHP2Shell regardless.No source
8.1, 8.0, 7.4No statement. These are end of life upstream; PHP published minimal source backports for PHP2Shell only.php.net
8.6.0 release candidatesNo statement. RC4 contains the PHP2Shell fixes; PHP says not to use release candidates in production.php.net
Distribution packagesNo CVE-2026-115560 entry in the Debian security tracker or Ubuntu's CVE pages when we checked.Debian, Ubuntu

Every server API (SAPI) that serves sessions is a candidate in principle, because the conditions are about session configuration and application code, not about mod_php versus FPM. PHP has not published a per-SAPI breakdown for this CVE, so do not assume one SAPI is safe.

How it works, in plain terms

PHP has not published the technical details of CVE-2026-115560 and will not until the next release. What follows explains the moving parts using PHP's own documentation and its public bug history, without guessing at the specific flaw and without any attack steps.

Three ways to write a session

When your code calls session_start(), PHP reads the stored session for that user and turns it back into the $_SESSION array. When the request ends, it does the reverse. The session.serialize_handler setting picks the format used for that conversion. The PHP manual lists three built in options today:

  • php, the default. It writes each top level key, a separator character, then the value.
  • php_binary, an older internal format that writes a length marker before each key instead of a separator.
  • php_serialize, which simply runs serialize() on the whole array and unserialize() on the way back. The manual says it does not have the limitations of the other two.

The manual is specific about those limitations: the older handlers cannot store numeric keys, or string keys containing the | or ! characters. That detail tells you something important. In the php format, certain characters are structure, not data. In the other two formats, the same characters are just data. Bytes written by one format and read by another can therefore mean something quite different from what was written.

Upload progress writes into the session before your code runs

Session upload progress lets a browser show a progress bar for a file upload. When a multipart POST contains a field named after session.upload_progress.name (by default PHP_SESSION_UPLOAD_PROGRESS), PHP records the upload's progress in $_SESSION under a key built from session.upload_progress.prefix plus the value the client posted. The manual says this data is written before any user script runs, and it includes each file's field name and file name.

The session configuration page carries a direct warning about this: the client supplied file name and field name are written to the session store without validation. That warning was originally about local file inclusion, but it carries over to every format question. Upload progress is one of the few ways a remote client can place strings of its own choosing into session storage before your application has a say.

Because this happens during request startup, upload progress uses whatever serialize handler is in effect at startup. That is the configured value: php_binary or php_serialize, in the scenario PHP describes.

Then the code changes the format

Now the application runs. It calls ini_set('session.serialize_handler', 'php'), which the manual allows because the setting is changeable at runtime (INI_ALL). Then it calls session_start(), and PHP reads the stored session using the php format. Data that upload progress wrote in one format is now parsed in another.

Bug #71101, filed in December 2015, described exactly this kind of mismatch and called it session data injection. The maintainers' position at the time was that it required a deliberate and mistaken action by the script, so it was not treated as a security bug, and in 2021 it was closed out as something to document. The bug record sums up the rule as: the handler must not be switched for existing sessions, "lest bad things may happen."

Why can format confusion lead to code execution? Session values are restored through PHP's unserialization machinery, and unserializing attacker shaped data is the root of the well known PHP object injection class, where the impact depends on which classes the application has loaded. We are not saying that this is the precise mechanism of CVE-2026-115560. PHP has not said so, and The Hacker News notes that PHP has not stated whether it shares a root cause with CVE-2026-109619, which also involves upload progress but is a memory safety bug. What we are saying is that the conditions PHP lists line up with a hazard the project has documented for over a decade, and that the configuration fix is the same in every case.

Why one setting closes it

Turning off session.upload_progress.enabled removes the part of the chain an outside client controls: nothing gets written into the session during request startup on the client's behalf. The application's handler switch may still be a bad idea, but without upload progress there is no early, client shaped write for it to misread. That is why PHP can say the single setting gives full protection for this issue, and why applications that never switch handlers can leave the feature on.

Technical details

Everything in this section comes from PHP's own publications, the PHP manual, the php-src repository and the public vulnerability databases. Values are as published; we have not added any.

Identifiers and status

FieldValueSource
CVECVE-2026-115560php.net, PHP Foundation on Bluesky
Advisory typeInformational advisory; full advisory to follow with the next regular releasephp.net
CVSSNot publishedphp.net, BleepingComputer, The Hacker News
Affected versionsNot publishedphp.net, The Hacker News
Fixed versionsNone yet; not fixed by 8.5.12, 8.4.27, 8.3.36 or 8.2.35php.net, The Hacker News
ReporterNot namedBleepingComputer, The Hacker News
ExploitationNone reportedThe Hacker News
CVE.org recordNot published at 17:22 UTC on 23 Oct (API: CVE_RECORD_DNE)CVE.org API
NVD recordNot present at 17:22 UTC on 23 Oct (totalResults: 0)NVD API
OSV recordNot present on 23 OctOSV API
Related advisoryGHSA-6wmf-4rv5-p3rp, CVE-2026-109619, which lists the same mitigationGitHub, php.net

Trigger conditions, as PHP states them

  1. session.serialize_handler is set to php_binary or php_serialize in configuration.
  2. The application switches it to php at runtime with ini_set() before calling session_start().
  3. session.upload_progress.enabled is still on.

Settings involved

DirectiveDefaultChangeableNotesSource
session.serialize_handlerphpINI_ALL (php.ini, server config and ini_set())Options php, php_binary, php_serialize; wddx only on builds compiled with itPHP manual, php.ini-production
session.upload_progress.enabled1 (On)INI_PERDIR (not settable with ini_set())Production value On in php.ini-productionPHP manual
session.upload_progress.cleanup1INI_PERDIRRemoves progress data once POST data is readPHP manual
session.upload_progress.prefixupload_progress_INI_PERDIRPrefix for the $_SESSION keyPHP manual
session.upload_progress.namePHP_SESSION_UPLOAD_PROGRESSINI_PERDIRPOST field that turns tracking onPHP manual
session.upload_progress.freq1%INI_PERDIRUpdate frequencyPHP manual
session.upload_progress.min_freq1INI_PERDIRMinimum seconds between updatesPHP manual

Two details in that table shape the audit. First, because session.serialize_handler is INI_ALL, the switch can happen anywhere: in ini_set(), and also in the options array of session_start(), which the manual says overrides session directives for that call. Second, because the upload progress settings are INI_PERDIR, application code cannot turn the feature off for you. It has to be done in configuration.

Related fix commit for PHP2Shell

The PHP2Shell fix is commit 3584a98bda0f2c0d367255b046798b0d3a221a19, authored by Arnaud Le Blanc on 18 October 2026 with co-authors Ilija Tovilo, Jakub Zelenka, Nora Dossche and Nicolas Grekas. Its message says it replaces the bailout on out of memory with an unwind exit and resets globals that may be accessed during startup, fixing GHSA-w8hw-w9fx-j79w (CVE-2026-109618) and GHSA-6wmf-4rv5-p3rp (CVE-2026-109619). Among many other files it changes ext/session/session.c (56 lines added, 11 removed) and adds the tests ext/session/tests/rfc1867_no_user_code.phpt and ext/session/tests/rfc1867_incomplete_class_reserved_props.phpt. RFC 1867 is the multipart upload format that upload progress hooks into. We list this so you know what the 21 October releases changed in the session code. PHP says those releases do not fix CVE-2026-115560, and nothing public links this commit to the new CVE.

The related PHP2Shell advisory

GHSA-6wmf-4rv5-p3rp (CVE-2026-109619) is rated CVSS v4.0 9.2 with vector CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N and CWE-416 among its weaknesses. It labels the upload progress path "confirmed", notes the feature is on by default and needs no session_start() call, and advises keeping it off after patching if unused.

Indicators

PHP has published no indicators of compromise, request signatures or log patterns for CVE-2026-115560, and we are not aware of any from an official source. General PHP2Shell hunting advice, such as alerting on web workers that spawn shells, still applies.

What to do now

The short version: set session.upload_progress.enabled=0 in server configuration, restart every PHP worker, and confirm the web workers see the new value. Then audit for runtime handler switching so you know whether you were ever in scope. The long version follows, with commands for each common setup.

Step 1. Make sure PHP2Shell is patched first

CVE-2026-115560 is the lower risk of the two stories. If any internet facing PHP is still below 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or lacks your distribution's backport, fix that first. Our fleet guide covers that work.

php -v
php-fpm8.3 -v        # adjust to your FPM binary name
apachectl -M 2>/dev/null | grep -i php

Step 2. Find out whether you use upload progress at all

Most applications do not. Search your code, templates and front end assets for the field name and the prefix.

grep -rnI --include='*.php' --include='*.phtml' --include='*.twig' \
  --include='*.blade.php' --include='*.html' --include='*.js' --include='*.vue' \
  -e 'PHP_SESSION_UPLOAD_PROGRESS' \
  -e 'session.upload_progress' \
  -e 'upload_progress_' .

If nothing turns up, you can turn the feature off with no functional impact.

Step 3. Turn it off in configuration, not in code

session.upload_progress.enabled is INI_PERDIR, so ini_set() cannot change it. PHP's 21 October advice for the same setting was to use php.ini, an FPM pool or server configuration that applies before request parsing, and not to rely on ini_set() or FPM .user.ini. Follow the same rule here.

php.ini or a conf.d file (any SAPI):

; /etc/php/8.3/fpm/conf.d/99-session-upload-progress.ini  (Debian and Ubuntu layout)
; /etc/php.d/99-session-upload-progress.ini                (RHEL, Alma, Rocky)
session.upload_progress.enabled = 0

Find the right directory for each SAPI with:

php --ini                                  # CLI only
php-fpm8.3 -i 2>/dev/null | grep -E 'Loaded Configuration|Scan this dir'

PHP-FPM pool (per site, cannot be overridden):

; /etc/php/8.3/fpm/pool.d/www.conf
php_admin_flag[session.upload_progress.enabled] = off

The FPM documentation says settings defined with php_admin_value and php_admin_flag cannot be overridden with ini_set().

Apache with mod_php (vhost or server config, not .htaccess):

<VirtualHost *:443>
    # ...
    php_admin_flag session.upload_progress.enabled off
</VirtualHost>

The PHP manual says php_admin_flag cannot be used in .htaccess files, and a value set with it cannot be overridden by .htaccess or ini_set().

Official Docker images:

FROM php:8.3-fpm
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
 && printf 'session.upload_progress.enabled = 0\n' \
    > "$PHP_INI_DIR/conf.d/99-session-upload-progress.ini"

The image documentation says the default configuration can be customised by copying files into $PHP_INI_DIR/conf.d/, and strongly recommends the production config for production images.

Kubernetes with a ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: php-session-hardening
data:
  99-session-upload-progress.ini: |
    session.upload_progress.enabled = 0
---
# In the Deployment's pod spec:
#   volumeMounts:
#     - name: php-session-hardening
#       mountPath: /usr/local/etc/php/conf.d/99-session-upload-progress.ini
#       subPath: 99-session-upload-progress.ini

Windows and IIS: run php --ini with the same PHP build IIS uses to find the loaded php.ini, add the line, and recycle the application pool so the FastCGI processes restart.

Shared hosting and control panels: use the panel's PHP settings screen for the domain if it offers this directive, then verify with Step 5. If it does not, ask the host to set it server side.

Step 4. Restart every worker

PHP reads this setting when workers start. A reload that leaves old workers running, or a container that was not rolled, keeps the old value.

sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2        # or httpd, for mod_php
kubectl rollout restart deployment/your-php-app

Step 5. Verify what the web workers see

php -i on the command line shows the CLI configuration, which is often a different file. Check the configuration the web SAPI actually loads.

# FPM: dump the parsed pool config and look for the admin flag
sudo php-fpm8.3 -tt 2>&1 | grep -i upload_progress

# Containers: ask the image itself
docker run --rm --entrypoint php your-registry/app:prod -i | grep upload_progress.enabled

# Running pod
kubectl exec deploy/your-php-app -- php -i | grep upload_progress.enabled

For the strongest check, temporarily deploy a one line script behind authentication that prints ini_get('session.upload_progress.enabled'), request it through the real web server, and remove it afterwards. A value of 0 or an empty string means the feature is off for that pool or vhost.

Step 6. Audit for runtime handler switching

This tells you whether you were ever in scope. Look in configuration, your code and your dependencies.

Configuration that sets a non-default handler:

sudo grep -rnE 'serialize_handler' \
  /etc/php* /etc/php.d /usr/local/etc/php* \
  /etc/apache2 /etc/httpd /etc/nginx 2>/dev/null

# Per-directory files inside web roots
find /var/www /srv -name '.user.ini' -o -name '.htaccess' 2>/dev/null \
  | xargs grep -nE 'serialize_handler' 2>/dev/null

# nginx passing PHP settings over FastCGI
sudo grep -rnE 'PHP_(ADMIN_)?VALUE' /etc/nginx 2>/dev/null

Code that switches it at runtime, including vendored libraries:

# Direct ini_set calls naming the handler
grep -rnI --include='*.php' -E \
  "ini_set\s*\(\s*['\"]session\.serialize_handler" .

# session_start() options arrays that override it
grep -rnI --include='*.php' -E "['\"]serialize_handler['\"]\s*=>" .

# Dynamic ini_set calls worth a manual look
grep -rnI --include='*.php' -E "ini_set\s*\(\s*\\\$" . | grep -v '/tests/'

Run these at the project root so vendor/ is included, since a Composer package can switch the handler on your behalf. For images, search inside the image, because the deployed vendor tree is what counts.

docker run --rm --entrypoint sh your-registry/app:prod -c \
  "grep -rnE \"serialize_handler\" /var/www 2>/dev/null | head -50"

Also search infrastructure as code, where the configuration half often lives:

git grep -nE 'serialize_handler|upload_progress' -- \
  '*Dockerfile*' '*.ini' '*.conf' '*.yaml' '*.yml' '*.tf' '*.j2'

Step 7. If you find a switch, fix the design

If the audit finds an application that configures one handler and switches to another at runtime, the setting from Step 3 protects you against this CVE, but the design is the real problem. Bug #71101 has said for a decade that the handler must not be switched for existing sessions. Choose one handler, set it in configuration, remove the runtime switch, and plan a session reset so old data written in the other format is not read back. php_serialize is the option the manual describes as free of the older formats' key limitations.

Once no code depends on switching, you can lock the handler so it cannot come back:

; FPM pool
php_admin_value[session.serialize_handler] = php_serialize

Do this only after testing. Code that still calls ini_set() on a locked directive will silently fail to change it, which can break session reads for that code path.

Step 8. Plan the upgrade

When PHP publishes the full CVE-2026-115560 advisory with the next regular release, upgrade to that release, read the advisory for affected versions, and only then decide whether you need upload progress back. GHSA-6wmf-4rv5-p3rp already suggests keeping the feature off if you do not need it.

What changed since the first reports

The story is only two days old, but the advice has shifted in a way that is easy to misread.

  • 21 October: session.upload_progress.enabled=0 appeared as a partial mitigation for CVE-2026-109619 while people waited to patch. Many teams treated it as temporary and planned to switch it back on after upgrading.
  • 21 October, same advisory: GHSA-6wmf-4rv5-p3rp suggested keeping it off even after patching if the feature is not needed. Fewer people read that far.
  • 23 October: PHP says the setting is also the full protection for a separate issue, CVE-2026-115560, which the PHP2Shell releases do not fix. If you turned it back on after patching, turn it off again.
  • Not changed: there is still no CVSS, version list, reporter or patch date for the new CVE, and no reported exploitation.

Coverage around the new advisory also carries updated PHP2Shell context. BleepingComputer reports threat intelligence teams saw attackers targeting PHP2Shell four days before the patches, and The Hacker News reports exploit attempts rising from tens of thousands to millions within a day of disclosure, with no confirmed code execution in the reports it cites. None of this activity concerns CVE-2026-115560.

Open questions

  • Which versions are affected? PHP has not said. The full advisory should answer it.
  • What is the severity? No CVSS has been published. PHP's own framing is unlikely, which reflects the preconditions, not necessarily the impact once they are met.
  • Is it related to CVE-2026-109619? Both involve upload progress and share a mitigation. The Hacker News reports that PHP has not said whether they share a root cause.
  • When will it be fixed? The next regular bugfix release is the only commitment. PHP has not given a date.
  • Will end of life branches get a fix? PHP shipped source backports for 7.4, 8.0 and 8.1 for PHP2Shell because of its severity. Whether it does the same for a lower risk issue is unknown.
  • Will distributions backport it? Not yet visible in the Debian or Ubuntu trackers.
  • Which libraries switch handlers? Nobody has published a list. Audit your own vendor tree rather than waiting for one.

What would have helped

This CVE tests whether a team can quickly say which configuration its web workers load, which code changes session settings at runtime, and whether a one line change can reach every runtime with proof.

Practices any team can use

  • Configuration as code for every PHP runtime. If php.ini, conf.d files and FPM pools come from a repository, Step 3 is one pull request and one rollout. If they were hand edited on each host over years, it is a fleet hunt.
  • An effective configuration check per SAPI. Keep a small, authenticated health endpoint or a deployment step that records ini_get_all('session') for each pool and image. It catches the classic mistake of checking the CLI and assuming the web workers match.
  • Turn off what you do not use. Upload progress is on by default and few applications use it. The session configuration page has warned for years that it writes unvalidated client data into the session store. Disabling it in your baseline image would have made both the 21 October and 23 October advisories a non-event for this setting.
  • Lint for runtime changes to security relevant directives. A CI rule that flags ini_set('session.serialize_handler' or 'serialize_handler' => in your code and your vendor tree turns a hidden design flaw into a review comment.
  • One session format per application. Pick a handler, set it in configuration, lock it with php_admin_value, and treat any change as a migration with a session reset.
  • Track reserved CVEs from the vendor, not only from databases. CVE-2026-115560 had no NVD, CVE.org or OSV record on the day it was announced. A feed of php.net announcements and the php-src security advisories page is the earliest signal.

Where Safeguard fits

Safeguard would not have prevented CVE-2026-115560, and it does not change PHP configuration on your servers by itself. It helps with inventory, prioritisation and making the change with approval.

  • Inventory of PHP and Composer dependencies. Safeguard scans repositories, container images and SBOM files (safeguard scan --dir ., safeguard scan --image <name>, safeguard scan --sbom <file>, with JSON results under ./output), covering Composer packages and container layers. The platform shows which projects contain a given component and version, so finding which images still ship a PHP build below 8.3.36 becomes a query rather than a hunt.
  • Matching depends on public records. Vulnerability matching draws on databases such as NVD, GHSA and OSV. For this CVE, use the configuration and code audit above rather than waiting for a finding.
  • Policies and gates. Once the full advisory and fixed versions are published, a policy can hold back builds that still carry an affected PHP version, so an old base image tag cannot slip back in.
  • Making the change with approval. The Safeguard CLI's assistant can search a repository for serialize_handler switches, add the conf.d file to a Dockerfile, run your build and tests, and ask before each command under its default permission mode. You can send that work from the desktop or phone app to a computer running the CLI and approve each step there. Autonomous remediation can open fix pull requests for dependency upgrades, and ships switched off by default.

Where Safeguard does not help: it does not read the live php.ini on a server it does not scan, it cannot tell you whether a running FPM pool has been restarted, it cannot see PHP inside managed hosting or appliances, and reachability analysis cannot judge this CVE because PHP has not published which code path is affected. It does not detect attacks or exploitation attempts.

Zero-Day Discovery and Gold

Safeguard's Zero-Day Discovery looks for previously unknown flaws before a CVE exists, in an agentic mode and a deterministic scanner mode, and issues findings as SGZ advisories before they reach public CVE databases. It covers seven package ecosystems. PHP core is a language runtime, not a package in those ecosystems, so it is outside what Zero-Day Discovery covers. Safeguard had no part in finding CVE-2026-115560 or PHP2Shell.

Gold Open Source at gold.safeguard.sh is free and needs no login; it shows CVSS, EPSS, KEV status, affected and fixed versions and a recommended safe version for a CVE, through the site, a free read API and a CI gate GitHub Action. We have not confirmed that it holds a record for CVE-2026-115560, which had no public CVE record at all when we wrote this. Gold Registry at registry.safeguard.sh provides hardened, signed components and container images held to zero exploitable critical or high CVEs; we make no claim that it carries a PHP image that addresses this issue. The habit worth taking from both is the one this week rewards: pull patched, signed components from a curated source, keep your hardening settings in the image, and rebuild fast.

Common mistakes

  • Assuming the PHP2Shell upgrade covered it. PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 do not fix CVE-2026-115560.
  • Turning upload progress back on after patching. If you used the 21 October mitigation and then reverted it, you reopened this path.
  • Setting it with ini_set() or in application bootstrap code. The directive is INI_PERDIR and cannot be changed at runtime. It has to be in configuration.
  • Relying on FPM .user.ini. PHP's 21 October guidance for the same setting says not to rely on it. Use the pool file or php.ini.
  • Checking the CLI value. php -i reads the CLI configuration. Web workers often load a different php.ini.
  • Changing the file but not restarting. Running workers keep the value they started with.
  • Reading no findings as not affected. NVD, CVE.org and OSV had no record on announcement day.

FAQ

What is CVE-2026-115560?

It is a PHP session handling issue that PHP describes as an unlikely remote code execution vector. It can only occur when configuration sets session.serialize_handler to php_binary or php_serialize, the application switches it to php with ini_set() before session_start(), and session upload progress is enabled.

Is there a patch?

Not yet. PHP says it will fix the issue in the next regular bugfix release and publish a full advisory then. Until that release, set session.upload_progress.enabled=0.

Does upgrading to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35 fix it?

No. Those releases fix PHP2Shell (CVE-2026-109618 and CVE-2026-109619). PHP's advice and The Hacker News both indicate the new CVE is not fixed by them. Upgrade anyway, because PHP2Shell is the more serious risk.

I already disabled session upload progress for PHP2Shell. Do I need to do anything?

PHP says no further action is needed if you applied it and kept it in place. Confirm it is set in server configuration, not in code, and that every worker has restarted since.

What breaks if I turn off session upload progress?

Only server side upload progress tracking. File uploads keep working. If your application shows an upload progress bar that polls $_SESSION, that bar will stop updating. Many applications track progress in the browser instead, which is unaffected.

We use the default php serializer and never touch it. Are we affected?

According to PHP's stated conditions, no. The issue needs a non-default handler in configuration and a runtime switch to php. Applications that never change the serialize handler at runtime can keep upload progress on. Verify by running the audit in Step 6, including your vendor directory.

Has it been exploited?

No exploitation has been reported. The Hacker News says so explicitly. The exploitation activity in recent news relates to PHP2Shell, not this CVE.

Why is there no CVSS score or NVD entry?

PHP has reserved the ID and will publish the full advisory with the next release. Until a CVE record is published, NVD, OSV and similar databases have nothing to show, and scanners that rely on them will not flag it.

Does a web application firewall help?

PHP has not published request signatures, and the condition depends on how your own code handles sessions. The configuration change is simple and complete for this issue, so use it rather than relying on a filter. Our post on PHP2Shell WAF rules and their limits explains why rules are a stopgap even when signatures exist.

Fix it without waiting for upstream

CVE-2026-115560 is a case where upstream has a fix planned but no date, and where the mitigation is configuration you control. The same gaps apply to the open source components around your PHP runtime.

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would have helped

These are real product sessions on other examples; none of them shows CVE-2026-115560.

The CLI demo shows the assistant fixing a lodash CVE: it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, and pushes nothing. For this advisory, the same flow applies to a PHP repository: ask it to search the project and vendor/ for serialize_handler switches, add the conf.d file that sets session.upload_progress.enabled = 0 to your Dockerfile, and approve the image build and your test run before anything is committed.

Click through it at your own pace, or open it full screen.

The desktop app demo shows a CLI session in the conversation list, a request for work on the computer, a folder choice, and Approve or Deny on each command and file edit, ending with an upgrade done and tests passing. Here you would pick the folder that holds your PHP-FPM pool files or Ansible role, ask for the php_admin_flag line to be added to each pool, and approve each edit and the restart command one at a time.

Click through it at your own pace, or open it full screen.

The Claude demo shows connecting Claude to the Safeguard MCP server with OAuth and choosing which tools Claude can use. Once connected, the estate wide question behind this week, which projects and images still ship a PHP build below the PHP2Shell fixed versions, is something you can ask in plain language against the projects already scanned in your account, before you move on to the configuration audit for CVE-2026-115560.

Click through it at your own pace, or open it full screen.

To try it on your own projects, download Safeguard and read the CLI documentation. Related reading: PHP2Shell explained, find and patch every PHP runtime, PHP security explained and our October 2026 roundup.

Quick check

Which three conditions must all be true for CVE-2026-115560?

Configuration sets session.serialize_handler to php_binary or php_serialize, the application switches it to php with ini_set() before session_start(), and session upload progress is enabled.

Can you disable session upload progress with ini_set() in your application?

No. session.upload_progress.enabled is an INI_PERDIR setting, so it must be set in php.ini, a conf.d file, an FPM pool or server configuration, followed by a restart of every worker.

Sources

Last reviewed: 23 October 2026. This is a developing story; check the linked advisories for the latest status.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.