Advent of CTF 2024
Every day from December 1st to 25th, solve beginner-oriented gamified cybersecurity challenges. Get familiar with capture-the-flag challenges and cyber concepts from a wide range of popular categories.
Day 1: Logical Exclusivity
64 6e 63 74 7f 4c 7a 37 7b 3c 50 37 3c 36 3e 59 79 3e 59 36 3a 50 37 3a 37 43 3b 37 72
cyberchef
Day 2: Screaming
AAAAA- I’m not screaming, I’m just buffer overflowing my emotions!
open in binja, reverse engineer the xor buffer
Day 3: ElfTV
Santa’s ElfTV license key checker got leaked! Finally, a break for a broke elf like you, starving for that sweet, sweet elf dopamine. The catch? You’ve got to reverse-engineer Santa’s “state-of-the-art” security to unlock it. Think you’re smarter than the guy who still uses reindeer for transportation? Prove it and claim your ElfTV fix!!!!
We’re provided a Rust program that does some checks on a string to see if it’s a valid key. Looking at the function:
We can determine the following:
- Key must be 12 characters
- Key 0-3 =
XMAS
- Key 4-8 = ASCII sum of 610
- Key 9-11 = Last three characters of the 482nd Fibonacci sequence
I first calculated an ASCII sum of 5 characters that equals 610, and one possibility is }}}}n
. Next, I added some debugging print statements to the Rust program to figure out where the key was failing (if at all) and to also quickly determine the last 3 of fib_482
. I compiled it with rustc source.c
, passed in a fake key: XMAS}}}}n000
and can determine that it is expecting 782
as the last three. With this, we have everything we need: XMAS
, }}}}n
, and 782
. We can connect to the remote nc server and submit our key: csd{Ru57y_L1c3N53_k3Y_CH3Ck3r}
Day 6: Epochrypt
It’s time to test out Tibel Elf’s new encryption method. He says once you encrypt it, you can’t unencrypt it. Sureeee…
The following function is used to perform the encryption of a string:
Based on this, it seems relatively simple to decrypt a string. We just need to get an encrypted string, determine the (rough estimate) epoch timestamp of when it was encrypted, and perform the reverse of the encryption: XOR → Base64 decode → Shift the bytes
I first connected to the remote server to get an encrypted flag: 6b59695a5359570367607f5e6670515a7a5c7d0357707a0760637d0356065c7677650809
Then, I wrote a Python script that reverses the epochrypt
functionality and bruteforces the past five minutes of epoch timestamps. This will yield a significant amount of false positives, but luckily the program has a flag checker built-in, so we can validate without needing to waste submissions on the actual CTF platform.
Day 7: Apple Fanatic
Welcome to the bunker, agent. We’ve evaluated your performance in solving (and guessing) our formative challenges and are excited to offer you a trial position at Elves Intelligence.
It appears that a secret society has become interested in our immense data on the world’s children—including names, birthdates, likes, dislikes, and social security numbers. (Don’t ask me why we store their social security numbers in plain text.)
Here at Elves Intelligence, we stop our threats at the source. The best defense is a good offense. (Usually?)
A member of the secret society dropped a note in Santa’s presents sack overnight. Somehow, they got past our sleeping elf watching the security cameras. Thankfully, the person didn’t seem to know OPSEC and included their personal website on the note. The note read:
We will be watching you.
- The Secret Society of K.U.N.A.L
https://apple-fanatic.csd.lol/The only thing they’ve taken with them is an apple from the sack of presents. Weird.
Our top SOC elves gathered two pieces of information from their initial observation of the site:
- This person seems to like apples. Like, a lot.
- The person claims a flag is intricately hidden on the site under a name that no one will be able to guess.
Good luck, agent. Santa is watching.
You are only allowed to test in the scope
https://apple-fanatic.csd.lol/*
. Blind brute-force request sending (e.g. using tools like DirBuster) can trigger Cloudflare rate limits. Do not attempt to bypass Cloudflare limits. Therefore, if you wish to brute-force, please limit your wordlists or attack scope.
Taking a look at the website, it’s very dedicated to apples! There’s a couple interesting points, like the footer: Fun fact: I love apples so much, I am Apple's biggest supporter. You will never see me use a Windows or Linux system (ew). _Especially not for creating this site :^)_
and if you inspect element, there’s a script at /my-secret-vault-of-scripts-n-files/ai-script.js
. There is nothing too useful in ai-script.js
, but I find the my-secret-vault-of-scripts-n-files
to be interesting; however, there’s no directory listing enabled. Additionally, the fact that the scope explicitly mentions dirbusting
is interesting.
We can use a .DS_Store parser to parse the file and find all associated files in the my-secret-vault-of-scripts-n-files
directory.
csd{5H3_w45_80RN_0N_7H3_d4y_0f_Chr157M4Z}
Day 9: resa?
Elf Theodred: Hey, I’m testing out a new… You: What? You lost me at “Hey, I’m testing.” Elf Theodred: What I said was, I encrypted… and missing q. You: Resa? Vesa? Are we talking about monitors or cybersecurity? And what’s this about a missing e and q? Is that supposed to be a type of screw? Huh? Elf Theodred: I’m not repeating myself to an intern. Figure it out, bud. And if you heard a word I said, it’s under 50.
We are given three values: n
, p
, and c
. This is a simple RSA problem.
n
: Public key modulus: the result ofp*q
.p
: A large prime number.c
: The encrypted ciphertext.
To decrypt RSA, we need the private key, d
, which relies on knowing p
, q
, and e
. So, we’re missing two values. Luckily, we have n
and p
, which means we can compute q
by doing q = n // p
. Then, we just need to figure out e
, which is (usually) 65537, but in this case, they mention “under 50”, so we can bruteforce it.
csd{V3sA_R3sa_RSa?_1D3k}
Day 12: Letter to Santa
A child sent Santa a letter but he forgot to include the password can you figure it out. YOU DO NOT NEED TO BRUTE FORCE
I first wanted to see what was inside of the encrypted zip, so I ran unzip North-Pole-Writing-Machine.zip
with an invalid password. The decryption fails, but we do see a lot of interesting files:
skipping: North-Pole-Writing-Machine/.env incorrect password
skipping: North-Pole-Writing-Machine/.git/config incorrect password
skipping: North-Pole-Writing-Machine/.git/description incorrect password
skipping: North-Pole-Writing-Machine/.git/HEAD incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/applypatch-msg.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/commit-msg.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/fsmonitor-watchman.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/post-update.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/pre-applypatch.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/pre-commit.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/pre-merge-commit.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/pre-push.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/pre-rebase.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/pre-receive.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/prepare-commit-msg.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/push-to-checkout.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/hooks/update.sample incorrect password
skipping: North-Pole-Writing-Machine/.git/index incorrect password
skipping: North-Pole-Writing-Machine/.git/info/exclude incorrect password
skipping: North-Pole-Writing-Machine/.git/logs/HEAD incorrect password
skipping: North-Pole-Writing-Machine/.git/logs/refs/heads/master incorrect password
skipping: North-Pole-Writing-Machine/.git/logs/refs/remotes/origin/HEAD incorrect password
skipping: North-Pole-Writing-Machine/.git/objects/pack/pack-b83ec11042642601eca2115dfcc114f66f4c32ec.idx incorrect password
skipping: North-Pole-Writing-Machine/.git/objects/pack/pack-b83ec11042642601eca2115dfcc114f66f4c32ec.pack incorrect password
skipping: North-Pole-Writing-Machine/.git/packed-refs incorrect password
skipping: North-Pole-Writing-Machine/.git/refs/heads/master incorrect password
skipping: North-Pole-Writing-Machine/.git/refs/remotes/origin/HEAD incorrect password
skipping: North-Pole-Writing-Machine/.gitignore incorrect password
skipping: North-Pole-Writing-Machine/data/kids-data.txt incorrect password
skipping: North-Pole-Writing-Machine/letters/invoices/.keep incorrect password
skipping: North-Pole-Writing-Machine/letters/naughty/.keep incorrect password
skipping: North-Pole-Writing-Machine/letters/nice/.keep incorrect password
skipping: North-Pole-Writing-Machine/nice_letter_writer.rb incorrect password
skipping: North-Pole-Writing-Machine/README.md incorrect password
skipping: North-Pole-Writing-Machine/templates/invoice_sample_letter.txt incorrect password
skipping: North-Pole-Writing-Machine/templates/naughty_sample_letter.txt incorrect password
skipping: North-Pole-Writing-Machine/templates/nice_letter_template.txt.erb incorrect password
Looks like it’s a git repository. If we look it up on GitHub, we will find https://github.com/bitmakerlabs/North-Pole-Writing-Machine. It doesn’t look like the repository implements any zip encryption functionality itself, so another possibility would be a known-plaintext attack using pkcrack
. If we assume that a simple git clone
was done, then majority of the files should be the same.
pkcrack
requires the two zip files to have the same compression method. file North-Pole-Writing-Machine.zip
reveals “compression method=store”, so we can use zip -0
when creating our “plaintext” zip.
We are performing known-plaintext on the .git/description
file, as it should be the same. I’m operating under the assumption that they just did a git clone
and modified some files, and didn’t actually fork the repo and make modifications to the repository metadata. After a while, we get a:
We can then do unzip cracked.zip
, cd North-Pole-Writing-Machine
, and cat .env
to get our flag!
csd{Uns3cu4e_Encrypti0n}
Day 13: Disoriented Santa
Sorry that we woke you up at this hour, but Santa is missing. We suspect the K.U.N.A.L Secret Society kidnapped Santa when he was flying over Europe while scoping out for some children. Thankfully, Santa was equipped with a state-of-the-art GPS tracker circa 2008. Anyways, it gave us these clues:
- Santa is trapped in a history museum.
- The museum charges 3 EUR for entry.
- There is a library within 1 km of the museum. Can you find the coordinates of the museum? The flag is in the format
csd{latitude,longitude}
, where each number is rounded (not truncated) to 3 decimal places. Numbers within an error of ±0.001 are accepted.
I started with some basic google dorking, but couldn’t really get anywhere. This is when I took a hint and learned about overpass turbo and open street map. I was able to take the query provided and modify it a bit:
[out:json][timeout:25];
// Find all museums with charge 3 EUR
node["charge"="3 EUR"]["tourism"="museum"]->.museums;
// Find all libraries within 1 km of those museums
node["amenity"="library"](around.museums:1000)->.libraries;
// Find museums within 1 km of those libraries
node["charge"="3 EUR"]["tourism"="museum"]["museum"="history"](around.libraries:1000);
out body;
>;
out skel qt;
This first finds locations with {charge: "3 EUR", tourism: "museum"}
. From there, we filter those that are within 1 kilometer (1,000 meters) of a library. Lastly, we then filter once again by adding the specific museum=history
tag because there were a few results (roughly 10). I’m sure theres ways to optimize the query, but I had to do it this way because if I tried doing library→museum, it would timeout (I assume because of the amount of libraries in Europe?). So, I had to do museum → library → museum.
csd{48.204,7.364}
Day 15: JETS
It seems like the Secret Society of K.U.N.A.L has invested in another business…Oh no.
If those planes come anywhere close to Santa—after his “adventure” in France—he’ll be scathed for good. Those reindeer don’t like inhaling kerosene!
Agent, we need you to infiltrate their system and gather some information for our engineers at Elves Intelligence. We believe the plane they’re using is a bit special…it may have been custom-built for K.U.N.A.L himself!
Here’s their website, agent: https://jets.csd.lol/. Best of luck.
The website offers simple functionality. There is a “Sign Up” button where you enter a username and password, and that then makes a POST
request to /signup
and sets a cookie, using JWT. If we take a look at DevTools, and specifically the Debugger tab, we see a custom script.js
with some interesting functionality:
It looks like there is some client-side conditional rendering based on the sub
(username) of the JWT token. It gets the username and if it’s S1VuNEw=
(base64) or KUn4L
(ASCII) then the footer is displayed (which just shows “VIP Treatment Center”). Then, the planes for that user are rendered. However, because the JWT decoding and validation is being done client-side, this reveals the JWT secret to us. This means we can forge a JWT token using the MWRkMjJiYjQyNzBjYjE0NTcyMzIyZTAzNDI1YzAwNTgzZTAyYmY2M2Y1YzdhZjdkMmYzODdlMjRlN2Q1YjkzMQ==
secret and set sub
to KUn4L
. Using the following CyberChef recipe, we can generate a JWT token, set that as our token
cookie, and refresh the page to get the flag!
csd{Wh47_D1D_KUN4l_do_7h1S_71M3}
Day 18: trng
True random numbers can’t hurt you … they’re not real. (Or are they?)
Given a binary that gets 8 bytes from /dev/urandom
and then performs
on it 1,000,000 times. This is a simple implementation of xorshift
. find this post https://stackoverflow.com/questions/31513168/finding-inverse-operation-to-george-marsaglias-xorshift-rng which explains how to inverse the left shift. steal use the code
Don’t ask me what it does. I don’t know.
csd{M47H_15nT_7H4t_5cARY_N0w_15_I7?}
Day 22: K.U.N.A.L. Consulting
Determine vulnerable to NoSQL injection using $ne
.
Determine length of username
and password
fields:
This uses the regex ^.{N}$
where N
is an integer 0-100 and tries to identify the “Login successful!” message to extract the length. With this, we determine length of username
is seven and length of password
is 97.
Figure out username
:
And then figure out password
:
This gets us XhaNy22:reasons_i_use_a_really_long_password_1_security_2_to_practice_my_typing_skills_3_to_mess_with_you
.
Lastly, login to the /employee-login
to get the flag!