Cloud & Hybrid Enumeration
Internal penetration tests increasingly need to identify cloud integration points. Azure AD Connect servers, Microsoft 365 endpoints, service principals, conditional access policies, and cloud sync configurations are critical targets discovered from within the internal network. This guide covers enumerating the cloud attack surface from an internal perspective.
Information
Danger
login.microsoftonline.com is indistinguishable from a password spray at Microsoft's edge,
and it is your source address that gets throttled or blocked.
Quick Navigation
Azure / Entra ID
M365 / Exchange
Cloud Resources
Why Cloud Enumeration Matters in Internal Tests
- Hybrid Reality: 90%+ of enterprises use Azure AD/Entra ID — cloud integration points are present in virtually every internal network.
- Lateral Movement to Cloud: Finding Azure AD Connect, PTA agents, or ADFS servers maps the on-prem→cloud pivot path.
- Credential Reuse: Synced accounts share passwords between on-prem AD and cloud — one hash, two environments.
- Expanded Scope: Demonstrating cloud impact from an internal compromise significantly increases finding severity.
Azure Tenant Discovery
# OpenID configuration (no auth required)
curl https://login.microsoftonline.com/DOMAIN.COM/.well-known/openid-configuration
curl https://login.microsoftonline.com/DOMAIN.COM/v2.0/.well-known/openid-configuration | jq .token_endpoint
# AADInternals — comprehensive tenant recon
Import-Module AADInternals
Invoke-AADIntReconAsOutsider -DomainName corp.com
# Check if user exists (user enumeration)
Invoke-AADIntUserEnumerationAsOutsider -UserName "admin@corp.com"
# Enumerate all domains registered to the tenant
Get-AADIntTenantDomains -Domain corp.comInformation
Invoke-AADIntReconAsOutsider returns: Tenant ID/name, authentication type (Managed/Federated),
SSO configuration, Seamless SSO status, MFA configuration, and all registered domains — all without authentication.
Finding Azure AD Connect Servers
Azure AD Connect servers are high-value Tier 0 targets. Find them from the internal network using these methods:
# Method 1: LDAP query for sync accounts (MSOL_ / AAD_ prefixed)
Get-ADUser -Filter { Name -like "MSOL_*" -or Name -like "AAD_*" } -Properties Description
# Method 2: Service connection point in AD
Get-ADObject -Filter { objectClass -eq "serviceConnectionPoint" -and
Name -eq "62a0ff2e-97b9-4513-943f-0d221bd30080" } -Properties keywords
# Method 3: Network scanning for AAD Connect hosts
nxc smb network-range -u '' -p '' --gen-relay-list relay.txt
# Method 4: BloodHound — find MSOL_ account and source computer
MATCH (u:User) WHERE u.name STARTS WITH "MSOL_"
MATCH (c:Computer)-[:HasSession]->(u) RETURN c.name, u.name
# Method 5: PTA Agent discovery via service connection point
Get-ADObject -Filter { objectClass -eq "serviceConnectionPoint" -and
Name -eq "497b03c2-c5d8-4199-84c1-ee44e5a18a28" } -Properties keywordsService Principal Enumeration
# ROADtools — comprehensive Azure AD enumeration (requires valid creds)
roadrecon auth -u user@corp.com -p 'Password1'
roadrecon gather
roadrecon gui
# AzureAD PowerShell — enumerate service principals
Connect-AzureAD
Get-AzureADServicePrincipal -All $true | Select-Object DisplayName, AppId,
ServicePrincipalType | Where-Object { $_.ServicePrincipalType -eq "Application" }
# Find service principals with privileged roles
Get-AzureADDirectoryRole | ForEach-Object {
$role = $_
Get-AzureADDirectoryRoleMember -ObjectId $_.ObjectId | ForEach-Object {
[PSCustomObject]@{ Role = $role.DisplayName; Member = $_.DisplayName; Type = $_.ObjectType }
}
} | Where-Object { $_.Type -eq "ServicePrincipal" }Microsoft 365 Endpoint Enumeration
# MX records reveal mail routing (Exchange Online vs hybrid)
nslookup -type=MX corp.com
# If MX points to *.mail.protection.outlook.com → Exchange Online
# If MX points to internal server → Exchange on-prem or hybrid
# Autodiscover — reveals Exchange configuration
curl https://autodiscover-s.outlook.com/autodiscover/autodiscover.json?Email=user@corp.com
# Federation metadata (ADFS detection)
curl https://login.microsoftonline.com/corp.com/FederationMetadata/2007-06/FederationMetadata.xml
# If federated, reveals ADFS server URL (e.g., sts.corp.com)
# From internal network — find ADFS servers
nslookup sts.corp.com
nslookup adfs.corp.com
nxc smb sts.corp.com -u '' -p ''
# Check for Exchange Online PowerShell endpoint
curl -I https://outlook.office365.com/PowerShell-LiveID
# Graph API — check available endpoints
curl https://graph.microsoft.com/v1.0/$metadataExchange Hybrid Detection
# Exchange hybrid environments have both on-prem and cloud mailboxes
# Identify hybrid configuration from internal network:
# Find Exchange servers via AD
Get-ADComputer -Filter { ServicePrincipalName -like "*exchangeMDB*" } -Properties ServicePrincipalName
# SPN-based enumeration for Exchange
setspn -Q */exchange*
setspn -Q */autodiscover*
# Exchange Management Shell (if accessible)
Get-ExchangeServer | Select-Object Name, ServerRole, Edition
Get-HybridConfiguration | Select-Object *
# Check for Exchange Web Services
nxc http exchange-server.corp.local -u user -p 'Pass123' -M ews
# Outlook Anywhere / MAPI
curl https://exchange-server.corp.local/owa/
curl https://exchange-server.corp.local/EWS/Exchange.asmxTeams & SharePoint Enumeration
# SharePoint Online tenant URL
# https://corp.sharepoint.com
# https://corp-my.sharepoint.com (OneDrive)
# Find SharePoint on-prem servers
Get-ADComputer -Filter { ServicePrincipalName -like "*SharePoint*" }
nxc smb network -u user -p 'Pass123' --spider "*.sharepoint.*"
# Teams — with valid credentials, enumerate via Graph API
# GET https://graph.microsoft.com/v1.0/me/joinedTeams
# GET https://graph.microsoft.com/v1.0/groups?$filter=resourceProvisioningOptions/Any(x:x eq 'Team')
# TeamFiltration — enumerate and spray Teams
TeamFiltration.exe --outpath results --enum --domain corp.com
# Search SharePoint for sensitive files (with access)
# Use ROADtools or manual Graph API queriesConditional Access Enumeration
# Understanding Conditional Access policies reveals MFA gaps and bypass paths
# ROADtools — dump CA policies
roadrecon auth -u user@corp.com -p 'Password1'
roadrecon gather --roadrecon-gather-conditional-access
roadrecon gui
# Navigate to Policies → Conditional Access
# AADInternals — enumerate authentication methods
Get-AADIntConditionalAccessPolicies -AccessToken $token
# Test different authentication parameters to map CA policy coverage:
# - Different client apps (desktop, browser, mobile, legacy)
# - Different platforms (Windows, macOS, iOS, Android)
# - Different locations (internal IP, VPN, external)
# - Different managed/unmanaged device states
# Legacy authentication often bypasses MFA requirements
# Test IMAP/POP/SMTP authentication
python3 -c "
import imaplib
m = imaplib.IMAP4_SSL('outlook.office365.com')
m.login('user@corp.com', 'Password1')
# If this works, legacy auth is allowed (bypasses MFA)
"AWS / GCP Detection from Internal
# Check for AWS integration
# EC2 metadata endpoint (if running on EC2)
curl -s http://169.254.169.254/latest/meta-data/
# IAM role credentials
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Find AWS credentials in files
findstr /s /i "AKIA" C:\Users* # AWS access key IDs start with AKIA
findstr /s /i "aws_access_key|aws_secret" C:\Users*.aws*
# GCP metadata
curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/service-accounts/
# Find GCP service account keys
dir /s /b C:\Users*service-account*.json
# Environment variables
set | findstr /i "AWS_|AZURE_|GCP_|GOOGLE_"Cloud Credential Discovery
# Search for cloud credentials across gathered machines
# Azure CLI tokens
type %USERPROFILE%\.azureaccessTokens.json
type %USERPROFILE%\.azuremsal_token_cache.json
# Azure PowerShell tokens
type %USERPROFILE%\.Azure\TokenCache.dat
type %USERPROFILE%\.Azure\AzureRmContext.json
# AWS credentials
type %USERPROFILE%\.awscredentials
type %USERPROFILE%\.awsconfig
# GCP credentials
type %APPDATA%\gcloudcredentials.db
type %APPDATA%\gcloudaccess_tokens.db
# Service principal certificates and secrets in AD
Get-ADObject -Filter { objectClass -eq "msDS-ManagedServiceAccount" } -Properties *
# Search config files for client secrets
findstr /s /i "client_secret|tenant_id|subscription_id" \\fileservershare\*.config
findstr /s /i "client_secret|tenant_id" \\fileservershare\*.jsonWhat the Defender Sees
Cloud-side telemetry is better than most internal telemetry, because the identity provider logs every sign-in and every directory read whether the client forwards it or not. What to ask for:
| Source | Detection |
|---|---|
| Azure AD Sign-in Logs | Unusual AADInternals / ROADtools user-agent strings, bulk Graph API queries |
| Azure AD Audit Logs | Service principal enumeration, directory role queries, app consent events |
| Event 4662 | Read access to AAD Connect replication account (MSOL_*) or DPAPI-NG keys |
| Event 4624 / 4648 | Logons from AAD Connect server to DC using MSOL_ account outside sync schedule |
| Cloud Audit Logs | AWS CloudTrail / GCP Audit Logs showing credential file access or IAM enumeration |
// KQL — Detect AADInternals / ROADtools enumeration via user-agent
SigninLogs
| where UserAgent has_any ("AADInternals", "ROADtools", "python-requests")
| project TimeGenerated, UserPrincipalName, AppDisplayName, UserAgent, IPAddress
// Detect bulk Graph API enumeration
AuditLogs
| where OperationName has_any ("List servicePrincipals", "List applications", "List directoryRoles")
| summarize Count=count() by InitiatedBy.user.userPrincipalName, bin(TimeGenerated, 5m)
| where Count > 50Evidence Collection
Cloud findings are tenant facts, so the evidence is the tenant identifier and the response that proves it — plus the on-prem host that bridges the two.
Tenant identity: Tenant ID, primary domain, verified domains, and whether authentication is managed or federated. Without the tenant ID the client cannot tell which of their directories you tested.
Identity bridge: The AAD Connect server (or PTA agent, or ADFS farm) identified from AD, with the method that found it — MSOL_ account, service connection point, or network presence.
Conditional-access coverage: The policies you could read, and the test matrix you ran (client app, platform, location, device state). The finding is the gap in the matrix, not the policy count.
Hybrid endpoint evidence: MX, autodiscover and federation-metadata responses that establish Exchange Online versus on-prem, and the ADFS endpoint if federated.
Credential artefacts: Files, token caches or service-principal secrets found on hosts, with path and file hash — never the secret value itself in the report body.
False Positive Identification
- A tenant that exists is not the client's tenant: A domain that resolves to an Entra tenant may belong to a subsidiary, a reseller or an acquired brand. Confirm ownership in writing before testing authenticated paths against it.
-
A metadata response is not a foothold:
169.254.169.254answering proves the host runs in a cloud, not that it has an attached role. Request the credentials document before calling it credential access. - Legacy auth blocked at the edge only: IMAP or SMTP failing from your host can be a rate limit or a tenant block, not a policy. Confirm with an authorised client app before reporting MFA coverage.
- A service principal with a role assignment: A directory role on a service principal is only useful if you hold its credential. Report the assignment and separately whether the secret was recoverable.
Testing Checklist
Cloud enumeration from an internal position produces tenant facts and bridge hosts. Both need confirming before either is a finding.
Confirm the tenant and the bridge
- ☐ An unauthenticated query (
userrealm,Get-AADIntTenantID) returns the tenant ID and verified domains, and they match the client's tenant in writing. - ☐ The AAD Connect host, PTA agent or ADFS farm is identified from AD (
MSOL_account, service connection point) and named. - ☐ MX, autodiscover and federation metadata establish Exchange Online versus on-prem, with the responses quoted.
- ☐
169.254.169.254is queried from an in-scope host and either returns role credentials or the reason it does not (IMDSv2 token, proxy).
Confirm the finding is the client's
- ☐ A discovered tenant is proved to belong to the client before any authenticated path is attempted.
- ☐ Conditional-access findings name the matrix cell that failed (client app, platform, location, device state), not the policy count.
- ☐ A service principal with a directory role is reported together with whether its credential was recoverable.
- ☐ Every credential artefact is recorded by path and hash; the secret itself stays out of the report body.
Remediation & Defense
The controls that make the on-prem to cloud pivot hard are identity controls, not network ones.
Protect the sync identity
- • Treat the AAD Connect server as Tier 0: separate admin accounts, no interactive logons, no shared admin
- • Do not sync privileged on-prem groups to the cloud, and keep Global Administrator cloud-only
- • Prefer password hash sync over PTA where the client has no hard requirement for on-prem validation
Close the authentication gaps
- • Block legacy authentication protocols tenant-wide, then verify with a real client
- • Apply conditional access to all apps, including the ones added after the policy was written
- • Enforce MFA for every user, with number matching to blunt prompt bombing
Detect the enumeration
- • Alert on AADInternals and ROADtools user agents, and on bulk Graph list operations per user
- • Monitor 4624/4648 from the AAD Connect host to the DC outside the sync window
- • Watch for new service principals, app consents and credential additions on existing apps