Evlz CTF 2019
Challenge: WeTheUsers
Category: Web (Basic)
Functionalities provided: Register user , login interface
Source code provided: Yes [https://pastebin.com/VWmk2Jdy]
Analysis:
We need to focus on following important code snippets -
1. 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.
2. Storage of user registration data
User registration form source code -
If you look carefully , the 3rd parameter is marked as 'false'@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'))
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
3. 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>:<password:true> and then try to login using <username>:<password> value.
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.