CVE-2026-115560: PHP's Unpatched Session Bug and the One-Line Fix

PHP's new session bug only reaches apps that switch serialize handlers at runtime with upload progress on. How to check yours, and where the one-line fix must go.

A shield protects a server with uploads switched off, illustrating mitigation for PHP session bug CVE-2026-115560.

If you followed PHP's emergency advice on Wednesday, you already have the fix for the bug it warned about on Friday. PHP's new informational advisory describes CVE-2026-115560, an unlikely but real path to remote code execution through session upload progress, and the protection is the same single setting it recommended for PHP2Shell: session.upload_progress.enabled=0.

The bug works only when three things line up in one application, and most PHP sites match none of them. PHP isn't shipping an emergency release for it: the fix comes with the next regular bugfix release, which has no date yet. The Hacker News covered the announcement; this post is the practical side of it. It shows how to check your code and configuration for the three conditions, where the setting has to live to take effect, and why the line that closes this bug does not close PHP2Shell.

Set session.upload_progress.enabled=0 till next PHP release to block an unlikely RCE vector (CVE-2026-115560). Already recommended for CVE-2026-109619 Wed, keep disabled. Only apps with non-default serialize_handler switching it to "php" at runtime can be affected. #php #phpc

— The PHP Foundation (@ThePHPF) October 23, 2026

The three conditions, and how to check each one

Every PHP session is stored as text between requests, and session.serialize_handler decides the format of that text (our older guide to sessions and authentication in PHP covers the basics). PHP's advisory says an application is exposed only when all three of these are true at once:

  1. The configuration sets session.serialize_handler to php_binary or php_serialize. The default is php, so this is a deliberate change in php.ini, a pool file, a virtual host or a .user.ini.

  2. Application code switches it back to php at runtime with ini_set(), before it calls session_start().

  3. Session upload progress is still enabled. It is on by default.

I'd start with the code, because the second condition is the rare one. Search your application and its dependencies for the setting's name rather than for one exact call, so that a switch made through a variable, or through the options array of session_start(), still turns up:

grep -rn --include='*.php' "serialize_handler" /var/www/your-app

Keep vendor/ in that search. The 2015 PHP bug report about this pattern pointed at Zend Framework code that switched the handler, so a match can sit in a library you never wrote. No matches at all means the second condition can't be met, and PHP says applications that never change the serialize handler at runtime “can keep the feature enabled”.

For the first and third conditions, ask PHP the way your web server runs it. On the command line, php -i reads the CLI's own configuration, and on Debian and Ubuntu that is a different php.ini from the one PHP-FPM or Apache loads. A throwaway script answers for the real thing:

<?php
// check.php: open it through your site, read the output, then delete it.
header('Content-Type: text/plain');
echo 'PHP ', PHP_VERSION, ' (', PHP_SAPI, ")\n";
echo 'session.serialize_handler = ', ini_get('session.serialize_handler'), "\n";
echo 'session.upload_progress.enabled = ', ini_get('session.upload_progress.enabled'), "\n";

A handler of php means the first condition fails. An upload_progress.enabled of 1 means the feature is live. Delete the file when you're done, because it tells anyone who finds it exactly which PHP version you run.

Where the switch has to go

The fix is one line, but it only works in the right place. PHP's manual lists session.upload_progress.enabled as changeable per directory or in the main configuration, never from code, so ini_set('session.upload_progress.enabled', '0') returns false and changes nothing. The official php:8.4 Docker image ships without a php.ini, so on it the feature stays on until you switch it off.

PHP's advisory for the PHP2Shell flaws says to set it “administratively and restart every worker”, and PHP's security announcement adds: “Do not rely on ini_set() or FPM .user.ini.” In practice, that means one of these:

You run PHP as

Put the setting in

Then

PHP-FPM (behind Nginx, or Apache with proxy_fcgi)

the pool file: php_admin_flag[session.upload_progress.enabled] = off

restart PHP-FPM

Apache with mod_php

the virtual host: php_admin_flag session.upload_progress.enabled off

