monstertov@php-session-confusion

CVE-2026-115560

· monster research · monstertov · tov.monster

One Session File, Two Different Formats

our lab reproduced session serializer confusion on php 8.5.12. under the tested configuration, upload-progress data reached an application gadget and resulted in command execution as www-data.

the important part is the transition between two session formats. php writes upload-progress information using the configured serializer. the application then switches serializers before opening the session, causing the same bytes to be interpreted differently.

CVE-2026-115560 is a publicly disclosed vulnerability. this writeup covers our lab reproduction, the application requirements and the negative controls that interrupted the chain.

php 8.5.12 does not fix this issue. it fixes CVE-2026-109618 and CVE-2026-109619, which are separate vulnerabilities. as of 24 october 2026, the published guidance for CVE-2026-115560 is to disable session upload progress until its fix arrives in the next regular bugfix release.

The Lab Configuration

the lab image's build recipe checks out the php-8.5.12 source tag and compiles php with apache mod_php. the saved fingerprint reports php 8.5.12, the apache php module and prefork.

the vulnerable profile uses:

session.save_handler=files
session.serialize_handler=php_binary
session.upload_progress.enabled=1
session.upload_progress.cleanup=0

the application registers its class autoloader, then runs:

ini_set('session.serialize_handler', 'php');
session_start();

that order creates the mismatch. the server configuration selects php_binary, while the application selects php before decoding the session.

the cleanup setting also matters. the direct positive reproduction described here uses session.upload_progress.cleanup=0. that is a non-default setting, allowing the progress information to remain after the upload completes.

What Upload Progress Adds

session upload progress lets php record information about an incoming upload inside a session. a multipart request containing the configured progress field can activate this processing before the application's own script runs.

the stored information includes the uploaded filename. that makes part of the session content externally influenced, even though the application has not assigned that content to $_SESSION itself.

receiving a filename containing unusual bytes should leave those bytes as data. the problem in this configuration is how that data gets interpreted when the application opens the session using another serializer.

Where the Meaning Changes

php_binary and php do not encode session records in the same way.

the php handler uses | to separate a session variable name from its serialized value. a pipe carried inside data written under another format can therefore acquire a different meaning during decoding.

the lab chain places serialized object data in an uploaded filename. upload-progress tracking carries that filename into the session record. the application then switches to php, and the mismatched decoding can expose the embedded object to deserialization.

the sequence looks like this:

configured serializer: php_binary
                 |
multipart upload creates progress data
                 |
application switches serializer to php
                 |
session_start() decodes the session
                 |
embedded object reaches an application gadget

there is no heap-offset calibration in this chain. the mechanism depends on a format mismatch and application object behaviour.

The Application Gadget

the lab includes a cache-writer class with two properties: a filesystem path and the contents to write.

its destructor persists those contents to that path. under normal use, the application would construct the object and choose both values. in the reproduction, deserialization supplies them.

that destructor turns object injection into a file write. the destination is inside the writable document root, where the resulting php file can be requested through the web server.

this is an essential condition of the demonstrated command execution. the lab contains a suitable gadget, the web process can write the destination, and the server executes php files there.

finding serializer confusion does not, by itself, establish the same command-execution outcome in every application.

The Recorded Positive Result

the saved direct-run transcript contains:

direct: arm+fire -> HTTP 200
shell OK (nonce verified)
uid=33(www-data) gid=33(www-data) groups=33(www-data)
exit=0

the driver generates a random filename for the written file. it also adds a fresh marker to the verification command and requires that marker in an HTTP 200 response before returning success.

our harness adds another check: the external process must return exit status zero, and its combined output must contain uid=.

thats stronger evidence than treating an HTTP 200 response alone as success. the saved output includes the expected web-process identity as well as successful marker verification.

the file-placement path is HTTP-triggered. the driver sends the serialized object in the multipart request, and the application gadget performs the filesystem write. the container inspection command recorded afterward only lists the resulting files.

Two Negative Controls

the useful comparison was changing one requirement at a time.

each profile has its own document root. a file written in the vulnerable profile therefore cannot appear in a control simply because both containers share the same directory.

profileconfiguration differencerecorded result
vulnerableupload progress enabled, php_binary switched to phpsuccess
nouploadupload progress disabledmiss
consistentconfigured serializer retained throughout the requestmiss

the noupload profile keeps the handler-switching application but changes:

session.upload_progress.enabled=0

the consistent profile keeps upload progress enabled and retains the gadget class. its application calls session_start() without changing the serializer.

the saved negative-control transcript records exit status 1 for both profiles in direct and race modes. it also records zero matching shell files before and after those runs.

our harness logs separately record misses against both control ports, alongside successful runs against the vulnerable profile.

these results match the proposed mechanism. disabling upload progress removes the source of the poisoned progress record. retaining the configured serializer removes the change in interpretation.

What the Evidence Establishes

the recorded results support command execution in the configured php 8.5.12 lab and failure of the tested chain under both negative-control configurations.

the evidence does not establish a complete affected-version range. it also does not establish that every php application contains a usable gadget or writable execution path.

there are measurement limits too. the saved session fingerprint comes from php CLI, so it should not be treated as a complete inspection of apache's effective configuration. the positive HTTP execution evidence and the configuration files provide additional context, but those are separate observations.

the success checks support the recorded result. they are not an unconditional guarantee against false positives, nor a complete history of every file left in the lab.

What to Check in an Application

the specific pattern to look for is a server-configured session serializer that application code changes before session_start().

for this issue, the disclosed combination is php_binary or php_serialize in configuration, followed by a runtime switch to php, with upload progress still enabled.

where upload progress is unnecessary, the published mitigation is:

session.upload_progress.enabled=0

apply it in configuration that takes effect before request parsing, then restart the php workers. changing it inside the application is too late for upload-progress processing that has already happened.

for this lab, the strongest result was the comparison: execution with the required combination present, and misses when either upload progress or the handler switch was removed.

scope: this writeup describes saved lab evidence and static source review. the demonstrated command-execution result depends on the tested configuration and application gadget.