A local exposure auditor for CVE-2026-109618 / CVE-2026-109619 — the PHP
session.upload_progress use-after-free (CVSS 9.2). It runs on the host you
own and tells you whether that host is exposed, with proof rather than a
version string.
The advisory says "PHP 5.4 through 8.5.x — all versions with upload_progress — fixed only in
8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 (2026-10-21). Everything else, including all EOL branches,
stays exposed." That is not
an answer a sysadmin can act on. Real fleets mix SAPIs, per-pool php.ini
overrides, containers built from three different base images, and a php -v on
the CLI that says nothing about what FPM is actually running.
This tool asks the questions that decide exposure:
- Is the PHP build in the affected range?
- Is
session.upload_progress.enabledactually on for this SAPI? - Does an unauthenticated multipart request carrying
PHP_SESSION_UPLOAD_PROGRESSactually cause a session file to be written?
Question 3 is the one that matters. It is the precondition for the UAF, and it is the one a version scan cannot answer.
No exploitation. No heap grooming, no crash attempt, no shell. The probe sends one ordinary multipart upload and then looks in the session directory for the marker it planted. If the file is there, the write path is reachable — that is the finding. Nothing is corrupted, nothing is executed.
The probe refuses any host that is not 127.0.0.1, localhost, or ::1
unless you pass --allow-remote, and that flag exists for auditing your own
remote hosts, not anyone else's.
Single file, standard library only.
curl -O https://raw.githubusercontent.com/TahuCryptsi/php2shell-check/main/php2shell-check.py
chmod +x php2shell-check.pyPassive — version and ini state only:
./php2shell-check.pyphp2shell-check — CVE-2026-109618 / CVE-2026-109619
----------------------------------------------------------
PHP version : 8.3.13
Version verdict : exposed (PHP < 8.3.14 — affected and unpatched)
upload_progress : 1
file_uploads : 1
save_handler : files
save_path :
----------------------------------------------------------
VERDICT: AT RISK
Active — prove the write path is reachable, against your own host:
./php2shell-check.py --probe http://127.0.0.1/ \
--session-dir /var/lib/php/sessionsprobe : {"sent": true, "status": 200, "session_file_found": true,
"session_file_path": "/var/lib/php/sessions/sess_d343e7e1...",
"session_file_excerpt": "upload_progress_php2shellcheck397dcecaa3f0|a:5:{...}"}
----------------------------------------------------------
VERDICT: EXPOSED
--session-dir is optional but it is the difference between "the server
accepted the field" and "the server wrote the file." Use it.
Machine-readable:
./php2shell-check.py --json| Code | Meaning |
|---|---|
| 0 | patched, not affected, or mitigated |
| 1 | exposed — preconditions met, write path proven reachable |
| 2 | at risk — preconditions met, reachability not proven |
| 3 | inconclusive — could not determine PHP state |
Exit codes make it usable in a fleet loop:
for host in $(cat fleet.txt); do
ssh "$host" './php2shell-check.py --json' || echo "REVIEW: $host"
donePHP >= fixed release -> PATCHED (0)
PHP < 5.4 -> NOT AFFECTED (0)
session.upload_progress.enabled = 0 -> MITIGATED (0)
file_uploads = 0 -> MITIGATED (0)
affected + progress on + probe proved write -> EXPOSED (1)
affected + progress on + probe unproven -> AT RISK (2)
version undeterminable -> INCONCLUSIVE (3)
Fixed releases: 8.2.35, 8.3.36, 8.4.27, 8.5.12 (all 2026-10-21). PHP 5.x is EOL and will
never receive a fix — it stays exposed by construction.
The tool reads state from the CLI SAPI of the binary you point it at with
--php. FPM pools frequently override php.ini per pool, and a container may
run a different build than the host CLI.
For an FPM host, confirm the pool's actual values:
php-fpm8.3 -tt 2>&1 | grep -i upload_progressor read the pool config directly. If the CLI and the pool disagree, the pool wins — that is what serves requests.
- Upgrade to 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 or later.
- Interim:
session.upload_progress.enabled=0in the effectivephp.ini, then reload FPM. Upload progress bars stop working; nothing else does. - If progress display is a product requirement, reject
PHP_SESSION_UPLOAD_PROGRESSin multipart bodies from unauthenticated clients at the edge.
Verified end to end on PHP 8.3.13 (php -S, session.upload_progress.cleanup=0)
— probe planted a marker, session file located at
/tmp/probe-test/sess3/sess_<sid>, excerpt captured showing the serialized
upload_progress_<marker>|a:5:{...} payload. Verdict EXPOSED, exit 1.
Not yet verified against PHP-FPM. If you run it on an FPM host and the result differs from what the pool config implies, that is a bug — open an issue.
MIT.