XSS: A Case Of Broken Encoding

Recently, I hit an interesting cross-site scripting (XSS) bug while fuzzing an ASP.NET web application. What got my attention was the distinct encoding applied to the attacker controlled data. Specifically, a non-standard implementation of percent encoding was applied to the untrusted data, which was then reflected in the HTTP response. For some reason, the the implementation encoded many unreserved characters, but failed to encode the reserved characters ', (, and ).

The untrusted data was emitted into a JavaScript string literal, so the relevant output looked something like this:

var x = 'untrusted%20data';

Normally, one might use a string terminator (') and slashes (/) to inject something like foo';alert(0);//.

var x = 'foo';alert(0);//';

Unfortunately, / and ; were percent encoded, so that wasn't possible.

var x = 'foo'%3B%2F%2F';

Similarly, < and > were covered by the encoding, so the script tag couldn't be closed.

var x = 'foo%3C%2Fscript%3E';

The Bypass

Luckily, JavaScript's syntax and type system are quite flexible. If a string literal can be terminated by untrusted data, code injection is probably possible one way or another. In my case, I simply had to limit my injection to the extant JavaScript statement.

With the ability to use the subtraction operator (-), I decided to exploit JavaScript's additive expression syntax and semantics. Pretty much any type of JavaScript expression can appear as an operand in an additive expression, and type coercion does its best to make it work.

Injecting a string terminator followed by a subtraction operator gave me free reign over the right hand side of the additive expression. Suffixing that with a substraction operator then a string terminator made the expression itself a left operand to another additive expression.

var x = ''-alert(0)-'';

Taking things a bit further, I leveraged eval and decodeURIComponent to use symbols that were otherwise filtered out.

var x = ''-eval(decodeURIComponent('alert(%2ftest%2f)%3b'))-'';

When tested on the most recent version of Chrome and Firefox, the built-in anti-XSS mitigations are successfully bypassed and the payload executed. Internet Explorer's filter managed to detect and mitigate the attack.


The dynamic nature of JavaScript serves as a potent tool to attackers looking evade filters and encoding. Any time untrusted data causes syntax errors in JavaScript, it should be assumed that code injection is possible. Ideally, untrusted data should never be emitted into JavaScript.

Copyright © 2018 AutoSec Tools LLC