Skip to content

About

Local exposure auditor for CVE-2026-109618 / CVE-2026-109619 (PHP session.upload_progress UAF). Answers whether YOUR host is exposed, with proof.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

php2shell-check

status scope cve python

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.

Why this exists

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:

  1. Is the PHP build in the affected range?
  2. Is session.upload_progress.enabled actually on for this SAPI?
  3. Does an unauthenticated multipart request carrying PHP_SESSION_UPLOAD_PROGRESS actually 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.

What it does NOT do

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.

Install

Single file, standard library only.

curl -O https://raw.githubusercontent.com/TahuCryptsi/php2shell-check/main/php2shell-check.py
chmod +x php2shell-check.py

Use

Passive — version and ini state only:

./php2shell-check.py
php2shell-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/sessions
probe : {"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

Exit codes

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"
done

Verdict logic

PHP >= 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.

Important caveat about SAPI

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_progress

or read the pool config directly. If the CLI and the pool disagree, the pool wins — that is what serves requests.

Remediation

  1. Upgrade to 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 or later.
  2. Interim: session.upload_progress.enabled=0 in the effective php.ini, then reload FPM. Upload progress bars stop working; nothing else does.
  3. If progress display is a product requirement, reject PHP_SESSION_UPLOAD_PROGRESS in multipart bodies from unauthenticated clients at the edge.

Tested against

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.

License

MIT.

About

Local exposure auditor for CVE-2026-109618 / CVE-2026-109619 (PHP session.upload_progress UAF). Answers whether YOUR host is exposed, with proof.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages