Introduction
Security is an onion. The more layers the better the protection. This blog post talks about the layer that deals with inspecting and filtering user input and disengaging harmful html output. Of course there are many other layers like secure transport (https), secure passwords, securing the web server, the database server, the PHP runtime, the file system and the operating system.
Figure 1: Karate Kata
Inspect and filter input
Input is any data that is not generated by your application, but rather provided by users. Consider all input like for example a string value to be purposely crafted with the intend to take your web server over or down. Consider input to be extremely hazardous. It needs to be quarantined, carefully analyzed and discarded, if bad.
Input can come from form data, URLs, cookies, http headers and RSS feeds. Also data coming form a database or a session server is considered input.
Validate values and make sure that they within a valid rage and expected range.
The PHP documentation has a whole chapter on data filtering.
Disengage output by escaping strings
Letting bad input pass through the system is not necessarily a direct thread to your application. Bad things like XSS (Cross site scripting) attacks are caused by having this bad input be used as output. Escaping output will protect you from the consequences of bad input. This is of course the Plan-B-Layer of the onion. The bad input should have ideally already be cleared during the input filtering process. Since it is sometimes hard to tell whether a variable value originated from clean or dirty input, make it a habit to escape everything, just to be safe.
Escape PHP strings that are going to be used as:
The following code sample shows the escape functions in action:
<h1>Self Defense</h1>
<?php
$bad_input = "transfer your money; format c:;";
echo $bad_input."</br>";
$safe_sql = mysql_real_escape_string($bad_input);
if($safe_sql === false)
{
echo "Error: mysql_real_escape_string worked on a bad puppy!</br>";
}
else
{
echo $safe_sql;
echo "</br>";
}
$safe_url = urlencode($bad_input);
$safe_html = htmlentities($bad_input, ENT_QUOTES, 'UTF-8');
echo "<a href=\"http://tellingmachine.com/upload?file=".$safe_url."\">Click me!<a/>";
echo "</br>";
echo $safe_html;
echo "</br>";
?>
Common attacks
With input filtering and output escaping we are ready to defend ourselves against the most common attacks.
XSS Cross site scripting
XSS attacks on output. Most of the times evil Javascript is entered as form input or used as cookie values. Important here is to filter input and escape output.
<?php
#XSS defense
$html = array();
$ok = array();
#Filter input
$ok['name'] = filter_input(INPUT_COOKIE, 'name', FILTER_SANITIZE_SPECIAL_CHARS);
#Escape output
$html['name'] = htmlentities($ok['name'], ENT_QUOTES, 'UTF-8');
echo "Hello {$html['name']}</br>";
?>
SQL Injection
A SQL Injection attacks target SQL queries with manipulated input data. An attacker might be able to take over the whole data center, if the defensive checks are not in place.
Here is an example of an attack:
<?php
#SQL Injection defense
$password = hash($_POST['pwd']);
$sqlstmt = "SELECT username, password
FROM customers
WHERE username = '{$_POST['unm']}'
AND password = '$password'";
#Danger the above query with the values below...
$_POST['pwd'] = "somethingdoesn'tmatter";
$_POST['unm'] = "KlausG' /*";
#... results in a valid login that doesn't require a password at all
$sqlstmt = "SELECT username, password
FROM customers
WHERE username = 'KlausG'";
?>
The defense is again really simple: Filter input and escape output. Here is how you do it using the mysql_real_escape_string() function.
<?php
$OK = array();
$SQL = array();
$password = hash($_POST['pwd']);
if (ctype_alnum($_POST['name']))
{
$OK['name'] = $_POST['name'];
}
if (ctype_xdigit($password))
{
$OK['name'] = $_POST['name'];
}
$SQL['name'] = mysql_real_escape_string($OK['name']);
$SQL['password'] = mysql_real_escape_string($OK['password']);
$SQLStmt = "SELECT count(*)
FROM customers
WHERE name = '{$SQL['name']}'
AND password = '{$SQL['password']}'";
mysql_query($SQLStmt)
?>
Note: Always prefer the native escaping functions instead of the generic ones like addslashes().
Besides this standard defense there is of course the strongest of all. Use prepared statements and bound parameters. In this case the input data never gets be treated anything else but raw data.
<?php
$stmt = $db->prepare("SELECT count(*)
FROM customers
WHERE name = :name
AND password = :password");
$stmt = $db->bindParam(':name', $OK['name']);
$stmt = $db->bindParam(':password', $OK['password']);
$stmt->execute();
?>
Session Fixation
<a href="http://spamparty.com/itsnowmine.php?PHPSESSION=34533223>Report Spam</a>
Session Fixation is an attack where the conventional defensive strategies like filter input and escape output are insufficient. The attack is carried out by putting a specific session id into a link that the user might click. If the user clicks the link she arrives with the predefined session id at the target site. If session_start() is executed in the responding script, the user gets a session id assigned that is known to the attacker. This is not good! The thread consists in the attacker knowing the session id of a user that is logged in.
The defense is very simple: As long as the user is anonymous there is no risk. Just regenerate the session id every time the user logs in. Luckily PHP handles the regeneration of the session id and preserves the session data. All you need to do is to call session_regenerate_id() every time a user changes the authorization level.
Here is a snippet that shows how it is done:
<?php
if(login($_POST['user'], $_POST['password']))
{
session_regenerate_id();
$_SESSION['login'] = TRUE;
}
?>
Download
The PHP file that contains all of the snippets uses in this blog post can be downloaded here: self-defense.zip
Ausblick
PHP is like Karate, the first three blocks that you learn will put you in the position to counter 95% of the possible attacks.