In this post were going to have a look at some of the best practices in PHP when it comes to security.
Disclaimer: I am not a security expert. This guide is purely based on the practices that I’m currently following that I believe to be secure. I’ve done a lot of research before putting any of the information here. But if you find something that you consider to be insecure please do share in the comments.
If possible always use the latest stable release of PHP because it contains some security updates and bug fixes. This will make applications written on top of it more secure.
- Disable exposure of which PHP version your server is using. You can do it by searching for
php.inifile and set it to
This will disable the inclusion of the PHP version in the response headers under the
Here’s an example of a site which has set
On. As you can see the value
X-Powered-By attribute is
PHP/5.4.17 so we pretty much know which PHP version the server is running. An attacker can use this information to exploit the security vulnerabilities of this specific PHP version.
Make sure that you don’t have any files in your server that calls the
phpinfo()function. If you want to make use of it, make sure the filename can’t easily be guessed like
phpinfo.phpand don’t store it on the root of your web accessible directory. Don’t forget to delete it once you’re done.
Log errors instead of displaying them. Errors, notices and warnings in your web application can provide valuable information to attackers such as filenames and the name of fields that you used on your tables. Make sure you set the following in your
1 2 3 4 5
- Disable file uploads when not needed.
If your web application has a file upload feature then you need to make sure that you know some of the best practices in securing file uploads. Here’s a good article from Sitepoint on how to create a secure file upload in PHP. You can also make use of a library that’s specifically created for handling file uploads such as the Upload library from Josh Lockhart(Codeguy).
- Disable remote file execution. If you don’t need to use functions such as
file_get_contentsthen you can just set
Curlcan provide with similar functionality so most of the time you won’t really need it.
- Limit the maximum size of POST data to a value that you think is enough for your web application needs. This is to prevent attackers from flooding your web application by POSTing huge amounts of data. Note that this can be expressed in kilo (K), mega (M) or giga (G).
Do note that the value that you set for
post_max_size should be larger than the
upload_max_filesize since uploaded files are also submitted via POST.
memory_limit should also be larger than the
- Limit maximum input time. This will limit the amount of time for PHP to parse input data from either
$_GET. Note that the value is expressed in seconds.`
- Limit maximum execution time to a reasonable value. This will automatically terminate a running PHP script once the maximum execution time is over. The default value of 30 seconds seems reasonable enough so in most cases you won’t really need to change it.
Limit the use of shell functions such as
popen. If there’s no other option for implementing something and you absolutely need to use it make sure that users of your web application will not be able to execute any system commands. If you need user input for executing system commands then make sure that you’re validating the data correctly.
Only allow execution of PHP files on a specific directory. Preferably this should be the web accessible root directory.
- Set temporary upload directory to a path outside of the
open_base_dir. This prevents files in the temporary upload directory from being executed.
- Make sure that your web accessible directory is set to
Always use the CURL extension when making requests to other servers especially if you’re working with sensitive data. This is because CURL by default makes requests securely over SSL/TLS (Secure Socket Layer/Transport Security Layer). Here’s an example on how to perform requests using CURL:
1 2 3 4 5 6
Also make sure to set the following options when you’re working with sensitive data:
- CURLOPT_SSL_VERIFYPEER – should be set to
TRUEalways. This will tell CURL to check if the remote certificate of the server where you’re performing a request is valid.
- CURLOPT_SSL_VERIFYHOST – should be set to
TRUEalways. This tells CURL to check that the Certificate was issued to the entity that you’re requesting to.
Input Validation and Filtering
Input validation is the first layer of defense when it comes to securing your PHP applications. User input should never be trusted thus we need to filter and validate. But first lets differentiate filtering from validation:
Filtering – also called sanitization. This is used for ensuring that the data is properly formatted before we try to validate. An example of filtering is removing whitespaces from a string or removing any invalid characters from an email address.
Validation – the process of making sure that the data is what you expect it to be. For example if the web form asks for the age then you expect the age to be a number so the code must validate that what is inputted in the age field is indeed a number. And not just any number. If you expect the users who will fill out the form to be between ages 20 – 40 then you must also validate that the age that was inputted falls within that range. There are lots of things to consider when validating user input, as programmers its our duty to ensure that we’ve covered most of the scenarios.
PHP comes with filtering functions that you can use to sanitize data before saving into the database.
- addslashes – adds a backslash before a single quote (
'), double quote (
"), and NULL byte (
- filter_var – sanitizes strings based on the filters listed here
- htmlspecialchars – converts HTML strings into their corresponding entity.
- htmlentities – the same as
htmlspecialcharsthe only difference is that
htmlentitiestry to encode all characters which have HTML character entity equivalents. What this means is that you will have a much longer resulting string if the string that you’re trying to use contains not only HTML but also characters which has an HTML entity equivalents.
- preg_replace – replaces all the string that matches the pattern that you specify.
- strip_tags – strips all HTML and PHP tags from the original string.
- trim – used for trimming leading and trailing whitespaces from the original string.
What function you use depends on your specific needs. If you need to save a string into the database and you expect that there will be a single quote or double quote on that string then you should call
addslashes before saving into the database. This ensures that you won’t get any unexpected character errors when inserting the string.
PHP also comes with validation functions one of those is the
filter_var. You can use it to validate different types of data:
- FILTER_VALIDATE_BOOLEAN – used for validating if the value is either
- FILTER_VALIDATE_EMAIL – used for validating if the value is a valid email
- FILTER_VALIDATE_REGEXP – used for validating if the value matches a specific expression
- FILTER_VALIDATE_URL – used for validating if the value matches the accepted pattern of a URL
- FILTER_VALIDATE_INT – used for validating if the value is an integer
- FILTER_VALIDATE_FLOAT – used for validating if the value is a float or a decimal number
- FILTER_VALIDATE_IP – used for validating if the value is a valid IPv4 or IPv6 IP address
Here’s how to use the
filter_var function to validate user input:
1 2 3 4 5 6 7 8
Note that the
filter_var function returns the original value that you specified as the first argument if the value is valid and returns
false if its not valid.
There are also a bunch of PHP functions that checks for a specific data type and returns
true if the value meets
- is_array – checks if a variable contains an array.
- is_bool – checks if a variable contains a boolean value.
- is_double – checks if a variable contains a double.
- is_float – checks if a variable contains a floating point number.
- is_integer|is_long|is_int – checks if value is a valid integer. Note that this doesn’t check for the data type since all user input is always in string so either the value
- is_null – checks if a variable is
- is_numeric – checks if a value is a valid number, the main difference of this function with
is_intis that it also checks for the data type so string numbers such as
- is_object – checks if a variable contains an object.
- is_resource – checks if a variable contains a resource.
- is_scalar – checks if a variable contains a scalar value.
- is_string – checks if a variable contains string.
And there are also those that checks for the presence of a specific value:
- isset – checks if a specific variable has been set or declared. Note that this disregards the actual value so if the variable in question doesn’t have a value assigned to it (aka
undefined) then it will still return
- empty – checks if a specific variable has a truthy value. Here’s a good reference on this subject: type comparisons
Input Filtering and Validation Libraries
Here are some libraries that you can use for input validation and filtering:
Working with Databases
Limit User Privileges
When working with databases its a good practice to not use the root user as the user of the database. Sometimes out of laziness we tend to use the default database user in MySQL when connecting to the database like this:
1 2 3
This is not a good practice since the root user has the privilege to perform almost all the operations that you can perform in all of the database that’s currently residing in the MySQL server. Selecting data, inserting new data, updating, deleting, truncating tables, dropping tables, dropping a whole database. All of these can be performed by the root user so a successful SQL injection attack can pretty much give an attacker the privilege to do all of these operations.
Limiting the user privileges is really simple. In the screenshot below I’m using a tool called phpmyadmin to create a user that has only read privileges:
While you’re there you can also set resource limit to the user. Setting a reasonable resource limit reduces the possibility of malicious users flooding your database with lots of queries. Just be sure to do your research first before setting resource limits to a specific database user, you don’t want the limit to run out on genuine users of your application:
Limiting user privileges effectively reduces the risk of a successful SQL injection attack. It means that even if an attacker manages to execute a query like the following:
It won’t be allowed by the database if the database user that was used doesn’t have a privilege to drop a table. But what if an attacker successfully gains access to a database user that has all the privileges to make a successful attack? For example a System Administrator user account has been hacked and now the attacker can simply use SQL injection to do all sorts of evil stuff with the database. That’s where the use of PDO and prepared statements comes in.
Use PDO Or MySQLi
Use the PDO or MySqli extension when building applications that connect to the MySQL database. The original PHP MySQL API is already deprecated and therefore no longer recommended. Using PDO or MySqli will give you the benefit of using parametrized queries which effectively reduces the risk of SQL injection attacks if used correctly. Here’s an example on how to perform database queries using PDO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
How does PDO make things more secure you ask? Its more secure in the sense that it sends the query and data (user input) separately to MySQL. So what happens is that the SQL string that you supplied as the argument for the
prepare method is parsed and then later on using
bindParam the placeholder is safely substituted to the user input. Finally the query is executed. In simple terms MySQL considers every user input as a string with no meaning when PDO is used so SQL injection is effectively prevented.
If you want to learn more about PDO be sure to check out the PDO tutorial for MySQL Developers
One more thing to consider when working with databases is how to safely store passwords. You probably already know that its a bad practice to simply store passwords in plain text. Because this means that when attackers were successfully able to dump the contents of a user table then they will basically have access to all of the users information which includes things such as credit card numbers, favorite TV show or the name of your first crush.
And it might already be old news to you but these functions for hashing passwords isn’t that safe as attackers can use brute force attack or rainbow tables in order to determine a password:
You can use the following functions instead:
Note that some of the hashing functions like
password_hash are only available on PHP 5.5.
crypt is available on PHP 4 and 5.
Here are some examples on how to use each of the above hashing methods:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
You can implement the
hash_pbkdf2 method by storing both the hash and the salt in a single field (prepend the salt to the hash).
1 2 3 4 5 6 7 8 9 10 11 12 13
Some people say that you should store your salt strings to a database separate from the database where you store your hashes. Maybe this is true if you don’t use random salts for each of the passwords. An attacker would still have difficulty in cracking a password even if he has access to both salt and hash as long as the salt is random for each user.
password_hash there’s no need to store the random salts separately since you can verify if the password is valid without specifying the salt that was used:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Note that you can also use the
password_verify method for verifying hashes that are created by using the
crypt methods as they both use the C Crypt Scheme).
You can also use password hashing libraries like PHPAss or Password-Compat if you want. The main benefit of using libraries is that they’re often compatible with lower PHP versions but are still secure. Here’s an example on how to use each of those:
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10 11 12 13
Note that the password-compat library uses the same syntax as the password hashing method
password_hash in PHP 5.5. But this library works for PHP 5.3.7 and above. So this library is intended for providing forward compatibility to PHP versions lower than 5.5. This means that there’s no real need to use this library if you’re already using PHP 5.5.
Other things to remember when storing passwords:
- Do not email or log passwords if your users forgot their password just email them a link that will allow them to update their password.
- Do not store passwords in plain text (yeah I know I said this already)
- Use random password salts
- Do not limit the length of passwords that can be entered by your users
- Encourage your users to use long, secure and random passwords by implementing password strength meters on the front-end of your application. Passwords doesn’t really need to be memorable as users can pretty much use password managers like keepas to store their passwords.
Working with Uploaded Files
When working with uploaded files do not use the
$_FILE super global in determining the type of the file as the can be easily spoofed by simply changing the file extension:
1 2 3 4 5
finfo class to determine the actual mime type of a file instead. This is slower than simply checking the file type from the
$_FILE super global but it does the job of determinining the real file type:
1 2 3 4 5 6
Better yet use a library that’s especially created for this type of task like the upload library by Josh Lockhart. Here’s how you can use it to verify that the file that was uploaded is an image file that’s not greater than 2 MB in size.
1 2 3 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
In this article you’ve learned some of the basic ways you can add security to your PHP projects. We’ve barely scratch the surface with this guide. There’s a lot more you can do to improve the security of the applications that you’re writing. Be sure to check out the resources below if you want to learn more about securing PHP applications.