|
| 1 | + |
| 2 | + |
| 3 | +# Security {#security_title} |
| 4 | + |
| 5 | + |
| 6 | +## Web Application Security {#web_application_security_title} |
| 7 | + |
| 8 | +There are bad people ready and willing to exploit your web application. It is important that you take necessary |
| 9 | +precautions to harden your web application's security. Luckily, the fine folks at |
| 10 | +[The Open Web Application Security Project][1] (OWASP) have compiled a comprehensive list of known security issues and |
| 11 | +methods to protect yourself against them. This is a must read for the security-conscious developer. |
| 12 | + |
| 13 | +* [Read the OWASP Security Guide][2] |
| 14 | + |
| 15 | + |
| 16 | +[1]: https://www.owasp.org/ |
| 17 | +[2]: https://www.owasp.org/index.php/Guide_Table_of_Contents |
| 18 | + |
| 19 | + |
| 20 | +## Password Hashing {#password_hashing_title} |
| 21 | + |
| 22 | +Eventually everyone builds a PHP application that relies on user login. Usernames and passwords are stored in a |
| 23 | +database and later used to authenticate users upon login. |
| 24 | + |
| 25 | +It is important that you properly [_hash_][3] passwords before storing them. Password hashing is an irreversible, one |
| 26 | +way function performed against the user's password. This produces a fixed-length string that cannot be feasibly |
| 27 | +reversed. This means you can compare a hash against another to determine if they both came from the same source string, |
| 28 | +but you cannot determine the original string. If passwords are not hashed and your database is accessed by an |
| 29 | +unauthorized third-party, all user accounts are now compromised. Some users may (unfortunately) use the same password |
| 30 | +for other services. Therefore, it is important to take security seriously. |
| 31 | + |
| 32 | +**Hashing passwords with `password_hash`** |
| 33 | + |
| 34 | +In PHP 5.5 `password_hash()` was introduced. At this time it is using BCrypt, the strongest algorithm currently |
| 35 | +supported by PHP. It will be updated in the future to support more algorithms as needed though. The `password_compat` |
| 36 | +library was created to provide forward compatibility for PHP >= 5.3.7. |
| 37 | + |
| 38 | +Below we hash a string, and then check the hash against a new string. Because our two source strings are different |
| 39 | +('secret-password' vs. 'bad-password') this login will fail. |
| 40 | + |
| 41 | + |
| 42 | +{lang="php"} |
| 43 | +~~~~~~~~ |
| 44 | +<?php |
| 45 | +require 'password.php'; |
| 46 | + |
| 47 | +$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT); |
| 48 | + |
| 49 | +if (password_verify('bad-password', $passwordHash)) { |
| 50 | + // Correct Password |
| 51 | +} else { |
| 52 | + // Wrong password |
| 53 | +} |
| 54 | +~~~~~~~~ |
| 55 | + |
| 56 | + |
| 57 | +* [Learn about `password_hash()`] [1] |
| 58 | +* [`password_compat` for PHP >= 5.3.7 && < 5.5] [2] |
| 59 | +* [Learn about hashing in regards to cryptography] [3] |
| 60 | +* [PHP `password_hash()` RFC] [4] |
| 61 | + |
| 62 | + |
| 63 | +[1]: http://php.net/function.password-hash |
| 64 | +[2]: https://github.com/ircmaxell/password_compat |
| 65 | +[3]: http://en.wikipedia.org/wiki/Cryptographic_hash_function |
| 66 | +[4]: https://wiki.php.net/rfc/password_hash |
| 67 | + |
| 68 | + |
| 69 | +## Data Filtering {#data_filtering_title} |
| 70 | + |
| 71 | +Never ever (ever) trust foreign input introduced to your PHP code. Always sanitize and validate foreign input before |
| 72 | +using it in code. The `filter_var()` and `filter_input()` functions can sanitize text and validate text formats (e.g. |
| 73 | +email addresses). |
| 74 | + |
| 75 | +Foreign input can be anything: `$_GET` and `$_POST` form input data, some values in the `$_SERVER` superglobal, and the |
| 76 | +HTTP request body via `fopen('php://input', 'r')`. Remember, foreign input is not limited to form data submitted by the |
| 77 | +user. Uploaded and downloaded files, session values, cookie data, and data from third-party web services are foreign |
| 78 | +input, too. |
| 79 | + |
| 80 | +While foreign data can be stored, combined, and accessed later, it is still foreign input. Every time you process, |
| 81 | +output, concatenate, or include data in your code, ask yourself if the data is filtered properly and can it be trusted. |
| 82 | + |
| 83 | +Data may be _filtered_ differently based on its purpose. For example, when unfiltered foreign input is passed into HTML |
| 84 | +page output, it can execute HTML and JavaScript on your site! This is known as Cross-Site Scripting (XSS) and can be a |
| 85 | +very dangerous attack. One way to avoid XSS is to sanitize all user-generated data before outputting it to your page by |
| 86 | +removing HTML tags with the `strip_tags()` function or escaping characters with special meaning into their respective |
| 87 | +HTML entities with the `htmlentities()` or `htmlspecialchars()` functions. |
| 88 | + |
| 89 | +Another example is passing options to be executed on the command line. This can be extremely dangerous (and is usually |
| 90 | +a bad idea), but you can use the built-in `escapeshellarg()` function to sanitize the executed command's arguments. |
| 91 | + |
| 92 | +One last example is accepting foreign input to determine a file to load from the filesystem. This can be exploited by |
| 93 | +changing the filename to a file path. You need to remove `"/"`, `"../"`, [null bytes][6], or other characters from the |
| 94 | +file path so it can't load hidden, non-public, or sensitive files. |
| 95 | + |
| 96 | +* [Learn about data filtering][1] |
| 97 | +* [Learn about `filter_var`][4] |
| 98 | +* [Learn about `filter_input`][5] |
| 99 | +* [Learn about handling null bytes][6] |
| 100 | + |
| 101 | +### Sanitization |
| 102 | + |
| 103 | +Sanitization removes (or escapes) illegal or unsafe characters from foreign input. |
| 104 | + |
| 105 | +For example, you should sanitize foreign input before including the input in HTML or inserting it into a raw SQL query. |
| 106 | +When you use bound parameters with [PDO](#databases), it will sanitize the input for you. |
| 107 | + |
| 108 | +Sometimes it is required to allow some safe HTML tags in the input when including it in the HTML page. This is very |
| 109 | +hard to do and many avoid it by using other more restricted formatting like Markdown or BBCode, although whitelisting |
| 110 | +libraries like [HTML Purifier][html-purifier] exists for this reason. |
| 111 | + |
| 112 | +[See Sanitization Filters][2] |
| 113 | + |
| 114 | +### Validation |
| 115 | + |
| 116 | +Validation ensures that foreign input is what you expect. For example, you may want to validate an email address, a |
| 117 | +phone number, or age when processing a registration submission. |
| 118 | + |
| 119 | +[See Validation Filters][3] |
| 120 | + |
| 121 | + |
| 122 | +[1]: http://php.net/book.filter |
| 123 | +[2]: http://php.net/filter.filters.sanitize |
| 124 | +[3]: http://php.net/filter.filters.validate |
| 125 | +[4]: http://php.net/function.filter-var |
| 126 | +[5]: http://php.net/function.filter-input |
| 127 | +[6]: http://php.net/security.filesystem.nullbytes |
| 128 | +[html-purifier]: http://htmlpurifier.org/ |
| 129 | + |
| 130 | + |
| 131 | +## Configuration Files {#configuration_files_title} |
| 132 | + |
| 133 | +When creating configuration files for your applications, best practices recommend that one of the following methods be |
| 134 | +followed: |
| 135 | + |
| 136 | +- It is recommended that you store your configuration information where it cannot be accessed directly and pulled in |
| 137 | +via the file system. |
| 138 | +- If you must store your configuration files in the document root, name the files with a `.php` extension. This ensures |
| 139 | +that, even if the script is accessed directly, it will not be output as plain text. |
| 140 | +- Information in configuration files should be protected accordingly, either through encryption or group/user file |
| 141 | +system permissions |
| 142 | + |
| 143 | +## Register Globals {#register_globals_title} |
| 144 | + |
| 145 | +**NOTE:** As of PHP 5.4.0 the `register_globals` setting has been removed and can no longer be used. This is only |
| 146 | +included as a warning for anyone in the process of upgrading a legacy application. |
| 147 | + |
| 148 | +When enabled, the `register_globals` configuration setting that makes several types of variables (including ones from |
| 149 | +`$_POST`, `$_GET` and `$_REQUEST`) available in the global scope of your application. This can easily lead to security |
| 150 | +issues as your application cannot effectively tell where the data is coming from. |
| 151 | + |
| 152 | +For example: `$_GET['foo']` would be available via `$foo`, which can override variables that have not been declared. |
| 153 | +If you are using PHP < 5.4.0 __make sure__ that `register_globals` is __off__. |
| 154 | + |
| 155 | +* [Register_globals in the PHP manual](http://php.net/security.globals) |
| 156 | + |
| 157 | + |
| 158 | +## Error Reporting {#error_reporting_title} |
| 159 | + |
| 160 | +Error logging can be useful in finding the problem spots in your application, but it can also expose information about |
| 161 | +the structure of your application to the outside world. To effectively protect your application from issues that could |
| 162 | +be caused by the output of these messages, you need to configure your server differently in development versus |
| 163 | +production (live). |
| 164 | + |
| 165 | +### Development |
| 166 | + |
| 167 | +To show every possible error during <strong>development</strong>, configure the following settings in your `php.ini`: |
| 168 | + |
| 169 | + |
| 170 | +{lang="ini"} |
| 171 | +~~~~~~~~ |
| 172 | +display_errors = On |
| 173 | +display_startup_errors = On |
| 174 | +error_reporting = -1 |
| 175 | +log_errors = On |
| 176 | +~~~~~~~~ |
| 177 | + |
| 178 | +> Passing in the value `-1` will show every possible error, even when new levels and constants are added in future PHP |
| 179 | +> versions. The `E_ALL` constant also behaves this way as of PHP 5.4. - |
| 180 | +> [php.net](http://php.net/function.error-reporting) |
| 181 | + |
| 182 | +The `E_STRICT` error level constant was introduced in 5.3.0 and is not part of `E_ALL`, however it became part of |
| 183 | +`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 |
| 184 | +use either `-1` or `E_ALL | E_STRICT`. |
| 185 | + |
| 186 | +**Reporting every possible error by PHP version** |
| 187 | + |
| 188 | +* < 5.3 `-1` or `E_ALL` |
| 189 | +* 5.3 `-1` or `E_ALL | E_STRICT` |
| 190 | +* > 5.3 `-1` or `E_ALL` |
| 191 | + |
| 192 | +### Production |
| 193 | + |
| 194 | +To hide errors on your <strong>production</strong> environment, configure your `php.ini` as: |
| 195 | + |
| 196 | + |
| 197 | +{lang="ini"} |
| 198 | +~~~~~~~~ |
| 199 | +display_errors = Off |
| 200 | +display_startup_errors = Off |
| 201 | +error_reporting = E_ALL |
| 202 | +log_errors = On |
| 203 | +~~~~~~~~ |
| 204 | + |
| 205 | +With these settings in production, errors will still be logged to the error logs for the web server, but will not be |
| 206 | +shown to the user. For more information on these settings, see the PHP manual: |
| 207 | + |
| 208 | +* [error_reporting](http://php.net/errorfunc.configuration#ini.error-reporting) |
| 209 | +* [display_errors](http://php.net/errorfunc.configuration#ini.display-errors) |
| 210 | +* [display_startup_errors](http://php.net/errorfunc.configuration#ini.display-startup-errors) |
| 211 | +* [log_errors](http://php.net/errorfunc.configuration#ini.log-errors) |
0 commit comments