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.