PHP is a language that is easy to learn. It’s a language that a lot of beginning web developers use to create their first website. They often don’t know about any of the security risks involved in building PHP applications.
This article is meant to help the new (and more experienced) PHP programmers to understand the security risks involved in PHP programming.
If you find this article incomplete, or have something to add to this article, please let me know.
The sections covered in this article are:
- User Input, SQL Injections
- Register Globals
- Includes
- Error reporting
- XSS
- CSRF
- If All Else Fails, Mitigate Damage on Breach
Things to cover in the next article:
- Cross zone scripting
- HTTP header injection / http response splitting
- Eval
- Database users
- Email header injection
Never Trust Your Users
You should ALWAYS verify user input. You can’t trust your users input to be valid. Basically any piece of information coming from the user can be malicious. This includes but is not limited to ALL $_GET, ALL $_POST, ALL $_COOKIE and some $_SERVER vars.
HTML:
<form>
<input type = “text” name = “username” />
<input type = “password” name = “password” />
</form>
PHP:
$rCheck = mysql_query (“SELECT COUNT(*) FROM users WHERE username = ‘” . $_POST[‘username’] . “’ AND password = ‘” . md5 ($_POST[‘password’] ) . “’” );
if ( mysql_result($rCheck,0,0) != 0 ) {
$aRow =mysql_fetch_row(mysql_query(“SELECT * FROM users WHERE username = ‘” . $_POST[‘username’] . “’ AND password = ‘” . md5($_POST[‘password’] ) . “’”);
$_SESSION[‘userID’] = $aRow[‘id’];
$_SESSION[‘rights’] = $aRow[‘rights’];
}
If your PHP code looks like this your application might be vulnerable to serious security risks. If your user would enter their username and password as expected, then everything will work fine. But if it was the users intension to hack into your system, he might enter “admin’ OR 1=1 /*” as his username. The query generated would be:
SELECT COUNT(*) FROM users WHERE username = ‘admin’ OR 1=1 /* AND password = ‘password’
The “/*” part will tell MySQL that the rest is a multiline comment, so this will be ignored. The OR 1=1 clause will always propagate to TRUE, so every row in the database will be selected. As your first user in the database most likely is a user with admin rights, the hacker will be logged in as an admin without ever entering a valid password.
Please be advised that ALL $_GET and all $_POST variables can be tempered with. This includes <select> boxes and checkboxes. You might think that a select box has a limited set of options, but this is a mistake often made. You can always temper with the data.
You should always escape the variables you put into an SQL query, by calling mysql_real_escape_string() or equivalent. If you know the format of a field, you could also check it using regular expressions ( only numbers, telephone, email ) etc.
Register Globals
Unfortunately the PHP setting register_globals is set to ‘On’ by default on servers runnig PHP << 4.2.0. As of PHP 4.2.0 the default is set to ‘Off’, and the functionality is completely removed as of PHP 6.0.0.
Register_globals will inject your script with all sorts of variables, coming from $_GET, $_POST, $_COOKIE and $_SESSION.
Why is this a bad thing?
Take a look at the following code snippet. It might look secure to you, because only if user_login() returns true, the user will be able to see the sensitive data.
if ( user_login() )
$authorized = 1;
If ( $authorized ) {
// show sensitive data
}
If register globals would be on, simply calling the script with ?authorized=1, would give you access to the sensitive data because register_globals will make $_GET[‘authorized’] accessible as $authorized.
The best fix for this is turning register globals off. You could also declare your variables before you start using them, so that you know the initial value of the variable. In our example above, adding $authorized = 0; at the top would do the trick.
Relying on register_globals is bad practice and highly discouraged.
Includes
Including files is a good way to break up sections of your program into separate files. You could for example have a default template, in which you include different files to show in the body of the page.
An easy setup would be something similar to this:
// template stuff
include “includes/” . $_GET[‘page’] ;
//template stuff
The page you request will be inserted into the body of the template, and it’ll show up nicely. But, if the user would enter “../.htpasswd” as a page, it would show up the htpasswd file of your website! You should always validate the file you’re including, before including them.
A simple solution would be using a switch statement, to check if the page is valid:
switch ( $_GET[‘page’] ) {
case ‘validPage.php’: case ‘validPage2.php’:
$sIncludePage = $_GET[‘page’];
break;
default:
$sIncludePage = ‘home.php’;
}
Include “includes/” . $sIncludePage;
Error Reporting
Error reporting is a useful tool when developing a website. It gives you all kind of detailed information on errors that occur. It also feeds a hacker with a lot of useful information to hack your site.
Therefore, it’s good practice to turn error reporting off on live servers, or have them logged into a log file which only you can view.
XSS
XSS ( cross site scripting ) is a bit harder to explain to beginning programmers. If your website is vulnerable, it allows a hacker to execute code he has written himself, and therefore he is able to steel sessions.
It’s best explained by an example. We’ll take a comments system, adding and displaying comments on a blog i.e.
if ( isset ( $_POST[‘add’] ) )
// properly escape strings with mysql_real_escape_string to prevent SQL injections
mysql_query (“INSERT INTO posts SET name = ‘” . mysql_real_escape_string($_POST[‘name’]) . “’, comment = ‘” . mysql_real_escape_string($_POST[‘ comment’] ) . “’”);
// get comments, pseudo code
while ( $comment = fetchComment() ) {
The example above is vulnerable to XSS. If I would add a comment with <script>alert(document.cookie)</script>, it would popup a box with the cookies from the local user. If you expand the script to mail you the session cookie, you could login as a different user just by hijacking the session.
You can easily prevent this by calling htmlspecialchars() when you output your name and comment.
CSRF
CSRF ( cross site request forgery ) is the opposite of XSS, it uses the trust that a site has for a particular user.
Let’s say I’m logged in into my online banking account, where I can go to the URL mybank.com/transferMoney?to=Name&amount=25 to transfer money to name. This is of course not the case for actual online banks, but for online RPG’s it’s very likely to work like this.
If I would run a website, and display a hidden iframe with the url mybank.com/transferMoney?to=MyName&amount=252525 it would transfer 252525$ to my account.
If we combine this with some sort of XSS attack, I would be able to put the hidden iframe on the profile page in an RPG, and have it transfer an X amount to my account for every user that visits my profile.
Protecting against CSRF in general is fairly simple. You could add a token to the form of a page, which is generated on request of the page, and validated on submit.
It’s also good practice to ask for a password again on secure pages, like a change password form. This is an effective method against CSRF.
If All Else Fails
You’ll probably never end up with a 100% secured system, so it’s a good idea to mitigate damage if your websites security is breached.
For example, do not safe passwords as plain text, but use some sort of hashing or encryption ( MD5, SHA-1 ).
It’s also good practice to create full backups frequently, so that you can always restore your website fast.