How register_globals Helps You Write Insecure Code

As a application development specialist for lifeBLUE Media. I see a lot of different coding styles. Some are organized and very readable; others look like raw fettucine with extra alfredo sauce. But one habit that I've seen numerous times, often among relatively inexperienced programmers, is the dependence on PHP's register_globals directive.

register_globals was introduced as a way of making it more convenient to access User input. For example, instead of having to type: echo 'Hello, ', $_GET['username'], '!'; With register_globals turned on, $username would automatically be assigned the value of $_GET['username'], so all you would have to do is: echo 'Hello, ', $username, '!'; Wow, what a timesaver!

So why are they getting rid of register_globals in PHP 6 if it's so helpful? I believe not even Sherlock Holmes could figure this one out. The problem, interestingly, is not that register_globals is bad; rather, the problem is that register_globals encourages very bad security habits. Let's break out the magnifying glass and our trusty pipe to take a look at a fictitous banking site. One of the pages on this site, transfer.php, provides an interface for the User to transfer funds between two accounts. Without register_globals, the code might look something like this: $_sql = " UPDATE `accounts` SET `balance` = `balance` - {$_POST['amount']} WHERE `accountid` = {$_POST['transFrom']} LIMIT 1"; $db->execute($_sql); $_sql = " UPDATE `accounts` SET `balance` = `balance` + {$_POST['amount']} WHERE `accountid` = {$_POST['transTo']} LIMIT 1": $db->execute($_sql); Simple enough. Now here's what it would look like using register_globals: $_sql = " UPDATE `accounts` SET `balance` = `balance` - {$amount} WHERE `accountid` = {$transFrom} LIMIT 1"; $db->execute($_sql); $_sql = " UPDATE `accounts` SET `balance` = `balance` + {$amount} WHERE `accountid` = {$transTo} LIMIT 1": $db->execute($_sql); This begs the question, "How does PHP know where these variables are coming from?" The answer is: It doesn't.register_globals looks in $_GET, $_POST, $_COOKIE and $_SESSION, so if there's a matching index in *any* of these superglobals, your code would have no way of knowing which value is the 'correct' one. For example, suppose you created a cookie whose name is 'amount'. An incorrectly-configured php.ini might cause the cookie to overrule the amount that the User specified in the form! And by (inadvertently) allowing the User to specify these variables in the URL, you open your site up to XSS attacks.

It was for this reason thatÃ? the default value for register_globals went from ON to OFF in PHP 4.2.0. But many programmers merely shrugged their shoulders and went on with their normal lives: $amount = $_POST['amount']; $transFrom = $_POST['transFrom']; $transTo = $_POST['transTo']; $_sql = " UPDATE `accounts` SET `balance` = `balance` - {$amount} WHERE `accountid` = {$transFrom} LIMIT 1"; $db->execute($_sql); $_sql = " UPDATE `accounts` SET `balance` = `balance` + {$amount} WHERE `accountid` = {$transTo} LIMIT 1": $db->execute($_sql); But this is really not much of an improvement. True, the code does now examine where the variables are coming from, but it's not validating the values. To use a rather dramatic example, suppose a (somewhat less-than-reputable) request comes in, and the value of $_POST['transFrom'] is "0 OR TRUE". Now the first SQL query looks like this: $_sql = " UPDATE `accounts` SET `balance` = `balance` - 500000 WHERE `accountid` = 0 OR TRUE LIMIT 1"; $db->execute($_sql); Here's a hint: you'll be getting a LOT of angry phone calls VERY soon! How would you protect yourself from such an irresponsible act (also known as an SQL Injection Attack)? Rather easily, as it turns out: $amount = (float) $_POST['amount']; $transFrom = (int) $_POST['transFrom']; $transTo = (int) $_POST['transTo']; Not even 20 characters later, your code is completely SQL-Injection-proof!

There are many, many opinions out there on the best way to validate User input. Certainly *some* kind of validation that makes sense is always better than none, and that is the purpose of this article...or is it?