Archive for the ‘PHP Security’ Category

(Post updated 08/08/11. View Update)

Recently a friend of mine who uses the eCommerce package Tradingeye had come under a couple of attacks due to vulnerabilities in the software. The main problem was that the admin username and password were not secured and were easily exploited. (I've written before about secure PHP and SQL injections and this only introduces some of what needs to be done to secure a database driven website). Eventually, after a lot of Tradingeye customers were getting hacked, a patch was released but it wasn't suitable as it encoded not only the single and double quotes (which were the main culprits) but also the less than, greater than and equals signs. It also caused further problems with any HTML code added via the admin, as this was getting encoded and therefore being stored and output as plain text rather than HTML code.

So as a favour to him I've taken a look through the files. Someone else had already released a basic fix which uses the mysql_real_escape_string() to secure the admin username and password during login, however I took this and have added to it to secure all _GET, _POST, _COOKIE, _SESSION, _FILES and _SERVER variables (essentially all data that a user can control/modify), as looking through the code I've seen a number of places where some of these are used without any sanitising. It's worth noting that it's very easy to change the value of any of these variables (including a _SESSION variable) if you have the right tools. Never assume anything is safe.
Continue Reading

One naivety that I often come across when having to update someone else's PHP code is that they've assumed the form being posted to the script will be the form on the site that should be posting. For example, if you have a questionnaire on your website which has a list of questions and perhaps a select list or radio buttons for a set of multiple choice answers. Too many PHP developers assume that the script that will deal with processing the results is going to get that specific form submitted to it, and not one that's virtually identical but submits from a different site, and instead of radio buttons it has a text input for each answer.

Another assumption is where the URL maybe contains a page ID to display and the developer has assumed that this page ID isn't going to be tampered with. I've written in the past about Secure PHP and SQL Injections, and I've seen people secure the input where they know it could be a mix of alphanumeric characters and symbols, however I've also seen people assume that a variable should just be an integer therefore they don't secure it.

These are both common mistakes and the wrong ones to make!
Continue Reading

I spent the last 1-2 weeks upgrading a site for a client and also securing it. How are a lot of PHP sites compromised? Well the point I'm looking it in this post is via forms and user entered data. There are many other security issues as well, this is just one of them. More commonly known as SQL Injections. PHP.net has a good page on this subject explaining SQL Injection giving real life examples. As per their description:

Direct SQL Command Injection is a technique where an attacker creates or alters existing SQL commands to expose hidden data, or to override valuable ones, or even to execute dangerous system level commands on the database host. This is accomplished by the application taking user input and combining it with static parameters to build a SQL query.

So what does this mean? When you have a form on a page requesting user input and then use this input in MySQL queries you must not rely on what the user has entered. 99% of the time when you ask a user for their username and password, they will enter this. However along comes someone who wants to crack your system, suddenly you won't be receiving a username and password through the form but most likely code aimed to inject extra operations into your MySQL query. Perhaps it's easier to understand by way of example. My own logins usually accept a username and password from a form. The usual method is to accept a username and password and then run a SELECT statement to count the number of records that contain both the username and password. If the result is zero then the username and/or password is of course incorrect. A loosely written PHP script could be:

Insecure code to control user login
  1. $sql = mysql_query("SELECT COUNT(*) FROM users WHERE username = '".$_POST['username']."' AND password = '".$_POST['password']."'");
  2. $count = mysql_fetch_row($sql);
  3. if ($count[0] == 0) {
  4. // user/password combo incorrect
  5. $errormsg = "Your Username and/or Password are incorrect. Please try again";
  6. } else {
  7. // user/password combo correct proceed with login
  8. header("Location:http://www.domain.com/secret/logged-in.php");
  9. exit;
  10. }

As you can see from the above, the user input from the $_POST array is entered straight into the query. Oh and

Insecure code to control user login
  1. $username = $_POST['username'];
  2. $password = $_POST['password'];
  3. $sql = mysql_query("SELECT COUNT(*) FROM users WHERE username = '".$username."' AND password = '".$password."'");

is no different ;)

So how can this be an issue? Well, all of your registered users will most likely enter their username and password. No issue there. Your MySQL statement would look like this:

SQL Statement
  1. SELECT COUNT(*) FROM users WHERE username = 'sarah' AND password = 'sunshine'

However, a visitor wanting to crack your system could enter the following information:

username: sarah
password: ' or '1'='1

Which would give:

SQL Injection Code
  1. SELECT COUNT(*) FROM users WHERE username = 'sarah' AND password = " or '1' = '1'

The above would return the total number of records in the table. So going by the very loosely written PHP script above, the if statement of

IF Statement
  1. if ($count[0] == 0) {
  2. } else {
  3. }

would provide a value from $count[0] to be above zero (providing there were records in the table), thus giving someone a login without a proper username or password.

Okay so yes, the example is a highly unsecure example, but it does happen. Plenty of people believe that providing there is no direct link to a file on a server, it won't be found out. Trouble is it can be. So pulling the above example apart the first steps security would be

1. Ensure all restricted pages check that the member is logged in by using cookies or sessions or both.
2. Switch the if statement to ensure that only one record is found and allow the user to log in from that, otherwise assume the username/password is wrong and prevent the login.
3. Escape the user entered data.

At this point I'll briefly mention Magic Quotes which is a setting in the php configuration file that can be set to on or off. If set to on then it will escape all user entered data, using the same method as the function addslashes(). It was introduced due to the amount of people who don't escape their user entered data, however it causes nightmares for most PHP developers and is even stated on the PHP.net site that most developers will choose to turn it off, preferring to escape the data when required. However on most shared hosting it's not necessarily easy to do this as you have no control over the php.ini file, so the best way is to check to see if Magic Quotes are on, clean the variable if they are (using stripslashes() function) and then escape the variable. The function that I use (courtesey of Khalid is:

