Filename contains PHP
The upload uses a filename such as <?php system($_GET['cmd']); ?>.
php2shell is a critical, unauthenticated RCE in PHP.
This demo shows the publicly documented Chain A:
filename → session.upload_progress → LFI → code execution.
It is a controlled lab simulation, not an attack on third-party systems.
id payload.
Click a card or play the animation. The demo uses a deliberately vulnerable lab with an LFI.
The upload uses a filename such as <?php system($_GET['cmd']); ?>.
session.upload_progress stores the filename unescaped in the session file.
An LFI like include($_GET['page']) loads the file from /tmp/sessions/.
The payload runs as the web user — in the lab as www-data, not as root.
The simulation shows the requests and the outcome. The switch changes between PHP 8.3.35 and 8.3.36.
These are the relevant values from a stock PHP 8.3.36 build (no application overrides). Distros and Docker images can differ — check php --ini and php -i on the target.
; Relevant stock settings (measured, not guessed) session.upload_progress.enabled = On session.upload_progress.cleanup = On session.upload_progress.prefix = upload_progress_ session.use_strict_mode = Off session.use_only_cookies = On session.cookie_httponly = Off session.cookie_secure = Off session.cookie_samesite = expose_php = On display_errors = "" ; CLI; production php.ini: Off allow_url_include = Off open_basedir = ""
session.upload_progress.cleanup=Off and session.use_only_cookies=Off to make the chain deterministic.
A stock PHP 8.3 build already has cleanup=On and use_only_cookies=On, which makes this exact Chain A harder — but it does not fix CVE-2026-109618/109619. Patching is still mandatory.
upload_progress writes upload metadata into the session; use_only_cookies decides whether a session ID may arrive in POST/URL; use_strict_mode rejects uninitialized IDs; cleanup deletes progress data after the upload.
| Directive | Stock 8.3 | Lab | Harden to | Effect |
|---|---|---|---|---|
| session.upload_progress.enabled | On | On | Off | Removes the upload-progress write path; may break upload progress UI. |
| session.upload_progress.cleanup | On | Off | On | Deletes progress data sooner; reduces the window, not a patch. |
| session.use_strict_mode | Off | Off | On | Rejects attacker-chosen session IDs. |
| session.use_only_cookies | On | Off | On | Ignores PHPSESSID in POST/URL; blocks this chain's SID fixation. |
| session.cookie_httponly | Off | Off | On | JavaScript cannot read the session cookie. |
| session.cookie_secure | Off | Off | On | Cookie only over HTTPS. |
| session.cookie_samesite | (empty) | (empty) | Lax / Strict | Reduces cross-site request risk. |
| expose_php | On | On | Off | Hides the PHP version from response headers. |
| display_errors | "" (CLI) | default | Off | Prevents error details from leaking to users; log instead. |
| allow_url_include | Off | Off | Off | Blocks remote includes (keep this). |
| open_basedir | (empty) | (empty) | app + session dir | Limits which files an LFI can reach; must be tested. |
PHPSESSID is ignored, so the attacker cannot fix the session ID. The lab disables this on purpose.; Defense in depth — NOT a replacement for the patch
session.upload_progress.enabled = Off
session.upload_progress.cleanup = On
session.use_strict_mode = On
session.use_only_cookies = On
session.cookie_httponly = On
session.cookie_secure = On
session.cookie_samesite = Lax
expose_php = Off
display_errors = Off
log_errors = On
allow_url_include = Off
; Adjust to your app; may break apps that need other paths
open_basedir = /var/www/html:/tmp
Where to change: php --ini shows the loaded file. On Debian/Ubuntu typically /etc/php/8.3/apache2/php.ini or /etc/php/8.3/fpm/php.ini; in Docker use a file under /usr/local/etc/php/conf.d/. Restart Apache/PHP-FPM after changes.
upload_progress=Off removes progress bars and some upload UX. Test the app; the patch is still the primary fix.
It can stop an LFI from reading /etc/passwd, but a wrong path breaks the app. Add only directories the app really needs.
display_errors=Off plus log_errors=On avoids leaking paths and stack traces to attackers.
An LFI, the upload-progress configuration, and a multipart request. No magic — that is what makes it dangerous.
<?php
// Deliberately vulnerable lab — local only!
if (isset($_GET['page'])) {
include($_GET['page']);
} else {
echo "php2shell lab";
}
All SAPIs are affected; mod_php is considered especially exposed because the trigger can run before application code.
| Branch | Vulnerable | Fix | Status |
|---|---|---|---|
| PHP 8.5 | < 8.5.12 | 8.5.12 | Fix available |
| PHP 8.4 | < 8.4.27 | 8.4.27 | Fix available |
| PHP 8.3 | < 8.3.36 | 8.3.36 | Fix available |
| PHP 8.2 | < 8.2.35 | 8.2.35 | Fix available |
| PHP ≤ 8.1 | EOL | Upgrade | No longer supported |
Patch first. Everything else is defense in depth, not a replacement for the update.
Upgrade to 8.2.35, 8.3.36, 8.4.27 or 8.5.12+. Restart Apache/PHP-FPM afterwards.
session.upload_progress.enabled=Off reduces the Chain A surface, but does not fix the core CVE.
No dynamic includes. Whitelist files, canonicalize paths, block .. and stream wrappers.
use_strict_mode=On, use_only_cookies=On, HttpOnly/Secure/SameSite, short lifetime.
Watch for unusual multipart filenames, PHPSESSID in bodies, and includes from /tmp/. Add WAF rules.
Run PHP as a dedicated user with minimal filesystem and network access. No root, no broad write paths.
include/require that touches user input.