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().
<form action="<?php echo esc_url( $_SERVER['PHP_SELF'] ); ?>">
The same applies to links… run the href attribute through esc_url().
<a href="<?php echo esc_url( $_SERVER['PHP_SELF'] . '?foo=bar' ); ?>">link title</a>
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:
<form action="<?php echo admin_url( 'admin.php?page=' . plugin_basename( __FILE__ ) ); ?>">
admin_url() takes care of escaping for you, and is an easy way to create a full WP admin URL from a wp-admin-relative URL.


What would an example of XSS attack be when using this? I’ve always run $_SERVER['PHP_SELF'] through htmlspecialchars. I’ve never heard of esc_url before either.
Ryan
September 21, 2009 at 10:57 pm
A naked
htmlspecialchars()won’t protect you completely.Consider a form with
htmlspecialchars($_SERVER['PHP_SELF'])as the action, enclosed with single quotes. This will defeat it:script.php/'%20onmouseover='alert(document.cookie)'For a double-quoted version using naked
$_SERVER['PHP_SELF'], this will defeat it:script.php/"%20onmouseover='alert(document.cookie)'For more on
esc_url(),esc_attr()and the escaping API updates for WP 2.8, read this post.Mark Jaquith
September 21, 2009 at 11:10 pm
And I’ve done this hundreds and hundreds of time. It was actually in the O’Reilly PHP 4 book that I learned from.
Chancey Mathews
September 21, 2009 at 11:52 pm
Hi Mark,
How does <form action="”> leave you open for XSS? I’ve never heard of this exploit before, do you have a link with more information?
I typically do this form actions, because there’s an issue with Safari. If you supplied a earlier in the document, the form action goes there instead. (Last I checked, anyhow)
Anthony
September 22, 2009 at 12:18 am
Thanks for writing this post! This knowledge really needs to be spread around.
Question though: are the other $_SERVER variables safe? I thought I had read somewhere it was possible for a user to modify $_SERVER['HTTP_HOST']. I could be remembering entirely wrongly though.
Alex (Viper007Bond)
September 22, 2009 at 12:42 am
Hey Anthony, I believe Mark mentioned that is a method to fix the vulnerability of XSS attack.
Robin Bastien
September 22, 2009 at 12:44 am
I didn’t say that it did… was there something specifically that seemed ambiguous on that point? Let me know and I’ll update to make it more clear.
Mark Jaquith
September 22, 2009 at 12:44 am
I wouldn’t consider any of them safe. Any info that you didn’t specifically and statically populate yourself should be considered unsafe.
Mark Jaquith
September 22, 2009 at 12:49 am
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).Kenn
September 22, 2009 at 1:17 am
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_SITEURLto allow for portable dev/production systems, you could whitelist certain domains (localhost/yourblog.com) or at least pass theHTTP_HOSTsetting 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 between0andFALSEcould cause your code to break.Mark Jaquith
September 22, 2009 at 2:44 am
How about this one:
<?php
$urlpath = explode("/", $_SERVER['REDIRECT_URL']);
$type_in_url = $urlpath[2];
Is it safe you reckon? I use that in two non-WordPress files.
Anne
September 22, 2009 at 12:51 am
Sorry folks, comment form took out the PHP tag, and I didn’t double check. My bad.
I typically do . I do this because in Safari, if you have a and then do , the action defaults to your BASE URL, instead of the current URL.
Do you have a link with more information on how users can modify the $_SERVER variables?
Anthony
September 22, 2009 at 12:52 am
Retry:
I typically do:
I do this because in Safari, if you have a
base href="#"then haveform action="", the action defaults to your base URL, not the current resource.Anthony
September 22, 2009 at 12:55 am
That’s a good point. You can use
REQUEST_URI, but you need to filter it. In WP,esc_url()is your ticket. In non-WP… copy WP’s security functions and use them.Mark Jaquith
September 22, 2009 at 2:50 am
FYI to all commenters: you can use the “sourcecode” shortcode in comments.
http://support.wordpress.com/code/#posting-source-code
<?php $urlpath = explode("/", $_SERVER['REDIRECT_URL']); $type_in_url = $urlpath[2]; ?>Alex (Viper007Bond)
September 22, 2009 at 12:58 am
@Anne: Again, I wouldn’t trust anything you didn’t explicitly and statically set yourself. If there is any amount of unpredictable variability, it’s suspect.
This is mostly a matter of good habits. Once you get in them, it’ll come naturally, and it won’t slow you down.
Mark Jaquith
September 22, 2009 at 1:00 am
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.
Anne
September 23, 2009 at 6:55 pm
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.
CoryMathews
September 22, 2009 at 1:09 am
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.
Mark Jaquith
September 22, 2009 at 2:21 am
“A quick search through the WordPress Plugin Directory”
any tool for this ?
Ozh
September 22, 2009 at 1:11 am
Google:
site:svn.wp-plugins.org/ form action=”$_SERVER[REQUEST_URI”
Michael Torbert
September 22, 2009 at 2:01 am
I have a local checkout of all the plugins in the repository, so I used
fgrep. Unfortunately I don’t know of a hosted way to search through all the plugins…Mark Jaquith
September 22, 2009 at 2:16 am
I think
<?php echo admin_url('admin.php?page='.plugin_basename(__FILE__)); ?>is a good choice also
Lester Chan
September 22, 2009 at 1:19 am
Yep, for plugin settings forms, that’s perfect.
Mark Jaquith
September 22, 2009 at 2:32 am
@Alex (Viper007Bond) – My gratitude (and probably Mark’s) for the link!
Looking around for examples of this vulnerability, found this:
http://www.mc2design.com/blog/php_self-safe-alternatives
It’s from 2007 – scratching my head at how I’ve not heard of this before.
Anthony
September 22, 2009 at 1:21 am
Great advice, although the people from WordPress itself aren’t using it:
http://codex.wordpress.org/Adding_Administration_Menus#Sample_Menu_Page
Peter Boosten
September 22, 2009 at 1:48 am
Thanks. I updated that. In the future feel free to edit it yourself — it’s a wiki!
Mark Jaquith
September 22, 2009 at 2:13 am
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.
sumwatt
September 22, 2009 at 2:16 am
Hi,
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.
Regards,
Tobias
Tobias
September 22, 2009 at 2:59 am
@Tobias, This isn’t an issue only with some esoteric server setup. Stock Apache 2.0 with stock PHP 5.2 on Linux is vulnerable to
PHP_SELFinjection like the following:script.php/"%20onmouseover='alert(document.cookie)'Mark Jaquith
September 22, 2009 at 3:11 am
Hi Mark, thanks for the nice article. I have a question:
I use
action="/". Do you think this is safe in your opinion?Chetan
September 22, 2009 at 3:11 am
Literal strings are always safe.
The danger is in variable data, specifically variable data that can be varied by an untrusted party.
Mark Jaquith
September 22, 2009 at 3:13 am
Oops, the previous comment got eaten away by the stripper:
I use bloginfo(‘home’) template tag for action; do you think this is safe?
Chetan
September 22, 2009 at 3:13 am
Yes, that’s safe.
Mark Jaquith
September 22, 2009 at 3:23 am
Classic attack, its nearly always safer to hard code the URL. Its surprising the number of websites that are still vulnerable to XSS and CSRF attacks today.
Phil
September 22, 2009 at 8:48 am
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 -
Giancarlo Colfer
September 22, 2009 at 10:09 am
Awesome tip! I didn’t even know that could be an issue. I had seen the $_SERVER['PHP_SELF'] trick, and have used it ever since. Appreciate the improved version! Thanks.
Kate
September 22, 2009 at 10:31 am
It’s good your talking about this exploit, but you don’t really explain how it would be used very well. Here’s a better description:
http://bit.ly/qWUK4
This is how an attack could happen…
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.
Cody
September 22, 2009 at 3:40 pm
>>>>Not all servers may be set up correctly, and may serve for *:80.
Can you please explain how this is bad? Thanks
Harold S
September 22, 2009 at 10:06 pm
It’s not bad per se, but it might be bad in combination with code that blindly accepts the HTTP HOST the server is given.
Mark Jaquith
September 23, 2009 at 9:39 am
As PHP_SELF can be modified by the user, but SCRIPT_NAME cannot, would you say that this would be sufficient?
Gary
September 23, 2009 at 6:30 pm
It may be, but why not escape, just in case? Consider this to be my version of Blaise Pascal’s Wager: What do you have to lose by believing that
$_SERVERvalues are all suspect?Mark Jaquith
September 23, 2009 at 9:55 pm
[...] Mark Jaquith's article about PHP $_Server variables made me realize that my tutorial on "How to add a horizontal navigation bar to THE MORNING AFTER" [...]
The Morning After – Security Update for Navigation Bar! | nickbohle.de
September 24, 2009 at 1:40 pm
Well said re: Pascal’s Wager. I would rather add a tiny bit of overhead to insure that I still own my data rather than find out the hard way.
It’s bad enough when it’s only your internal code, but the vulnerability footprint is huge if it’s popular. Consider WP itself, in self-hosted terms.
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.
Appreciate the reminder.
John
September 24, 2009 at 3:01 pm
[...] WordPress Mu/BuddyPress Contest Sociable Gets a New Home Should We all Dump Trackbacks? Careful with $_SERVER variables Punish IE6 [...]
WPWeekly Episode 73 – The Mystery Show
September 28, 2009 at 1:01 pm
[...] Shared PHP $_SERVER variables are not safe for use in forms, links « Mark on WordPress [...]
Daily Digest for October 4th | More Than Scratch The Surface
October 4, 2009 at 9:29 am
Just thought I’d say thanks for bringing this up. It’s been an oft-ignored issue for a long time (Writeup from 2005 that brought it to my attention 4+ years ago: http://seancoates.com/xss-woes).
aarondcampbell
October 20, 2009 at 2:33 pm
[...] I found this on my WordPress news thang when I logged in today. Another WordPress user decided to write this article so that other developers would know that $_SERVER variable is NOT [...]
PHP $_SERVER variables are not safe for use in forms, link << Mark on Wordpress « Sammaye's Blog
November 9, 2009 at 6:36 am
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.
But I’m wondering since it’s using an internal WP function, is escaping still necessary?
Darrin
December 18, 2009 at 1:39 pm
[...] but Mark Jaquith (WP core developer) seems to think it's a really bad idea so best not use them: http://markjaquith.wordpress.com/200…orms-or-links/ Personal blog | Twitter Menu plugin | CSS Generator | Premium [...]
Broken plugin when upgraded to 2.9 - WordPress Tavern Forum
December 22, 2009 at 9:24 pm