Forum Moderators: coopster

Message Too Old, No Replies

PHP Security

Defend against MySQL injections & more

         

max4

5:53 am on May 17, 2009 (gmt 0)

10+ Year Member



PHP security is an issue that has eluded me for the past month or so. Indeed I am still new to PHP in relative terms, only learning the language within the past month. I recently discovered a major vulnerability in my work, and that vulnerability is MySQL injection. Of course, I have come up with a solution to defend against this; one that I am now sharing with anyone reading this post.

A typical query for a login script may look something like this:

[fixed]
$email=$_POST['email'];
$userpass = md5($_POST['userpass']);
$sql = "SELECT email , userpass FROM table WHERE
email='$email' AND userpass='$userpass'";
$result = mysql_query($sql);
[/fixed]

Note - With my level of knowledge, the only way I know of increasing password security is using the md5() function to encode a password.

The above script makes it very easy for a malicious user to perform any type of MySQL injection. The simplest form would be to bypass entering a password, and logging in with just the email - thereby accessing any account whose email they know. This can be achieved by the following injection - Email field, user enters example@example.com';# Do you see what just happened? They altered the query, which now looks like this:

[fixed]
SELECT email , userpass FROM table WHERE email='$email';# AND userpass='$userpass'
[/fixed]

The # comments out the rest of the query, allowing the user to login using just an email address. Of course, this poses a serious problem and not just for privacy issues. The malicious user can choose to put any bit of code after that semicolon; including the command to drop all of your tables. Luckily, there is a defense for this type of attack. One would simply have to use the mysql_real_escape_string() function. Here is an example of this in a login script:

[fixed]
$email=$_POST['email'];
$userpass = md5($_POST['userpass']);
$sql = "SELECT email , userpass FROM table WHERE
email='" . mysql_real_escape_string($email) . "' AND
userpass='" . mysql_real_escape_string($userpass) . "'";
$result = mysql_query($sql);
[/fixed]

This method will defend against MySQL injections by forcing the processing of the query regardless of what characters may be present.

I have one more PHP trick up my sleeve that I will share with you. It's not that I have many more and am hording them to myself, but really this is the limit of my knowledge in PHP security. Now, if there is a malfunction of some sort on your server; that could lead to users having access to your PHP source. This is bad if your database username and password are located in the public domain of your server. Protecting this information is easy though. PHP has a neat little quality that allows the server to access PHP documents anywhere on the machine. From what I have learned; the best way to secure this data is to put it in a PHP document in a folder above the public domain and include it in your scripts. The tricky part for me was being able to access that in my PHP scripts. Of course, again, I have come up with a solution.

Rather than trying to do:

include '../con.php';

I do:

include '..\con.php';

The \ , from what I have deduced, tells the script that the document is in one folder above it's current location, whereas the / tells the script that it is one level above the current location in a URL. I could be wrong, but nevertheless it does indeed work.

That my friends is the extent of my knowledge of PHP security. I hope this post will help someone somewhere.

Thank you for reading,
Mohamed

bkeep

4:48 pm on May 17, 2009 (gmt 0)

10+ Year Member



I have a function that I use almost strait off the php.net page. Sometimes you may be deploying a script in an alternate environment that you can't be sure of the exact configuration so you may want to check for get_magic_quotes first.


//Check if magic qoutes is on then stripslashes if needed
function codeClean($var)
{
if (is_array($var)) {
foreach($var as $key => $val) {
$output[$key] = codeClean($val);
}
} else {
$var = strip_tags(trim($var));
$output = mysql_real_escape_string((get_magic_quotes_gpc())? stripslashes($var): $var);
}
if (!empty($output))
return $output;
}

then to use it pass your code through the function use with any variable or array $_POST, $_GET, $_FILES, $data or $array. In some instances you may wish to allow html tags if that's the case remove the line for strip tags.


//exanple using $_POST
if ($_POST) {
$_POST = codeClean($_POST);
}

Regards,
Brandon

vordmeister

6:32 pm on May 17, 2009 (gmt 0)

