CSAW (see-SAW) is the world’s most comprehensive student-run cybersecurity event. It serves as an engaging platform for experiential learning and aims to inspire students to pursue education and careers in the field of cybersecurity.
Forensics
1black0white
We received this file of seemingly random numbers, but the person that sent it is adamant that it is a QR code. Can you figure it out for us?
We are given a file containing 29 lines of different numbers. Each line contains 9 numbers (except for 2 lines). From the title, we know we somehow need to convert the data we have been given into binary, which will represent a QR code in someway.
I first tried to convert each number into its binary representation:
We can use this binary to QR Code converter, but the output isn’t a valid QR Code. The next idea was to treat each line as a number and do the same thing:
This gave us something that resembled a QR Code, but it wasn’t able to be decoded properly. I used QRazyBox to force decode, and it was in flag format, just not the correct letters— so on the right track, but our conversion is wrong somewhere.
I then considered the idea that maybe we just need to plot the black and white pixels ourself, and not rely on a QR Code generator. I used some ANSI coloring magic to help print our “pixels”:
This was printing something even more similar to a QR Code, but still not quite right.
We are missing pixels still. Looking back at the text file, we are given 29 lines of numbers. Each line (except for the 2 mentioned previously) when converted to binary is 29 bits. All we need to do is pad the shorter numbers to 29 bits, and we should fill in the missing pixels.
Running this script and decoding it gives us the flag:
`csawctf{1_d1dnt_kn0w_th1s_w0uld_w0rk}`
Web Exploitation
Philanthropy
Can you break into the Philanthropy website and get more information on Snake and Otacon?
Looking at the website, we have a register and login function. I started with registering an account to see what functionality we had as a user, but there wasn’t much. Next, I decided to test for SQL injection with sqlmap.
However, this didn’t yield anything promising. Next, I started to analyze the client-side code. One unique thing I found was that for every page visit, a GET request is made to /verify, which returns a JSON response of your current user
I thought maybe this hinted towards us needing to craft a session cookie to where Member is true. Looking at the cookie access_token, and using Flask Session Cookie Decoder, we can determine the website is using Flask and the cookies are JWT tokens.
Next, I used flask-unsign to try and bruteforce the Flask SECRET_KEY.
This also didn’t yield anything. Going back to client-side code review, I also noticed every page visit console.logs the response of /verify. I went to look at the Javascript to see how this was being handled, and noticed it was obfuscated. I used some online tools to deobfuscate, but that wasn’t even necessary. Just by looking at the minified and obfuscated code, we are able to pull out two unique URLs:
We have this encrypted file and the only information we got is that the key follows the pattern of 1,2,4,8,16,.... Can you figure out what the key is and decrypt this file?
We are given an encrypted file along with the code that was used to encrypt it. From this, we know AES-256 CBC was used because of to_bytes(32,"big"). Additionally, we know the IV is r4nd0m_1v_ch053n.
All we are missing the key. From the description, the key follows the geometric sequence 1,2,4,8,16,.... First intuition is the geometric sequence:
We can automate the process of decrypting by bruteforcing. The logic is as follows:
Generate a key using the geometric sequence
Decrypt the file using the generated key
Ensure the decrypted file has the PNG header
However, this fails to decrypt the file properly. Referencing back to the challenge, “circle” stood out to me. Some Googling of “1,2,4,8,16 circle” revealed Dividing a circle into areas which also follows the geometric sequence 1,2,4,8,16,.... The sequence is OEIS A000127. Visiting the page, we are fortunate to find sample Python on how to generate the sequence:
Another thing I thought about was the need to bruteforce. I felt that I shouldn’t need to bruteforce the key. Looking back at server.py, I realized they gave us the n for the sequence: 0xcafed3adb3ef1e37. Putting it all together, our final decryption script is:
When it comes to APK files, the best thing to do is first decompile it. First, we need to use dex2jar to convert the APK to a JAR file:
d2j-dex2.jar dropper.apk
Now, we can decompile the JAR file using jd-gui:
jd-gui dropper-dex2jar.jar
Looking at the structure, there are a lot of random packages. However, the one we care about is com.example.dropper.MainActivity.class. The main thing that stands out is the following snippet:
We are taking a base64 stream and writing it to a file called dropped.dex. Let’s drop this file for ourselves:
This time, we only have one class: com.example.dropped.Dropped.class:
It makes a connection to http://misc.csaw.io:3003 and reads the response. The response is then decoded using Base64 and passed to obf() which does some obfuscation with XOR. All we need to do is reverse the logic;
And we get the flag! The flag is csawctf{dyn4m1c_lo4deRs_r_fuN!}. Oops, we didn’t do any dynamic analysis ¯_(ツ)_/¯. Either way, we got the flag!
Discord Admin Bot
Join discord and get the flag.
We are given a Discord bot written in Discord.py. It’s relatively simple:
!flag
!add +
!sub -
I first joined the Discord and went over to #discord-admin-bot. A lot of people were spamming commands trying to get the flag:
However, the first thing I noticed when reading the source code was:
There is a conditional check when you run commands, and so if admin_flag does not evaluate to True, then you will never get to the pyjail() function which is where we can execute arbitrary Python code.
I had to think of a way to get ‘ADMIN_ROLE’ and spent some time researching how message contexts are passed. Then, I remembered an older CTF challenge I solved where we had to find a Discord server based on the server ID and nothing else. We are in the Discord, and we have access to the Bot’s Client ID (assuming Developer mode is enabled). With a Client ID, you can generate a bot invite link and invite the bot to your own server.
Once you have the bot in your own server, the path forward is trivial. We need to create the role ‘admin’ and assign it to ourselves. Now, when we execute !flag, !add, !sub instead of a help message, we are able to actually execute commands.
We know that !add and !sub call pyjail(). Let’s take a look:
So, take the command !add 3 + 3 for example. The arguments will be [‘3’, ’+’, ‘3’]. Then, we join the list into a space separated string: “3 + 3”. Lastly, it gets passed to pyjail() and executed in a subprocess:
Because we can control what gets passed to pyjail(), we have remote code execution. The only thing is to bypass the blacklist. Typically, you can call something like __import__('os').system('ls'), but the blacklist prevents us from using import. Luckily, HackTricks has a page for Bypass Python Sandboxes, and I learned you can pass hex encoded strings to eval() and it will still execute!
A quick Python script to convert our payload to hex: