I've been reading all day about securing my output from xss. At the moment I am using :
htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
and it is working great for preventing code when displayed in my html. I have come across another problem though and that is displaying usable urls.
The urls are user submitted and stored in the db. I am using PDO with prepared statements/binding upon insertion so I am not worried about SQL injection, but rather xss upon output and use of these urls.
Typical example is something along the lines of this :
$str = "http://www.example.com/
value as it is stored in the databasehtmlspecialchars($str, ENT_QUOTES, 'UTF-8');
to escape it (I do this by default on all values I get from the db as I display most of them in my html)'<a href="'.$str.'" data-window="external"><i class="fa fa-external-link"></i> '.$str.'</a>'
where the $str has already been escapedjson_encode(array of all the values)
All is good right? Nope. I am running some tests and found this xss is possible. Here is a test value which shows a js alert when clicked in the resulting html table :
javascript://%0Aalert('XSS');
So, aside from filtering this on database insertion (I would still like to display exactly as is in the db for display purposes)... how can I prevent the xss here? I've read plenty of stuff today and many mention urlencode, however, that is not for full urls. Others mention FILTER_VALIDATE_URL, however, it seems this does not work very well at all.
Is there no way to display as is yet prevent the url from actually working (xss working)?
EDIT:
Okay, so looking into the 'dupe' that I was given it gives the following solution :
$url = 'http://username:password@hostname/path?arg=value#anchor';
$tmp = parse_url($url);
if ($tmp['scheme'] === 'http') {
echo 'is http://';
}
if (in_array($tmp['scheme'], array('http', 'skype', 'call')) {
echo 'is allowed protocol';
}
In this case it really is not solving my issue as I preferably want to display as is, just make sure the link does not work. This is starting to seem like it is not possible though.
Regarding the above solution it seems as though the answer is to filter on db input so javascript as a protocol is never entered into the db or I display an 'error' message instead of the actual url when this protocol is triggered.
Any other ideas or possibilities?
Is there no way to display as is yet prevent the url from actually working (xss working)?
Yes, there is: do not place it within an anchor tag. If filtering URLs were as easy as passing it through a native PHP function that could magically display one type of URL in a clickable anchor tag, and another type of URL as unclickable in an anchor tag, XSS would be relegated to a footnote of PHP Web application security, if even that.
You will have to analyze every URL prior to output. This analysis will conclude with one of two possibilities:
href
attribute of an anchor tag.href
attribute of an anchor tag.If it is the latter, either leave the href
attribute empty, or use another HTML tag that will not perform some browser action when clicked; a good candidate for this would be a span
tag.
To be clear, there is no way you can do this without extra work. There is no single function that will preserve how a URL looks and also not make it clickable in an anchor.