|Are you afraid to put your PHP site online because it’s unsafe? We have all been there. It’s easy to frighten when reading horror stories about hacked sites, leaked user data and so on.
Probably every site can be hacked, no matter how well it’s protected. But having the most important security measures in your code will lower the probability to something like 1 in millions.
And it is not really hard to do so. Here are 10 of the most important tips to make your PHP code secure:
1. Validate numeric input
Most tutorials talk about validating user input in general. I prefer to split the validations depending on the input type. When you know what kind of input you need it’s super-easy to protect your code from malicious attacks (SQL injections, cross-site scripting and so on).
Very often PHP sites rely on passing URL parameters. Contrary to the popular opinion, there is nothing wrong about passing your product ID or user ID in the URL. You just need to make sure that the ID is really numeric:
$id = intval($_GET['id']);
This is really all you need. If the user has passed some SQL injection or anything like that in the URL it will simply be converted to 0. Alternatively, if you prefer output an error message:
if(!is_numeric($_GET['id'])) die("ID parameter must be numeric");
I prefer the first approach because the second helps the attacker know that you are validating the ID and they’ll quickly decide to try something else.
2. Validate textual input
Textual input is a little bit harder to validate because you don’t know what exactly to expect. The simplest and usually sufficient action is to escape the input before inserting it in the DB. You can do this with mysql_real_escape_string or a similar function. However if you know what data you need, it’s much better to use the filter_var() function with the most appropriate filter. For example you can sanitize user’s email entered in a form like this:
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
The rule is to strictly limit the input to what you need.
3. Validate html input
Sometimes you want your users to input rich formatted HTML code – for example when you use some kind of rich text editor in a text box. Obviously in this case the only thing you can do to avoid SQL injection is to escape the input with mysql_real_escape_string or something like that. You shouldn’t remove special characters because they may be part of the HTML formatting. However there are two sure things: you don’t want users to embed script tags and you don’t want them to embed inline frames. These two can be very unsafe when displayed on your site. So make sure you don’t let such kind of content go through:
if( stristr($_POST['input'], "<iframe") or stristr($_POST['input'], "<script") ) die("We can't accept your input!");
4. Prevent xss injections
If you want a really detailed knowledge about cross-site scripting attacks, and want to become expert in preventing them, I recommend you checking this really great guide. And don’t forget to download the cheat-sheet. For the rest of us there is simple and usually sufficient solution:
Do this always when you have to output user’s input on the screen. Unless you want users to input rich formatted text – in which case check tip 3 from this post.
5. Don’t expose mysql errors
This one is simple. Simple don’t do this:
mysql_query($sql) or die(mysql_error());
You don’t want to show SQL errors to everyone. Not to mention that using mysql_query() directly in your code is dumb. You have to use the improved mysqli, and you need a database wrapper. Your database wrapper should log the errors instead of directly outputting them on the screen. Writing a DB wrapper is out of the scope of this tutorial but if you want to learn more and download a free ready wrapper, see here. Or spend few bucks and buy one of the cool wrappers available here.
6. Don’t allow external db connections
Another simple tip. Many programmers are obsessed with hiding their file wiht DB conenction details outside of the web root. This is not convenient because it makes moving the site harder. It’s nearly impossible if you distribute out of the box scripts, because you don’t want your customers to deal with the complexity of installing files on different folders, worrying about server paths, etc.
It’s so much simpler to just disallow DB connections from hosts other than localhost. This way even if someone manages to download your config file, they’ll be unable to do anything with it.
Still, don’t forget that your DB connection file should have .php expension. Don’t do stupid things to create file with name like db-connect.inc. It can be seen or downloaded from the browser.
7. Build spam protection
Spam itself usually isn’t considered exactly a security issue. But it is. Especially when large amount of spam comments start generating too many emails or DB queries that take your site down. So take care to:
a) implement captcha
b) validate user input for spam – cleanup spammy words, check for URLs in comments etc
c) block unusual activity from one user. This is easy to do with sessions for example – if you see someone posts 10 comments in a minute, they clearly aren’t human
8. Check mime type of uploaded files
When letting users to upload files always make sure they are within allowed formats. For example you definitely don’t want users to upload PHP files for their avatars. It’s usually enough to check for the file extension because even if a PHP file is renamed to .png it won’t run when accessed in browser. So you can do something like this:
$allowed_file_types = array("png", "jpg", "gif"); $parts = explode(".", $_FILES['uploaded_file']['name']; $extension = array_pop($parts); $extension = strtolower($extension); if(!in_array($extension, $allowed_file_types)) die("Error!");
But if you want a really deep check you should use the Fileinfo class.
9. Check who owns the data
This is one of the most important things to do in sites that allow their members do things with data. A simple example would be a community site where each user has their own bookmarks. Normally when selecting their bookmarks you would do something like this:
SELECT * FROM bookmarks WHERE user_id=$user_id
(after of course validating that $user_id is a number)
Most developers get this – obviously, otherwise they wouldn’t be able to select the proper bookmarks.
But then when deleting a bookmark many developers do the following mistake:
DELETE FROM bookmarks WHERE id=$_GET[id]
This is a big mistake (let’s assume that $_GET['id'] is checked for being an integer before the query). This way one can delete anyone else’s bookark as long as they know the ID. Or they can just drop random IDs to delete them. You should always always make sure that user manipulates only their own data:
DELETE FROM bookmarks WHERE id=$_GET[id] AND user_id=$user_id
10. Protected files? Deny from all
This is not exactly a PHP thing as you can use it in any site. For example if you are running a membership site and letting users who pay download vector images, you should make sure that the images are served by a PHP script and not by direct download links. Otherwise everyone would be able to access the files as long as they know where they are. Not only you should never expose the real directory location, but you should also make it totally inaccessible from browser. An easy way to do this is by a .htaccess file that contains:
<Limit GET POST PUT> order deny,allow deny from all </Limit>
That’s it. Implement the ten steps and your site will be hundred times more secure.