WordPress puts food on my table.

PHP $_SERVER variables are not safe for use in forms, links

A common security mistake I see WordPress plugin authors (and PHP coders in general) make is using $_SERVER['PHP_SELF'] or $_SERVER['REQUEST_URI'] as the action of a form or part of an anchor’s href attribute. This is not safe to do, and opens your code up to XSS (cross-site scripting) exploits.

Common example:

<form action="<?php echo $_SERVER['PHP_SELF']; ?>">

Another example:

<a href="<?php echo $_SERVER['PHP_SELF']' ?>?foo=bar">link title</a>

Here are my two rules regarding $_SERVER['PHP_SELF'] or $_SERVER['REQUEST_URI'] in forms:

Do not use them

If you use one of them, escape it with esc_url()

Most uses of $_SERVER['PHP_SELF'] and $_SERVER['REQUEST_URI'] are in HTML forms. If you want the action attribute to point to the current URL, leave it blank. URI references that are blank point to the current resource.

<form action="">

If you do want to specify the action (and there are good reasons for wanting to do that, such as stripping the query string from the current URL), you must run it through esc_url().

A quick search through the WordPress Plugin Directory showed that this problem is far too common.

Updates:

Examples of URLs that could exploit this for double-quoted actions:

script.php/"%20onmouseover='alert(document.cookie)'

And single-quoted actions:

script.php/'%20onmouseover='alert(document.cookie)'

No, just using a plain old htmlentities() wrapper is not going to help! That’s still vulnerable to XSS in certain situations. If you’re not using WordPress, you should copy the WordPress escaping functions (just remove the apply_filters() portions).

If you are using the base tag, Safari will apply that base to the blank action attribute. So if you use the base tag (I never do), a blank action isn’t going to be for you. Use what you’ve been using, but escape it.

Lester Chan has a handy snippet for the form action of WordPress plugin settings pages:

But of course that depends on how and where they’re used, right? We briefly discussed at WCPDX the use of $_SERVER['HTTP_HOST'] in the WP_SITEURL and WP_HOME constants to make wp-config.php more portable between dev and production systems. In this use case, the web server is doing the filtering for you, in that any hostname it doesn’t recognize will never hit the VirtualHost in question (assuming a properly configured web server).

Not all servers may be set up correctly, and may serve for *:80. When coding a plugin for public consumption, I wouldn’t assume they haven’t done that.

For your example of using WP_SITEURL to allow for portable dev/production systems, you could whitelist certain domains (localhost/yourblog.com) or at least pass the HTTP_HOST setting through a [a-z0-9.-] filter.

You’re right about context, but when dynamically populating a powerful constant like WP_SITEURL, it might be prudent to filter out unexpected data. That’s good coding practice anyway — especially in a dynamically-typed language like PHP where the difference between 0 and FALSE could cause your code to break.

Mark, thanks, I will search for how would be the best to change that bit of the code for something else instead. Previously it was (EmptyReferrence!) also in form url. I am not programmer but, guys like you make looking for a related stuff easier. Thank you Mate.

So how exactly is that a security vulnerability?… I could claim anything is a vulnerability. Some examples would be really great otherwise this post is worthless to us and I leave not learning anything.

It allows for script injection — the running of arbitrary Javascript code. See the second comment on this post. The precise method of leveraging XSS is executing cookie-stealing Javascript in an authenticated user’s browser. With this access to an administrator-level account, an attacker can gain deeper levels of access.

My goal with this post wasn’t to teach XSS from scratch, but bring to light a common XSS-enabling mistake that I’ve seen even XSS-aware coders make.

Interesting catch – there are a ton of books out there that use this method to teach you how to get up and running with form handling quickly. I’ve been doing it this way for over 6 years – except I never used it in production scenarios.

come on guys, don’t go bashing on those book authors from a few years ago. They were just trying to teach programming in a general way, not for security.

Although Mark is of course right about the dangers of XSS and the need to properly escape things, I don’t really see this problem with PHP_SELF. It is set by the server and if the server can be manipulated to have PHP_SELF contain a string like in Mark’s example, there are probably other security issues with that server.
The problem is more severe with HTTP_USER_AGENT though.

