Php Security


Web Application Security

There are bad people ready and willing to exploit your web application. It is important that you take necessary precautions to harden your web application’s security. Luckily, the fine folks at The Open Web Application Security Project (OWASP) have compiled a comprehensive list of known security issues and methods to protect yourself against them. This is a must read for the security-conscious developer. Survive The Deep End: PHP Security by Padraic Brady is also another good web application security guide for PHP.

    Read the OWASP Security Guide

Password Hashing

Eventually everyone builds a PHP application that relies on user login. Usernames and passwords are stored in a database and later used to authenticate users upon login.

It is important that you properly hash passwords before storing them. Password hashing is an irreversible, one way function performed against the user’s password. This produces a fixed-length string that cannot be feasibly reversed. This means you can compare a hash against another to determine if they both came from the same source string, but you cannot determine the original string. If passwords are not hashed and your database is accessed by an unauthorized third-party, all user accounts are now compromised. Some users may (unfortunately) use the same password for other services. Therefore, it is important to take security seriously.

Hashing passwords with password_hash

In PHP 5.5 password_hash() was introduced. At this time it is using BCrypt, the strongest algorithm currently supported by PHP. It will be updated in the future to support more algorithms as needed though. The password_compat library was created to provide forward compatibility for PHP >= 5.3.7.

Below we hash a string, and then check the hash against a new string. Because our two source strings are different (‘secret-password’ vs. ‘bad-password’) this login will fail.

<?php
require 'password.php';

$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);

if (password_verify('bad-password', $passwordHash)) {
    // Correct Password
} else {
    // Wrong password
}

    Learn about password_hash()
    password_compat for PHP >= 5.3.7 && < 5.5
    Learn about hashing in regards to cryptography
    PHP password_hash() RFC

Data Filtering

Never ever (ever) trust foreign input introduced to your PHP code. Always sanitize and validate foreign input before using it in code. The filter_var() and filter_input() functions can sanitize text and validate text formats (e.g. email addresses).

Foreign input can be anything: $_GET and $_POST form input data, some values in the $_SERVER superglobal, and the HTTP request body via fopen('php://input', 'r'). Remember, foreign input is not limited to form data submitted by the user. Uploaded and downloaded files, session values, cookie data, and data from third-party web services are foreign input, too.

While foreign data can be stored, combined, and accessed later, it is still foreign input. Every time you process, output, concatenate, or include data in your code, ask yourself if the data is filtered properly and can it be trusted.

Data may be filtered differently based on its purpose. For example, when unfiltered foreign input is passed into HTML page output, it can execute HTML and JavaScript on your site! This is known as Cross-Site Scripting (XSS) and can be a very dangerous attack. One way to avoid XSS is to sanitize all user-generated data before outputting it to your page by removing HTML tags with the strip_tags() function or escaping characters with special meaning into their respective HTML entities with the htmlentities() or htmlspecialchars() functions.

Another example is passing options to be executed on the command line. This can be extremely dangerous (and is usually a bad idea), but you can use the built-in escapeshellarg() function to sanitize the executed command’s arguments.

One last example is accepting foreign input to determine a file to load from the filesystem. This can be exploited by changing the filename to a file path. You need to remove "/", "../", null bytes, or other characters from the file path so it can’t load hidden, non-public, or sensitive files.

    Learn about data filtering
    Learn about filter_var
    Learn about filter_input
    Learn about handling null bytes

Sanitization

Sanitization removes (or escapes) illegal or unsafe characters from foreign input.

For example, you should sanitize foreign input before including the input in HTML or inserting it into a raw SQL query. When you use bound parameters with PDO, it will sanitize the input for you.

Sometimes it is required to allow some safe HTML tags in the input when including it in the HTML page. This is very hard to do and many avoid it by using other more restricted formatting like Markdown or BBCode, although whitelisting libraries like HTML Purifier exists for this reason.

See Sanitization Filters
Unserialization

It is dangerous to unserialize() data from users or other untrusted sources. Doing so can allow malicious users to instantiate objects (with user-defined properties) whose destructors will be executed, even if the objects themselves aren’t used. You should therefore avoid unserializing untrusted data.

If you absolutely must unserialize data from untrusted sources, use PHP 7’s allowed_classes option to restrict which object types are allowed to be unserialized.
Validation

Validation ensures that foreign input is what you expect. For example, you may want to validate an email address, a phone number, or age when processing a registration submission.

See Validation Filters
Configuration Files

When creating configuration files for your applications, best practices recommend that one of the following methods be followed:

    It is recommended that you store your configuration information where it cannot be accessed directly and pulled in via the file system.
    If you must store your configuration files in the document root, name the files with a .php extension. This ensures that, even if the script is accessed directly, it will not be output as plain text.
    Information in configuration files should be protected accordingly, either through encryption or group/user file system permissions.
    It is a good idea to ensure that you do not commit configuration files containing sensitive information eg passwords or API tokens to source control.

Register Globals

NOTE: As of PHP 5.4.0 the register_globals setting has been removed and can no longer be used. This is only included as a warning for anyone in the process of upgrading a legacy application.

When enabled, the register_globals configuration setting that makes several types of variables (including ones from $_POST, $_GET and $_REQUEST) available in the global scope of your application. This can easily lead to security issues as your application cannot effectively tell where the data is coming from.

For example: $_GET['foo'] would be available via $foo, which can override variables that have not been declared. If you are using PHP < 5.4.0 make sure that register_globals is off.

    Register_globals in the PHP manual

Error Reporting

Error logging can be useful in finding the problem spots in your application, but it can also expose information about the structure of your application to the outside world. To effectively protect your application from issues that could be caused by the output of these messages, you need to configure your server differently in development versus production (live).
Development

To show every possible error during development, configure the following settings in your php.ini:

display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On

    Passing in the value -1 will show every possible error, even when new levels and constants are added in future PHP versions. The E_ALL constant also behaves this way as of PHP 5.4. - php.net

The E_STRICT error level constant was introduced in 5.3.0 and is not part of E_ALL, however it became part of E_ALL in 5.4.0. What does this mean? In terms of reporting every possible error in version 5.3 it means you must use either -1 or E_ALL | E_STRICT.

Reporting every possible error by PHP version

    < 5.3 -1 or E_ALL
      5.3 -1 or E_ALL | E_STRICT
    > 5.3 -1 or E_ALL

Production

To hide errors on your production environment, configure your php.ini as:

display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On

With these settings in production, errors will still be logged to the error logs for the web server, but will not be shown to the user. For more information on these settings, see the PHP manual: