XSS passable through htmlentities()
18 May 2008
I used to think that using htmlentites() with ENT_QUOTES is a pretty good "universal" protection against cross site scripting. I used it in all of my projects, and basically relied on it completely. Whenever I got any user input, I passed it through this filter and felt completely safe to use the returned value anywhere in the HTML output. Apparently, this was nothing but a false sense of security.
I can see why I missed it, and that's because the "vulnerability" is pretty obscure, none the less it's there - and I bet many sites can be affected.
Take a look at this code:
<?php $filtered = htmlentities($_GET["arg"], ENT_QUOTES); echo $filtered; ?>
This is completely fine, and it will always work, providing good security. The problem arises when you try to do something a little more "advanced" with the filtered result. For example:
<?php
echo "<body onload=\"alert('$filtered')\">Hmm</body>";
?>
If you can see what's the problem with this code right away, than good for you. I didn't, and it took me a little by surprise to see that my filter has completely no effect here.
Just try this input:
?arg=lol'); alert('xss
With Magic Quotes off (and it's already off by default since PHP 6), this results in XSS.
What really happens here, is that HTML entities inside URLs, Event handlers (onload for example), or any other HTML tag properties- are being "read" by browsers as the values they represent. So ' becomes ', and that's what the script interpreter sees. This effectively cancels out the effect of my filtering script.
The source of the XSS'd HTML will look like this:
<body onload="alert('lol'); alert('xss')">Hmm</body>
On first glance you may think that this shouldn't work, but it does. Actually, HTML entities are meant to do that. There is no browser bug or something, only a matter of this feature being slightly hard to grasp.
Also, you can argue that the code I've shown above would be rarely used, and you are right. Yet, look at this code:
<a href="javascript:comments(18)">Comments</a>
This code is taken from this very page. The only thing that saves me from XSS here, is that the ID of the comment is an integer (I cast it that way) and that I don't show the "Comments" link in case the post with the specified ID doesn't exist (I don't show anything, actually).
I bet many web applications use something like this. I can also bet that not many developers are aware of the issue, and think (as I did) that their "universal" HTML filters are truly universal.
Posted by: kGen | In category: XSS | Comments (0)