XSS without parentheses and semi-colons

Published: 15 May 2019 at 14:54 UTC

Updated: 20 March 2020 at 07:50 UTC







A few years ago I discovered a technique to call functions in JavaScript without parentheses using onerror and the throw statement. It works by setting the onerror handler to the function you want to call and the throw statement is used to pass the argument to the function:

<script>onerror=alert;throw 1337</script>

The onerror handler is called every time a JavaScript exception is created, and the throw statement allows you to create a custom exception containing an expression which is sent to the onerror handler. Because throw is a statement, you usually need to follow the onerror assignment with a semi-colon in order to begin a new statement and not form an expression.

I encountered a site that was filtering parentheses and semi-colons, and I thought it must be possible to adapt this technique to execute a function without a semi-colon. The first way is pretty straightforward: you can use curly braces to form a block statement in which you have your onerror assignment. After the block statement you can use throw without a semi-colon (or new line):

<script>{onerror=alert}throw 1337</script>

The block statement was good but I wanted a cooler alternative. Interestingly, because the throw statement accepts an expression, you can do the onerror assignment inside the throw statement and because the last part of the expression is sent to the onerror handler the function will be called with the chosen arguments. Here's how it works:

<script>throw onerror=alert,'some string',123,'haha'</script>

If you've tried running the code you'll notice that Chrome prefixes the string sent to the exception handler with "Uncaught".

In my previous blog post I showed how it was possible to use eval as the exception handler and evaluate strings. To recap you can prefix your string with an = which then makes the 'Uncaught' string a variable and executes arbitrary JavaScript. For example:

<script>{onerror=eval}throw'=alert\x281337\x29'</script>

The string sent to eval is " Uncaught=alert(1337) ". This works fine on Chrome but on Firefox the exception gets prefixed with a two word string "uncaught exception" which of course causes a syntax error when evaluated. I started to look for ways around this.

It's worth noting that the onerror/throw trick won't work when executing a throw from the console. This is because when the throw statement is executed in the console the result is sent to the console and not the exception handler.

When you use the Error function in Firefox to create an exception it does not contain the "uncaught exception" prefix. But instead, just the string "Error":

throw new Error("My message")//Error: My message

I obviously couldn't call the Error function because it requires parentheses but I thought maybe if I use an object literal with the Error prototype that would emulate the behaviour. This didn't work - Firefox still prefixed it with the same string. I then used the Hackability Inspector to inspect the Error object to see what properties it had. I added all the properties to the object literal and it worked! One by one I removed a property to find the minimal set of properties required:

<script>{onerror=eval}throw{lineNumber:1,columnNumber:1,fileName:1,message:'alert\x281\x29'}</script>

You can use the fileName property to send a second argument on Firefox too:

<script>{onerror=prompt}throw{lineNumber:1,columnNumber:1,fileName:'second argument',message:'first argument'}</script>

After I posted this stuff on Twitter @terjanq and @cgvwzq (Pepe Vila) followed up with some cool vectors. Here @terjanq removes all string literals:

<script>throw/a/,Uncaught=1,g=alert,a=URL+0,onerror=eval,/1/g+a[12]+[1337]+a[13]</script>

Pepe removed the need of the throw statement completely by using type errors to send a string to the exception handler.

<script>TypeError.prototype.name ='=/',0[onerror=eval]['/-alert(1)//']</script>





Visit our Web Security Academy to learn more about cross-site scripting (XSS)