Skip to main content

Auditing PHP, Part 1: Understanding register_globals

Get started by taking a walk through PHP and its security issues

Chuck Hagenbuch (chuck@zend.com), Senior Technical Consultant, Zend Technologies Inc.
Chuck Hagenbuch
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.

Summary:  Chances are that at some point you've had a concern about the security of a PHP application. When you're faced with an auditing task, do you know what to look for? This series walks you through PHP and helps you understand it enough to know what you're looking for when conducting a security audit. Part 1 walks you through understanding the register_globals setting.

Date:  10 May 2005
Level:  Introductory
Activity:  1594 views

Getting started

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
phpinfo() output

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.


Recognizing user input

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.


Unintended user input

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.


Resources

Learn

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

About the author

Chuck Hagenbuch

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.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=82821
ArticleTitle=Auditing PHP, Part 1: Understanding register_globals
publish-date=05102005
author1-email=chuck@zend.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers