PHP’s session_register() And The Fine Art Of Variable Poisoning

Recently asked on Yahoo! Answers:

Using session_register() to register objects?
I’ve just started with OOP, and I’m making an app that uses objects that need to retain their data. Looking for an answer to this, I’ve discovered serialization. Whenever I go to a tutorial page about it, they always seem to mention the use of session_register() to automatically serialize and unserialize objects with PHP’s session feature. For example, the bottom 3 paragraphs of this page http://www.php.net/manual/en/language.oo…

However, I can’t seem to find ANY solid tutorials or instruction on how to accomplish this. How do I use session_register() to automatically have all my objects serialized?

Normally, I would answer this question within Answers itself, because the short answer is direct: You don’t need to call session_register() in order to create an object with session scope. You can simply assign any object to a session variable using the $_SESSION superglobal.

Consider this PHP code:

$bar = "hello world";
$_SESSION['foo'] = serialize($bar);
echo unserialize($_SESSION['foo']); //will echo "hello world"

Using direct assignment to a $_SESSION superglobal seems preferable over using session_register(), since (as the documentation states) session_register() will automatically unserialize the variable on every page, requiring your class to be present on every page in the application.

I’ve tested this: If you explicitly serialize an object and explicitly assign it to the $_SESSION superglobal, you need not have your class present on every page. However, if you use session_register(), and pass the variable through a page that does not include the class, it is effectively destroyed.

I’d like to expand here, as well, on session_register(): why it was created, why it remains, and more specifically, why maintaining the minimum scope necessary for each variable keeps your applications secure.

To do that, I need more room than is afforded in a Yahoo! answer, where anything over a few sentences easily falls into the tl;dr realm. (And contrary to the opinions voiced on Urban Dictionary, it’s not trite or disingenuous to tell a blowhard he needed to shut up five minutes ago; witness Edward Everett vs. Abraham Lincoln.)

By now, anyone but the n00biest n00b in the P & D category knows if I link to this blog, he’s going to get a long answer.

The Purpose Behind session_register() And register_globals

In the days before PHP 4.2.0, session_register() was of more use, largely because of the ability of PHP to automatically register all variables as globals.

In those early versions of PHP, by default, any time you declared a variable, it automatically had global page scope; and because of that, any superglobal could also be referenced by calling its key as a PHP variable.

For example, if I had this form:

<form name="myform" action="form.php" method="post">
	<input type="text" name="foo" />
	<input type="submit" name="bar" value="Submit" />
</form>

Once I POST this form, it will create two $_POST superglobal variables: $_POST[‘foo’], the contents of a text field; and $_POST[‘bar’], the contents of the submit button.

If I have registered globals in my PHP install, I can reference those variables not only via $_POST[‘foo’] and $_POST[‘bar’], I can also access them with $foo or $bar. In other words:

echo $_POST['foo']; // echos value of text field
echo $foo; // also echos value of text field

//function below will output text field variable twice, just like above
function showPostValue() {
	echo $_POST['foo'];
	echo $foo;
}

The purpose of session_register() in earlier versions of PHP was to allow people who were used to working with globally scoped variables an easy way to assign those variable values to the session.

Or, to be more specific, because all variables in early versions of PHP had global scope, PHP needed some way to identify which variables should be assigned to the session variables. Enter session_register().

$foo = "Hello World";
session_register("foo");

Variable Poisoning Made Simple (And Unintentional)

At first blush, having global scope for all variables seems like a Hell of an idea. But it’s actually fraught with trouble.

The problem is that global variables can not only be read anyplace, they can be reassigned anyplace. Including, for example, via a query string, which allows someone to “poison” your variables.

Let’s assume you have a PHP install with register_globals set to true. And let’s assume you have the same form as above, plus some simple logic to output it. Let’s call this page form.php.

<?php
if(isset($foo)) {
	echo "<p>You entered $foo in the text box</p>";
}
?>
<form name="myform" action="form.php" method="post">
	<input type="text" name="foo" />
	<input type="submit" name="bar" value="Submit" />
