# JACC

This task was part of the 'Web' category at the 2020 Hexion CTF (during 11-13
April 2020).

It was solved [The Maccabees](https://ctftime.org/team/60231) team.

## The challenge

The challenge description:

```  
Say hello to JACC - Just Another Cookie Clicker ?

http://challenges1.hexionteam.com:2002  
```

When browsing to the site, we are prompted to enter our username in order to
play:

![jacc-user-sign](jacc-user-sign.png)

After entering our username, we are prompted to a page containing a cookie-
clicker game, with our username at the bottom.

This game works as follows: every time we click the cookie, the counter at the
top of the screen increases by 1. If we hit "Save State", our current counter
is saved (so refreshing or exiting the site still leaves the counter at its
saved state).

![](jacc-cookie.png)

Before diving into the solution, some failed attempts & observations:

1\. Saving a state with arbitrary cookie count is easy (we can just
`/site?cookies=%d` with any number we want) - it doesn't help in any way.  
2\. If we try to put some cookie count which is not a number (like
`/site?cookies=hello`), the server responds with the error `Error: invalid
literal for int() with base 10: 'hello'`. This indicates some python
implementation (we know this already because the server is `gunicorn/19.7.1` -
gunicorn is a variation of Flask).

## Understanding the cookie

When saving the state of the current cookie counter, we can assume that this
information is stored with some web cookie (get the pun?). Indeed, when
inspecting the web cookies using a browser, we see that we have one cookie
named `seesions`, with its value looks something like that:

```  
.eJw1jM1OgzAAgF_F9OyhoB5GsoPNCkgsWOjP6A0oyYBSmBjpWPbuLhrP388VNNM0dO0CAvgIjBsNCK7goQYB4DgNNXaMDonI5ZcVoydbSS51rLpq3FkmCNTQnanUhG-
vn9Sfn1v5dskYosrf2YKnSHko5uNpyCG_c4MUNIe7n1AYWo5PsjVzWVihxBYuPFpcKlTJRtfXTEyl0SLD6ybsXEk8e1I0rg6Rrf2__vcPDW5j1BNmJjk0romScxprSjezlht-
qcI5JkfUVX3-_u_rJ1XlsFyloLDw0CE7mo70w8oj_f1B93twu_0ArtJd0Q.XptNxA.iZfJAils5Y3VeaC_fBWrx3opaVE  
```

Because its structure and because we know this is a Flask application, we can
assume this is the **Flask Session Cookie**. A very useful article is ["MITRE
CTF 2018 - My Flask App - CTF
Writeup"](https://terryvogelsang.tech/MITRECTF2018-my-flask-app/) - a CTF
writeup which inspects and fiddles with Flask session cookies.

As we can learn from this great article, the structure of the cookie is:

```  
.payload.timestamp.signature  
```

\- The dot suffix indicates that the payload is compressed using gzip  
\- The payload is base64 encoded and gzipped  
\- Then follows the timestamp and the signature, both base64 encoded

In addition, this article assisted us with a python script snippet that allows
us to decrypt the cookie content. When decrypting the above cookie, we get the
following JSON:

```json  
{"cookies":0,"lxml":{"
b":"UENFdExTQkJVRWtnVm1WeWMybHZiam9nTVM0d0xqQWdMUzArQ2p4eWIyOTBQZ29nSUNBZ1BHUmhkR0UrQ2lBZ0lDQWdJQ0FnUEhWelpYSnVZVzFsUGsxNVZYTmxjbTVoYldVOEwzVnpaWEp1WVcxbFBnb2dJQ0FnSUNBZ0lEeHBjMTloWkcxcGJqNHdQQzlwYzE5aFpHMXBiajRLSUNBZ0lEd3ZaR0YwWVQ0S1BDOXliMjkwUGdvPQ=="}}  
```

The `cookies` field indeed contains the number of cookie we clicked on. The
`payload["lxml"][" b"]` is a base64 payload, that if we decode it using base64
**twice**, we get the following XML:

```xml

<root>  
<data>  
<username>MyUsername</username>  
<is_admin>0</is_admin>  
</data>  
</root>  
```

Before proceeding, one important point to understand here: we can't just edit
the cookie content, because it is cryptographically-signed (with the value in
the `signature` field in the cookie). Any attempt to change the content of the
payload will fail, because the signature won't match. One way to beat it is to
somehow leak Flask's `SECRET_KEY` configuration variable, which is used to
sign the session cookies (but this is not the solution in this challenge).

## Getting admin privilege

It's easy to see the `<is_admin>0</is_admin>` field in the above XML of the
cookie. It only makes sense that this field is verified, and that if the user
is indeed an admin - we'll have access to more information / interfaces.

When trivially testing the username we enter, trying to create malformed XML,
we can see that our username is not sanitized at all before inserted to this
XML. For example, trying to play with a username of `<`, we get a XML parsing
error from the site: `Error: StartTag: invalid element name, line 4, column 20
(, line 4)`.

It is clear know that the idea should be injecting a username, that creates an
XML which, if parsed, returns 1 for the `is_admin` element. Notice that
because we are injecting in the middle of a `username` element, which is
inside `data` and `root` elements, we need to somehow ignore the existing
`</username>`,` <is_admin>0</is_admin>`, `</data>`, and `</root>`. We can just
rely on the fact that if multiple elements with the same name exists, it seems
that python's `lxml` parser will just choose the first.

So, we can insert the following username:

```  
AdminUser</username> <is_admin>1</is_admin> </data> <data>
<username>RegularUser  
```

So, the constructed XML after hitting "Play" (which will replace the above
`MyUsername` with our injected string) will look like that (spacing for
readability):

```xml

<root>  
<data>  
<username>AdminUser</username>  
<is_admin>1</is_admin>  
</data>  
<data>  
<username>RegularUser</username>  
<is_admin>0</is_admin>  
</data>  
</root>  
```

And again - because the XML parsing, when the parser parses this XML - it will
only take into consideration the first `<data>` element, thus reading the
value of `is_admin` element as 1.

Entering this user (which is now admin), we get a different page (no longer
the cookie game):

![](jacc-admin-login.png)

This is not over yet! If we just try to enter some random password, we get
`500` ( `Internal Server Error` ) error page.

In the response HTML page, there is a comment:

```html

```

After trying to user `/admin_pass` as a URI (and failing), we understood that
this is a path to a file, located on the server. So our goal now is clear: to
leak the contents of this file, in order to be able to login as an admin.

## Reading the admin password file (using XXE injection)

Now that our goal is to read a file from the remote server, we tried to
leverage our existing primitive (injecting data into the web-cookie XML) in
order to do so.

There is a known exploit technique knowns as XXE (XML External Entity)
Injection (a lot of resources are available online - I recommend you start
with the article [Exploiting XML External Entity (XXE)
Injections](https://medium.com/@onehackman/exploiting-xml-external-entity-xxe-
injections-b0e3eac388f9)).

This exploit relies on a vulnerability that allows the attacker to control an
XML file that is parsed by the server, and leverages advanced feature of XML
parsers in order to read remote files from the server, or interact with other
entities in an unexpected manner.

Example of an XML file that uses the XXE feature in order to contain a file
contents when it's parsed:

```xml

]>  
<data>&xx;;</data>  
```

When parsing this XML, the `<data>` tag will be filled with the contents of
the `/path/to/file` file on the server.

Although this seems promising - we do have control over the XML content - we
didn't find any way to leverage the `username` injection into a working XXE
injection. This because the main technique to leak file contents using XXE
Injection (as we showed here) requires that the `  
<div style="text-align: center;"><h4>Just Another</h4><h1>Cookie
Clicker</h1></div>  
<input class="good" name="username" placeholder="Username" />  
<input name="version" value="1.0.0" hidden>  
<button class="good" type="submit">Play</button>  
</form>  
```

One things that pops-up is the `version` field - it cannot be seen in the
browser because of the `hidden` attribute, but it sends the value
`version=1.0.0` on every POST request.

To refresh our memory, the original XML looks like that:

```xml

<root>  
<data>  
<username>MyUsername</username>  
<is_admin>0</is_admin>  
</data>  
</root>  
```

When sending `version=2.0.0` instead, we get the following XML:

```xml

<root>  
<data>  
<username>MyUsername</username>  
<is_admin>0</is_admin>  
</data>  
</root>  
```

Aha! The `version` field seems to be injected in the comment at the start of
the XML, and is turns out that without any sanitation as well!

So, in order to leak the contents of the `/admin_pass` file, we will send a
request with the following fields (should be URL encoded, of course):

```  
version = '1.0.0 --> ]> ]>  
<root>  
<data>  
<username>&xx;;</username>  
<is_admin>0</is_admin>  
</data>  
</root>  
```

When this XML is parsed in search for `username` (in order to display "Good
Luck" under the cookie), the XML parser will read the contents of the
`/admin_pass` file, and insert it inside the `username` tags, resulting in the
following page:

![](jacc-admin-pass.png)

This means we successfully leaked the admin password! Which is:
`gLTAqe12Z3OufWG7`.

## Finale

After getting the password and logging-in again with the
`<is_admin>1</is_admin>` cookie, entering the password shows us the following
image:

![](admin.png)

Which means the flag is `hexCTF{th3_c00ki3_m0nst3r_at3_my_c00ki3s}`. Yay!

Original writeup (https://github.com/TheMaccabees/ctf-
writeups/blob/master/HexionCTF2020/JACC/README.md).