- Published on
BYUCTF 2023 – urmombotnetdotnet - 1
- Authors
- Name
- Lumy
urmombotnetdotnet - 1 (308 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 register
@app.route('/api/register', methods=['POST'])
def post_register():
# ensure needed parameters are present
if (request.json is None) or ('email' not in request.json) or ('username' not in request.json) or ('password' not in request.json) or ('bitcoin_wallet' not in request.json):
return jsonify({'message': 'Missing required parameters'}), 400
email = request.json['email']
username = request.json['username']
password = request.json['password']
bitcoin_wallet = request.json['bitcoin_wallet']
# ensure parameters are strings
if type(email) is not str or type(username) is not str or type(password) is not str or type(bitcoin_wallet) is not str:
return jsonify({'message': 'Invalid parameter data'}), 400
# ensure email is valid
if not re.fullmatch(r'\b[A-Za-z0-9._+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b', email):
return jsonify({'message': 'Invalid email'}), 400
# 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
# ensure password is valid
if len(password) < 12 or len(password) > 255:
return jsonify({'message': 'Password doesn\'t fit length requirements'}), 400
# ensure bitcoin wallet is valid
if not re.fullmatch(r'0x[0-9a-fA-F]+', bitcoin_wallet):
return jsonify({'message': 'Invalid bitcoin wallet'}), 400
# byuctf{fakeflag1}
# insert user into database
cur = mysql.connection.cursor()
cur.execute("INSERT INTO User (email, username, password, blocked, bitcoin_wallet) VALUES (%s, %s, %s, %s, %s)", (email, username, sha256(password.encode()).hexdigest(), 0, bitcoin_wallet))
mysql.connection.commit()
user_id = cur.lastrowid
cur.close()
# add user as affiliate
cur = mysql.connection.cursor()
cur.execute("INSERT INTO Affiliates (user_id, Money_received, total_bots_added) VALUES (%s, %s, %s)", (user_id, 0, 0))
mysql.connection.commit()
cur.close()
response = {"user_id": user_id}
return jsonify(response), 200
Solution
Vulnerability : the code checks if the username is not already taken, but not if the email is taken :
# ensure email is valid
if not re.fullmatch(r'\b[A-Za-z0-9._+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b', email):
return jsonify({'message': 'Invalid email'}), 400
# 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
Looking at database/initial.sql, we can see that the Email must be unique, just as username field :
CREATE TABLE User
(
User_ID SERIAL NOT NULL,
Email VARCHAR(128) NOT NULL,
Username VARCHAR(128) NOT NULL,
Password VARCHAR(128) NOT NULL,
Blocked INT NOT NULL,
Bitcoin_Wallet VARCHAR(256) NOT NULL,
PRIMARY KEY (User_ID),
UNIQUE (Email),
UNIQUE (Username)
);
Thus, attempting creating a user with the same email than another registered user will create a stacktrace.
POST /api/register HTTP/1.1
Host: 127.0.0.1:40010
...
Content-Type: application/json
{
"email":"lumy@lumy.com",
"username":"lumy",
"password":"AwesomePassword",
"bitcoin_wallet":"0xa7ABa709a"
}
POST /api/register HTTP/1.1
Host: 127.0.0.1:40010
...
Content-Type: application/json
{
"email":"lumy@lumy.com",
"username":"lumy2",
"password":"AwesomePassword",
"bitcoin_wallet":"0xa7ABa709a"
}
Within the stacktrace, we could see the flag
Flag : byuctf{did_you_stumble_upon_this_flag_by_accident_through_a_dup_email?}