Mark on WordPress

WordPress puts food on my table.

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

with 66 comments

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.

Written by Mark Jaquith

September 21, 2009 at 10:50 pm

Posted in wordpress

Tagged with

66 Responses

Subscribe to comments with RSS.

  1. 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

  2. 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

  3. 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

  4. 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

  5. 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

  6. 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

  7. 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 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

  8. 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 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_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 Jaquith

      September 22, 2009 at 2:44 am

  9. 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

  10. 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

  11. Retry:

    I typically do:

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

    I do this because in Safari, if you have a base href="#" then have form 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

  12. 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

  13. @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

  14. 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

  15. “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

  16. 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

  17. @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

  18. 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

  19. 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

  20. 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_SELF injection like the following:

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

      Mark Jaquith

      September 22, 2009 at 3:11 am

  21. 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

  22. 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

  23. 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

  24. 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

  25. 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

  26. 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

  27. >>>>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

  28. 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 $_SERVER values are all suspect? :-)

      Mark Jaquith

      September 23, 2009 at 9:55 pm

  29. [...] 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" [...]

  30. 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

  31. [...] WordPress Mu/BuddyPress Contest Sociable Gets a New Home Should We all Dump Trackbacks? Careful with $_SERVER variables Punish IE6 [...]

  32. [...] Shared PHP $_SERVER variables are not safe for use in forms, links « Mark on WordPress [...]

  33. 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

  34. [...] 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 [...]

  35. 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.

    <?php echo esc_url( get_permalink($post->ID) . '?action=post-new' );?>
    

    But I’m wondering since it’s using an internal WP function, is escaping still necessary?

    Darrin

    December 18, 2009 at 1:39 pm

  36. [...] 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 [...]

  37. I’m confused. You are using $_SERVER['HTTP_HOST'] in one of your own plugins … http://wordpress.org/extend/plugins/subscribe-to-comments/

    ryanhellyer

    January 11, 2010 at 9:48 am

    • 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).

      aarondcampbell

      January 11, 2010 at 12:03 pm

  38. php not supported the esc_url().We can use the strip tag functions

    anish

    January 12, 2010 at 12:44 am

  39. 今天百度挂了,呵呵,流量减低了一大半,真是的。郁闷之余来看看博主的博客,写的不错,加油。

    小区

    January 12, 2010 at 3:37 am

  40. 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).

    Ryan

    January 19, 2010 at 3:01 am

  41. @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.

    Ryan

    January 19, 2010 at 3:18 am

  42. Thanks so much for this post, v. valuable info.
    I’m somewhere between noob and intermediate with php, and have been using $_SERVER['PHP_SELF'] for form processing for quite some time now. I’ll go fix.

    Quick Q, not too off topic I hope – is it safe to use $_SERVER['DOCUMENT_ROOT'] within an include call? I use this method all the time for pulling in header, nav etc.

    aljuk

    January 19, 2010 at 10:26 am

  43. [...] those of you who are curious, the XSS vulnerability was due to an issue discussed here, by Mark Jaquith. While I understand how one could trigger the exploits, I don’t fully [...]

  44. 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.

    Simon

    February 7, 2010 at 3:56 pm

  45. [...] Mark Jaquith has written a bit more detailed about the action attribute. Tags: action attribute, forms [...]

  46. [...] Insecure use of $_SERVER data in header.php. WordPress developer Mark Jaquith elaborated on this issue on his blog. [...]

  47. [...] too. So I've stripped it out with esc_url. More info. about this security vulnerability here … http://markjaquith.wordpress.com/200…orms-or-links/ I'll post the new version here shortly for anyone who has a use for it. The original credits are [...]

  48. [...] to use $_SERVER['PHP_SELF'], better not to use it, but if you do, then make sure you escape it.  http://markjaquith.wordpress.com/2009/09/21/php-server-vars-not-safe-in-forms-or-links/View full post on WordPress TracLeave a Reply Name (required) Mail (will not be published) [...]

  49. [...] Much nicer all round, and avoids using $_SERVER['PHP_SELF'], which is bad. [...]

  50. Thank you for your beautiful and useful information.

    Sağlık

    August 15, 2010 at 9:31 am

  51. I enjoy this blog


Leave a Reply