In the first part of this guide, we focused on the most common and most dangerous (according to OWASP.org) security issues in PHP code: SQL Injection vulnerabilities. We explained, how important input validation is, how bad it is to include untrusted data (user input) directly in an SQL query, and how prepared statements help you avoid SQL Injection attacks. In the second part, we focus on two other common and dangerous PHP vulnerabilities and attack types: directory traversal and code injections attacks. In both cases, these vulnerabilities are also caused by unsanitized user data.
Directory Traversal
Directory traversal (path traversal) refers to an attack that affects the file system. In this type of attack, an authenticated or unauthenticated user can request and view or execute files that they should not be able to access. Such files usually reside outside of the root directory of a web application or outside of a directory to which the user is restricted (for example, /var/www). In many cases, an attacker may read any file accessible for the user that is running the web server (usually www-data). If the server has badly configured file permissions (very common), this attack can be escalated further.
Insecure Code Sample
In the following example, the script passes an unvalidated/unsanitized HTTP request value directly to the include()
PHP function. This means that the script will try to include whatever path/filename is passed as a parameter:
$file = $_GET['file'];
include($file);
For example, if you pass /etc/passwd as the argument, this file is readable for all users. Therefore, the script returns the content of the file with information about all system users:
Secure Code Sample
This vulnerability may be mitigated in different ways, depending on the specific case. However, the most common and generic way to do it is by using the basename()
and realpath()
functions.
The basename()
function returns only the filename part of a given path/filename: basename("../../../etc/passwd")
= passwd. The realpath()
function returns the canonicalized absolute pathname but only if the file exists and if the running script has executable permissions on all directories in the hierarchy: realpath("../../../etc/passwd")
= /etc/passwd
.
$file = basename(realpath($_GET['file']));
include($file);
Now, if we request the same file as above, we get an empty response:
Avoid Blacklisting
Blacklisting is bad practice because there are more ways to make the same request. Intelligent attackers always find ways to bypass restrictions for user-supplied input. For example ../../../etc/ can also be written like this: ..%2F..%2F..%2Fetc%2F.
If you need to have access to specific files, use a whitelist instead.
Code Injection/Execution
In the case of PHP code injection attacks, an attacker takes advantage of a script that contains system functions/calls to read or execute malicious code on a remote server. This is synonymous to having a backdoor shell and under certain circumstances can also enable privilege escalation.
Insecure Code Sample
In this example, a script uses the exec()
function to execute the ping
command. However, the host is dynamic (passed via an HTTP GET request):
exec("ping -c 4 " . $_GET['host'], $output);
echo "<pre>";
print_r($output);
echo "</pre>";
Passing www.google.com returns the output of the ping google.com
command:
This snippet has a code injection vulnerability. It allows an attacker to pass multiple commands to the function using a semicolon. In Linux, this delimiter is used to execute multiple commands inline.
For example, if you pass www.google.com;whoami, the script returns the following output:
Secure Code Sample
There are two functions that you can use in PHP applications and that can help harden command line calls such as exec()
, shell_exec()
, passthru()
, and system()
: escapeshellcmd()
and escapeshellarg()
. The escapeshellcmd()
function escapes any characters in a string that might be used to execute arbitrary commands. The following characters are escaped by including a backslash before them: &#;`|*?~<>^()[]{}$\
, \x0A
, and \xFF
. Single and double quotes are escaped only if they are not paired. For example, escapeshellcmd("ping -c 4 www.google.com;ls -lah")
= ping -c 4 www.google.com\;ls -lah.
The escapeshellarg()
function adds single quotes around a string and escapes any existing single quotes. As a result, the entire string is being passed as a single argument to a shell command.
escapeshellcmd()
and escapeshellarg()
functions might behave unpredictably with different operating systems, especially on Windows.// #1 Restrict multiple commands
exec(escapeshellcmd("ping -c 4 " . $_GET['host']), $output);
// #2 Restrict multiple commands and multiple arguments
exec(escapeshellcmd("ping -c 4 " . escapeshellarg($_GET['host'])), $output);
If you pass www.google.com;whoami to the secure script, this is what you get in return:
To avoid security issues, we recommend that you disable exec()
, shell_exec()
, passthru()
, and system()
functions in PHP configuration unless it is absolutely necessary to use them. You can also create a whitelist of accepted commands/arguments.
Frequently asked questions
Directory traversal (path traversal) is a type of web vulnerability. If such a vulnerability exists, an attacker may trick a web application into reading and processing the contents of files outside of the document root directory of the application or the web server. For example, an attacker may make the application display the /etc/passwd file in the browser.
The simplest way to avoid directory traversal vulnerabilities in PHP is to use a combination of basename() and realpath() functions. However, this does not protect your application from local file inclusion attacks.
Code injection (remote code execution – RCE) is a type of web vulnerability. If an RCE vulnerability exists, the attacker may inject code in the application back-end language and the application executes this code. This may even let the attacker get full control of the web server.
To avoid code injection, we recommend that you disable exec(), shell_exec(), passthru(), and system() functions in PHP configuration unless it is absolutely necessary to use them. You can also create a whitelist of accepted commands/arguments.