It’s not easy to become a great PHP developer which may very well take years of training and practice, but this doesn’t mean you shouldn’t do your best to not be a bad one that undermines every project he’s involved in. Based on the project experiences of my team and some recent researches done on PHP security issues, I have come up with a list of things you should know and do in your PHP code to achieve this goal. A few of them may be subjective and opinionated but most of them are actually security bottom lines that every self-deemed good PHP developer must definitely adhere to.
Below is a statistic breakdown of web security vulnerabilities in the first half of 2009, to give you a rough idea of what are the major security problems websites and web applications suffer:
You can download the full version in PDF prepared by Cenzic. It has some very interesting web attacks data.
There are a lot more to consider other than PHP to secure your application. This is just a starting point if you are not also a system administrator who is equally responsible in maintaining a secure server (OS, web server, etc.). Oh and there’s browser security (such as phishing) that you essentially have no control over. So we will just stick to PHP here.
php.ini
Some of the default settings in php.ini in earlier PHP versions are pretty dangerous. Modify the original php.ini if you are a server administrator or create custom php.ini in the webroot (directory of the web documents, accessible to the public via web server) to override the unsafe settings or use in-code functions such as ini_set():
- Disable register_globals and don’t rely on it in your code:
register_globals = Off
- Disable magic quotes and don’t rely on it in your code:
magic_quotes_gpc = Off
- Disable error reporting:
display_errors = Off
This is for production deployment, otherwise achievable runtime by
error_reporting(0)
. For development and debugging, make sure you turn on the full error reporting in your code byerror_reporting(E_ALL)
so that you get a full grip of what’s going on with your application. - Enable error logging and save the log file to a directory below webroot:
log_errors = On ignore_repeated_errors = On html_errors = Off error_log = /path/below/webroot/logs/php_error_log
Normally the errors will be displayed to the users / crackers when something goes wrong thus disclosing internal information about your application. Now that we have disallowed them to display publicly, enabling error logging helps capture all PHP errors and store them somewhere the users / crackers cannot access yet we can retrieve and analyze when necessary.
- Store session data below webroot:
session.save_path = /path/below/webroot/sessions
In most cases, you don’t have to worry about more than just the error logging part because the most up-to-date version of PHP has been well optimized in security by default. For example, register_globals and magic_quotes_gpc are turned off as factory settings, and session data is automatically stored outside of webroot. Other than these, feel free to override things by the ini_set() function when you feel obligated to.
Note that magic_quotes_gpc cannot be set by ini_set() any more after PHP version 4.2.3, you have to do it in a local php.ini or .htaccess.
.htaccess
Disable directory listing site wide by adding this line to the .htaccess file (hidden) placed in the document root of your domain:
Options -Indexes
And allow it in the specific directory where it is absolutely necessary and no files that are meant to be shown publicly are stored:
Options +Indexes
Valuable files and sensitive data
This includes member only materials, administrator stuff and site wide configuration files containing the vital data of your site, or whatever you feel uncomfortable exposed to the public. In fact, if you are having doubts whether some file is all right to be exposed, don’t expose it at all.
- Store them below (outside) webroot so they cannot be retrieved by anyone via web server requests.
- Hide the file path and use a PHP script to provide download of it.
Uploaded files
Compulsory security practices when handling uploaded files:
- Validate the file name in $_FILES against potential data manipulation. For instance, discard anything that’s not alphanumeric or dot in the file name string.
- Validate the mime type against potential spoof and discard anything that seems not what you expect.
- After validation, change the file name and move it somewhere confidential below webroot. You can also optionally tar it for storing.
- Never execute / serve uploaded files with include() nor require().
- Never serve files with mime types of “application/octet-stream”, “application/unknown” nor “plain/text”.
Incoming requests
Cross Site Request Forgery (CSRF) Attacks: Just as the name suggests, the request is forged / fabricated from the authenticated user’s computer yet without his awareness and acknowledgement. For example, the malicious attacker creates a sneaky link (Clickjacking) or a form and manages to trick the legally logged user to use it to submit a hidden request to your application to perform something that he doesn’t authorize at all such as deletion. To prevent it:
- Create a confirmation page for the legitimate user to make a final call by clicking ‘Yes’ or ‘No’. The request is then submitted to the server by POST method. Don’t just delete something (or perform other important operations) upon a simple GET request.
- Generate a unique token (whatever name = value) in the user’s session and include it in every form as a hidden field so whenever the user submits a POST request, you can check if the form contains the correct token against that in the session variable to make sure if it is submitted by the user by true intentions.
Incoming / User provided data
Always filter or sanitize incoming data in $_GET, $_POST, $_COOKIE or $_REQUEST before using them in your code. Validate that a value is just what you expect and discard any characters suspicious / unneeded. Better yet, white list a few value prototypes by regular expressions and ignore anything that doesn’t match the criteria.
Path Traversal Attacks: By browsing through and trying different combinations of path input to your application, the cracker aims to access files and directories outside of the webroot, probably with a chain of ‘../’ in the path input. To prevent the attack:
- Never use user input data directly in your code before it is sanitized or tested against the white list, especially when it is used to determine the subject of file open, include / require, file create and file delete operations.
- Let users select indexes rather than the literal path string / file name. For example, open file “/home/test/whatever.txt” when “7” is selected by the user.
- In fact, don’t give users the chance to make the call of which file / path to be used / included at all.
- Don’t disclose your directory structure to the users in any way, for example, as a hidden field in the form.
SQL Injection Attacks: Exploits of secure vulnerabilities that occur in the database layer of an application wherein user input is not filtered for reserved characters that may cause the database to falsely interpret and execute the SQL query. To prevent this attack:
- Escape a string value before using it as part of a SQL query:
$mysqli -> real_escape_string($str)
You can also use PDO to prepare the SQL queries, which will automatically sanitize any literal values by escaping it before using them in the query.
Cross-Site Scripting (XSS) Attacks: Or JavaScript injection, security vulnerabilities that allow malicious users to inject HTML code into your web pages that other users can view and execute. It can mess up the page, more fatally, it can load an arbitrary JavaScript script (hosted on another domain) in the user’s browser and steal their cookies thus identity. To prevent this attack:
- Cookie should be set with the HttpOnly option enabled (true).
- Escape anything and everything that goes live on a web page to be seen by your users:
htmlentities($str)
Passwords
- Optionally enforce strong passwords to your users by only accepting passwords of certain lengths and complexity.
- Never store plain text passwords in your database. Instead, salt and hash the passwords. Bottom line is sha1(). Better yet, use hash() with various more advanced algorithms. Never use md5().
- Optionally use pass phrases instead of passwords.
Sessions
- Regenerate the session ID every time a user’s privileges are upgraded, for example, from visitor to registered member by logging in or from registered member to administrator by further logging in the administrator control panel:
session_regenerate_id();
- Completely destroy session variables (not just empty them) by:
session_destroy();
- Store IP address of initial authentication in session variables and compare request source IP every time you receive a request from the user. However, IP address can unexpectedly change during a legal session and can be a public proxy in the first place.
Cookies
- When you need to wipe out some cookie variable, delete it from both the user’s browser AND your server:
setcookie('SomeCookie', '', time() - 3600); // deletes it from client side unset($_COOKIE['SomeCookie']); // deletes it from server side
Other things to consider
- All helper / utility scripts in your application that helps develop and debug should be removed from the production deployment. Only necessary files are to remain.
- Never talk about your application structure or any other vital information regarding it as real examples in public places such as developer / server administrator discussion boards.
- Maintain your own private PHP framework to employ these security practices in a general level. So you will not need to worry about the security particulars of all the projects that derive from this framework. Or use one of the popular PHP frameworks who have gone a long way in security and have been broadly tested by thousands of projects and billions of end users.
- It’s not enough to just check and fix your code against these attacks. You have to assimilate these attack prevention tips into your daily coding arsenal and make them as natural as they must be done wherever they are needed. They have to become part of your blood and just feel right to you. Bobince makes a good point on this by asking for a PHP tutorial that preaches the right thing from the very beginning. For example, when you echo something with PHP to the output, even if you are an absolute beginner, it doesn’t absolve you from escaping them first:
$str = 'Hi, I\'m on a web page.'; echo htmlentities($str);
Please don’t hesitate to tip in by commenting below to make this security checklist as complete and useful as possible. To start a serious learning session of developing secure web applications, these books will provide a kickass ride for you.
Which security issue mitigate by magic_quotes_gpc=off. More than 90% security can be assured by – 1. data/input filtering and 2. Output escaping.
Nice post.
Nice sum up! These 2 were exactly the bare bone gist of web application security.
Magic Quotes was originally conceived to automatically escape incoming data from GET and POST so that the developer doesn’t have to. It’s soon outdated because new reserved characters are introduced that should be escaped but Magic Quotes doesn’t cover. Plus, the developer has to manually unescape things that don’t go into the database.
It’s just neither secure nor easy to use any more.
Good practice!
Thanks, wensheng. 🙂
when you use escaped string as html attribute, you should supply 2nd argument to htmlentities, like:
htmlentities($str, ENT_QUOTES);
without 2nd argument, htmlentities won’t escape single quote.
see: http://php.net/htmlentities
And if you want to support i18n, the 3rd argument is also required.
without that, htmlentities treat input string as ISO-8859-1. So you must supply correct encoding.
Thanks for the very useful contribution. This guide serves as but a starting point of PHP security that doesn’t intend to elaborate on each of the practices for potentially unlimited scenarios.
But that’s for sure a really nice tip for escaping strings to be used in html attributes! Good lesson I’ve learnt. Thanks!
Never use numerable values such as natural numbers to externally identify something though they are so easy to use in many cases. For example, better not use the ID of a record in any URL of your site. Instead, generate something that’s not numerable so the attackers cannot jeopardize your site by both knowing something about its internal structure thus fabricating operations by unexpected input values and committing exhaustion attacks by navigating through all possible combinations of URL.
Pingback: Useful Security Pages | Toby's Development Blog
Awesome post!
Exactly what every PHP programmer should know and follow, thanks for sharing your thoughts.
Nicolas.
Pingback: Links recomendados AVLog | AVLog - AgeValed
Fantastico Post!!!
God Bless !!
Great summary of security threats and nice presentation of concepts. Thanks for sharing!
This is awesome, thanks!
If any of you PHP geniuses are ever looking for PHP jobs check out http://www.technojobs.co.uk/jobs/php
Excellent Post! This is exactly what I was looking for. Thanks for sharing!
This post is fantastic, thanks!
Thank you man. Now my application will be more secure.
thank you very much
i have got very important things about PHP from you thank you, thank you and thank you
Pingback: Anonymous
Pingback: Wordpress et Sécurité
Let users select indexes rather than the literal path string / file name. For example, open file “/home/test/whatever.txt†when “7†is selected by the user….wht does this mean?? thank you 🙂