Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ venv/
env/
__pycache__/
.DS_Store
.idea/
.venv/
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,27 @@ The following plugins are currently supported:


Example Use:
```

```shell
python3 credmaster.py --plugin {pluginname} --access_key {key} --secret_access_key {key} -u userfile -p passwordfile -a useragentfile {otherargs}
```

or

```
```shell
python3 credmaster.py --config config.json
```

This tool requires AWS API access keys, a walkthrough on how to acquire these keys can be found here: https://bond-o.medium.com/aws-pass-through-proxy-84f1f7fa4b4b
This tool primarily requires AWS API access keys, a walkthrough on how to acquire these keys can be found here: https://bond-o.medium.com/aws-pass-through-proxy-84f1f7fa4b4b

However: if you want to use a custom proxy instead of AWS IP gateways, you can use the `--proxy` and `--proxy-auth` arguments to specify a proxy to use. If your proxy does not require auth, then only use the `--proxy` argument.
ex:

```shell
python3 credmaster.py --plugin {pluginname} --proxy 'foo.bar.baz:12345' --proxy-auth 'proxyuser:proxypass' -u userfile -p passwordfile -a useragentfile {otherargs}
```

currently, `--proxy` option only supports HTTP proxies and doesn't yet support multiple proxies, but this would be relatively easy to add support for.