restart Apache

The official PHP Docker image

a file in /usr/local/etc/php/conf.d/ with session.upload_progress.enabled = 0

rebuild and redeploy the image

Any of them, server-wide

the php.ini of the SAPI that serves the site, or a file in its conf.d folder

restart that SAPI

I'd use the pool or virtual-host line where you can. On the official php:8.4-fpm image, a pool-level php_admin_flag stopped upload tracking even with a .user.ini beside the script trying to turn it back on. A .user.ini that sets it to 0 does work once PHP-FPM reads it, but PHP-FPM reads it only when the web server passes the document root. Without that, the same file was skipped silently and uploads were still tracked, which is exactly the kind of quiet failure PHP's warning is about.

After the restart, load the check script again. Seeing session.upload_progress.enabled = 0 there is the proof I'd trust: editing a file without restarting leaves every running worker on the old value.

Switching it off costs very little. Session upload progress matters only to upload bars that ask the server for a percentage while a file is still arriving. A bar built on the browser's own XMLHttpRequest upload events doesn't use it, and PHP's manual already suggests disabling the option “in applications that do not use upload progress bars”.

Why switching serialize handlers is the dangerous part

PHP hasn't published the technical details yet, so here is what its documentation says about the moving parts, and why these three conditions are a known bad combination.

Session upload progress writes to the session while a file is still uploading, “before any scripts are executed”, in the words of PHP's manual. What it writes comes from the client: the value of the upload's progress field becomes part of the session key, and the file's name and form field name are stored with it. PHP's configuration reference warns that these names “are not validated and may contain PHP code”.

