Secure PHP Programming!

11Apr07

When a web site is cracked, it typically tends to be a simple common sense mistake by a program author who forgets what an attacker can do with his code. By keeping good, secure practices when programming, you should be able to produce solid code.

NOTE: I expect you to be a somewhat fluent PHP developer who has a general idea of what’s going on in computer/network security. If you don’t understand some part of the code, you should probably read up on some PHP tutorials, and also read some of the security guides on php.net and other locations on the Internet.

However, before I even get into the code part of this shin dig, one obvious fact should be stated that you should never give a public_html directory (or any other directory that you are using to host your files) nor any of its files ownership by any user with root privileges. This and any other techniques you can think of must be implemented in order to minimize the damage an attacker can do. That being said, on with coding!

Let’s look at a typical attack of a PHP program. The following code is an example of an unsecure script to ‘echo’ the visitor’s name:

<?php
if (isSet($_POST['name']))
{
echo "Hello, " . $_POST['name'];
}
else
{
echo '<form action="' . $PHPSELF . '" method="post"><br>';
echo 'Your name, please:  <input type="text" name="name"><br>';
echo '<input type="submit" value="Submit">';
echo "</form>";
}
?>

Now this may look fine if you were to input a name such as ‘John’ or ‘Marry’, but what if the attacker were to input something like:

<script language="JavaScript" type="text/javascript"> window.location.href="http://www.bad-side-to-go-to.com/"; </script>

This means that this script would allow the attacker to deface your page (among many other evil little deeds such as setting up a backdoor with a little skill). As a practice, you should never, ever output code that is directly inputted from the user. In this case, PHP has a few functions that would allow us to solve this problem.

First off, strip_tags() simply removes HTML and PHP tags from a string. For example:

<?php
$str = "<html>hi</html>";
echo strip_tags($str);
// outputs 'hi'
?>

This works for most cases, but there are also ways of outputting the HTML code without executing it. Two functions, htmlentities() and htmlspecialchars(), convert any sort of <, >, ” and & to their HTML entities (HTML entities output code instead of executing it, ex. a web site might give your browser “& gt;”, but the web browser outputs that as “>”). Here’s an example of using htmlentities():

<?php
$str = "<html>hi</html>";
echo htmlentities($str);
// outputs '<html>hi</html>'
?>

This way, you can see the actual code but it won’t be executed (as in you would see the JavaScript code I showed you above, but you won’t have the page redirect). I like to use htmlentities() because it converts ALL of HTML entities to their representitive codes, but htmlspecialchars() is fine for simply wanting to thwart off an attacker. (Note that htmlspecialchars() works exactly the same as htmlentities() does as far as usage is concerned.)

So, here’s an example of a fixed version of the script I showed you earlier:

<?php
if (isSet($_POST['name']))
{
echo "Hello, " . htmlspecialchars($_POST['name']);
}
else
{
echo '<form action="' . $PHPSELF . '" method="post"><br>';
echo 'Your name, please:  <input type="text" name="name"><br>';
echo '<input type="submit" value="Submit">';
echo "</form>";
}
?>

NOTE: About 90% or more of PHP-related exploits are from some kind of way of attacking the developer’s input ($_GET, $_POST…).

Outputted code is not the only kind of input attack we have to worry about. Another kind is that which involve the programmer reading arbitrary files. For example, let’s say we have a script like:

<?php
if (isSet($_GET['file']))
{
$theFile = fopen($_GET['file'], "r");
echo fread($theFile, filesize($_GET['file']));
fclose($theFile);
}
else
{
echo '<form action="' . $PHPSELF . '" method="get"><br>';
echo '<select name="file"><option value="testFile.html">testFile.html</select><br>';
echo '<input type="submit" value="Submit">';
echo "</form>";
}
?>

This may look all fine and dandy, but it’s flawed out the kazoo. First of all, since this form uses the ‘get’ method of handling input, the user can simply change the file from the URL line of the browser. Second, an attacker could simply create a HTML page on his/her own site with code like:

<form action="http://yoursitehere.com/scriptijustwrote.php" method="get">
<input type="text" name="file"> <input type="submit" value="Submit">
</form>

Either way, they can change the file you open to anything they like. For example, let’s say that they set $_GET[‘file’] to be something like ‘/etc/passwd’. They could have that easily obtained and possibly cracked into your account from the UNIX ‘passwd’ file! (Not to mention any other sensitive files they could’ve opened from this script…)

Clearly this script is very, very dangerous for you as a programmer. There are a few options you can do to prevent this:

