Published on

BYUCTF 2023 – urmombotnetdotnet - 4

Authors
  • avatar
    Name
    Lumy
    Twitter

urmombotnetdotnet - 4 (483 points)

During my databases class, my group and I decided we'd create a web app with the domain urmombotnetdotnet.com, and wrote the relevant code. At first glance, it looks pretty good! I'd say we were pretty thorough. But were we thorough enough??

Oh... we also forgot to make the front end :)

Table of Contents

  1. Source code
  2. Solution

Source code

# POST login
@app.route('/api/login', methods=['POST'])
def post_login():
    # ensure needed parameters are present
    if (request.json is None) or ('username' not in request.json) or ('password' not in request.json):
        return jsonify({'message': 'Missing required parameters'}), 400

    username = request.json['username']
    password = request.json['password']

    # ensure parameters are strings
    if type(username) is not str or type(password) is not str:
        return jsonify({'message': 'Invalid parameter data'}), 400

    # ensure password is valid
    if len(password) < 12 or len(password) > 255:
        return jsonify({'message': 'Password doesn\'t fit length requirements'}), 400

    # check if username exists
    cur = mysql.connection.cursor()
    cur.execute("SELECT user_id,password,blocked FROM User WHERE username=%s", (username,))
    users_found = cur.rowcount
    response = cur.fetchone()
    cur.close()
    exists = (users_found > 0)

    if not exists:
        return jsonify({'message': 'Invalid username or password'}), 401


    user_id = response[0]
    hash = response[1]
    blocked = response[2]

    # check if user is staff
    cur = mysql.connection.cursor()
    cur.execute("SELECT * FROM Support_Staff WHERE user_id=%s", (user_id,))
    staff_found = cur.rowcount
    cur.close()
    is_staff = (staff_found > 0)

    # check if password is correct
    if sha256(password.encode()).hexdigest() != hash:
        return jsonify({'message': 'Invalid username or password'}), 401

    # check if user is blocked
    if blocked:
        return jsonify({'message': 'User is blocked'}), 401

    # generate JWT
    token = jwt.encode({'user_id': user_id, "is_staff": is_staff}, app.config['SECRET_KEY'], algorithm='HS256')

    resp = make_response(jsonify({'message': 'Successfully logged in', 'flag':('byuctf{fakeflag4}' if len(username) < 4 else 'Nope')}), 200)
    resp.set_cookie('token', token, httponly=True, samesite='Strict', max_age=None)

    return resp

Solution

As we can see, the flag will be returned if our username length is less than 4 characters. So let’s take a look again at the register part of the code.

# ensure username is valid
    if len(username) < 4 or len(username) > 255:
        return jsonify({'message': 'Invalid username length'}), 400

    # ensure username isn't already taken
    cur = mysql.connection.cursor()
    cur.execute("SELECT username FROM User WHERE username=%s", (username,))
    users_found = cur.rowcount
    cur.close()
    username_taken = (users_found > 0)

    if username_taken:
        return jsonify({'message': 'Username already taken'}), 500

As we can see, this time, it look for the lenght limit of the username, we cant make an username of less than 4 characters and maximum 255 characters.

So how can we login as a username of less than 4 characters?

By using Unicode Null characters, we can register an account of less than 4 characters because the Null characters will be ignored.

This mean that \u0000t\u0000i\u0000m will be interpreted as tim.

POST /api/register HTTP/1.1
Host: 127.0.0.1:40010
...
Content-Type: application/json

{
    "email":"lumy@lumy.com",
    "username":"\u0000t\u0000i\u0000m",
    "password":"AwesomePassword",
    "bitcoin_wallet":"0xa7ABa709a"
}

Flag : byuctf{I_used_unicode_to_make_a_username_under_4_chars_wbu?}