o365spray: How Endpoint Diversity Beats Microsoft's Authentication Rate Limiting
Hook
Microsoft O365 has seven different authentication endpoints, and each one handles rate limiting independently. That architectural decision turned security feature into reconnaissance goldmine.
Context
Microsoft O365 dominates enterprise email and collaboration, powering over 345 million paid seats worldwide. For penetration testers and red teamers, this ubiquity makes O365 credentials a prime target—compromise one account and you potentially gain access to email, SharePoint, Teams, and the broader corporate network. But Microsoft knows this. They've implemented sophisticated rate limiting, account lockout policies, and IP-based throttling to prevent credential stuffing and brute force attacks.
Traditional password spraying tools hit a single authentication endpoint repeatedly, making detection trivial. They trigger lockouts after a few failed attempts, get IP-banned within minutes, and leave obvious logs. o365spray emerged from the recognition that Microsoft's authentication infrastructure isn't monolithic—it's a constellation of different services (OAuth2, ActiveSync, Autodiscover, ADFS, RST, Autologon) that authenticate users differently and implement rate limiting inconsistently. By intelligently rotating between these endpoints and leveraging AWS API Gateway for IP rotation, o365spray transforms a noisy, easily-blocked attack into a surgical reconnaissance operation that can run for hours without triggering alerts.
Technical Insight
The genius of o365spray lies in its modular endpoint architecture. Rather than treating O365 as a single authentication surface, it implements separate modules for different Microsoft services, each targeting a specific API with tailored request patterns. The OAuth2 module hits https://login.microsoft.com/common/oauth2/token, the ActiveSync module targets Exchange's mobile sync endpoint, and the OneDrive module probes https://{tenant}-my.sharepoint.com. Each endpoint returns different response codes for valid versus invalid usernames, and crucially, each maintains separate rate limiting counters.
The enumeration flow demonstrates this architectural approach. Here's how the OAuth2 enumeration module validates usernames without triggering lockouts:
def enum_oauth2(user, domain, proxy=None):
url = "https://login.microsoft.com/common/oauth2/token"
body = {
'resource': 'https://graph.windows.net',
'client_id': '1b730954-1685-4b74-9bfd-dac224a7b894',
'client_info': '1',
'grant_type': 'password',
'username': f'{user}@{domain}',
'password': 'invalid_password',
'scope': 'openid'
}
response = requests.post(url, data=body, proxies=proxy)
json_resp = response.json()
# Valid user returns AADSTS50126 (invalid password)
# Invalid user returns AADSTS50034 (user doesn't exist)
if 'AADSTS50126' in str(json_resp.get('error_description')):
return True # Valid username
return False
This technique exploits Microsoft's helpful error messages—they tell you whether the username exists before even checking the password. The tool submits an intentionally invalid password, then parses the specific error code. AADSTS50126 means "bad password, but user exists" while AADSTS50034 means "this user doesn't exist in our directory." Different modules use different signals: the OneDrive module checks for 401 versus 404 responses, while the Office.com module looks for specific redirects.
The lockout avoidance system is equally sophisticated. o365spray doesn't just add random delays—it models typical Microsoft lockout policies and tracks attempts across the entire operation:
# Configure lockout policy awareness
parser.add_argument('--lockout', type=int, default=5,
help='Account lockout policy threshold')
parser.add_argument('--lockout-reset', type=float, default=15.0,
help='Lockout reset window in minutes')
# During spraying, track attempts per user
if attempt_count >= lockout_threshold:
print(f"[!] Reached lockout threshold. Sleeping {lockout_reset} minutes")
time.sleep(lockout_reset * 60)
attempt_count = 0
This lets penetration testers configure the tool to match known organizational policies. If a client's O365 tenant locks accounts after 5 failed attempts with a 15-minute reset window, you configure those exact parameters and o365spray automatically pauses operations to let counters reset, avoiding account lockouts that would alert security teams and potentially disrupt legitimate users.
The FireProx integration takes evasion to another level. FireProx creates AWS API Gateway endpoints that proxy requests to O365, making each request appear to come from a different AWS IP address. Since Microsoft's rate limiting is heavily IP-based, this effectively gives you unlimited IP addresses:
# Create FireProx endpoint
python fire.py --access_key AWS_KEY --secret_access_key AWS_SECRET \
--region us-east-1 --command create \
--url https://login.microsoft.com
# Use with o365spray
python o365spray.py --enum -U users.txt --domain target.com \
--proxy-url https://randomid.execute-api.us-east-1.amazonaws.com/fireprox
Each request routes through API Gateway with a different source IP, making traditional IP-based blocking ineffective. The rate limiter sees hundreds of different IPs each making just one or two requests—completely normal traffic patterns.
The threading implementation uses Python's concurrent.futures.ThreadPoolExecutor with configurable worker counts, enabling users to balance speed against detection risk. Combined with --sleep and --jitter parameters that add randomized delays between requests, operators can tune the tool's noise profile from "fast and obvious" to "glacially slow but nearly invisible."
Gotcha
The biggest gotcha is that several enumeration modules—specifically OAuth2, Autologon, and RST—submit actual authentication attempts that count against lockout thresholds. The documentation isn't always clear about this distinction. If you run OAuth2 enumeration against 1,000 users, then immediately run a password spray, you've already consumed one failed attempt per account before your spray even begins. On tenants with strict 3-attempt lockout policies, this can trigger widespread lockouts. You need to manually track which enumeration methods consume lockout budget and adjust your timing accordingly, or stick to truly passive methods like OneDrive enumeration.
Speaking of OneDrive enumeration—it only works for users who have actually logged into OneDrive at least once. Many organizations provision O365 accounts but users never touch OneDrive, particularly service accounts, room mailboxes, or employees who only use Outlook. You'll get false negatives on perfectly valid accounts. The Office.com enumeration module has similar issues with federated authentication setups. And for OAuth2 password spraying against federated tenants, it only works if password hash synchronization is enabled; pure federated setups with no cloud password hashes always fail authentication regardless of whether credentials are valid. You'll spray valid passwords and see nothing but failures, wasting time and lockout budget on a technique that can't possibly succeed in that environment.
Verdict
Use if: You're conducting authorized penetration tests or red team engagements against O365 environments and need comprehensive username enumeration with sophisticated lockout avoidance. This tool excels when you have AWS credentials for FireProx integration and need to evade IP-based rate limiting during multi-hour or multi-day campaigns. It's invaluable when attacking mature security programs that monitor for authentication anomalies—the multiple enumeration methods and intelligent throttling provide options when obvious approaches fail. Use it when you need granular control over detection risk versus speed tradeoffs. Skip if: You lack explicit authorization for authentication attempts against the target environment (this is an offensive security tool that generates logs and can cause lockouts). Skip if you need something simple for quick validation—o365spray's complexity and configuration options create overhead for basic tasks. Skip if you're targeting non-O365 platforms; the tool is hyper-specialized for Microsoft's ecosystem. Also skip if you don't understand the target tenant's lockout policies—running this tool blind can lock out hundreds of accounts and get you fired from the engagement. Finally, consider the author's newer Omnispray framework instead if you need a more modular, actively maintained solution that targets multiple platforms beyond just O365.