Skip to content

Commit

Permalink
Add support for user agent randomization
Browse files Browse the repository at this point in the history
  • Loading branch information
0xZDH committed Nov 3, 2022
1 parent 14659a7 commit 281e005
Show file tree
Hide file tree
Showing 17 changed files with 4,975 additions and 17 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,5 @@ cython_debug/
enum/
spray/
output/
!requirements.txt
!requirements.txt
!user-agents.txt
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# CHANGELOG

## v3.0.1 (03/11/2022)
- Add support for User-Agent randomization handling

## v3.0.0 (06/08/2022)
- Update how paired lists are handled during password spraying to account for users with more than one password so lockout thresholds are respected
- Update how modules are called and handled to allow for more modular handling
Expand Down
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ o365spray ia a username enumeration and password spraying tool aimed at Microsof
- [FireProx URLs](#fireprox-base-urls)
- [Enumeration](#enumeration-1)
- [Spraying](#spraying-1)
- [User Agent Randomization](#user-agent-randomization)
- [Acknowledgments](#acknowledgments)
- [Previous Versions](#using-previous-versions)

Expand All @@ -38,10 +39,11 @@ usage: o365spray [-h] [-d DOMAIN] [--validate] [--enum] [--spray]
[--paired PAIRED] [-c COUNT] [-l LOCKOUT]
[--validate-module] [--enum-module] [--spray-module]
[--adfs-url ADFS_URL] [--rate RATE] [--safe SAFE]
[--timeout TIMEOUT] [--proxy PROXY] [--proxy-url PROXY_URL]
[--useragents USERAGENTS] [--timeout TIMEOUT]
[--proxy PROXY] [--proxy-url PROXY_URL]
[--output OUTPUT] [-v] [--debug]
o365spray | Microsoft O365 User Enumerator and Password Sprayer -- v3.0.0
o365spray | Microsoft O365 User Enumerator and Password Sprayer -- v3.0.1
optional arguments:
Expand Down Expand Up @@ -104,6 +106,9 @@ optional arguments:
--safe SAFE Terminate password spraying run if `N` locked accounts are
observed. Default: 10
--useragents USERAGENTS
File containing list of user agents for randomization.
--timeout TIMEOUT HTTP request timeout in seconds. Default: 25
--proxy PROXY HTTP/S proxy to pass traffic through
Expand Down Expand Up @@ -178,6 +183,14 @@ To use FireProx with o365spray, create a proxy URL for the given o365spray modul
| reporting | `https://reports.office365.com/` |
| rst | `https://login.microsoftonline.com/` |

## 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>

## 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.
Expand All @@ -201,7 +214,7 @@ The o365spray framework has been ported to a new tool: [Omnispray](https://githu

## Using Previous Versions

o365spray has recently been rewritten and could have some hidden/unknown bugs. If issues are encountered, try checking out the commit prior to the code rewrites:
If issues are encountered, try checking out previous versions prior to code rewrites:

```bash
# v1.3.7
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 = 3
_V_MIN = 0
_V_MNT = 0
_V_MNT = 1
__version__ = f"{_V_MAJ}.{_V_MIN}.{_V_MNT}"
13 changes: 13 additions & 0 deletions o365spray/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ def parse_args() -> argparse.Namespace:
)

# HTTP configurations
parser.add_argument(
"--useragents",
type=str,
help="File containing list of user agents for randomization.",
)
parser.add_argument(
"--timeout",
type=int,
Expand Down Expand Up @@ -218,6 +223,14 @@ def parse_args() -> argparse.Namespace:
"otherwise, --paired is required."
)

# Validate user agent file and load data set
if args.useragents:
if not Path(args.useragents).is_file():
parser.error("invalid user agent file provided")

else:
args.useragents = Helper.get_list_from_file(args.useragents)

# Handle sleep randomization
if args.sleep == -1:
args.sleep = randint(1, 120)
Expand Down
38 changes: 36 additions & 2 deletions o365spray/core/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,42 @@
import urllib3 # type: ignore
import requests # type: ignore
from random import randint
from typing import Dict, Any, Union
from o365spray.core.utils import Defaults
from typing import (
Any,
Dict,
List,
Union,
)
from o365spray.core.utils import (
Defaults,
Helper,
)


urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


class BaseHandler(object):
"""Module Base"""

def __init__(
self,
useragents: List[str] = None,
*args,
**kwargs,
):
"""Initialize a module base handler.
Note:
This is to allow all modules to provide user agent
lists for randomization easily.
Arguments:
<optional>
useragents: list of user agents
"""
self.useragents = useragents

def _send_request(
self,
method: str,
Expand Down Expand Up @@ -60,6 +89,11 @@ def _send_request(
logging.debug(f"Sleeping for {throttle} seconds before sending request.")
time.sleep(throttle)

# Retrieve random user agent and overwrite
# the existing value
if self.useragents:
headers["User-Agent"] = Helper.get_random_element_from_list(self.useragents)

return requests.request(
method,
url,
Expand Down
1 change: 1 addition & 0 deletions o365spray/core/handlers/enumerator/enumerate.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def enumerate(args: argparse.Namespace, output_dir: str) -> object:
sleep=args.sleep,
jitter=args.jitter,
proxy_url=args.proxy_url,
useragents=args.useragents,
)

def enum_signal_handler(signal, frame):
Expand Down
3 changes: 3 additions & 0 deletions o365spray/core/handlers/enumerator/modules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
ThreadWriter,
)


urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


Expand Down Expand Up @@ -69,6 +70,8 @@ def __init__(
ValueError: if no output directory provided when output writing
is enabled
"""
super().__init__(*args, **kwargs)

if writer and not output_dir:
raise ValueError("Missing 1 required argument: 'output_dir'")

Expand Down
3 changes: 3 additions & 0 deletions o365spray/core/handlers/sprayer/modules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
text_colors,
)


urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


Expand Down Expand Up @@ -76,6 +77,8 @@ def __init__(
ValueError: if no output directory provided when output writing
is enabled
"""
super().__init__(*args, **kwargs)

if writer and not output_dir:
raise ValueError("Missing 1 required argument: 'output_dir'")

Expand Down
1 change: 1 addition & 0 deletions o365spray/core/handlers/sprayer/spray.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def spray(args: argparse.Namespace, output_dir: str, enum: object):
sleep=args.sleep,
jitter=args.jitter,
proxy_url=args.proxy_url,
useragents=args.useragents,
)

def spray_signal_handler(signal, frame):
Expand Down
5 changes: 4 additions & 1 deletion o365spray/core/handlers/validator/modules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
Tuple,
Union,
)
from o365spray.core.handlers.base import BaseHandler # type: ignore
from o365spray.core.handlers.base import BaseHandler


urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

Expand Down Expand Up @@ -43,6 +44,8 @@ def __init__(
sleep: throttle http requests
jitter: randomize throttle
"""
super().__init__(*args, **kwargs)

# If proxy server provided, build HTTP proxies object for
# requests lib
if isinstance(proxy, str):
Expand Down
4 changes: 1 addition & 3 deletions o365spray/core/handlers/validator/modules/getuserrealm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import html
import xml.etree.ElementTree as ET
from typing import Tuple
from o365spray.core.utils import (
Defaults,
)
from o365spray.core.utils import Defaults
from o365spray.core.handlers.validator.modules.base import ValidatorBase


Expand Down
1 change: 1 addition & 0 deletions o365spray/core/handlers/validator/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def validate(args: argparse.Namespace) -> argparse.Namespace:
proxy=args.proxy,
sleep=args.sleep,
jitter=args.jitter,
useragents=args.useragents,
)
(valid, adfs) = v.validate(args.domain)

Expand Down
10 changes: 5 additions & 5 deletions o365spray/core/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from o365spray.core.utils.colors import text_colors # type: ignore
from o365spray.core.utils.defaults import ( # type: ignore
from o365spray.core.utils.colors import text_colors
from o365spray.core.utils.defaults import (
Defaults,
DefaultFiles,
)
from o365spray.core.utils.helper import Helper # type: ignore
from o365spray.core.utils.logger import init_logger # type: ignore
from o365spray.core.utils.writer import ThreadWriter # type: ignore
from o365spray.core.utils.helper import Helper
from o365spray.core.utils.logger import init_logger
from o365spray.core.utils.writer import ThreadWriter
4 changes: 4 additions & 0 deletions o365spray/core/utils/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class Defaults:
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en-US,en;q=0.5",
"Upgrade-Insecure-Requests": "1",
Expand Down
18 changes: 17 additions & 1 deletion o365spray/core/utils/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import string
import random
import argparse
from random import sample
from typing import (
Any,
List,
Expand Down Expand Up @@ -100,6 +101,14 @@ def write_data(
for account in creds:
f.write(f"{account}\n")

@classmethod
def get_random_element_from_list(cls, l: List[Any]) -> Any:
"""Select a random element from a given list
:returns: random element from list
"""
return sample(l, 1)[0]

@classmethod
def get_chunks_from_list(
cls,
Expand Down Expand Up @@ -291,9 +300,16 @@ def banner(cls, args: argparse.Namespace, version: str):
):
continue

value = _args[arg]
space = " " * (15 - len(arg))

BANNER += "\n > %s%s: %s" % (arg, space, str(_args[arg]))
# Handle conditions to show custom values
# 1) If a user agents file is provided, show 'random' instead
# of full list of agents - include number of random agents
if args.useragents and arg == "useragents":
value = f"random ({len(_args[arg])})"

BANNER += "\n > %s%s: %s" % (arg, space, str(value))

# Add data meanings
if arg == "count":
Expand Down
Loading

0 comments on commit 281e005

Please sign in to comment.