All other usage details can be found [on the wiki](https://github.com/knavesec/CredMaster/wiki/Usage)

Expand Down
106 changes: 74 additions & 32 deletions credmaster.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from utils.fire import FireProx
import utils.utils as utils
import utils.notify as notify
from urllib.parse import quote


class CredMaster(object):
Expand Down Expand Up @@ -93,6 +94,10 @@ def parse_all_args(self, args):
self.jitter_min = args.jitter_min or config_dict.get("jitter_min")
self.delay = args.delay or config_dict.get("delay")

# Proxy settings
self.proxy = args.proxy or config_dict.get("proxy")
self.proxy_auth = args.proxy_auth or config_dict.get("proxy_auth")

self.batch_size = args.batch_size or config_dict.get("batch_size")
self.batch_delay = args.batch_delay or config_dict.get("batch_delay")
if self.batch_size != None and self.batch_delay == None:
Expand Down Expand Up @@ -155,25 +160,29 @@ def do_input_error_handling(self):
self.log_entry(f"Useragent file {self.useragentfile} cannot be found")
sys.exit()

# AWS Key Handling
if self.session_token is not None and (self.secret_access_key is None or self.access_key is None):
self.log_entry("Session token requires access_key and secret_access_key")
sys.exit()
if self.profile_name is not None and (self.access_key is not None or self.secret_access_key is not None):
self.log_entry("Cannot use a passed profile and keys")
sys.exit()
if self.access_key is not None and self.secret_access_key is None:
self.log_entry("access_key requires secret_access_key")
sys.exit()
if self.access_key is None and self.secret_access_key is not None:
self.log_entry("secret_access_key requires access_key")
sys.exit()
if self.access_key is None and self.secret_access_key is None and self.session_token is None and self.profile_name is None:
self.log_entry("No FireProx access arguments settings configured, add access keys/session token or fill out config file")
sys.exit()

# Region handling
if self.region is not None and self.region not in self.regions:
# Determine if AWS/FireProx creds are required (utilities or no proxy provided)
require_aws = self.clean or self.api_list or (self.api_destroy is not None) or (self.proxy is None)

# AWS Key Handling (only when required)
if require_aws:
if self.session_token is not None and (self.secret_access_key is None or self.access_key is None):
self.log_entry("Session token requires access_key and secret_access_key")
sys.exit()
if self.profile_name is not None and (self.access_key is not None or self.secret_access_key is not None):
self.log_entry("Cannot use a passed profile and keys")
sys.exit()
if self.access_key is not None and self.secret_access_key is None:
self.log_entry("access_key requires secret_access_key")
sys.exit()
if self.access_key is None and self.secret_access_key is not None:
self.log_entry("secret_access_key requires access_key")
sys.exit()
if self.access_key is None and self.secret_access_key is None and self.session_token is None and self.profile_name is None:
self.log_entry("No FireProx access arguments settings configured, add access keys/session token or fill out config file")
sys.exit()

# Region handling (only matters for FireProx mode)
if require_aws and self.region is not None and self.region not in self.regions:
self.log_entry(f"Input region {self.region} not a supported AWS region, {self.regions}")
sys.exit()

Expand Down Expand Up @@ -230,7 +239,7 @@ def Execute(self, args):
##
pluginargs['thread_count'] = self.thread_count

self.start_time = datetime.datetime.utcnow()
self.start_time = datetime.datetime.now(datetime.UTC)
self.log_entry(f"Execution started at: {self.start_time}")

# Check with plugin to make sure it has the data that it needs
Expand Down Expand Up @@ -268,6 +277,22 @@ def Execute(self, args):
self.log_entry(f"Setting static X-Forwarded-For header to: \"{self.xforwardedfor}\"")
pluginargs["xforwardedfor"] = self.xforwardedfor

# Build requests proxies if external proxy provided
if self.proxy is not None:
proxy_input = self.proxy
if not proxy_input.startswith("http://") and not proxy_input.startswith("https://"):
proxy_input = "http://" + proxy_input
if self.proxy_auth is not None:
user, pwd = self.proxy_auth.split(":", 1)
user_e = quote(user, safe='')
pwd_e = quote(pwd, safe='')
scheme, rest = proxy_input.split("://", 1)
proxy_url = f"{scheme}://{user_e}:{pwd_e}@{rest}"
else:
proxy_url = proxy_input
pluginargs["proxies"] = {"http": proxy_url, "https": proxy_url}
self.log_entry(f"External proxy configured: {proxy_input} (auth={'yes' if self.proxy_auth else 'no'})")

# this is the original URL, NOT the fireproxy one. Don't use this in your sprays!
url = pluginargs["url"]

Expand Down Expand Up @@ -342,19 +367,19 @@ def Execute(self, args):

if self.delay is None or len(passwords) == 1 or password == passwords[len(passwords)-1]:
if self.userpassfile != None:
self.log_entry(f"Completed spray with user-pass file {self.userpassfile} at {datetime.datetime.utcnow()}")
self.log_entry(f"Completed spray with user-pass file {self.userpassfile} at {datetime.datetime.now(datetime.UTC)}")
elif self.userenum:
self.log_entry(f"Completed userenum at {datetime.datetime.utcnow()}")
self.log_entry(f"Completed userenum at {datetime.datetime.now(datetime.UTC)}")
else:
self.log_entry(f"Completed spray with password {password} at {datetime.datetime.utcnow()}")
self.log_entry(f"Completed spray with password {password} at {datetime.datetime.now(datetime.UTC)}")

notify.notify_update(f"Info: Spray complete.", self.notify_obj)
continue
elif count != self.passwordsperdelay:
self.log_entry(f"Completed spray with password {password} at {datetime.datetime.utcnow()}, moving on to next password...")
self.log_entry(f"Completed spray with password {password} at {datetime.datetime.now(datetime.UTC)}, moving on to next password...")
continue
else:
self.log_entry(f"Completed spray with password {password} at {datetime.datetime.utcnow()}, sleeping for {self.delay} minutes before next password spray")
self.log_entry(f"Completed spray with password {password} at {datetime.datetime.now(datetime.UTC)}, sleeping for {self.delay} minutes before next password spray")
self.log_entry(f"Valid credentials discovered: {len(self.results)}")
for success in self.results:
self.log_entry(f"Valid: {success['username']}:{success['password']}")
Expand All @@ -376,7 +401,7 @@ def Execute(self, args):
self.log_entry("Second KeyboardInterrupt detected, unable to clean up APIs :( try the --clean option")

# Capture duration
self.end_time = datetime.datetime.utcnow()
self.end_time = datetime.datetime.now(datetime.UTC)
self.time_lapse = (self.end_time-self.start_time).total_seconds()

# Print stats
Expand All @@ -389,10 +414,19 @@ def load_apis(self, url, region=None):
self.log_entry("Thread count over maximum, reducing to 15")
self.thread_count = len(self.regions)

if self.proxy is not None:
self.log_entry(f"External proxy mode enabled; creating {self.thread_count} logical workers for {url}")
self.apis = []
for x in range(0, self.thread_count):
worker_region = f"proxy-{x+1}"
self.apis.append({"api_gateway_id": None, "proxy_url": url.strip(), "region": worker_region})
self.log_entry(f"Worker created - {worker_region} - {url}")
return

self.log_entry(f"Creating {self.thread_count} API Gateways for {url}")

self.apis = []

# slow but multithreading this causes errors in boto3 for some reason :(
for x in range(0,self.thread_count):
reg = self.regions[x]
Expand Down Expand Up @@ -474,9 +508,13 @@ def destroy_single_api(self, api):


def destroy_apis(self):


# In external proxy mode, nothing to destroy
if self.proxy is not None:
self.log_entry("External proxy mode: no AWS APIs to destroy")
return

for api in self.apis:

args, help_str = self.get_fireprox_args("delete", api["region"], api_id = api["api_gateway_id"])
fp = FireProx(args, help_str)
self.log_entry(f"Destroying API ({args['api_id']}) in region {api['region']}")
Expand Down Expand Up @@ -629,7 +667,7 @@ def ww_calc_next_spray_delay(self, offset):

spray_times = [8,12,14] # launch sprays at 7AM, 11AM and 3PM

now = datetime.datetime.utcnow() + datetime.timedelta(hours=offset)
now = datetime.datetime.now(datetime.UTC) + datetime.timedelta(hours=offset)
hour_cur = int(now.strftime("%H"))
minutes_cur = int(now.strftime("%M"))
day_cur = int(now.weekday())
Expand Down Expand Up @@ -675,7 +713,7 @@ def log_entry(self, entry):

self.lock.acquire()

ts = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
ts = datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
print(f"[{ts}] {entry}")

if self.outfile is not None:
Expand Down Expand Up @@ -739,6 +777,10 @@ def log_success(self, username, password):
adv_args.add_argument('--weekday_warrior', default=None, required=False, help="If you don't know what this is don't use it, input is timezone UTC offset")
adv_args.add_argument('--color', default=False, action="store_true", required=False, help="Output spray results in Green/Yellow/Red colors")
adv_args.add_argument('--trim', '--remove', action="store_true", help="Remove users with found credentials from future sprays")

proxy_args = parser.add_argument_group(title='External Proxy Options')
proxy_args.add_argument('--proxy', type=str, default=None, help="External proxy endpoint 'host:port' or URL. If set, FireProx AWS creds are not required for spraying.")
proxy_args.add_argument('--proxy-auth', type=str, default=None, help="External proxy basic auth in 'user:pass' format. Optional.")

notify_args = parser.add_argument_group(title='Notification Inputs')
notify_args.add_argument('--slack_webhook', type=str, default=None, help='Webhook link for Slack notifications')
Expand Down
3 changes: 2 additions & 1 deletion plugins/adfs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ def testconnect(pluginargs, args, api_dict, useragent):

headers = utils.add_custom_headers(pluginargs, headers)

resp = requests.get(api_dict["proxy_url"], headers=headers)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.get(api_dict["proxy_url"], headers=headers, proxies=proxies)

if resp.status_code == 504:
output = "Testconnect: Connection failed, endpoint timed out, exiting"
Expand Down
4 changes: 2 additions & 2 deletions plugins/adfs/adfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ def adfs_authenticate(url, username, password, useragent, pluginargs):
headers = utils.add_custom_headers(pluginargs, headers)

try:

resp = requests.post("{}/adfs/ls/".format(url), headers=headers, params=params_data, data=post_data, allow_redirects=False)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.post("{}/adfs/ls/".format(url), headers=headers, params=params_data, data=post_data, allow_redirects=False, proxies=proxies)

if resp.status_code == 302:
data_response['result'] = "success"
Expand Down
3 changes: 2 additions & 1 deletion plugins/azuresso/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ def testconnect(pluginargs, args, api_dict, useragent):

headers = utils.add_custom_headers(pluginargs, headers)

resp = requests.get(api_dict['proxy_url'], headers=headers)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.get(api_dict['proxy_url'], headers=headers, proxies=proxies)

if resp.status_code == 504:
output = "Testconnect: Connection failed, endpoint timed out, exiting"
Expand Down
3 changes: 2 additions & 1 deletion plugins/azuresso/azuresso.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ def azuresso_authenticate(url, username, password, useragent, pluginargs):
headers = utils.add_custom_headers(pluginargs, headers)

try:
r = requests.post(f"{url}/{pluginargs['domain']}/winauth/trust/2005/usernamemixed?client-request-id={requestid}", data=tempdata, headers=headers, verify=False, timeout=30)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
r = requests.post(f"{url}/{pluginargs['domain']}/winauth/trust/2005/usernamemixed?client-request-id={requestid}", data=tempdata, headers=headers, proxies=proxies, verify=False, timeout=30)

xmlresponse = str(r.content)
creds = username + ":" + password
Expand Down
3 changes: 2 additions & 1 deletion plugins/azvault/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ def testconnect(pluginargs, args, api_dict, useragent):

headers = utils.add_custom_headers(pluginargs, headers)

resp = requests.get(api_dict['proxy_url'], headers=headers)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.get(api_dict['proxy_url'], headers=headers, proxies=proxies)

if resp.status_code == 504:
output = "Testconnect: Connection failed, endpoint timed out, exiting"
Expand Down
3 changes: 2 additions & 1 deletion plugins/azvault/azvault.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ def azvault_authenticate(url, username, password, useragent, pluginargs):
headers = utils.add_custom_headers(pluginargs, headers)

try:
resp = requests.post(f"{url}/common/oauth2/token", headers=headers, data=body)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.post(f"{url}/common/oauth2/token", headers=headers, data=body, proxies=proxies)

if resp.status_code == 200:
data_response['result'] = "success"
Expand Down
3 changes: 2 additions & 1 deletion plugins/ews/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ def testconnect(pluginargs, args, api_dict, useragent):

headers = utils.add_custom_headers(pluginargs, headers)

resp = requests.get(url, headers=headers, verify=False)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.get(url, headers=headers, verify=False, proxies=proxies)

if resp.status_code == 504:
output = "Testconnect: Connection failed, endpoint timed out, exiting"
Expand Down
4 changes: 2 additions & 2 deletions plugins/ews/ews.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def ews_authenticate(url, username, password, useragent, pluginargs):
headers = utils.add_custom_headers(pluginargs, headers)

try:

resp = requests.post(f"{url}/ews/", headers=headers, auth=HttpNtlmAuth(username, password), verify=False)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.post(f"{url}/ews/", headers=headers, auth=HttpNtlmAuth(username, password), verify=False, proxies=proxies)

if resp.status_code == 500:
data_response['output'] = f"[*] POTENTIAL: Found credentials, but server returned 500: {username}:{password}"
Expand Down
3 changes: 2 additions & 1 deletion plugins/fortinetvpn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def testconnect(pluginargs, args, api_dict, useragent):

headers = utils.add_custom_headers(pluginargs, headers)

resp = requests.get(api_dict['proxy_url'] + "/remote/login?lang=en", headers=headers)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.get(api_dict['proxy_url'] + "/remote/login?lang=en", headers=headers, proxies=proxies)

if resp.status_code == 504:
output = "Testconnect: Connection failed, endpoint timed out, exiting"
Expand Down
4 changes: 2 additions & 2 deletions plugins/fortinetvpn/fortinetvpn.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ def fortinetvpn_authenticate(url, username, password, useragent, pluginargs):
post_params['realm'] = pluginargs['domain']

try:

resp = requests.post("{}/remote/logincheck".format(url),data=post_params,headers=headers)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.post("{}/remote/logincheck".format(url),data=post_params,headers=headers, proxies=proxies)

if resp.status_code == 200 and 'redir=' in resp.text and '&portal=' in resp.text:
data_response['result'] = "success"
Expand Down
3 changes: 2 additions & 1 deletion plugins/gmailenum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ def testconnect(pluginargs, args, api_dict, useragent):

headers = utils.add_custom_headers(pluginargs, headers)

resp = requests.get(api_dict['proxy_url'], headers=headers)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.get(api_dict['proxy_url'], headers=headers, proxies=proxies)

if resp.status_code == 504:
output = "Testconnect: Connection failed, endpoint timed out, exiting"
Expand Down
4 changes: 2 additions & 2 deletions plugins/gmailenum/gmailenum.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def gmailenum_authenticate(url, username, password, useragent, pluginargs):
headers = utils.add_custom_headers(pluginargs, headers)

try:

resp = requests.get(f"{url}/mail/gxlu",params={"email":username},headers=headers)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.get(f"{url}/mail/gxlu",params={"email":username}, headers=headers, proxies=proxies)

if "Set-Cookie" in resp.headers.keys():
data_response['result'] = "success"
Expand Down
3 changes: 2 additions & 1 deletion plugins/httpbrute/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ def testconnect(pluginargs, args, api_dict, useragent):

headers = utils.add_custom_headers(pluginargs, headers)

resp = requests.get(api_dict['proxy_url'], headers=headers)
proxies = pluginargs.get('proxies') if isinstance(pluginargs, dict) else None
resp = requests.get(api_dict['proxy_url'], headers=headers, proxies=proxies)

if resp.status_code == 504:
output = "Testconnect: Connection failed, endpoint timed out, exiting"
Expand Down
Loading