That write uses the serializer from the configuration, php_serialize or php_binary. The script then switches to php and starts the session, so the same bytes are read back in a different format. The php format marks where each key ends and its serialized value begins with a | character. On the official php:8.4-fpm image with default settings, one ordinary upload left this in the session file before the script had run a single line (the progress field's value was check):

upload_progress_check|a:5:{s:10:"start_time";i:1792759175;s:14:"content_length";i:208;…

Read with the wrong handler, client-chosen text that contains a | can decide what PHP unserializes. Unserializing data an attacker shaped is PHP object injection. It turns into code execution only if the application loads classes whose magic methods can be chained into something harmful, so the risk depends on each codebase as much as on PHP.

None of this is new. In December 2015, PHP bug #71101 described upload progress data written with one handler and read with another after an ini_set() switch, and warned that “arbitrarily session data injection is possible”. The maintainers didn't treat it as a vulnerability, since it needs the application to make an unusual change itself. In 2021 they turned it into a documentation issue titled “serialize_handler must not be switched for existing sessions”.

PHP hasn't said whether CVE-2026-115560 is that old report or a new route to the same place. My guess is that the attention around PHP2Shell sent someone back to a corner the project had filed away.

The one-line fix does not cover PHP2Shell

This is the misreading I'd watch for. PHP says that if you applied session.upload_progress.enabled=0 for PHP2Shell and kept it, “no further action is needed” for CVE-2026-115560. That sentence is about the new bug alone. For the two PHP2Shell flaws, PHP's release announcements are blunt: “There are no complete mitigations, updating is the only safe choice.”

Here is how the three issues compare, from PHP's own advisories:

CVE

What PHP calls it

Severity

Fixed in

Does upload_progress=0 protect you?

CVE-2026-109618

RCE via OOM corruption

Critical, CVSS 9.2

8.5.12, 8.4.27, 8.3.36, 8.2.35

No: update

CVE-2026-109619

RCE via stale global state

Critical, CVSS 9.2

8.5.12, 8.4.27, 8.3.36, 8.2.35

Only on the session paths: update

CVE-2026-115560

An unlikely RCE vector in session upload progress

Not published yet

The next regular bugfix release

Yes, until the fix ships

So the order of work is: update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or to your distribution's patched package, for PHP2Shell, then keep the upload progress switch off for CVE-2026-115560 until the regular release ships. PHP 8.1 and older no longer get updates from the PHP project. Our guide to confirming that PHP2Shell fixes reach the patched workers covers checking that the new build is the one actually serving requests, and the PHP2Shell explainer covers how those two flaws work.

I'd leave the switch off after the fix ships too, unless something on your site uses it. PHP's advisory for CVE-2026-109619 makes the same suggestion: it “might be prudent” to keep the feature disabled if you don't need it, “even after applying the patch”.

What PHP hasn't said yet

The advisory is short, and the gaps matter for planning. PHP has not said:

  • which PHP versions are affected;

  • which releases will contain the fix, or when they ship;

  • how severe the bug is, as a score;

  • whether it shares a root cause with CVE-2026-109619.

A full advisory is due with the next release. No exploitation of CVE-2026-115560 has been reported so far.

The attacks since 21 October target the two PHP2Shell flaws instead, and they started early. Amazon said its honeypots logged an attempt on 17 October, four days before the flaws were disclosed, and Cloudflare saw attempts climb from tens of thousands in the first 45 minutes after disclosure to millions by early 22 October. Sophos and Arctic Wolf reported attempts to install a Mirai botnet variant. Previdian, Wordfence and others report scanning and probing but no confirmed code execution, and researchers say a reliable exploit is proving harder to build than expected.

What I'd do today

Run the grep first. If nothing in your code or its vendor folder touches serialize_handler, CVE-2026-115560 can't reach you through this path, and your real work this week is still the PHP2Shell update. If something does, switch upload progress off at the pool or virtual-host level today, restart, confirm it with the check script, and then change the code so the handler is set once in the configuration and never switched. One setting closes this bug, but it was never a substitute for patching PHP2Shell.

FAQ

Frequently asked questions

Is CVE-2026-115560 being exploited?

No exploitation has been reported as of 23 October 2026. The attacks seen since 21 October target the two PHP2Shell flaws, CVE-2026-109618 and CVE-2026-109619.

Does updating to PHP 8.4.27 or 8.5.12 fix CVE-2026-115560?

No. Those releases fix PHP2Shell. PHP will fix CVE-2026-115560 in its next regular bugfix release; until then, set session.upload_progress.enabled=0.

Can I turn off session upload progress with ini_set()?

No. The setting can only be changed per directory or in the main configuration, so ini_set() returns false. Set it in php.ini, the PHP-FPM pool (php_admin_flag) or the Apache virtual host, then restart PHP.

Will disabling session upload progress break file uploads?

No. Uploads keep working. Only progress bars that poll the server for upload status lose their data; bars built on the browser's XMLHttpRequest upload events are unaffected.

Vishal
· Developer

Vishal writes Trickyworld's tutorials and interview-preparation guides on programming, databases, cyber security and AI. Vishal has been shipping production software long enough to have firm opinions about Python, PHP, Node.js, and JavaScript, and to have been wrong about each of them at least once. The work has run the whole stack: Drupal and Laravel applications, React front ends, MySQL and MongoDB underneath, Redis and Memcached for the things that had to be fast, Apache Solr for the things that had to be findable. That last part turned out to matter more than anyone expected. Tune a Solr index for a season, then watch a language model confidently retrieve the wrong document, and you recognise an old problem wearing new clothes. Most of what is hard about AI systems is not the model. It is retrieval, caching, latency budgets, and deciding what the system does when the answer comes back wrong — which is work he has been doing since well before anyone called it AI engineering. He plays a lot of games, which is less a hobby disclosure than an admission of where a good share of his engineering instinct came from. Real-time systems with a frame budget. State that has to stay coherent while a player does something no designer anticipated. The permanent question of what happens when the frame cannot finish in time. Those habits carry over more directly than they have any right to. He has written several books on programming, all built on the same conviction: most technical writing is unclear because the author never decided what to leave out. More from Vishal → · Editorial policy

More on Cyber Security.

Latest articles

$ subscribe --weekly

Get the next tutorial in your inbox.

Programming, security and AI — the practical bits, no spam. Unsubscribe any time.