Monday 23 February 2015

PHP: Common mistakes when creating secure PHP websites

Do you remember the last website which was hacked by some kids? What was your first thought, maybe “Phew, that’s not mine”? A hacked website is terrible and the clean-up is a lot of work for the site owner. As a website owner you need to be sure that your site is always secure. It’s important for your business and of course for your site listing in Google. Remember these tips when creating secure PHP websites:

MySQL queries and SQL injection attacks

If your web page accepts user input a SQL injection might happen if the data isn’t validated or sanitized before the database query is done. The following example is a common mistake, made by the beginning programmer or webmaster. Check this code, do you ever wrote a MySQL query like this one?

$result = mysql_query("SELECT * FROM users WHERE username = '". $_POST["username"] ."' && password = '". $_POST["password"] ."');

This row of code is written to accept two values from a login form: a user name and a password. But what if someone will submit an empty user name and for the password value the following string: ‘ OR username = ‘admin the query would look like:

$result = mysql_query("SELECT * FROM users WHERE username = '' && password = '' OR username = 'admin'"); 

If this query was used for a login script, the hacker would get access to the account for the user with the name “admin”. Imagine what a hacker could do in your web application if he has administrator rights. Don’t worry there are several ways to protect your queries against SQL injections.


First of all it’s better to use the MySQL improved extension (MySQLi). The old PHP functions for MySQL are still available, but the MySQLi extension offers more secure features. If you use the MySQLi functions you can choose between procedural style and the object oriented style. I use for my examples the object oriented style.


Before I escape the values for our queries we use the function filter_var() to sanitize the input values.
$username = filter_var($_POST["username"], FILTER_SANITIZE_STRING);
$password = filter_var($_POST["password"], FILTER_SANITIZE_STRING);
My example is using the default value for the filter type FILTER_SANITIZE_STRING. Next I use the MySQLi variant of mysql_real_escape_string() to prepare the strings for the database query. Before I can continue with the input validation, I need to create a database object first.

$db = new mysqli("localhost", "db_user", "db_password", "db_name");

if (mysqli_connect_errno()) { /* check connection */
    die("Connect failed: ".mysqli_connect_error());
}


$username = $db->mysqli_real_escape_string($username);


$password = $db->mysqli_real_escape_string($password);



Now it’s safe to pass these values to our database query:

$result = $db->query(sprintf("SELECT FROM users WHERE username = '%s' && password = '%s'", $username, $password));
$db->close();
In my example I used the function sprintf() to add the values to the query and use the $db->close() to destroy the database object. Another great and secure MySQLi feature the prepared statementsfunction.

Cross Site Request Forgery (CSRF) Attacks

The basic principal behind a CSRF attack is not to get access to a site, but forcing a user or admin to an unwanted action. For example we have an admin page with a HTML structure like this one.

<ul>


<li><a href="delete.php?page_id=10">delete home page</a></li>


<li><a href="delete.php?page_id=20">delete news page</a></li>


</ul>
We have protected the page named delete.php with some login script, that a user without permissions can’t access the page or script.
if( logged_in() == false ) {


// User not logged in


die();


} else {


// User logged in


$db->query(sprintf("DELETE FROM pages WHERE page_id = %d", $_GET['page_id']));


}

The script dies if the user isn’t logged in and otherwise the page with the page ID from the GET variable will be deleted. By using the function sprintf() I’m able to format the value to an integer value by using the type specifier %d. Seems to be safe or not?


Let’s say the authorized (logged in) would visit a page where a comment was posted including an image. If a hacker has posted an image with the URL like the one below you wouldn’t notice it, because the image doesn’t show up because the URL doesn’t exists.

<img src="http://www.yoururl.com/delete_page.php?page_id=20" />



Sure this is a very stupid example and the hack is only possible if the hacker knows your PHP website security. A much better solution would be to use a unique token for each important action. The best way is to create a unique token for the admin user during login. Let’s say the code below is a part from your login script. Inside the login_user() function I create a session variable than contains a md5() encrypted string.
session_start();



function logged_in() {


// your code to check a valid login


}


function login_user() {


// your authentication process


// comes


$id = md5(uniqid(mt_rand(), true));


$_SESSION['token'] = $id;


}


function get_token() {


return (!empty($_SESSION['token'])) ? $_SESSION['token'] : 0;


}



Inside the page with the HTML structure I need to add the token variables after each link.


$token = get_token();


echo '


<ul>


<li><a href="delete.php?page_id=10&token='.$token.'">delete home page</a></li>


<li><a href="delete.php?page_id=20&token='.$token.'">delete news page</a></li>


</ul>';



With this additional token variable inside the delete.php script I’m able to validate the data input.

if( logged_in() == false ) {


// User not logged in


die();


} else {


// User logged in


if (empty($_GET['token']) || $_GET['token'] != $_SESSION['token']) {


die();


} else {


$db->query(sprintf("DELETE FROM pages WHERE page_id = %d", $_GET['page_id']));


}


}

This simple validation will stop the script if the token is missing or not equal to the token session variable.
Cross site scripting (XSS) Attacks


The basic idea of XSS attack is that a hacker has embedded some client-side code on your web site which is executed or download by a visitor. This happens in different ways. For example by using a link to your website where some malicious JavaScript is added or the hacker has posted some JavaScript code to your website. The last one happens mostly by using unsafe comment forms where the content find a place on your website.


In any situation it’s important that your web application sanitizes the user’s input before the data is stored or parsed in your web page. Use different validation functions like preg_match(), filter_var() or better htmlspecialchars() to filter or convert possible attacks from hackers. The function htmlspecialchars() will convert HTML tags into entities.
Limit script functionality


If your website has a login function, it’s possible that a hacker will use a script that will try guess a user name and password. While using thousands of combinations it’s possible that the hacker will succeed. Throttle down the access to your login page if a visitor has made more than X submissions and use always passwords which are hard to guess. Common passwords like “welcome” or “default” are most of the time the cause of getting hacked.


Be careful with the function to recover a password. Never send the recovery instructions to some new email address and let the owner of the registered email address execute the recovery action.


Captcha images are a great and simple way to stop bots accessing your web forms. Use them where a remote submission can harm your web application.
Disable PHP error reporting


There are many reasons why some PHP error can happen on a website. Many of them doesn’t have any influence on the web site’s functionality. For a hacker is a notice or error message a source to get information about your website and/or sever configuration. Test your website always for possible error, because they are bad for your website and/or business, too. Would you trust a service that is broken?

There is also a functions that disables the output of error messages, add error_reporting(0); to your script and allow error reporting ONLY on your test location!


I hope this tutorial helps to understand some basic concepts how a hacker will try to attack your websites. Don’t make these mistakes when creating secure PHP websites!

0 comments:

Post a Comment