Tuesday, February 5, 2019

Evlz CTF 2019 : WeTheUsers [ Basic ]

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 -
@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

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.

Previous Posts