Victor Biro - 2002-11-09

Hi all,

I came across this FAQ on the register globals setting, and how to address most of the problems people have been having since the default setting was changed in PHP.

The list I got this from was the Wrox Publishing p2p list "beginning php" (http://p2p.wrox.com/list.asp?list=beginning_php). It, and the other Wrox forums and books are a great source of info and peer support. Worth checking out.

cheers
Victor
http://www.terrorism.net

FAQ: register_globals and $_REQUEST
From: "Nikolai Devereaux" <yomama@ucsd.edu>
Date: Thu, 7 Nov 2002 19:03:32 -0800
X-Message-Number: 2

Oops, left out a section.  Take II:

[Introduction]

99% of all the problems I've seen posted to the beginning_php list are related in some way to  the
register_globals setting.  Most of the time, the posted problem is about form values being  lost, and/or
"undefined variable" warnings being displayed.

First, let me describe what "register_globals" does.  It copies all ENVIRONMENT, GET, POST,  COOKIE, and
SESSION (EGPCS) variables into global scope.  That's all.

But it is enough to cause loads of security problems.  Why?

[Those damn PHP authors wrote broken code!]

Most PHP4 books on the shelves were written using early versions of PHP4 to write and test the  code in the
books.  Most PHP4 developers (and book authors) were already familiar with PHP3  before playing with PHP4, so
they wrote the code for their books using the PHP configurations  they've always used; that is, the default
configuration.

After all, you're a developer, not a sysadmin, right?  If PHP is installed and your scripts  work, then PHP
must be configured properly.  Why bother reading through a long ini file or  reading the documentation when
it's not necessary?

Of course, the drawback is that all of these books were written by people who've always used  PHP configured
with a very loose configuration, security-wise.  When the maintainers of the  PHP language decided that things
should be tightened up a bit, they turned off  register_globals by default, and all of a sudden, none of the
code from these "books by  experts" works anymore.

[So how do I fix my problems?]

Odds are you're reading this because your form values are disappearing, and you might even be  seeing an
"undefined variable" warning.  The problem is that you're attempting to access the  global copy of a form
variable that was never copied.

To fix this, you must find all the places you access form input data via global variables, and  replace them
with the appropriate $_GET or $_POST array index.

For example:

// the wrong way
echo "Your favorite author is: " . $Author;

// the correct way
echo "Your favorite author is: " . $_GET['Author'];

This might seem like a hassle, since you now have to keep track of how form input data is  being submitted to
the server, and have to type 9 or 10 additional characters every time you  want to access a form variable. But
the security benefits far outweigh the inconvenience.

So a very important question is: Why would turning register_globals off make things more  secure?

[Explicit code means more secure code.]

Consider this code:

<?php
session_start();
session_register('logged_in');

if($logged_in != true)
{
   Header('Location: login_page.php');
   exit();
}

// valid user's only page here:

?>

On the surface, this might look completely fine.  The problem is that the developer assumes  that $logged_in
will ONLY be copied to global scope from the SESSION variables.

With register_globals = on, anyone can simply access your "users-only" page by tacking on a  "?logged_in=true"
to the URL for a page.  The $logged_in being tested in the if() block was  copied into global scope from the
GET parameters, _NOT_ from the session.

Furthermore, by registering 'logged_in' with the session, you've allowed a malicious user to  spoof a valid
login by simply passing a parameter on the GET string.  Not very secure.

Consider this alternative:

<?php
session_start();

if($_SESSION['logged_in'] !== true)
{
    Header('Location: login_page.php');
}

// valid user's only page here:

?>

This is much more secure, because the developer is explicitly stating that ONLY the  'logged_in' session
variable is used to determine whether or not a user has logged in  successfully.

[Order matters.]

Variables from the EGPCS arrays are copied into global scope in an order defined in php.ini.   That means as a
developer, you might have no control over the webserver configuration, and  cannot guarantee the order in which
things are copied.

The default order is EGPCS.  Environment, Get, Post, Cookie, and Session.  That seems  reasonable, doesn't it?

Consider a file, 'shopping_cart.php', containing this line of code:

echo "<form action=\"$PHP_SELF\" method=\"POST\">\n";

Seems harmless enough, and I'm willing to bet you've generated some forms just like it.

So what happens when a user tacks on "?PHP_SELF=mydomain.com/evil.php" to the URL?  The  correct PHP_SELF is an
environment variable found in the $_SERVER array.  This value is  overwritten in global scope by the GET
parameter.

The generated form will post all the submitted data to 'mydomain.com/evil.php'. Not quite  what you, the
developer, intended, is it?

Again, being explicit about where you expect your variables to come from solves this problem.

echo "<form action=\"$_SERVER[PHP_SELF]\" method=\"POST\">\n";

Problem solved.

[Copies cause confusion.]

I said it before, but I stress it here: register_globals copies values from the original  source into global
variables.  Modifying one has no effect on the other.  This loss of  synchronization violates your data
integrity.

Suppose this form exists:

Send this page to your friends:   <br />
<form method="post" action="mailer.php">
  Email 1: <input type="text" name="emails[]" />
  Email 2: <input type="text" name="emails[]" />
  Email 3: <input type="text" name="emails[]" />
  Email 4: <input type="text" name="emails[]" />
  Email 5: <input type="text" name="emails[]" />

  <input type="submit" value="Send!" />

</form>

This form allows the user to send an email to as many as 5 people. mailer.php performs some  simple validity
check to make sure that no email addresses contain spaces, and that all of the  submitted values are valid
email addresses.  If the user only submitted a username, then a  default domain name will be appended to the
username to create a complete email address.

For example, submitting 'nikolai' should send an email to 'nikolai@domain.com', and submitting  "nikolai
devereaux@domain.com" should send an email to "nikolai_devereaux@domain.com".

<?php

// simple validity check:  replace all spaces with underscores,
// and if there isn't an "@" in the email address, append @domain.com.
for($i = 0; $i < count($emails); ++$i)
{
   $emails[$i] = preg_replace('/ /', '_', $emails[$i]);

   if(preg_match('/^[^@]+$/', $emails[$i]))
   {
      $emails[$i] .= '@appended.domain.com';
   }
}

$subject = "Thought you'd like this page:";
$body = "Go to " . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];

foreach($_POST['emails'] as $email)
{
   mail($email, $subject, $body);
}

?>

Okay... what's the problem?  Any changes made by the validity checks are useless, since those  changes didn't
affect the original form submission values.  Similarly, modifying the original  values but using the global
copies would cause the same problem.

[$_REQUEST is a horrible, horrible thing.]

Using $_REQUEST instead of register_globals = on does not offer you ANY security benefits at all.  It's just as
bad, if not worse.  It's worse because you, the  developer, probably feel that you're writing safer code
because you're explicitly accessing  your variables via a superglobal array.

However, this array is populated the exact same way that the global variables are created when
register_globals is on.  You still do not know from where your values come, cannot guarantee  the order in
which they are copied into $_REQUEST, and are dealing with copies of the original  values.

Simply put -- $_REQUEST is just a variation of "register_globals" that copies values into the  $_REQUEST array
instead of global scope.

[RTFM!]

http://www.php.net/security
http://www.php.net/security.registerglobals
http://www.php.net/security.variables

http://www.php.net/configuration.directives#ini.register-globals
http://www.php.net/configuration.directives#ini.variables-order

http://www.php.net/reserved.variables
http://www.php.net/variables.predefined

----------------------------------------------------------------------