Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V3.1.0 #31

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# CHANGELOG

## v3.1.20240830 (08/30/2024)
- [Oddvar Moe](https://github.com/api0cradle) Update MFA message for `AADSTS50079`
- Update `office` enumeration module to continue past request throttling as it is per user
- Remove spraying modules that use Basic Authentication (ActiveSync, Autodiscover, Reporting) due to [Microsoft's Deprecation of Basic Authentication](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online)
- Remove the sleep and jitter during domain validation
- Add support for FireProx handling directly through the tool via fire.py
- Update tested data written to include timestamps and resource/client id

## v3.0.4 (11/19/2023)
- [@Macmod](https://github.com/Macmod) Update how enumeration performs concurrent requests by limiting the number of blocking tasks at any one time

Expand Down
67 changes: 40 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ o365spray is a username enumeration and password spraying tool aimed at Microsof
- [Validation](#validation)
- [Enumeration](#enumeration)
- [Spraying](#spraying)
- [FireProx URLs](#fireprox-base-urls)
- [FireProx](#fireprox)
- [Enumeration](#enumeration-1)
- [Spraying](#spraying-1)
- [User Agent Randomization](#user-agent-randomization)
Expand All @@ -40,7 +40,7 @@ Perform password spraying against a given domain:<br>
```
usage: o365spray [flags]

o365spray | Microsoft O365 User Enumerator and Password Sprayer -- v3.0.4
o365spray | Microsoft O365 User Enumerator and Password Sprayer -- v3.1.20241105

options:
-h, --help show this help message and exit
Expand Down Expand Up @@ -134,6 +134,27 @@ Output Configuration:
--output OUTPUT Output directory for results and test case files.
Default: current directory

Fireprox Configuration:
--profile-name PROFILE_NAME
AWS Profile Name to store/retrieve credentials.

--access-key ACCESS_KEY
AWS Access Key.

--secret-access-key SECRET_ACCESS_KEY
AWS Secret Access Key.

--session-token SESSION_TOKEN
AWS Session Token.

--region REGION AWS Region.

Fireprox Utilities:
--api-list List all fireprox APIs.

--api-destroy API_ID
Destroy single API instance, by API ID.

Debug:
-v, --version Print the tool version.

Expand All @@ -155,23 +176,24 @@ Debug:
> 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'.

### Spraying
* activesync
* adfs
* autodiscover
* autologon
* oauth2 (default)
* reporting
* rst
* activesync `deprecated`
* autodiscover `deprecated`
* reporting `deprecated`

> The oAuth2 module can be used for federated spraying, but it should be noted that this will ONLY work when the target tenant has enabled password synchronization - otherwise authentication will always fail. The default mechanic is to default to the 'adfs' module when federation is identified.

## FireProx Base URLs
## FireProx

Microsoft has made it more difficult to perform password spraying, so using tools like [FireProx](https://github.com/ustayready/fireprox) help to bypass rate-limiting based on IP addresses.

To use FireProx with o365spray, create a proxy URL for the given o365spray module based on the base URL tables below. The proxy URL can then be passed in via `--proxy-url`.

> NOTE: Make sure to use the correct `--enum-module` or `--spray-module` flag with the base URL used to create the FireProx URL.
There are two methods to using FireProx with o365spray:
1. As of v3.1, FireProx management can be done directly through o365spray. See the `Fireprox Configuration` and `Fireprox Utilities` command line flags.
2. Create a proxy URL for the given o365spray module based on the URL tables below. The proxy URL should then be passed in via `--proxy-url`.
- *NOTE*: Make sure to use the correct `--enum-module` or `--spray-module` value corresponding with the base URL used to create the FireProx URL.

### Enumeration

Expand All @@ -190,25 +212,27 @@ To use FireProx with o365spray, create a proxy URL for the given o365spray modul

| Module | Base URL |
| --- | --- |
| activesync | `https://outlook.office365.com/` |
| adfs | Currently not implemented |
| autodiscover | `https://autodiscover-s.outlook.com/` |
| autologon | `https://autologon.microsoftazuread-sso.com/` |
| oauth2 | `https://login.microsoftonline.com/` |
| reporting | `https://reports.office365.com/` |
| rst | `https://login.microsoftonline.com/` |
| activesync | Deprecated |
| autodiscover | Deprecated |
| reporting | Deprecated |

## User Agent Randomization

User-Agent randomization is now supported and can be accomplished by providing a User-Agent file to the `--useragents` flag. o365spray includes an example file with 4,800+ agents via [resc/user-agents.txt](resc/user-agents.txt).

The agents in the example data set were collected from the following:
- https://github.com/sqlmapproject/sqlmap/blob/master/data/txt/user-agents.txt
- https://www.useragentstring.com/pages/useragentstring.php?name=<browser>
- https://www.useragentstring.com/pages/useragentstring.php?name=browser

## Omnispray

The o365spray framework has been ported to a new tool: [Omnispray](https://github.com/0xZDH/Omnispray). This tool is meant to modularize the original enumeration and spraying framework to allow for generic targeting, not just O365. Omnispray includes template modules for enumeration and spraying that can be modified and leveraged for any target.
The o365spray framework was previously ported to a new tool: [Omnispray](https://github.com/0xZDH/Omnispray). This tool is meant to modularize the original enumeration and spraying framework to allow for generic targeting, not just O365. Omnispray includes template modules for enumeration and spraying that can be modified and leveraged for any target.

Omnispray is **not** meant to replace o365spray, but instead offer an alternative for varying targets.

## Acknowledgments

Expand All @@ -226,19 +250,8 @@ The o365spray framework has been ported to a new tool: [Omnispray](https://githu
| [Optiv](https://github.com/optiv) (Several Authors) | Go365: RST user enumeration and password spraying module | [Go365](https://github.com/optiv/Go365) |
| [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/) |
| [ustayready](https://github.com/ustayready) | FireProx: AWS API Gateway management tool | [FireProx](https://github.com/ustayready/fireprox) |

## Bugs

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.

## Using Previous Versions

If issues are encountered, try checking out previous versions prior to code rewrites:

```bash
# v1.3.7
git checkout e235abdcebad61dbd2cde80974aca21ddb188704

# v2.0.4
git checkout a585432f269a8f527d61f064822bb08880c887ef
```
If any bugs/errors are encountered, please open an Issue with the details (or a Pull Request with the proposed fix).
4 changes: 2 additions & 2 deletions o365spray/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3

_V_MAJ = 3
_V_MIN = 0
_V_MNT = 4
_V_MIN = 1
_V_MNT = 20241105
__version__ = f"{_V_MAJ}.{_V_MIN}.{_V_MNT}"
124 changes: 121 additions & 3 deletions o365spray/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from random import randint

from o365spray import __version__
from o365spray.core.fire import helper as fire
from o365spray.core.handlers.enumerator import enumerate
from o365spray.core.handlers.sprayer import spray
from o365spray.core.handlers.validator import validate
Expand Down Expand Up @@ -149,9 +150,7 @@ def parse_args() -> argparse.Namespace:
"--poolsize",
type=int,
default=10000,
help=(
"Maximum size of the ThreadPoolExecutor. Default: 10000"
),
help="Maximum size of the ThreadPoolExecutor. Default: 10000",
)
scan_args.add_argument(
"--safe",
Expand Down Expand Up @@ -198,6 +197,53 @@ def parse_args() -> argparse.Namespace:
),
)

# FireProx configuration
fp_args = parser.add_argument_group(title="Fireprox Configuration")
fp_args.add_argument(
"--profile-name",
type=str,
help="AWS Profile Name to store/retrieve credentials.",
)
fp_args.add_argument(
"--access-key",
type=str,
help="AWS Access Key.",
)
fp_args.add_argument(
"--secret-access-key",
type=str,
help="AWS Secret Access Key.",
)
fp_args.add_argument(
"--session-token",
type=str,
help="AWS Session Token.",
)
fp_args.add_argument(
"--region",
type=str,
choices=fire.AWS_REGIONS,
help="AWS Region.",
)

# FireProx utilities
fpu_args = parser.add_argument_group(title="Fireprox Utilities")
fpu_args.add_argument(
"--api-list",
action="store_true",
help="List all fireprox APIs.",
)
fpu_args.add_argument(
"--api-destroy",
type=str,
help="Destroy single API instance, by API ID.",
)
# fpu_args.add_argument(
# "--api-destroy-all",
# action="store_true",
# help="Destroy all fireprox AWS APIs from every region (Warning: this is irreversible).",
# )

debug_args = parser.add_argument_group(title="Debug")
debug_args.add_argument(
"-v", "--version", action="store_true", help="Print the tool version."
Expand All @@ -218,6 +264,66 @@ def parse_args() -> argparse.Namespace:
# If not getting the tool version and flags have been provided, ensure
# all required flags and valid flag combinations are present

# FireProx handling
args.fireprox_args = None
if (
args.profile_name
or args.access_key
or args.secret_access_key
or args.session_token
or args.region
or args.api_list
or args.api_destroy
# or args.api_destroy_all
):
# Build initial fire.py argument handling
args.fireprox_args = {
"profile_name": args.profile_name,
"access_key": args.access_key,
"secret_access_key": args.secret_access_key,
"session_token": args.session_token,
"region": args.region,
"api_id": args.api_destroy,
}

# Handle fire.py api callers
if args.api_list:
sys.exit(fire.list_api(**args.fireprox_args))

if args.api_destroy:
sys.exit(fire.destroy_api(**args.fireprox_args))

# if args.api_destroy_all:
# sys.exit(fire.destroy_all_apis(**args.fireprox_args))

# Handle fire.py conflicts
if args.validate:
parser.error("the validate module does not support fire.py handling")

if args.proxy_url:
parser.error("proxy url already provided, can not run fire.py")

# Get the base url to proxy through FireProx based on module name
enum_module_base_url = None
if args.enum:
enum_module_base_url = fire.get_module_url(args.enum_module, "enum")

spray_module_base_url = None
if args.spray:
spray_module_base_url = fire.get_module_url(args.spray_module, "spray")

# If enum and spray both enabled, make sure the module base urls are
# the same to avoid conflicts (for now)
if (
enum_module_base_url
and spray_module_base_url
and (enum_module_base_url != spray_module_base_url)
):
parser.error("can not use conflicting enum and spray modules with fire.py")

# Add module URL to fire.py argument handling
args.fireprox_args["url"] = enum_module_base_url or spray_module_base_url

# Ensure a domain has been provided
if not args.domain:
parser.error("-d/--domain is required.")
Expand Down Expand Up @@ -304,6 +410,14 @@ def main():
# Perform domain validation
args = validate(args)

# Handle fire.py api creation
if args.fireprox_args and (args.enum or args.spray):
api = fire.create_api(**args.fireprox_args)

# Update args namespace
args.proxy_url = api["proxy_url"]
args.fireprox_args["api_id"] = api["api_gateway_id"]

# Perform user enumeration
if args.enum:
enum = enumerate(args, output_directory)
Expand All @@ -313,6 +427,10 @@ def main():
if args.spray:
spray(args, output_directory, enum)

# Handle internal creation/deletion with fire.py
if args.fireprox_args and args.fireprox_args.get("api_id", None):
fire.destroy_api(**args.fireprox_args)

elapsed = time.time() - start
logging.debug(f"o365spray executed in {elapsed:.2f} seconds.")

Expand Down
Loading