|
 |
beginning_php thread: FAQ: register_globals and $_REQUEST
Message #1 by "Nikolai Devereaux" <yomama@u...> on Thu, 7 Nov 2002 18:34:53 -0800
|
|
[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, which is
generally 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 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@d...',
and submitting "nikolai devereaux@d..." should send an email to
"nikolai_devereaux@d...".
<?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] .= '@a...';
}
}
$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
Message #2 by "Nikolai Devereaux" <yomama@u...> on Thu, 7 Nov 2002 19:03:32 -0800
|
|
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@d...',
and submitting "nikolai devereaux@d..." should send an email to
"nikolai_devereaux@d...".
<?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] .= '@a...';
}
}
$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
Message #3 by Jefferis Peterson <jefferis@p...> on Fri, 08 Nov 2002 15:20:11 -0500
|
|
Nik,
Thanks for your summary. I have a question or two for you though...
If you use this formula $_GET['Author'], you are going to have to set
variables for every form element received or you are going to have to keep
requesting $_GET['Author'] every time you want to use it on the same page...
Correct? This makes typing the code laborious.
So $author = $_GET['Author'] will set your variable on the page.
Now what about passing $author variable to another page? For all your
strings and values and variables you are going to have to keep typing
$_GET['Author'] as you pass from page to page?
For example, I have a popup window based on retrieved values from a mysql
database. This is the code on the html page:
> <a href="#" onclick="MM_openBrWindow('<?php echo
> "show_image.php?img={$row_yellow['pictures']}"; $img
> $row_yellow['pictures'];?>','','toolbar=no,scrollbars=yes,resizable=yes,width
> 550,height=500,left=100, top=<?php echo $top ?>');return false">
> <img src='<?php echo $row_yellow['thumbnail']; ?>' alt="<?php echo
> $row_yellow['item_name']; ?>" border="0"></a>
So on my pop window, is my code going to retrieve these values IF global
registers is off? Or am I going to have to use get statements even though
no form is being used and the info is just passing from one page to another?
This is the popup window code:
> <TITLE>Large View of <?php echo $iden; ?></TITLE>
> <meta http-equiv="Content-Type" content="text/html;
> charset=iso-8859-1"></HEAD>
> <body bgcolor="black" text="#FFFFCC" link="#FFFFCC" vlink="#FFFFCC"
> alink="#FFFF99" leftmargin="0" topmargin="0" marginwidth="0"
> marginheight="0">
> <DIV align="center">
> <p><font face="Copperplate Gothic Light, Copperplate31ab, Verdana, Georgia,
> Trebuchet MS, Times New Roman, serif"><br>
> Large View of Item no. <?php echo $iden; ?></font></p>
> <hr width="50%">
> <br>
> <p><IMG src="/<?php echo $img; ?>"><br>
On 11/7/02 10:03 PM, "Nikolai Devereaux" <yomama@u...> wrote:
> 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
~~~~~~~~~~~~
Jefferis Peterson, Pres.
Web Design and Marketing
http://www.PetersonSales.net
Tel . xxx-xxx-xxxx
ICQ 19112253
http://www.Slippery-Rock.com - 7,000 hits per year
Such is God's economy: "One man gives freely, yet grows all the richer;
another withholds what he should give, and only suffers lack." - Proverbs
11:24
Message #4 by "Nikolai Devereaux" <yomama@u...> on Fri, 8 Nov 2002 14:37:12 -0800
|
|
> Nik,
>
> Thanks for your summary. I have a question or two for you though...
>
> If you use this formula $_GET['Author'], you are going to have to set
> variables for every form element received or you are going to have to keep
> requesting $_GET['Author'] every time you want to use it on the same page...
> Correct? This makes typing the code laborious.
> So $author = $_GET['Author'] will set your variable on the page.
Sort of -- but remember that $author is a *COPY* of $_GET['Author']. The
correct way would be to do this:
$Author = & $_GET['Author'];
That initializes the global variable to be a reference to the original.
Still, though, I don't find that this is at all laborious, especially when you
gain the advantage of knowing explicitly where the variable comes from. Sure,
in smaller scripts, $Author would make perfect sense. But the larger a website
application becomes, the more it makes sense to organize your data. That FORM
data is already organized for you (in handy superglobal arrays, no less), you
gain the benefit of knowing that 'Author' was submitted to your page via a
hyperlink. (After all, forms using the GET method simply have the browser
construct the URL query string from the form values and append it to the form
target.
> Now what about passing $author variable to another page? For all your
> strings and values and variables you are going to have to keep typing
> $_GET['Author'] as you pass from page to page?
Well, if you're passing the value from page to page via the URL, then yes --
"Author" is a GET variable, and should be accessed via the $_GET array.
> For example, I have a popup window based on retrieved values from a mysql
> database. This is the code on the html page:
<a href="#"
onclick="MM_openBrWindow('<?php
echo "show_image.php?img={$row_yellow['pictures']}";
$img = $row_yellow['pictures'];?>',">
<img src='<?php echo $row_yellow['thumbnail']; ?>'
alt="<?php echo $row_yellow['item_name']; ?>" border="0">
</a>
> So on my pop window, is my code going to retrieve these values IF global
> registers is off? Or am I going to have to use get statements even though
> no form is being used and the info is just passing from one page to another?
Of course your popup is going to retrieve the value -- as $_GET['img'].
> This is the popup window code:
<snip>
<p><IMG src="/<?php echo $_GET['img']; ?>"><br>
Take care,
Nik
Message #5 by "Fernando Sotano Camilo" <fcamilo@u...> on Mon, 11 Nov 2002 19:04:55
|
|
For example:
// the wrong way
echo "Your favorite author is: " . $Author;
// the correct way
echo "Your favorite author is: " . $_GET['Author'];
I didn't understand one thing: if you use $_GET['Author'] instead of
$Author, it will be still possible to change the value using the URL...
It has obvious differences when I use $_ENV or $_POST, but $_GET didn't
seem to have any benefit...
By the way, thanks for explaining about register_globals, I needed that!
Fernando.
Message #6 by "Nikolai Devereaux" <yomama@u...> on Mon, 11 Nov 2002 12:29:08 -0800
|
|
I'm sorry, Fernando, but I didn't understand what you meant by this:
> I didn't understand one thing: if you use $_GET['Author'] instead of
> $Author, it will be still possible to change the value using the URL...
> It has obvious differences when I use $_ENV or $_POST, but $_GET didn't
> seem to have any benefit...
I think you're saying that a malicious user can still override the value of the
"Author" get variable by adding their own to the URL string.
This is true, but the only time that the PHP script would expect
$_GET['Author'] to be set is when it's on the commandline. That becomes less
an issue of overwriting variables as it does performing basic input validation.
GET variables are sent on the URL query string, either explicitly by
hyperlinks, or generated by <form method="get"..> forms.
All this aside, using $_GET['Author'] instead of $_REQUEST['Author'] or the
global version solves all the other issues that I mentioned; that is, you know
that the Author variable was sent via the URL (not in a cookie, session, or
POST), and you're always dealing with the original version, not a copy.
Take care,
nik
Message #7 by "Fernando Sotano Camilo" <fcamilo@u...> on Tue, 12 Nov 2002 17:34:50
|
|
> I think you're saying that a malicious user can still override the value
of the "Author" get variable by adding their own to the URL string.
Yes Nik, that's what I meant. And I understood your explanation again.
Thanks one more time! Things are getting clearer now...
Fernando.
Message #8 by "Mike Harding" <michel.harding@v...> on Tue, 26 Nov 2002 03:48:02
|
|
How would you address dealing with a form that submits a list box that
allows selection of multiple values? The array might not have values for
all possibilities. If, for example, a list contains 3 items, using:
<form method="get" action="mypage.php">
<select name="variable[]" multiple>
<option> ...</option>
<option> ...</option>
<option> ...</option>
</select>
<input type="submit">
</form>
mypage.php might contain:
echo $_GET['variable']['0'];
echo $_GET['variable']['1'];
echo $_GET['variable']['2'];
It will result in an index error if all three are not selected in the input
form. I'm a beginner and have not yet tried to address this. Would
conditions be used to check for existance before generating output?
Thanks.
Message #9 by "Nikolai Devereaux" <yomama@u...> on Tue, 26 Nov 2002 12:11:53 -0800
|
|
> How would you address dealing with a form that submits a list box that
> allows selection of multiple values? The array might not have values for
> all possibilities. If, for example, a list contains 3 items, using:
>
> <form method="get" action="mypage.php">
> <select name="variable[]" multiple>
> <option> ...</option>
> <option> ...</option>
> <option> ...</option>
> </select>
> <input type="submit">
> </form>
>
> mypage.php might contain:
>
> echo $_GET['variable']['0'];
> echo $_GET['variable']['1'];
> echo $_GET['variable']['2'];
>
> It will result in an index error if all three are not selected in the input
> form. I'm a beginner and have not yet tried to address this. Would
> conditions be used to check for existance before generating output?
Well, my first reaction would be "Don't do that."
The safest way to iterate across an array is to ensure that you remain
in-bounds. I prefer foreach() to a regular for() loop, since foreach makes it
possible to iterate across all values in an array, regardless of how it's
indexed. A simple for() loop won't work so well if your array indexes are 3,
5, "hello", and "world".
foreach($_GET['variable'] as $key => $value)
{
echo "\$GET['variable'][$key] = $value\n";
}
For a <select multiple> input, this doesn't make much of a difference, but
consider checkboxes:
What movies do you currently own?
<input type="checkbox" name="movies[HP1]" />
Harry Potter and the Sorcerer's Stone
<input type="checkbox" name="movies[LOTR1]" />
The Lord of the Rings : Fellowship of the Ring
<input type="checkbox" name="movies[ARMY]" />
Army of Darkness
hth,
nik
|
|
 |