Add support for user agent randomization

This commit is contained in:
0xZDH
2022-11-03 17:28:08 -04:00
parent 14659a77b6
commit 281e005fd1
17 changed files with 4975 additions and 17 deletions
+2 -1
View File
@@ -165,4 +165,5 @@ cython_debug/
enum/
spray/
output/
!requirements.txt
!requirements.txt
!user-agents.txt
+3
View File
@@ -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
+16 -3
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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)
+36 -2
View File
@@ -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'")
+1
View File
@@ -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)
+5 -5
View File
@@ -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
View File
@@ -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",
+17 -1
View File
@@ -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":
+4864
View File
File diff suppressed because it is too large Load Diff