</form>

Suppose we put the value “Hello World” into the text box and then click the submit button. Once the page posts back, we’ll receive the message “You entered Hello World in the text box” above our form.

Let’s reload the page so that the form is empty and the message is removed. Now, let’s change the URL in our browser to append a query string value for foo — e.g., form.php?foo=bar — and hit Enter.

If register_globals is true in our PHP install, the page will echo out, “You entered bar in the text box”, even though we didn’t enter anything in the text box.

This is variable poisoning: using the global scope of a known variable name to reset the variable to some value other than the programmer intended. Needless to say, global variables are an easy venue for hackers to attack your site — out on the Web, even today, are countless PHP applications relying on session variables named “id” to secure the site, and global variables enabled.

But most often, it’s not hackers who wind up doing damage via poisoned variables: you, as programmer, tend to be the biggest danger to your own applications.

Consider the same PHP form as above. But let’s suppose that this time, we want to be responsible and specifically address the proper scope for our variables — that is, we want to call POST variables via the $_POST superglobal — but register_globals is set to true in our PHP install.

We can still assign a new value to $foo, even if that’s not what we intended to do.

<?php
if(isset($_POST['foo'])) {
	$foo = "bar";
	echo "<p>You entered $_POST[foo] in the text box, but the value of local variable $foo is $foo</p>";
}
?>
<form name="myform" action="form.php" method="post">
	<input type="text" name="foo" />
	<input type="submit" name="bar" value="Submit" />
</form>

If register_globals is set to true in our PHP install, and we execute the code above, the page will always output “You entered bar in the text box, but the value of local variable $foo is bar”, regardless of the value we enter in the text box.

That’s because when register_globals is set to true, there’s only one $foo, and it consolidates, into one variable, all the possible methods of referring to the variable with the key of foo.

Consider this:

$_POST['foo'] = "Hello";
$_GET['foo'] = "World";
$_SESSION['foo'] = "Hello World";
$foo = "bar";

echo $_POST['foo'] . "<br />";
echo $_GET['foo'] . "<br />";
echo $_SESSION['foo'] . "<br />";
echo $foo;

If we have set register_globals set to false, we get this output:

Hello
World
Hello World
bar

If register_globals is true, we get this:

bar
bar
bar
bar

That’s because when register_globals is set, line 4 of the code block reassigns the value of $_POST[‘foo’], $_GET[‘foo’] and $_SESSION[‘foo’] to be the value of $foo. And that’s because, if $foo has global scope, there can be only one $foo. It must refer to all possible instances of foo, be it a post, querystring or session variable.

The Importance Of Addressing Variables Within An Appropriate Scope

A new programmer might find addressing a variable within a specific scope — e.g., using $_POST[‘foo’] rather than $foo — to be odious. But limiting the scope of variables serves an important purpose: It significantly reduces both unintentional errors and limits the ability of others to harm our programs by maliciously changing variable values.

Think of variables, then, as a box of rat poison:

  • You only buy rat poison if you have a rat infestation; keeping a box of rat poison on hand when there are no rats is at best paranoid, at worst suspect.
  • You only put rat poison out where you think rats will be; anyplace else is an opportunity for the kids, dog, etc. to eat it.
  • You throw out rat poison once you’re free of rats.

This is not to suggest that passing an object between pages via a session variable may not be appropriate; in fact, it’s often an elegant way of holding on to data specific to a user.

For example, many shopping cart applications are created as session objects, freeing them from the need to query a database until the order is ready to be placed. You can build a shopping cart as a database store, but the endless queries involved can often overtax a server, and you wind up with records from many abandoned sessions that need to be cleaned up. Therefore, a session object will often outperform a shopping cart built on a data store backbone.

This is merely to explain how it is session_register() came to be and why using it is not necessary within the context of strongly scoped PHP applications; and most PHP-enabled servers today require strong scoping of variables.

For more discussion about variable scope, check out my post titled “Variable Scope Made Simple.”

Comments

Trackbacks

Leave a Reply

Your email address will not be published. Required fields are marked *