XSS puzzler

On Friday 26th of August at 12:25 I published an XSS puzzler on twitter:
https://twitter.com/webtonull/status/769118489059725312

At 14:30 the first valid solution was delivered by @steike, and I got some valid solutions from others as well, but these were all rather long.

At 21:19 Masato Kinugawa submitted a 28 character solution which at least to my knowledge is the shortest vector possible:

\'-a\u{6c}e\u{72}t(1))%0a-->

Mario quickly followed, and as can be seen on the hall of fame, several others submitted the same or a very similar solution of 28 characters.

Dissecting the vulnerability

The content of the query parameter is reflected in two different places. It’s echoed into the input field’s value attribute, but in HTML encoded form. It’s also echoed inside a JavaScript string. There is some escaping added here. It escapes single quotes so we can’t exit the string. It escapes forward slashes so we cannot terminate the current script-tag by putting a </script> inside. However the escaping is flawed. It has the same bug as the Ruby on Rails escape_javascript function once had. It forgets to escape the escape character. Thus we can escape the string by entering:

\’

Next we start to realise that when we add JavaScript keywords or other words for that matter, the JavaScript disappears. The intention was to mimic a search function, where the analytics script would only run if the search returned some results. When the indexing pages for the search, the indexer will often drop stuff. This (fake) indexer focuses on words, and thus drops number and special characters, and also drops single characters (because a search for a letter like “e” or “a” would return a lot of results, but the results wouldn’t be very useful). So in this puzzle we can introduce single letters, but we cannot type alert or script or eval or something like that. Some of the submitted solutions was circumvented this limitation by building strings from single characters:

\'[k="c"+"o"+"n"+"s"+"t"+"r"+"u"+"c"+"t"+"o"+"r"][k]("a"+"l"+"e"+"r"+"t(1)")())

This works, but it becomes a rather long vector as + has to be encoded into %2B. The code above would take the String class’ constructor, and then take the constructor of the constructor, which is Function. It would then call Function with “alert(1)”, which would create a function. The resulting function is then called with the remaining ().

Mathias Bynens wrote two blog posts about valid identifiers in ES5 and ES6. These blog posts show us how we can use unicode escapes as a part of the names of a function we want to invoke. And indeed I got several submissions using the ES5 method:

a\u006c\u0065\u0072t(1)

Masato Kinugawa was the first one to submit with ES6:

a\u{6c}e\u{72}t(1))

This is quite clever as there are no two letters next to each other.

The last piece of the puzzle was to get rid of the trailing:

')

This can be solved by reading up on some specs:

\n
-->');

The new line has to be URL encoded though, so the final vector becomes:

\'-a\u{6c}e\u{72}t(1))%0a-->')

I’d also like to give a mention to @izanbf1803 who solved the challenge with 28 chars, and is only 15 years old.

You can also find a lot of good tricks in this presentation by Martin Kleppe: https://www.youtube.com/watch?v=-QZSJx8oXus