Iâm going to give away a secret that Iâve successfully used at every interview Iâve had for a security position.
âCross-Site Scriptingâ (XSS) is a remarkably poor term for the attack or vulnerability (code can be particularly vulnerable to a cross-site scripting attack) it describes. The correct term should be âHTML Injectionâ, because it succinctly explains the source of the problem, and should provide developers with pretty much all the information they need to recognise, avoid, prevent, and address the vulnerabilities that lead to these attacks being possible.
[Note that I am not suggesting that prior work into XSS protections is bad, just that it is not complete if it focuses on JavaScript / ECMAScript / whatever you want to call it.]
Failure to understand XSS has led to people assuming that they can protect themselves by simple measures â disabling JavaScript, filtering â<script>â tags, âjavascript:â URLs, and so on. Such approaches have uniformly failed to work, in much the same way as other âblack-listâ methods of defeating attacks on security flaws â it pretty much dares the attacker to find a way to exploit the bug using something else.
Particularly galling is when I look at code whose developers had heard about XSS, had looked about for solutions, and had found a half-baked solution that made them feel better about their code, made the bug report go from âreproducibleâ to âcannot reproduceâ, but left them open to an attacker with a little more ingenuity (or simply a more exhaustive source of sample attacks) than they had. It seems that developers often try, but are limited by the resources they find on the Intarweb â particularly blogs seem to provide poor solutions (ironic, I know, that I am complaining about this in my blog).
Alright then, hereâs something that appears to be new to many â a demonstration of Cross-Site Scripting without scripting.
We all know how XSS happens, right? A programmer wants to let the user put some information on the page. Letâs say he wants to warn the user that his password was entered incorrectly, or his logon session has expired. So, he needs to ask the user to enter username and password again, but probably wants to save time by putting the userâs name in place for the user, to save on typing.
Hereâs what the form looks like â youâve all seen it before:
The code for this form is simple, mostly to make the example easy. Iâll write it in Perl and Javascript â because the Perl version is exploitable everywhere, and because the Javascript version demonstrates how DOM-based XSS attacks work (and how browser strangeness can cause them to be flakey).
[Note: A DOM-based attack, for those that donât know, is an attack that uses Javascript to modify the HTML page while it is at the browserâs site. These are difficult to detect, but generally result from the use of unsafe functionality such as the innerHTML call.]
Needles to say â donât use these as examples of good code â these are EXAMPLES of VULNERABLE CODE. And lousy code at that.
Perl:
use CGI;
$query = new CGI;
print $query->h1("Session timed out."), $query->p("You have been logged out from the site - please enter your username and password to log back in."), $query->start_form(-action=>"happyPage.htm", -method=>"get"), "Username: <input name=\"username\" value=\"" + $query->param("username") + "\" />",$query->br(),
"Password: ", $query->input(-name=>"password",-type=>"password"),$query->br(), $query->submit(),
$query->end_form();
Javascript:
<h1>Session timed out.</h1> <p >You have been logged out from the site - please enter your username and password to log back in.</p> <form id="safeForm" action="happyPage.htm" method="get"> Username: <input name="username" value="name placeholder" /><br /> Password: <input name="password" type="password" /><br /> <input type="submit" /> </form> <script type="text/javascript"> var queries; function getQueryString(key) { if (queries == undefined) { queries = {}; var s = window.location.href.replace(/[^?]+\?/, "").split("&"); for (var x in s) { var v = s[x].split("="); if (v.length == 2) queries[v[0]] = decodeURIComponent(v[1].replace(/\+/g, " ")); } } if (key in queries) return queries[key]; return ""; } var myField = document.getElementById("safeForm"); if (myField != null) { var un = getQueryString("username"); // Build the form with the username passed in. myField.innerHTML = myField.innerHTML.replace(/name placeholder/,un); } </script>
Now, why did I specifically include the âgetQueryStringâ in my Javascript version above? I could have simply said âassume this function exists with ordinary behaviourâ. Well, I chose that one (downloaded from, naturally, a blog advising how to do this properly) because it processes the entire href, anchor and all.
If we modify our URL by adding â#&â between the query and the variable âusernameâ, it demonstrates one of the more frightening aspects of DOM-based attacks. Those of you who are aware of what an anchor does to a browser will already have figured it out, but hereâs a quick explanation.
The âanchorâ is considered to be everything after the â#â in a URL. Although it looks like itâs part of the query string, itâs not. Browsers donât send the â#â or anything after it to the server when requesting a web page, so itâs not seen in a network trace, and itâs not seen in the server logs. This means that DOM-based attacks can hide all manner of nastiness in the anchor, and your scanners wonât pick it up at all.
So, this page would normally get executed with a parameter, âusernameâ which would be the username whose account weâre asking for credentials for â and certainly it works with http://localhost/XSSFile.htm?username=Fred@example.com :
The trouble is, it also works with the XSS attackersâ favourite test example, alert("XSS")%3b’>http://localhost/XSSFile?username="><script>alert("XSS")%3b</script> :
Now, Iâve seen developers who are given this demonstration that their page is subject to an XSS attack. What do they do? They block the attack. Note that this is not the same as removing or fixing the vulnerability. What these guys will do is block angle brackets, or the tag â<script>â. As a security guy, this makes me sigh with frustration, because we try to drill it into peopleâs heads, over and over and over again, that blacklisting just doesnât work, and that âmaking the repro go awayâ is not equivalent to âfixing the problem demonstrated by the reproâ.
The classic attackerâs response to this is to go to http://ha.ckers.org/xss.html for the XSS Cheat Sheet, and pull something interesting from there. Maybe use the list of events, say, to decide that you could set the âonfocusâ handler to execute your interesting code.
But no, letâs suppose by some miracle of engineering and voodoo the defender has managed to block all incoming scripts. Even so, weâre still vulnerable to XSS.
What happens if we try this link:
[The â%3dâ there is a hex value representing the â=â character so that the query-string parser doesnât split our attack.]
OK, thatâs kind of ugly â but it demonstrates that you can use an XSS vulnerability to inject any HTML â including a new <form> tag, with a different destination â âbadpageâ in our URL above, but it could be anywhere. And by hiding the attacked input field, we can engineer the user into thinking itâs just a display issue
With some piggery-jokery, we can get to this:
Looks much better (and with more work, we could get it looking just right):
So, there you have a demonstration of scriptless cross-site scripting. XSS, or HTML Injection, as Iâd prefer you think of it, can inject any tag(s) into your page â can essentially rewrite your page without your knowledge. If you want to explain the magnitude of XSS, it is simply this â an attacker has found a way to rewrite your web page, as if he was employed by you.
[Of course, if I hadnât been trying to demonstrate that XSS is a misnomer, and prove that you can shove any old HTML into it, I would simply have used a piece of script, probably on the onmouseover event, to set the action of the form to post to my bad site. Fewer characters. Doing so is left as an exercise for the reader.]
It doesnât work in Internet Explorer, but in other browsers, it seems to work just fine. At first look, this would seem to suggest that âinnerHTMLâ on a <form> tag is allowing the â</form">â in the XSS to escape out from the parent form. I can assure you thatâs not the case, because if you could escape out, that would be a security flaw in the browsersâ implementation of innerHTML. So, whatâs it doing, and how do you find out?
10 Responses to Cross-Site Scripting (XSS) â no script required