A XSS Vulnerability in Almost Every PHP Form I’ve Ever Written
I’ve spent a lot of time over the past few months writing an enterprise application in PHP. Despite what some people may say, I believe that PHP is as secure or insecure as the developer who is writing the code. Anyway, I’m at the point in my development lifecycle where I decided that it was ready to run an application vulnerability scanner against it. What I found was interesting and I think it’s worth sharing with you all.
Let me preface this by saying that I’m the guy who gives the training to our developers on the OWASP Top 10, writing secure code, etc. I’d like to think that I have a pretty good handle on programming best practices, input validation, and HTML encoding. I built all kinds of validation into this application and thought that the vulnerability scan would come up empty. For the most part I was right, but there was one vulnerability, one flaw in particular, that found it’s way into every form in my application. In fact, I realized that I’ve made this exact same mistake in almost every PHP form that I’ve ever written. Talk about a humbling experience.
So here’s what happened. I created a simple page with a form where the results of that form are submitted back to the page itself for processing. Let’s assume it looks something like this:
<html> <body> <?php if (isset($_REQUEST['submitted']) && $_REQUEST['submitted'] == '1') { echo "Form submitted!"; } ?> <form action="<?php echo $_SERVER['PHP_SELF']; ?>"> <input type="hidden" name="submitted" value="1" /> <input type="submit" value="Submit!" /> </form> </body> </html>
It looks fairly straightforward, right? The problem has to do with that $_SERVER[‘PHP_SELF’] variable. The intent here is that PHP will display the path and name of the current page so that the form knows to submit back to the same page. The problem is that $_SERVER[‘PHP_SELF’] can actually be manipulated by the user. Let’s say as the user I change the URL from http://www.webadminblog.com/example.php to http://www.webadminblog.com/example.php”><script>alert(‘xss’);</script>. This will end the form action part of the code and inject a javascript alert into the page. This is the very definition of cross site scripting. I can’t believe that with as long as I’ve been writing in PHP and as long as I’ve been studying application security, I’ve never realized this. Fortunately, there are a couple of different ways to fix this. First, you could use the HTML entities or HTML special character functions to sanitize the user input like this:
htmlentities($_SERVER[‘PHP_SELF]);
htmlspecialchars($_SERVER[‘PHP_SELF]);
This fix would still allow the user to manipulate the URL, and thus, what is displayed on the page, but it would render the javascript invalid. The second way to fix this is to use the script name variable instead like this:
$_SERVER[‘SCRIPT_NAME’];
This fix would just echo the full path and filename of the current file. Yes, there are other ways to fix this. Yes, my code example above for the XSS exploit doesn’t do anything other than display a javascript alert. I just wanted to draw attention to this issue because if it’s found it’s way into my code, then perhaps it’s found it’s way into yours as well. Happy coding!
February 24th, 2010 at 5:14 am
http://seancoates.com/xss-woes
February 24th, 2010 at 7:40 am
When my application vulnerability scanner found the vulnerability I started searching around in Google and that was the very first page that came up and how I figured out the fix for the issue. Big thanks to Sean for writing that and everyone who commented on it afterward. An excellent blog post.
February 24th, 2010 at 1:46 pm
Yeah, seems like there should be an almost automatic routine to scrub all that “other” user input before it even gets put into the variables. All HTTP headers, cookies… Anything else from the HTTP request you’re tempted to use like user-agent all suffer from the same problem.
February 25th, 2010 at 11:39 pm
Ernest, you just described http://php.net/filter which lets you set a default filter that is applied to all user data.
March 2nd, 2010 at 3:16 am
But… index.php” isn’t a valid URL?
March 2nd, 2010 at 3:17 am
(Incidentally, if you want a script to post back to itself, it’s easier just to leave out the action=”” part altogether in your tag).
November 11th, 2010 at 9:13 pm
this is also supposed to be open to XSS issues (and is considered “bad form”):
quote: (Incidentally, if you want a script to post back to itself, it’s easier just to leave out the action=”” part altogether in your tag).
November 12th, 2010 at 8:38 am
I am wondering is it a vulnerability when a script has a form on it that posts to itself?
November 12th, 2010 at 8:43 am
I’m not exactly sure why a script having a form that posts to itself would be considered a vulnerability in and of itself, but without proper input validation and output encoding, there’s a high likelihood that you’ll find actual vulnerabilities in that script.
~josh
November 21st, 2010 at 6:26 pm
Great post, very helpful. I have created several projects in much the same way. Thanks for the insight regarding $_SERVER[‘SCRIPT_NAME’]. There are a lot of similar posts out there, but very few of them explain or even suggest using SCRIPT_NAME instead of PHP_SELF.
April 19th, 2011 at 5:44 pm
Am I missing something?
If the user knowingly manipulates $_SERVER[‘PHP_SELF’] in the context of submitting a form then all they can do is inject Javascript into a page that only they themselves are viewing.
That must be a proud moment when you fall to an XSS attack of your own doing?
April 26th, 2011 at 3:49 pm
Ben, the idea behind reflected cross-site scripting is that the attacker crafts a malicious URL and then tries to manipulate the victim (usually via some form of social engineering) into clicking on the link. So if you take my example above using http://www.webadminblog.com/example.php“><script>alert(‘xss’);</script>, you’d see that if someone were tricked into going to that URL, they would indeed trigger the JavaScript which was embedded into the link. So the user is not falling into their own XSS as you put it, they are falling into the attackers. The real issue behind this attack, however, is that the script gets executed within the victim’s browser. This means that the attacker could steal the victim’s cookies, use their login credentials to make requests (CSRF), create fake login forms, change page content, etc. Does that make sense?
April 26th, 2011 at 8:03 pm
Not following this at all – when I tried it, the page failed to load as that url didn’t exist.
Did you mean to make it a parameter?
eg
http://www.webadminblog.com/example.php?“>alert(‘xss’);
But when I tried that, the browser encoded the brackets. Are there browsers that don’t do that?
Fortunately, I still know I haven’t made this mistake – the first thing I do is check that the path is the correct one, and 301 to the proper page if it isn’t. I do this for SEO reasons; its nice to know its helped security 🙂
April 29th, 2011 at 5:06 pm
Right, I get it now, thanks for the clarification. Very dangerous stuff.
May 11th, 2011 at 2:21 pm
Thanks for the “$_SERVER[‘SCRIPT_NAME’]” pointer – so simple – cannot believe that had not occurred to me…
May 13th, 2011 at 10:03 am
Lea, some of the newer browsers have built in some XSS defenses. The newer versions of IE, for example, will display a message in the bar at the top saying that it blocked a potential XSS attack. Also, my example is just a sample of what a URL would look like but it does not actually exist which could also be creating issues. In any case, yes there are browsers that are vulnerable to XSS and the only way to completely prevent a XSS exploit from an end-user perspective is to turn off JavaScript in your browser. Unfortunately, that breaks a good portion of other web functionality as well.
January 10th, 2012 at 3:14 pm
or just simply use <form action="" or action="."
February 6th, 2012 at 8:28 am
[…] Warum wird $_SERVER['PHP_SELF'] immer als Standard für das Affenformular angegeben? Unter http://www.webadminblog.com/index.ph…-ever-written/ wird $_SERVER['SCRIPT_NAME'] als Alternative vorgeschlagen. Für einen Anfänger nur Mit leerem […]
July 15th, 2012 at 4:18 pm
Using $_SERVER[‘SCRIPT_NAME’] is not the answer for all attacks. There are still URL’s which can inject malicious code using a special character that will get into ALL of the $_SERVER variables holding the current path to the script on the web server. The only way to truly secure the form is to either hardcode the final path, or run it through htmlentities(). Although htmlentities will neutralize the XSS code by escaping it and keeping it inside action=””, the problem still exists that a user is able to inject *something* into your page. I’m still not comfortable with that and looking for a better solution that still allows the form to detect its own path. Might have to do some manual filtering or regex…
September 9th, 2012 at 6:21 am
On top of each php page, I check the full URL for any malicious code.
$check_url is my full url.
if ((preg_match(“#]*script*\”?[^>]*>#i”, $check_url)) || (preg_match(“#]*object*\”?[^>]*>#i”, $check_url)) ||
(preg_match(“#]*iframe*\”?[^>]*>#i”, $check_url)) || (preg_match(“#]*applet*\”?[^>]*>#i”, $check_url)) ||
(preg_match(“#]*meta*\”?[^>]*>#i”, $check_url)) || (preg_match(“#]*style*\”?[^>]*>#i”, $check_url)) ||
(preg_match(“#]*form*\”?[^>]*>#i”, $check_url)) || (preg_match(“#\([^>]*\”?[^)]*\)#i”, $check_url)) ||
(preg_match(“#\”#i”, $check_url))) {die ();/*or do something else*/}
Any comments?
September 11th, 2012 at 9:59 am
Always take what I say with a grain of salt, but blacklisting is never a good approach when it comes to input validation. It can take upwards of 90 regular expressions to eliminate known malicious software and each regex needs to be run over every field. Also, adopting this strategy means that you will have to maintain the list of “known bad” characters and patterns forever. Even simple things like URL encoding could overcome your suggested method of checking for malicious code.
May 9th, 2013 at 8:27 pm
neither htmlentities or htmlspecialchars will protect you from
/”%20onmouseover=’alert(“XSS”)’
June 12th, 2014 at 8:41 am
U know I have just wasted a couple of minutes and will waste couple more by reading this small post and by writing this comment.
Because the point you have raised in your post is discussed in almost every php tutorial material. Even you can read about this in some initial chapters of php on w3schools.
I’ve learnt php from a course on lynda.com by kevin skoglund and he stated this issue quite well.
June 12th, 2014 at 5:06 pm
Hey Ghufran! Congratulations! You win the award for the most passive-aggressive comment to date! While some modern tutorials and websites may have information about this issue, when I wrote the pages that I was talking about those references did not exist. Regardless, if you don’t like my blog or don’t find it valuable, then I’d encourage you to move along to another website.
July 8th, 2014 at 2:03 pm
Indeed! Good job Ghufran on keeping a 4 year old thread alive by comparing it to modern day php resources and sharing with us your abundance of ignorance. Keep up the hard work and good luck on those pricy Lynda.com tuts mate!
November 30th, 2014 at 6:18 am
Hi
As alex comment states,
neither htmlentities or htmlspecialchars will protect you from
/”%20onmouseover=’alert(“XSS”)’
It leaves me wondering that what is the fix / solution in that case. Are we not looking for a bullet-proof solution here. I wish someone would explain with and example snippet how we may protect from this for all possible conditions – which means whether the xss is in the URL or in the mouse events. Maybe in some keyborad event too. How to comprehensively protect the code in all situations.
Thanks!
November 30th, 2014 at 11:27 am
Ajoo, this is an old post. More recently, I’ve been using the suggestion in the comments to simply use action=”” to have the page post to itself. And in my new SimpleRisk project (http://www.simplerisk.org), I’ve been using the ZendFramework’s Escaper Component (https://github.com/zendframework/Component_ZendEscaper) to do scrubbing of my variable outputs for HTML code. This is based on using HTMLEntities, but is packaged a little better.
I’m curious though, did you actually try your example? I was under the impression that htmlentities will convert both single and double quotes as long as you use the proper flag “ENT_QUOTES”. I haven’t tried it, but I believe that would render at least the example you posed harmless.
December 1st, 2014 at 3:35 am
Hi Josh, Thanks for the reply. Yes I did try out the example and while nothing happened on mouse over, upon clicking the characters changed. I tried without any sort of sanitization.
The URL: http://localhost/xampp/Security/xss1.php/%E2%80%9D%20onmouseover=%E2%80%99alert%28%E2%80%9CXSS%E2%80%9D%29%E2%80%99
however changed to :
http://localhost/xampp/Security/xss1.php/%C3%A2%E2%82%AC%C2%9D%20onmouseover=%C3%A2%E2%82%AC%E2%84%A2alert%28%C3%A2%E2%82%AC%C5%93XSS%C3%A2%E2%82%AC%C2%9D%29%C3%A2%E2%82%AC%E2%84%A2
on mouse click. And it goes on changing and lengthening on each subsequent mouse click. The alert “XSS” never appears.By the way that is not my example but one by Alex V. in one of the replies. I was curious and tried it. Did not try to use htmlentities or anything else since the alert never appeared. Also I have never used htmlentities since I read that htmlspecialchars are a much better option.
Still looking for a complete solution to this problem of sanitization.
Thanks.
December 23rd, 2015 at 5:55 am
It’s sad that most websites still have XSS vulns scattered around them. Just helped fix a content injection exploit in dod.mil ¬_¬
August 28th, 2016 at 10:47 am
[…] A XSS Vulnerability in Almost Every PHP Form I’ve Ever Written | Web Admin Blog (tags: WebDev PHP Security XSS Hacking JavaScript Bug Fix) […]