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.