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.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
// Deployment diagnostic for the no-LFI session attack surface.
|
||||
// Drop on the target, request once, then REMOVE it.
|
||||
//
|
||||
// Reports whether the two preconditions of the served-path chain hold:
|
||||
// 1. session.save_path is inside (or aliased to) a web-served tree
|
||||
// 2. security.limit_extensions is empty (fpm would execute
|
||||
// extensionless session files via PATH_INFO)
|
||||
|
||||
header('Content-Type: text/plain');
|
||||
|
||||
$sp = session_save_path();
|
||||
if ($sp === '') {
|
||||
$sp = sys_get_temp_dir();
|
||||
}
|
||||
// strip the optional "N;/path" depth prefix
|
||||
if (($p = strrchr($sp, ';')) !== false) {
|
||||
$sp = substr($p, 1);
|
||||
}
|
||||
$doc = rtrim($_SERVER['DOCUMENT_ROOT'] ?? '', '/');
|
||||
$real_sp = realpath($sp) ?: $sp;
|
||||
$real_doc = realpath($doc) ?: $doc;
|
||||
|
||||
$under_doc = ($real_doc !== '' && strncmp($real_sp, $real_doc, strlen($real_doc)) === 0);
|
||||
$le = ini_get('security.limit_extensions');
|
||||
$sapi = php_sapi_name();
|
||||
|
||||
echo "sapi = {$sapi}\n";
|
||||
echo "session.save_path = {$sp}\n";
|
||||
echo "DOCUMENT_ROOT = {$doc}\n";
|
||||
echo "save_path under docroot = " . var_export($under_doc, true) . "\n";
|
||||
echo "security.limit_extensions = [" . $le . "]\n";
|
||||
echo "upload_progress.enabled = " . ini_get('session.upload_progress.enabled') . "\n";
|
||||
echo "upload_progress.cleanup = " . ini_get('session.upload_progress.cleanup') . "\n";
|
||||
echo "memory_limit = " . ini_get('memory_limit') . "\n";
|
||||
echo "post_max_size = " . ini_get('post_max_size') . "\n";
|
||||
echo "open_basedir = [" . ini_get('open_basedir') . "]\n";
|
||||
|
||||
$verdict = array();
|
||||
if (ini_get('session.upload_progress.enabled')) {
|
||||
if ($under_doc) {
|
||||
$verdict[] = "save_path is web-served: session files are requestable";
|
||||
}
|
||||
if ($sapi === 'fpm-fcgi' && trim($le) === '') {
|
||||
$verdict[] = "limit_extensions empty: extensionless session files EXECUTE via PATH_INFO";
|
||||
}
|
||||
}
|
||||
if ($verdict) {
|
||||
echo "\nEXPOSED:\n";
|
||||
foreach ($verdict as $v) {
|
||||
echo " - {$v}\n";
|
||||
}
|
||||
echo "\nSee nolfi/FINDINGS.md. Set security.limit_extensions and move save_path.\n";
|
||||
} else {
|
||||
echo "\nNo served-path exposure detected.\n";
|
||||
}
|
||||
Reference in New Issue
Block a user