Evlz CTF 2019: WeTheUsers [ Basic ]
- Challenge: WeTheUsers
- Category: Web (Basic)
- Functionalities provided: Register user , login interface
- Source code provided: https://pastebin.com/VWmk2Jdy
Analysis
We need to focus on following important code snippets -
Password data storage structure
@staticmethod
def _pack_data(data_dict):
"""
Pack data with data_structure.
"""
return '{}:{}:{}'.format(
data_dict['username'],
data_dict['password'],
data_dict['admin']
)
The password data is stored in “username:password:is_admin” format.
For solving this challenge we need to control third part i.e.is_admin section.
Storage of user registration data
User registration form source code -
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'GET':
return render_template('register.html')
elif request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
acl.add_record(username, password, 'false')
return redirect(url_for('index'))
If you look carefully , the 3rd parameter is marked as ‘false’
def add_record(self, username, password, admin, *args, **kwargs):
"""
Add record to ACL.
- Client Facing
"""
record = {
'username': username,
'password': password,
'admin': admin
}
self._append_record(data_dict=record)
Based on following code, the third parameter controls if user has administrative privileges or not.Therefore, newly created users will always have admin = false.
Let’s check how application validates user permissions -
def verify(self, username, password):
"""
Verify if username and password exist in ACL.
- Client Facing
"""
for line in self.acl_lines:
try:
data = self._unpack_data(line)
except:
continue
if username == data['username'] and password == data['password']:
return True, data
return False
def _unpack_data(self, buffer):
"""
Unpack the buffer and extract contents.
"""
unpacked_data = buffer.strip()
unpacked_data = unpacked_data.split(':')
record = {
'username': unpacked_data[0],
'password': unpacked_data[1],
'admin': unpacked_data[2],
}
return record
Parsing of password data
How application distinguish between username and password is defined by following code -
unpacked_data = unpacked_data.split(':')
The data in password storage is split based on ’:’ character , therefore our original data username:password:is_admin is split into -
username = Username value
password = password value
is_admin = if admin ?
The username and password values are then verified -
if username == data['username'] and password == data['password']:
return True, data
The unpack code is suffering from critical issue, the value separation is only based on delimiter character ’:’. This allows us to control how password values are parsed. To exploit this issue and gain admin access, create user with following credentials-
Username = "any username"
Password = "password:true"
then try to login using -
Username = "any username"
Password = "password"
When application try to unpack values it will treat stored value -
username:password:true as –> username:password:true
Here 3rd parameter ( admin permission ) is true now, so we have admin access.Once we login as admin, flag will be displayed.