I just headed over here to read this article from a WordPress Dashboard…The title caught my attention big time because I work for a National Company and about a month ago during a routine security probe by controlscan-com, the only report that came back was this Javascript XSS expolit by my use of having Server Variables un-escaped.

Great read, an a little extra work for me this morning wordpress and non-wordpress –

1. Fred, who is a member of your site, gets an email from a scammer saying their account has been compromised.
2. In the message, the scammer asks the user to confirm their account using a link (to your website).
3. That link includes extra JavaScript code added to the end of the URL that sends whatever is entered into the form to the scammer’s website
4. Fred follows the link and logs in. His account looks fine, but he’s just inadvertently given away his account login.

Hey Cody! your the only person i actually know who has explained this perfectly. OMG i have read a ton of articles on this and still did not understand it. Now i do. I am wondering which is the best way to protect myself from this, i’m not sure.

Any vulnerability can crop up, and it may take a day or two for even their dedicated folks to fix and post a new incremental build. I believe in good hygiene as to login attempts, filtering the admin directory via other methods, etc. in addition to the default.

This is how I currently have it set up for a form I’m developing for a client. The form returns to itself, and instead of leaving the action blank, I thought to set it to the permalink of the page the form is on.

HTTP_HOST doesn’t suffer from the same issues as PHP_SELF and REQUEST_URI because it only holds the host part of the URL which can’t be modified by the user (or it wouldn’t be processed by your server).

I’ve been trying to figure out how this is a security vulnerability for months. But a lightbulb just went off while looking through a few other links on the subject. That’s a nasty little trap to fall into! Thanks for the heads up. I don’t think I’ve used it before, but I can see how I could have used it without blinking an eye at it if I hadn’t read this blog post (numerous times).

@Mark – You recently found this security issue in a plugin in the repository. I won’t name it since it hasn’t been fixed yet, but I’m confused as to how this hack could affect that plugin since the form was something that is only displayed in the admin page. So wouldn’t that just mean that the only person who could hack the site, is the site owner? …. which really doesn’t seem like a security vulnerability.

This was a stupid question. Clearly the answer was that someone simply had to send a link to the admin page to the site owner. Whammo, instant XSS attack. Now that I’ve learned a lot more about this sort of thing it’s scary to see the number of plugins that still do this.

In a more general context, I’ve always been torn between the “filter what you use” and “pre-filter” arguments (for certain types of data).

I think a valid argument could be made for at least running the $_SERVER and $_GET arrays through a filtering function before any other code is executed (re-building $_REQUEST afterwards). The data they hold shouldn’t be complex enough to get mangled by an xss filter IMO.

So many times you see examples like:

header("Location: " . $_GET['redirect']);

echo $_SERVER['HTTP_USER_AGENT'];

Of course you should still filter what you use as well, but if others are writing plugins then it can make a difference.

My own benchmarking has shown that to pre-filter every argument in the $_SERVER array with quite a thorough function (decoding data, removing unsafe strings such as data: and htmlspecialchars) takes only a few thousandths of a second, so it would seem to be worth considering.

Nice info, i’ve never really considered main server vars as a potential injection source.

Be nice to have a list of the server created vars for reference but always best to escape everything anyway. I used to use htmlentities myself as well but my favourite escaping method now is using ctype i like the way you can set what characters are allowed

Anyone with Chrome can just use the developer console to alert the cookies or tamper with the form action. It really doesn’t matter. They just break the form for themselves. Anything client side can be altered together with all of the $_SERVER[‘HTTP_xxx’]; variables.

I saw this in the code I have some students working on. I asked them where they learned to do it. They showed me a “model PHP form” from their lecture notes, outlining the optimal way to handle forms … it included an unescaped PHP_SELF in the form😦

Awesome post, I never even though of leaving it blank. I think I’ll do that form now on. I have been trying to figure out how to pass the current page to a class I’m working on, but this makes life a lot easier.

Ask a WordPress Dev

Do you have an interesting WordPress-related question? Submit your questions, and I'll periodically pick the best one and answer it here on my blog! It can be anything from usage tips to hardcore WP development questions.