PHP security highlights

·

4 min read

SQL Injection

Use prepared statements and parameterized queries.

These are SQL statements that are sent to and parsed by the database server separately from any parameters. This way it is impossible for an attacker to inject malicious SQL.

Basically, there are two options to achieve this:

  • Using PDO(PHP Data Object):

$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');

$stmt->execute([ 'name' => $name ]);

foreach ($stmt as $row) {
    // Do something with $row
}
  • Using MySQLi(improved MySQL):
$stmt = $dbconnection->prepare('SELECT * FROM employees WHERE name = ?');
$stmt->bind_param('s', $name);//'s' specifies the variable type => 'string'
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
  // do sth with the rows
 }

If the connection DB is different from MySQL, there is a driver-specific second option that you can refer to( for example, pg_execute and pg_prepare() for Postgres). PDO is universal.

Can prepared statements be used for dynamic queries?

While you can still use prepared statements for the query parameters, the structure of the dynamic query itself cannot be parametrized and certain query features cannot be parameterized.

For these specific scenarios, the best thing to do is use a whitelist filter that restricts the possible values.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}

Directory Traversal & Code Injection

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.

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() the function returns only the filename part of a given
path/filename: basename(“../../../etc/passwd”) = passwd.

The realpath() the 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.

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 "&ltpre>";
print_r($output);
echo "&lt/pre>";

Passing https://example.com/file.php?host=www.google.com// returns the output of the ping google.com command.

code injection vulnerability

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. https://example.com/file.php?host=www.google.com;whoami//

Secure code sample

There are two functions that you can use in PHP applications 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() the function adds single quotes around a string and escapes any existing single quotes. As a result, the entire string is passed as a single argument to a shell command.

Note: The 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);
Note: Version one guarantees that the user cannot run multiple commands. However, the user can still use multiple arguments of the same command. In version two, the user cannot pass multiple commands or arguments.

To avoid security issues, we recommend that you disable exec(), shell_exec(), passthru(), and system() functions in the PHP configuration unless it is absolutely necessary to use them. You can also create a whitelist of accepted commands/arguments.