Saturday, April 13, 2019

Byte Bandits CTF 2019 : EasyPHP [ Basic ]

Challenge Code:
<?php
$hashed_key '79abe9e217c2532193f910434453b2b9521a94c25ddc2e34f55947dea77d70ff';
$parsed parse_url($_SERVER['REQUEST_URI']);
if(isset($parsed["query"])){
    $query $parsed["query"];
    $parsed_query parse_str($query);
    if($parsed_query!=NULL){
        $action $parsed_query['action'];
    }

    if($action==="auth"){
        $key $_GET["key"];
        $hashed_input hash('sha256'$key);
        //echo $hashed_input.'\n';
        if($hashed_input!==$hashed_key){
            die("GTFO!");
        }

        echo file_get_contents("/flag");
    }
}else{
    show_source(__FILE__);
}?>

To solve this challenge  if($hashed_input!==$hashed_key) condition should be matched.

There is one interesting PHP function is used in challenge - parse_str().

As per PHP manual - https://www.php.net/manual/en/function.parse-str.php


In challenge, "result" parameter is not used with parse_str(), therefore it suffers from dynamic variable value assignment issue.

To satisfy  if($hashed_input!==$hashed_key) condition, value of $hashed_key can be overwritten with SHA256 hashed value of "key" variable.

  • key = abcd
  • hashed_input = sha256(abcd) = 88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589

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