10+ Year Member Top Contributors Of The Month



It's well worth Googling php security. Some very useful info there, and on this forum too.

I'm not great at PHP security, but I would worry about what is going to happen when you pull that data out of the database and use it. If the malicious visitor has added a bit of PHP or PERL code in there the mysql escape is unlikely to stop it from causing trouble when you retrieve the data.

I use a function to clean all inputs (post, referrer, cookie etc). It replaces any characters that aren't in $pattern with a question mark.

function Clean($string) {
$pattern = '/[^a-z0-9_ -.,@]/i';
$replacement = '?';
$string = preg_replace($pattern, $replacement, $string);
return $string;
}

The function can be called using something like:


$SUBMIT = Clean($_POST["submit"]);

Also in the bit of code above any variables that haven't been cleaned are in lower case. Anything that has gone through the function is in upper case. Helps keep track of things.

It's perhaps a bit over the top, but I'd rather allow only characters from the visitor that I think won't do any damage rather than keep on top of what the latest thing is to deny. Later on in my code I'll check for valid entries - ie if it's supposed to be a number it won't have developed question mark.

Up for comments on whether my solution is hackable? Could be annoying on a discussion forum, but I tend to work in fields where I know roughly what to expect in the inputs.

penders

8:22 pm on May 17, 2009 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Rather than trying to do:

include '../con.php';

I do:

include '..\con.php';

The \ , from what I have deduced, tells the script that the document is in one folder above it's current location, whereas the / tells the script that it is one level above the current location in a URL. I could be wrong, but nevertheless it does indeed work.

Hhhmmm, I'm not sure that this is correct. The ".." part in the path tells PHP to move up a directory from the current directory. The "/" (forward slash) or "\" (backslash) is just the directory separator and is dependant on the OS, nothing more. "\" (backslash) for Windows, "/" (forward slash) for Linux. However, you can use either "\" or "/" under Apache on Windows. Can you use a backslash on Linux? Personally, I would ALWAYS use a "/" (forward slash), or use the PHP defined constant DIRECTORY_SEPARATOR (which will hold the appropriate value depending on the OS you are on).

The path used in the include statement never refers to the URL (although it might look similar, and I suppose you could use a full absolute path like "http://www.example.com/includes/myfile.php" to include an external resource - but that's a bit different). A simple path is always relative to the server.

Does the "\" or "/" have additional meaning that I'm unaware of here?

max4

9:44 pm on May 17, 2009 (gmt 0)

10+ Year Member



Thank you all for contributing to this post!

vordmeister,

Wouldn't it be possible to avoid that problem by using either htmlspecialchars() or htmlentities() on the extracted data; or is there a security vulnerability in between the process of extraction and display?

vordmeister

9:22 am on May 18, 2009 (gmt 0)

10+ Year Member Top Contributors Of The Month



What about if someone posts something like this in the email field:

hacker@example.com '; mysql_drop_db(); //

Then your code

$email=$_POST['email'];

Becomes:

$email='hacker@example.com' ;
mysql_drop_db();
//';

(and if I was more clever and accurate with my coding that sort of thing would delete your database).

So I do feel it is important to clean post data to make it reasonably safe rather than try to second guess all the different ways someone might be able to hack you. Especially for those new to PHP development.

max4

2:04 pm on May 18, 2009 (gmt 0)

10+ Year Member



Oh I agree. That is where the mysql_real_escape_string() function comes in to play.

So let's say you had no filtering in your script and some one enters this:


hacker@example.com '; mysql_drop_db(); //

If your select statement looks like this:


$email=$_POST['email'];
$userpass = md5($_POST['userpass']);
$sql = "SELECT email , userpass FROM table WHERE
email='" . mysql_real_escape_string($email) . "' AND
userpass='" . mysql_real_escape_string($userpass) . "'";
$result = mysql_query($sql);

Then the injection will fail and your database will not be destroyed. Definitely clean post data with filters; and then to be thorough throw in the mysql_real_escape_string() function and you can be decently confidant in your script's ability to combat injection.