I assume you have a basic understanding of PHP's syntax and can write at least a "Hello World"-type program. If you're not there, you should start with the PHP manual and some of the basic PHP tutorials (see Resources). Many publishers have good PHP books. A beginner's book or a cookbook-style one is a good starting place.
Conduct your audit on an exact copy of your production environment. You don't need to duplicate the hardware, but you want to make sure the software versions are as close as possible. The PHP configuration must match exactly, as specified in the php.ini file, the Apache directives in .htaccess files, or httpd.conf. You need a separate environment because you will display and log errors that might reveal sensitive passwords and other information. Also, you will try to break the security of the site, which is something you want to avoid with live applications.
The first step is to turn PHP's error_reporting setting up to E_ALL. This will result in PHP reporting a warning every time an uninitialized variable is used, each bad file access, and other (mostly) harmless errors, but might also represent a potential attack vector. These errors nearly always indicate sloppy programming, so if it's your code, you should clean them up anyway.
The setting is shown below:
error_reporting = E_ALL |
If you don't know where your php.ini file is, you can locate it by creating a .php script that contains the following text:
<?php phpinfo(); |
The top section of the output will have a row listing where PHP is looking for php.ini:
Figure 1. Where PHP looks for php.ini
The value may differ, but /usr/local/lib/php.ini is the most common location on UNIX® systems, and C:\php\php.ini or C:\WINDOWS\php.ini are the most common locations on Microsoft® Windows®. If the file doesn't exist, create it and just type the error_reporting line above into the file. After modifying php.ini, you will need to restart your Web server so PHP picks up the new setting.
If you didn't create a phpinfo() page earlier, do so now. The second main section is labeled "Configuration" and contains a lot of useful information about how PHP is set up. There are three columns: the name of the setting, the local value and the xmaster value. The master value is the one set globally for all PHP scripts on your machine by a php.ini directive. The local value is the value in effect for the current script. This can be affected by .htaccess settings, settings inside <Location> or <Directory> portions of httpd.conf, or by ini_set calls within a PHP script. Only some settings are changeable at run time. See the PHP manual in Resources for details.
Two other settings you want to customize are display_errors and log_errors. You will need to enable one or both of these. log_errors tells PHP to log any notices, warnings, or errors to a file, and display_errors causes those same notices, errors and warnings to be displayed on the screen. They are not mutually exclusive. Enabling at least one of them is a useful tool for finding programming errors that may result in security holes.
What kind of security problems should I look for?
The good news is many common programming errors that result in security holes simply aren't possible in PHP. Stack and buffer overflows are two of the most common problems in the C and C++ world. Because PHP manages memory for you, there's no PHP code that can result in stack and buffer overflow exploits.
However, PHP itself is written in C, and memory problems deep in PHP's core surface occasionally. Accordingly, you need to stay on top of security bulletins and updates. The PHP Web site (see Resources) announces new PHP versions and states whether they contain security fixes.
Most problems in PHP applications relate to taking user-supplied data and doing something with it without first validating or sanitizing it. You've probably heard this called cross-site scripting (XSS) vulnerabilities. XSS attacks work by supplying input that a program does not expect and exploiting how it handles rogue input. A well-written program avoids those assumptions. In airport security parlance, PHP programs need to screen their baggage.
Other kinds of problems are subtle logic errors. For example, checking a series of variables to see if a user is granted access to a resource and misplacing a pair of parentheses so that some users get in who shouldn't. We hope your application is well organized and has this kind of logic centralized.
One of the hardest things to do is to differentiate between untrusted input from an external source -- such as a user, another Web site, or some other resource -- and data that is validated already. There are proponents of a "trust nothing" philosophy who say that every function should validate its own data no matter where it came from. This leaves a few things to be desired: Validation means different things in different contexts; doing this in every level of your application quickly becomes tiresome and error-prone; and you're auditing this application, not rewriting it from scratch. You need to trace user input through existing code to see if the application behaves securely, instead of wrapping every variable you see in a validation function.
So, where does user input come from? The first source we'll look at is GET, POST, and COOKIE data. This is commonly called GPC data. How recognizable this data is depends on one controversial php.ini setting: register_globals. After PHP V4.3.0, register_globals defaults to Off. But for years before that, PHP had register_globals turned on by default, so there's a lot of code out there that requires it.
register_globals is not, in and of itself, a security hazard. It does, however, make it harder to trace user input and harder to make sure your application is secure. Why does it do this? Because if register_globals is on, any variable passed to the PHP script by GET, POST, and COOKIE will be created in the global namespace, as well as in the $_GET, $_POST, or $_COOKIE arrays.
Let's look at an example of how this works, and why it's important:
Listing 1. Security with
COOKIEs
1 <?php
2
3 // See if the user has the secret cookie.
4 if (!empty($_COOKIE['secret'])) {
5 $authorized = true;
6 }
7
8 // Now let's go through a list of press releases and show them.
9 $releases = get_press_releases();
10 foreach ($releases as $release) {
11
12 // Some releases are restricted. Only show them to people who can
13 // see secrets.
14 if ($release['secret']) {
15 if (!$authorized) {
16 continue;
17 }
18 }
19
20 // We must be allowed to see it.
21 showRelease($release);
22 }
|
You should notice a few things. First of all, it's a bad idea to rely on a cookie to say whether or not a user is authenticated -- people can set their own cookie values easily. We'll get to that in another article. Worse, however, is that this script has no security if register_globals is on.
Say the script is called press.php. Normally, when a user accesses the
press release script, his browser will show http://www.example.com/company/press.php.
Now, what happens if the user gets sneaky, and changes that to: http://www.example.com/company/press.php?authorized=1?
Look back at the code: $authorized is set only if the user has the cookie. It's never set to false. Then register_globals comes in -- and instead of just being available as $_GET['authorized'], there will also be a variable $authorized in global scope, with the value 1. Therefore, even if the user fails the cookie check, $authorized, when referenced later in the foreach loop, will still evaluate to true.
There are two ways to fix this. One, of course, is to turn off register_globals. If you can get away with this on your production site, it's a great idea. You'll want to test the application to make sure it doesn't break, though.
The other way is a bit of what's called "defensive programming." We simply change the cookie check to look like this:
Listing 2. Improved security with
COOKIEs
1 <?php
2
3 // See if the user has the secret cookie.
4 $authorized = false;
5 if (!empty($_COOKIE['secret'])) {
6 $authorized = true;
7 }
...
|
Now, when the user tacks on ?authorized=1 to the script URL, the $authorized variable will still be set to 1 -- but then it's immediately overwritten by $authorized = false, and only users who actually have the secret cookie will see restricted press releases. They can still set their own cookies, though.
The lesson for auditing code: Try to turn register_globals off. If the application won't work without register_globals and you can't modify it, or you can't control the PHP configuration where the
application must run, you need to look for any global variables set inside a conditional block, or which are only put into the global scope by certain function calls. Either of those situations results in a variable that could be set to an arbitrary value by the user if register_globals is on.
A good way to spot those variables is to set the php.ini setting error_reporting to E_ALL, and use log_errors or display_errors so any PHP warnings and errors are logged to a file or displayed on the screen, respectively. You will get an E_NOTICE any time an uninitialized variable is used in a way that assumes it has a value. This still isn't the same as having PHP require that variables be declared, like in C and the Java™ language. As a result, when the first version of our script runs, the error message will be:
Notice: Undefined variable: authorized in C:\var\www\articles\press.php on line 15 |
The error occurs on line 15 only when the user isn't authorized, instead of on line 5 when the variable is first set. PHP interprets an undefined variable in a boolean context as false (see the PHP manual type casting page in Resources below), so the code "works" anyway -- except when someone sneaks in another way of defining $authorized.
User input with expected values
That's unintended user input. Next time, we'll talk about when you expect user input, but you don't expect the value of that input.
Learn
-
PHP.net is the starting point for all things PHP.
-
Read the Type Juggling chapter of the PHP Manual.
-
PHP Builder is popular PHP site with tutorials and code libraries.
-
Zend Technologies contains plenty of useful links and information about PHP. Zend is the PHP Optimizer.
-
Read Andi Gutmans', Stig Bakken's, and Derick Rethans' book PHP 5 Power Programming and its PDF version.
-
Visit IBM developerWorks PHP project resources to learn more about PHP.
-
Stay current with developerWorks technical events and webcasts.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
Get products and technologies
-
Get evaluation products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere® and start building applications and deploying them on IBM middleware. Select the Linux® or Windows version of the Software Evaluation Kit (SEK).
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
Discuss
-
Get involved in the developerWorks community by participating in developerWorks blogs.

Chuck Hagenbuch is a senior technical consultant for Zend Technologies. He's been programming in PHP for more than eight years, including founding The Horde Project.




