mirror of
https://github.com/0xZDH/o365spray
synced 2026-06-08 10:07:16 +00:00
Add support for user agent randomization
This commit is contained in:
+2
-1
@@ -165,4 +165,5 @@ cython_debug/
|
||||
enum/
|
||||
spray/
|
||||
output/
|
||||
!requirements.txt
|
||||
!requirements.txt
|
||||
!user-agents.txt
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_V_MAJ = 3
|
||||
_V_MIN = 0
|
||||
_V_MNT = 0
|
||||
_V_MNT = 1
|
||||
__version__ = f"{_V_MAJ}.{_V_MIN}.{_V_MNT}"
|
||||
|
||||
@@ -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,
|
||||
@@ -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)
|
||||
|
||||
@@ -5,13 +5,42 @@ import logging
|
||||
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,
|
||||
@@ -60,6 +89,11 @@ class BaseHandler(object):
|
||||
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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -19,6 +19,7 @@ from o365spray.core.utils import (
|
||||
ThreadWriter,
|
||||
)
|
||||
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
|
||||
@@ -69,6 +70,8 @@ class EnumeratorBase(BaseHandler):
|
||||
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'")
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from o365spray.core.utils import (
|
||||
text_colors,
|
||||
)
|
||||
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
|
||||
@@ -76,6 +77,8 @@ class SprayerBase(BaseHandler):
|
||||
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'")
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -7,7 +7,8 @@ from typing import (
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
from o365spray.core.handlers.base import BaseHandler # type: ignore
|
||||
from o365spray.core.handlers.base import BaseHandler
|
||||
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
@@ -43,6 +44,8 @@ class ValidatorBase(BaseHandler):
|
||||
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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -7,6 +7,7 @@ import struct
|
||||
import string
|
||||
import random
|
||||
import argparse
|
||||
from random import sample
|
||||
from typing import (
|
||||
Any,
|
||||
List,
|
||||
@@ -100,6 +101,14 @@ class Helper:
|
||||
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,
|
||||
@@ -291,9 +300,16 @@ class Helper:
|
||||
):
|
||||
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":
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user