escapeString Function
  1. function escapeString($string) {
  2. if (get_magic_quotes_gpc()) $string = stripslashes($string);
  3. return mysql_real_escape_string($string);
  4. }

Here this function uses the mysql_real_escape_string() built in function. The function will add a backslashes to the beginning of any of the characters \x00, \n, \r, \, ', " and \x1a. This will protect you from any SQL injections thus protecting your database.

However don't be fooled into thinking that when I say "user entered data" that I just mean text inputs. Going back to the site I secured last week the original developers had not thought about a couple of issues, which could be have been used in conjunction with one another and allowed someone to wreak havoc on the site or in the database. Considering the site accepted money this is even more important to ensure its security. What was wrong?

Firstly they were storing the user's id and password in cookies. Not necessarily a problem however they were then taking the raw data from the cookies and using it to query the database. Cookies can be edited therefore must be escaped. Then some user entered data was being escaped (but not as securely as the function above will do) but other data wasn't. The site had even had a security update before me and there was still an emerging pattern. When a page had some secured data on it, the unsecured data wasn't secured because the form variables were from checkboxes, radio buttons or a select list. I admit, when I first start out writing PHP I figured that this information couldn't be tampered with therefore didn't require securing. However take a normal form that's there for people to complete and the details go into a database. A questionnaire for example. Plenty of input boxes, radio buttons, check boxes and select lists. The form is submitted to perhaps the same page or another page. We know full well that another completely unrelated site could submit to that php script as well. Take the same form names, which are plain to see in the HTML source, but then instead of a radio button having a prespecified value suddenly someone could submit to the processing script a radio button with their own value.

The simple rule is, never trust data that could have been tampered with. If it's coming from $_GET, $_POST, $_COOKIE or even $_SERVER (after all if the user agent or http referrer can be altered who knows what it would be altered to?) then escape it. These are the 4 I deal with, if there are any further global arrays that should be escaped feel free to add to this list.

For people who use ASP, this Site Point article will be of use.

CGI forms have been notorious for abuse by spammers. The most used one of all is the famous formmail.cgi script. Most people have either moved on to other more secure scripts or turned to PHP for emailing their forms. That's all well and good however PHP can be abused just as much. I started to receive a lot of spam through the script on my business site a few weeks ago so I put in a simple trap in the script which checks the content entered before emailing it to me and returning the visitor to a thank you page. If the trap is triggered the visitor gets set to Google.com instead (bye bye!). Nothing's been received since. I'll go into the trap more in my PHP learning section when I start to explore string functions.

However the other day I received an attempt to use my web form to spam others. Below is a copy of the email I received, with the intended recipient's email blanked out. I've added an X in the middle of my domain to prevent other spambots picking it up, and XXX to the IP recorded.

From: sift4526@3emediaX.co.uk
Company: [email protected]
Telephone: [email protected]
Mobile: moonlight
Content-Type: multipart/mixed; boundary=b3b8beb3795e1c824f717fcad62d230f
MIME-Version: 1.0
Subject: the
bcc: [email protected]

This is a multi-part message in MIME format.

–b3b8beb3795e1c824f717fcad62d230f
Content-Type: text/plain; charset=\"us-ascii\"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

pa apers ivry mornin . ayciption at th hite ouse. mong th casulties was so
–b3b8beb3795e1c824f717fcad62d230f–

.

Fax: [email protected]
Location: [email protected]
Country: [email protected]
E-mail: [email protected]
Query: [email protected]

IP: XXX.174.190.170

So that's what I received. Why didn't it work? Well I control the headers of my script and set it to come from my web site and not the address entered in the from box. I originally did this because I don't believe that everyone has an email address (my Dad doesn't for a start!) and also just because I decided I wanted to control the headers more to be recognisable in my spam programs. However after a check with a very knowledgeable friend I've also learnt my script is more than secure (yay!). It sounds bizarre maybe that I'm saying this but until the above email I've never had or seen an attempt on what is commonly known as a header injection. Since the spamming happened I started to grab the IPs of the users too so the attempt has been emailed to the abuse address of the IP owner and the IP has now been blocked from my web site.

Last week I had a client email me in a panic. A form mailer that his previous developer had set up had been disabled by his host as it was being used for spamming and sure enough there was the typical, most used method of sending email using PHP:


mail("[email protected]", "Web Form Results", $msgbody, "From: $name <$email>");

I changed this to


mail("[email protected]", "Web Form Results", $msgbody, "From: [email protected]");

So how can you be careful? Well in my opinion the best way to be truly careful is to prevent anything that is associated with visitor input to go into your email headers. This way you have complete control and there are no security risks whatsoever. However that is not always how clients want it, they like to just be able to hit reply to an email and reply back to the potential customer. So therefore some extra security checks or spam traps as I like to call them, need to be in place.

Instead of writing out various methods of what to do there's a great page from Khalid that offers a few validation functions which should help you check the email given is valid.

There are other methods and checks that can be made. More will be introduced in my PHP Learning category as and when I get to the functions involved. But for now secure your scripts as much as possible!

PHP, if done wrongly can leave your site and subsequently the server that your site is hosted on, open to attack. There are various security measures you can take to limit the possibility of this happening.

Khalid over at Jelly and Custard has started to write a few tutorials on this subject. To save me writing them too, go read his post on Variable Casting

Latest Tweets