Files
php2shell/fcgi/FINDINGS.md
T
neko 8147ce0edb FastCGI auto_prepend_file exploit: RCE without LFI on default PHP config
New exploit at fcgi/exploit_fcgi.py combines the session upload progress
write primitive (CVE-2026-109618/109619) with direct FastCGI access to
PHP-FPM. A slow-drip multipart upload holds one FPM worker in the
multipart parser while a second FastCGI request includes the poisoned
session file via auto_prepend_file.

Works on default PHP configuration, no LFI, no framework, no libraries.
security.limit_extensions does not block auto_prepend_file inclusion.
php_admin_value in pool config does not block PHP_VALUE from raw FastCGI.

Tested and confirmed RCE on:
  PHP 8.3.6  (local, pre-patch)
  PHP 8.3.35 (Docker, pre-patch)
  PHP 8.3.36 (Docker, patched)
  PHP 8.4.27 (Docker, patched)

Mitigated by session.upload_progress.enabled=0 or blocking FPM access.

Also includes nolfi/ exploit (served save_path + pathinfo routing) from
previous work, and updated README with mitigation matrix for all vectors.
2026-10-21 21:19:25 +00:00

5.4 KiB

RCE via FastCGI + Session Upload Progress (No LFI)

Summary

When an attacker can reach PHP-FPM's FastCGI socket (TCP or unix), the session upload progress write primitive from CVE-2026-109618 / CVE-2026-109619 becomes RCE without any application-level vulnerability.

No LFI needed. No framework needed. No special PHP configuration needed. Works on default php.ini settings.

Attack chain

Worker A (held open)                Worker B (executes)
---                                 ---
FastCGI POST with                   FastCGI GET with
multipart body:                     PHP_VALUE:
  PHP_SESSION_UPLOAD_PROGRESS         auto_prepend_file=
  = <?php system(...); ?>             /tmp/sess_<SID>
                                    SCRIPT_FILENAME=
File upload content                   /var/www/html/index.php
dripped one byte at a time
  -> session file persists          PHP includes the session
  -> parser blocks for              file before index.php
    the entire drip window            -> attacker PHP runs
                                      -> RCE

Step by step

  1. Open a FastCGI connection to FPM worker A. Send a POST request with Content-Type: multipart/form-data and PHPSESSID=<SID> in the HTTP_COOKIE param. The body contains:

    • A PHP_SESSION_UPLOAD_PROGRESS field with embedded PHP code
    • A file upload whose content is dripped slowly (byte by byte)
  2. FPM worker A enters the multipart parser. As soon as the progress field and file header are read, session.upload_progress writes the attacker's PHP code into /tmp/sess_<SID>. The parser then blocks reading the file content from STDIN, which we control.

  3. While worker A is blocked, open a second FastCGI connection to worker B. Send a GET request with:

    • SCRIPT_FILENAME = /var/www/html/index.php (any existing .php file)
    • PHP_VALUE = auto_prepend_file = /tmp/sess_<SID>
  4. Worker B executes. PHP's auto_prepend_file directive causes it to include() the session file before running the main script. The session file contains the attacker's <?php ... ?> code, which executes.

  5. Cleanup. The drip finishes, worker A completes normally (or times out), the session file is cleaned up. The attacker's code has already executed on worker B.

Why it works on default configuration

Setting Default Required
session.upload_progress.enabled 1 1
session.save_path /tmp Any known path
pm.max_children 5 (dynamic) >= 2
security.limit_extensions .php .phar Any (not checked for auto_prepend)
session.upload_progress.cleanup 1 Does not matter (drip defeats it)

security.limit_extensions blocks execution of files without .php extension as SCRIPT_FILENAME. But auto_prepend_file loads files via include(), which ignores security.limit_extensions entirely. The session file (which has no extension) is included as PHP code regardless of limit_extensions.

When FPM is reachable

This is the main precondition. The attacker needs raw TCP or unix socket access to the FPM process. This is realistic in:

  1. Docker containers. The official php:*-fpm images configure FPM to listen on TCP 0.0.0.0:9000. Any container on the same Docker network can reach it.

  2. Kubernetes pods. PHP-FPM pods are typically addressed by service name. Adjacent pods, including compromised ones, can send raw FastCGI.

  3. SSRF. An SSRF vulnerability in any application on the same host can reach 127.0.0.1:9000 or the unix socket. The gopher:// scheme can carry arbitrary TCP payloads including FastCGI.

  4. Exposed port. Misconfigured firewalls, cloud security groups, or Docker port mappings exposing 9000 to the network.

  5. Same-host access. A low-privilege shell on the same machine can often read the unix socket (permissions vary by distro).

Test results

Tested against PHP-FPM 8.3.6 with default settings:

  • session.upload_progress.enabled=1 (default)
  • session.save_path=/tmp (default)
  • session.upload_progress.cleanup=1 (default)
  • security.limit_extensions = .php .phar (default)
  • pm = static, pm.max_children = 2

Results: 3/3 tests successful. Race hit on first attempt within 200ms of the drip starting. Reliable with --drip-time 5 (5 second window).

Mitigations

  1. Set session.upload_progress.enabled=0 if upload progress tracking is not needed. This removes the write primitive entirely.

  2. Block network access to FPM. FPM should only be reachable from the web server process. Use unix sockets with restricted file permissions, or firewall TCP port 9000.

  3. Use pm.max_children=1 as a last resort. With only one worker, the racer cannot execute while the drip holds a worker. This severely impacts performance and is not practical.

  4. Setting security.limit_extensions does NOT help. The inclusion happens via auto_prepend_file, which bypasses limit_extensions.

  5. Setting php_admin_value[auto_prepend_file] in the FPM pool config does NOT help. Verified: with php_admin_value[auto_prepend_file] = /dev/null in the pool, a FastCGI request with PHP_VALUE: auto_prepend_file = /tmp/sess_xxx still overrides it. Raw FastCGI access bypasses the web server that would normally enforce admin values. The only effective mitigation is blocking FastCGI access.