22 Jun 2007

$_SERVER[‘PHP_SELF’] can not be trusted, but there are safe alternatives

Web Development, PHP, Security posted by Luke Visinoni

I spend a good deal of time reading about PHP security over at the PHP Developer’s Network forums. In one of the many discussions I have had over there, I recall one in particular that really opened my eyes to how easy it can be to overlook a very serious security issue. As you may know, there is a predefined array of server-related variables in PHP, aptly named $_SERVER. For years I used a certain element “PHP_SELF” within this array in instances where I needed to output what page I was currently working within. One of the most common of such instances is when you need to tell a form to post back to itself (a very common practice in PHP).

<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
<!-- form contents -->
</form>

How could this possibly be a security issue? I mean this is an untainted value set by PHP itself, right?

Wrong!

Unfortunately, this is not the case at all. The PHP_SELF element can in fact be altered by the user to include any kind of malicious XSS code he/she desires. They will be limited only by their imaginations, and the amount of data that is actually allowed though the url (I believe this depends upon the browser). Don’t feel too down on yourself if you weren’t aware. Using this element in an irresponsible manner has been a staple in online PHP tutorials for as long as I can remember.

Where’s the beef?

So where is this supposed user-polluted input? If you take a moment to examine the entire $_SERVER array (hint: phpinfo()), you’ll notice another element called PATH_INFO. See, it is perfectly valid to tack on extra information after the page name in the URL. For example, the following is a valid URL in PHP.

http://www.example.com/index.php/news/category/javascript

In this case, PATH_INFO would be populated with the data appended to the end of the URL. PHP_SELF will also contain this data.. If you were to examine your $_SERVER array given this URL, you’d see something like the following:

[PATH_INFO] => /news/category/javascript
[PHP_SELF] => /php/phpselftest.php/news/category/javascript

Now let’s revisit our form example from earlier. Using $_SERVER['PHP_SELF'] directly within the form action, you can see how this may present an XSS vulnerability. I realize how paranoid I have sounded about XSS in the last several blog posts, but I assure you it’s for good reason. If you are unfamiliar with XSS, I encourage you to read up on it. Wikipedia’s article on the subject is a good starting point.

Let’s say the user enters http://www.example.com/form.php/%22%3E%3Cscript%3Ealert(‘xss attack’)%3C/script%3E%3Cbr%20class=%22irrelevant

Now let’s take a look at what effect this has on our form output:

<form method="post" action="http://www.example.com/form.php/"><script>alert('xss attack')</script><br class="irrelevant">
<!-- form contents -->
</form>

This is not good. While I can assure you that the simple javascript I injected is harmless, I can’t guarantee the same for your users. It would be a somewhat trivial task for an attacker to write a javascript capable of stealing sensitive data and tricking an unsuspected user into executing it by way of a link such as the one above.

Don’t worry, there are many solutions

Among the many solutions, I prefer the simplest. In many cases, you can get away with just using a pound sign instead of explicitly naming the page.

<form method="post" action="#">
<!-- form contents -->
</form>

However, should you require the use of the <base> HTML element, this solution will generally fail miserably. The browser will no longer assume it should post back to the current page, and instead post to whatever you have assigned to your base tag’s href attribute.

In this case, you can either sanitize the value of PHP_SELF, or you can get the basename() of either the magic constant, __FILE__ (available for php v4.0.2 or later) or the basename() of $_SERVER['SCRIPT_FILENAME'].

Comments are disabled for this article.