First off, let’s say that you only have a list of available files that the script will open. Change the file opening code to first see if the file is one that you will let it open, such as:

if ($_GET['file'] == "testFile.html")
{
// open the file
}
else
{
// don't open the file
}

Or, even better would be something like:

switch ($_GET['file'])
{
case "testFile.html":
$file = "testFile.html";
break;
case "otherFile.html":
$file = "otherFile.html";
break;
}
// open the file using $file

Another option would be to remove any slashes or dots from the filename, but then again you would still allow the attacker to open any file in that single directory. If that directory is safe, then use str_replace() like so:

$file = str_replace("/", "", str_replace("\", "", str_replace(".", "", $_POST['file'])));

Now, only open the $file variable.

This option is definitely not recommended simply because that entire directory is vulnerable to be publicly accessible, but if you don’t mind then go ahead.

Now then, let’s have a look at another type of input attack in the form of running arbitrary programs. Let’s say you have a script that will use the system command ‘ping’ to see if a network target is alive. Here’s an example of this script as a vulnerability:

<?php
if (isSet($_POST['target']))
{
system("ping " . $_POST['target']);
}
else
{
echo '<form action="' . $PHPSELF . '" method="post"><br>';
echo '<input type="text" name="target" value="Target Address"><br>';
echo '<input type="submit" value="Submit"></form>';
}
?>

This would be fine if the user simply inputted a target, like “yahoo.com”. However, what if the attacker inputted a statement like “; rm -rf /”? Just like that, your entire filesystem got whipped clean from one vulnerable script (assuming your account has root privileges, but either way a lot of important stuff is getting deleted).

There are a few solutions here, chiefly to filter out any bad input using a function like preg_replace(), but even I had trouble learning how to use that function. A more simple, but not as secure, solution is to use escapeshellcmd(). This function returns a string from your command that prevents the user from running any other program than the one you want it to run (“ping” in this example). As an example, you’d simply do this:

system(escapeshellcmd("ping " . $_POST['target']));

While this prevents any other programs from being run, it does not prevent the attacker from inputting invalid data to ‘ping’. If there was an exploit for that program or some kind of input that would make it exit or run another program itself, then you’re still screwed. Your best bet is to try and stay away from allowing visitors to access any kind of system() commands.

Other than input attacks, there are plenty of code problems that you as a programmer may not even know about. One thing you should always remember, however, is to keep your code from being shown as much as possible. Let’s say an attacker somehow renames your file from index.php to index.phps. On most PHP web servers, this will show all the code in index.php (including the PHP code) so that the attacker can find a flaw they wouldn’t have found without seeing the code. A good bet to keep this from happening is to make your scripts executable only in a cgi-bin directory.

However, some web servers do not support scripts in a cgi-bin directory. If yours does, then simply try putting a test script in that directory and ‘chmod +x’ the file to make it executable. The code will change a little bit, because instead of PHP being inside of the HTML coded file, the HTML will be inside your PHP script! Here’s an example:

#!/usr/bin/php
// if the path to PHP is different, then you'll have to change that lineecho "<html>n<body>nTesting!n</body>n</html>";

Save this as something like ‘test.php’ and put it in your cgi-bin directory. From your web browser, try to execute it. If it works, then it’s a good idea to use your scripts in that style in the cgi-bin to prevent code leakage. If it doesn’t work, then either e-mail your admin or rebuild your PHP/Apache or whatever installation to include this option.

An entirely different topic in PHP security is encryption. There are many different kinds of encryption, but compared to other languages PHP makes them all easy as pie. The easiest example of encryption is the one-way 32 bit MD5 encryption. “One way” encryption means that the data CANNOT be decrypted. This means that even if the attacker gets the encrypted data, they cannot decrypt, but instead will have to ‘crack’ it. For example, let’s say we have a password file in our home directory (‘/home/user/pass_info.php’):

<?php
$user = 'testuser';
$pass = md5("mypasswd");
?>

The md5() function converts “mypasswd” to a 32 bit “hash” (encrypted data, basically) that is about 32 or so characters long and each character is either a letter or a number. Any other text than “mypasswd” will produce a different hash, so we can rely on what the encrypted version of “mypasswd” is. Now that we have $pass set, let’s say we have another script like this that sets the user’s $_SESSION data to show he or she is logged in according to their password:

<?php
include("/home/user/pass_info.php");if (isSet($_SESSION['user']) && isSet($_SESSION['pass']) && $_SESSION['user'] == $user && $_SESSION['pass'] == $pass)
{
echo 'You are logged in.';
}
else if (isSet($_POST['user']) && isSet($_POST['pass']) && $_POST['user'] == $user && md5($_POST['pass']) == $pass)
{
$_SESSION['user'] = $user;
$_SESSION['pass'] = $pass;
?>
<script language="JavaScript" type="text/javascript">
alert("you are now logged in");
window.location.href = window.location.href;
</script>
<?php
}
else
{
?>
<form action="?" method="post">
<input type="text" name="user"><br>
<input type="password" name="pass"><br>
<input type="submit" value="Login">
</form>
<?php
}
?>

In this way, $_SESSION[‘pass’] is a 32 bit encrypted hash instead of a plaintext password. If the attacker was to get your $_SESSION data and see that a variable names $_SESSION[‘pass’] was equal to a plaintext password, then he/she just cracked your user/pass! Now that we have it encrypted via md5(), he/she can only see the encrypted version. Now the attacker would have to run a bruteforce style attack on your encrypted password data which can take months depending on how good your password is. (This is also a good reason to change passwords often.)

If you think that md5() is out of your style, or you just want to use a different encryption key, you can use crypt() as well. crypt() uses the same method as UNIX /etc/passwd files, and is farely simple. First, let’s encrypt a string:

<?php
$encryptedData = crypt("testPass");
?>

Now, let’s make a function to compare your crypt() data with a string to see if the password is correct:

<?php
function verifyEncryptedData($str, $data)
{
$salt = substr($data, 0, CRYPT_SALT_LENGTH); // gets the crypt() encryption key into $salt
$encryptedStr = crypt($str, $salt); // encrypt the given data using the current key
return ($encryptedStr == $data);
// returns a TRUE/FALSE statement based on if $str is indeed the encrypted test in $data
}
verifyEncryptedData("testPass", $encryptedData); // should return TRUE
verifyEncrytpedData("hahaha", $encyptedData); // should return FALSE
?>

Here we see an example of encryption that uses different encryption ‘keys’, where as md5() uses the same key every time. crypt() generates a random key every time something is encrypted with it.

Now you may be wondering what I was talking about when I said an attacker would try a brute-force attack on your encrypted data. This means that he/she would try to encrypt every possible value from a-z 1-9 for however many characters it takes in whatever kind of encryption you are using until they have cracked the password. Let’s say you had a password of the letter ‘c’. A brute force program would first try to encrypt ‘a’ without success, ‘b’ without success, and then found out your password when it tries ‘c’. As you can see, the more characters and the more different of a letter/number/symbol combination you use in a password will make a password cracking program take longer to crack your password. Your best bet is to change passwords often so that even if he/she cracks the password, they’ll return to find that you’ve already changed the password and they’ll have to start all over again.

If you want to use encryption that allows you to decrypt its contents, look into PHP’s mcrypt module. (http://www.php.net/ comes in handy, folks!) It’s the best encryption module for PHP I know of as it lets you choose your encryption method from a wide selection though it takes just a tad bit of learning, but I’ll leave that up to you.

All in all, just use common sense and follow safe practices when it comes to PHP. Don’t let one vulnerable script be the reason your whole network gets cracked!



8 Responses to “Secure PHP Programming!”

  1. Would Just like to point out that $PHPSELF can sometimes
    be over written (check the hardened php project)

  2. 2 spatz

    Never use $PHPSELF without checking the value.
    Use $_SERVER[‘PHPSELF’] instead OR insert this code before

    $PHP_SELF = htmlentities($_SERVER[‘PHP_SELF’]);

  3. 3 Mann

    Very informative. Thanks guys.

  4. Hi,
    Good website, I love this subject. I have worked too much about web security.
    anybody has anyquestion please send message.

  5. 5 Matthew

    I have login password string showing in the url string. This has a serious security implication as other users of a system can browse the history and discover the login password of the last user. I have used the POST method for the form tag but when you view the page information you see the password sticking out. So I would like to know of any means of preventing this completely or to encrypt the password.

    Is it also possible to disable mouse “right click” completely for the length of the session.

    I work with PHP and Javascript.

    Thanks in anticipation of your urgent help.

    matthew

  6. Hello, with the abundance of crappy blogs around it’s great to see that there are still some filled with great information! Is there any way I can be emailed when you create a new post? thanks!

  7. Aw, this was an exceptionally good post. Taking a few minutes
    and actual effort to produce a good article…
    but what can I say… I hesitate a whole lot and never manage to
    get nearly anything done.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: