T1526

Cloud Service Discovery

Discovery Last updated:

Adversaries who have gained access to a cloud environment may enumerate cloud services, resources, and configurations to identify valuable targets, understand security controls, and plan follow-on actions. This includes enumerating Azure resources via Azure Resource Manager API, Microsoft Graph API calls to list applications and service principals, AWS service enumeration via Pacu or direct CLI, and discovery of security services such as GuardDuty, Defender for Cloud, CloudTrail, and logging configurations. Tools like Stormspotter, AADInternals, and ROADTools automate this reconnaissance and are commonly observed in pre-ransomware and espionage campaigns.

What is T1526 Cloud Service Discovery?

Cloud Service Discovery (T1526) maps to the Discovery tactic — the adversary is trying to figure out your environment in MITRE ATT&CK.

This page provides production-ready detection logic for Cloud Service Discovery, covering the data sources and telemetry it touches: Cloud Service: Cloud Service Enumeration, Azure Activity Logs, Microsoft Entra ID Audit Logs, Azure Resource Manager. The queries below are rated medium severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.

MITRE ATT&CK

Tactic
Discovery
Technique
T1526 Cloud Service Discovery
Canonical reference
https://attack.mitre.org/techniques/T1526/
Microsoft Sentinel / Defender
kusto
// Detection 1: Azure Resource Manager bulk enumeration
let EnumerationWindow = 10m;
let ListOperationThreshold = 20;
AzureActivity
| where TimeGenerated > ago(1h)
| where ActivityStatusValue =~ "Success"
| where OperationNameValue has_any ("list", "List", "LIST") 
    or OperationNameValue has_any ("/read", "/get", "/Get")
| where CategoryValue !in ("Policy", "Alert", "Autoscale")
| summarize
    OperationCount = count(),
    DistinctOperations = dcount(OperationNameValue),
    DistinctResourceTypes = dcount(tostring(split(ResourceId, "/")[3])),
    DistinctSubscriptions = dcount(SubscriptionId),
    OperationList = make_set(OperationNameValue, 30),
    ResourceList = make_set(ResourceId, 30)
    by Caller, CallerIpAddress, bin(TimeGenerated, EnumerationWindow)
| where OperationCount >= ListOperationThreshold or DistinctResourceTypes >= 8
| extend RiskScore = case(
    DistinctResourceTypes >= 15, "Critical",
    DistinctResourceTypes >= 8, "High",
    OperationCount >= 50, "High",
    "Medium"
)
| project TimeGenerated, Caller, CallerIpAddress, OperationCount, DistinctOperations, DistinctResourceTypes, DistinctSubscriptions, RiskScore, OperationList, ResourceList
| sort by DistinctResourceTypes desc, OperationCount desc
// ---
// Detection 2: Microsoft Graph API service enumeration (via AuditLogs)
// Run separately
// AuditLogs
// | where TimeGenerated > ago(1h)
// | where Category in ("Core Directory", "Application Management", "Policy")
// | where OperationName has_any (
//     "Get servicePrincipal", "List servicePrincipals",
//     "Get application", "List applications",
//     "Get policy", "List policies",
//     "Get organization", "Get domain",
//     "Get directoryRole", "List directoryRoles",
//     "List roleAssignments", "List groupMembers"
// )
// | extend InitiatedByUser = tostring(InitiatedBy.user.userPrincipalName)
// | extend InitiatedByApp = tostring(InitiatedBy.app.displayName)
// | extend SourceIP = tostring(InitiatedBy.user.ipAddress)
// | summarize
//     EnumOperations = count(),
//     DistinctOps = dcount(OperationName),
//     OperationSet = make_set(OperationName, 20)
//     by InitiatedByUser, InitiatedByApp, SourceIP, bin(TimeGenerated, 10m)
// | where EnumOperations >= 10 or DistinctOps >= 5
// | sort by EnumOperations desc

Detects bulk cloud service enumeration via Azure Resource Manager. The primary query identifies callers who issue many List/Get/Read operations across multiple resource types within a short window — a pattern consistent with tools like Stormspotter, AADInternals, ROADTools, and manual reconnaissance. DistinctResourceTypes is the strongest signal: legitimate users rarely enumerate 8+ distinct Azure resource type namespaces in 10 minutes. The commented-out secondary query uses AuditLogs to detect Microsoft Graph API enumeration of applications, service principals, policies, and directory roles — run as a separate rule. Both can be combined with DistinctSubscriptions > 1 to flag cross-subscription recon.

medium severity medium confidence

Data Sources

Cloud Service: Cloud Service Enumeration Azure Activity Logs Microsoft Entra ID Audit Logs Azure Resource Manager

Required Tables

AzureActivity AuditLogs

False Positives

  • Cloud infrastructure automation tools (Terraform, Pulumi, Bicep) performing state refresh operations that enumerate all resource types across a subscription
  • Azure Security Center, Microsoft Defender for Cloud, or third-party CSPM platforms performing continuous posture assessments that enumerate resources
  • DevOps pipelines with service principals that run 'az resource list' or similar commands during environment validation steps
  • Cloud governance tools (Azure Policy compliance scans, Azure Advisor) that regularly enumerate resources to generate recommendations
  • IT administrators conducting authorized cloud inventory or migration assessments using tools like Azure Migrate or Azure Resource Graph

Sigma rule & cross-platform mapping

The detection logic for Cloud Service Discovery (T1526) above is provided in a vendor-neutral form so you can deploy it on any SIEM. The same logic is shipped here as native KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the following logsource:

logsource:
  product: azure

Browse the community-maintained Sigma rules for this technique:


Testing Methodology

Validate this detection against 4 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.

  1. Test 1Azure Resource Enumeration via Azure CLI

    Expected signal: AzureActivity log entries for each az CLI command with OperationNameValue containing Microsoft.Compute/virtualMachines/read, Microsoft.Network/virtualNetworks/read, Microsoft.Storage/storageAccounts/read, Microsoft.KeyVault/vaults/read, Microsoft.Web/sites/read, Microsoft.ContainerService/managedClusters/read, Microsoft.Security/autoProvisioningSettings/read. Caller will be the authenticated user or service principal. CallerIpAddress will reflect the source machine's IP.

  2. Test 2Entra ID Enumeration via AADInternals PowerShell Module

    Expected signal: AuditLogs entries with Category='Core Directory' for tenant and domain read operations. User-Agent field in AdditionalDetails will contain 'AADInternals'. SigninLogs will show authentication events for the token acquisition. MicrosoftGraphActivityLogs (if enabled) will show HTTP GET requests to /v1.0/organization, /v1.0/domains, /v1.0/servicePrincipals with User-Agent='AADInternals'.

  3. Test 3Microsoft Graph API Service Principal Enumeration via PowerShell

    Expected signal: AuditLogs entries: OperationName='List servicePrincipals', 'List applications', 'List directoryRoles', 'Get organization', 'Get policy' with Category='Core Directory' and 'ApplicationManagement'. InitiatedBy will reflect the authenticated user. MicrosoftGraphActivityLogs will show GET requests to /v1.0/servicePrincipals, /v1.0/applications, /v1.0/directoryRoles, /v1.0/organization, /v1.0/policies/authorizationPolicy.

  4. Test 4AWS Cloud Service Discovery via Pacu Framework

    Expected signal: AWS CloudTrail logs: DescribeTrails, ListDetectors, DescribeInstances, ListRoles, ListBuckets, ListFunctions, ListClusters, ListSecrets events with eventSource matching cloudtrail.amazonaws.com, guardduty.amazonaws.com, ec2.amazonaws.com, iam.amazonaws.com, s3.amazonaws.com, lambda.amazonaws.com, ecs.amazonaws.com, secretsmanager.amazonaws.com. userAgent will contain 'aws-cli'. sourceIPAddress will reflect the caller's IP.


Response Playbook

Triage

  1. Identify the enumerating identity — is this a user account (UPN), managed identity, service principal, or application? Pull the full ObjectId and check Azure AD for the account's assigned roles and recent creation date.
  2. Review the source IP address — is it associated with a corporate egress IP, known automation infrastructure, or a residential/VPN/TOR address? Use AzureActivity CallerIpAddress and cross-reference with SigninLogs for the same IP.
  3. Assess the breadth of enumeration — how many distinct resource types, subscriptions, and resource groups were accessed? Breadth across unrelated resource types (storage + compute + security + identity) is more suspicious than deep enumeration of a single type.
  4. Check for known tool signatures — search AuditLogs for User-Agent strings associated with AADInternals ('AADInternals'), ROADTools ('python-requests' combined with high-volume Graph API reads), or Stormspotter. Also check for Graph API calls to /beta endpoints which tooling commonly uses.
  5. Determine temporal context — did enumeration start immediately after an unusual sign-in (new location, first-time IP, MFA bypass)? Correlate with SigninLogs using the same Caller identity within the prior 30 minutes.
  6. Check if security services were specifically targeted — look for enumeration of Microsoft Defender for Cloud policies, Azure Security Center, Log Analytics workspaces, Sentinel configurations, or storage account enumeration that might indicate logging infrastructure discovery.
  7. Review what the identity does normally — pull a 30-day baseline of the caller's AzureActivity operations. An identity that has never enumerated resources suddenly doing broad discovery is a strong anomaly signal.

Containment

  1. If the enumerating identity is a user account and compromise is suspected: immediately revoke all active sessions and refresh tokens via Azure AD (Revoke-AzureADUserAllRefreshToken or portal Revoke Sessions), then disable the account pending investigation.
  2. If the enumerating identity is a service principal or application: rotate all associated client secrets and certificates immediately, then review all OAuth consent grants for that application to identify delegated permissions that may have been abused.
  3. If the source IP is external and not associated with legitimate infrastructure: create a Conditional Access policy blocking sign-ins from that IP range and review all other accounts that signed in from the same IP.
  4. If enumeration touched security configurations (Defender for Cloud policies, Sentinel workspaces, storage accounts with logs): immediately verify that log forwarding, audit log settings, and Defender policies remain intact and have not been modified — check for T1562.008 (Disable or Modify Cloud Logs) as a follow-on.
  5. Apply temporary Azure Policy with deny effect on the affected subscription to block non-approved read operations while investigation is ongoing if breadth of enumeration indicates active threat actor reconnaissance.

Evidence Collection

  1. AzureActivity log export: all operations by the Caller identity for the past 7 days, including OperationNameValue, ResourceId, CallerIpAddress, ActivityStatusValue, and CorrelationId — CorrelationId groups related API calls from a single tool execution.
  2. AuditLogs (Azure AD): all operations by InitiatedBy.user or InitiatedBy.app for the past 7 days, focusing on targetResources, operationName, and additionalDetails which may contain HTTP User-Agent strings revealing the tool used.
  3. SigninLogs: all sign-in events for the identity within 24 hours before and after the enumeration, including DeviceDetail, LocationDetail, ClientAppUsed, ConditionalAccessStatus, and RiskDetail.
  4. Microsoft Graph API access logs (if enabled): access to /v1.0/servicePrincipals, /v1.0/applications, /v1.0/policies, /v1.0/organization, /beta/* endpoints. Enable via Azure AD diagnostic settings → SignInLogs → MicrosoftGraphActivityLogs.
  5. Azure AD risk detections: check Identity Protection for any flagged sign-in risk events (anonymized IP, atypical travel, malicious IP address, unfamiliar sign-in properties) correlated with the enumeration identity.
  6. Key Vault access logs (if applicable): if the identity accessed Key Vault during or after enumeration, collect vault access logs from Azure Monitor — credential harvesting often follows service discovery.
  7. Resource Graph query to enumerate all resources the identity has read permissions on: run 'az role assignment list --assignee <ObjectId>' to understand the blast radius of what the identity could access.

Escalation Criteria

  • ! Enumeration identity shows simultaneous or prior sign-in risk flags in Azure AD Identity Protection (risk level medium or high) — active token compromise scenario.
  • ! Discovery of security services specifically targeted: enumeration includes Defender for Cloud policies, Log Analytics workspaces, Sentinel alert rules, or storage accounts that contain diagnostic logs — indicates attacker mapping defenses before disabling them.
  • ! Cross-subscription enumeration (DistinctSubscriptions > 1) — suggests the attacker has credentials with broad scope, likely a high-privilege service principal or a compromised Global Administrator account.
  • ! Post-enumeration activity observed: AzureActivity shows write/delete/modify operations (not just reads) following the discovery phase — the attacker has moved from reconnaissance to exploitation.
  • ! Tool-specific User-Agent detected in audit logs: strings matching AADInternals, ROADTools, Stormspotter, Pacu, or 'python-requests' with high-volume Graph API calls to identity endpoints indicate use of known red team / attacker tooling.
  • ! Enumeration followed by privilege escalation attempts: role assignment creation events, application permission grants, or conditional access policy modifications within 1 hour of discovery activity.

Investigation Guide

Forensic Artifacts

  • > Azure Activity Log: AzureActivity table in Log Analytics — all control plane operations including caller identity, source IP, operation name, resource path, and HTTP status. Retained 90 days by default.
  • > Azure AD Audit Logs: AuditLogs table — all identity operations including Graph API reads of applications, service principals, policies, and directory objects. User-Agent in additionalDetails field when Graph API is used directly.
  • > Microsoft Graph Activity Logs (preview): MicrosoftGraphActivityLogs table in Log Analytics when enabled via diagnostic settings — captures each Graph API request with requestUri, userAgent, clientAuthMethod, and tokenIssuedAt.
  • > Azure AD Sign-in Logs: SigninLogs and AADNonInteractiveUserSignInLogs — authentication context for the identity performing enumeration including deviceDetail.operatingSystem, locationDetail.city, and conditionalAccessStatus.
  • > Azure AD Risk Detections: AADRiskyUsers and AADUserRiskEvents tables — Identity Protection signals that may correlate with the enumeration event (token theft, anonymous IP, atypical travel).
  • > Azure AD Service Principal Sign-ins: AADServicePrincipalSignInLogs — for service principal or managed identity enumeration, includes clientCredentialType and resourceDisplayName.
  • > Key Vault audit logs: AzureDiagnostics with ResourceType=VAULTS — if enumeration progressed to credential access, Key Vault access events include CallerIPAddress, operationName (SecretGet, SecretList), and httpStatusCode.
  • > Cloud Shell command history: if the adversary used Azure Cloud Shell, commands may be recoverable from the mounted Azure Files share at ~/clouddrive/.cloudconsole/ or shell history files in the user's storage account.

Tuning Guidance

The primary tuning challenge is distinguishing legitimate IaC and automation enumeration from adversarial reconnaissance. Start by building an allowlist of known automation service principals (Terraform, Pulumi, Azure DevOps pipeline identities) and excluding their ObjectIds from the high-volume enumeration alert. These identities will reliably exceed the DistinctResourceTypes threshold during plan/apply operations. Raise the DistinctResourceTypes threshold to 12+ for environments with heavy automation, and lower it to 5+ for environments where human-initiated Azure activity is the norm. For the Graph API enumeration query, focus on non-application identities (user accounts making bulk Graph calls are higher fidelity than service principals, since most legitimate Graph enumeration comes from apps). Enable MicrosoftGraphActivityLogs in Azure AD Diagnostic Settings — this provides the HTTP User-Agent field which is the single most reliable discriminator for tool-based enumeration (AADInternals inserts 'AADInternals' directly into the User-Agent). The risky-sign-in-plus-enumeration hunting query has high fidelity and should be promoted to an alert rule — the combination of anomalous authentication followed by immediate discovery is rarely benign. For AWS environments not covered here, apply the same logic to CloudTrail events: look for ListBuckets, DescribeInstances, ListFunctions, DescribeSecurityGroups, ListUsers, GetCallerIdentity calls from a new or unusual source IP within a compressed timeframe.


Hunting Queries

Hunt for known cloud enumeration tool signatures (AADInternals, ROADTools, Stormspotter, PowerZure, MicroBurst) in Azure AD Audit Log User-Agent fields. These tools are used by red teamers and threat actors for automated Azure/Entra ID enumeration. Generic HTTP clients (python-requests, Go-http-client) combined with bulk directory reads are also suspicious.

Hunting — KQL
kql
// Hunt for AADInternals and known cloud enumeration tool signatures in Graph API calls
AuditLogs
| where TimeGenerated > ago(7d)
| extend UserAgent = tostring(AdditionalDetails[0].value)
| extend InitiatedByUser = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatedByApp = tostring(InitiatedBy.app.displayName)
| extend SourceIP = tostring(InitiatedBy.user.ipAddress)
| where UserAgent has_any (
    "AADInternals", "roadtools", "ROADtools",
    "Stormspotter", "PowerZure", "MicroBurst",
    "python-requests", "Go-http-client", "axios"
  )
  and Category has_any ("Core Directory", "ApplicationManagement", "Policy")
| summarize
    OperationCount = count(),
    DistinctOps = dcount(OperationName),
    UserAgents = make_set(UserAgent),
    Operations = make_set(OperationName, 20)
    by InitiatedByUser, InitiatedByApp, SourceIP, bin(TimeGenerated, 1h)
| sort by OperationCount desc
Hunting — SPL
spl
index=azure sourcetype="azure:aad:audit" category IN ("Core Directory", "ApplicationManagement", "Policy")
| spath input=additionalDetails{} output=detail_value path={}.value
| eval user_agent=mvindex(detail_value, 0)
| where isnotnull(user_agent) AND (
    lower(user_agent) LIKE "%aadinternals%" OR
    lower(user_agent) LIKE "%roadtools%" OR
    lower(user_agent) LIKE "%stormspotter%" OR
    lower(user_agent) LIKE "%powerzure%" OR
    lower(user_agent) LIKE "%microburst%" OR
    lower(user_agent) LIKE "%python-requests%" OR
    lower(user_agent) LIKE "%go-http-client%"
)
| eval initiator=coalesce('initiatedBy.user.userPrincipalName', 'initiatedBy.app.displayName', "unknown")
| stats count as op_count, dc(operationName) as distinct_ops, values(operationName) as operations, values(user_agent) as user_agents
    by initiator, _time span=1h
| sort - op_count

Hunt for identities that perform cloud resource enumeration within 60 minutes of a risky or anomalous sign-in event. This chain (risky sign-in → immediate enumeration) is characteristic of compromised credential use where an attacker is rapidly assessing the environment after gaining access. Correlates SigninLogs risk signals with AzureActivity enumeration patterns.

Hunting — KQL
kql
// Hunt for identity performing enumeration immediately after a risky or first-time sign-in
let RiskySignins = SigninLogs
| where TimeGenerated > ago(7d)
| where RiskLevelDuringSignIn in ("medium", "high") 
    or IsRisky == true
    or LocationDetails has_any ("Tor", "Anonymous")
| project SigninTime=TimeGenerated, UserPrincipalName, IPAddress, RiskLevel=RiskLevelDuringSignIn, Location=LocationDetails;
let PostSigninEnum = AzureActivity
| where TimeGenerated > ago(7d)
| where ActivityStatusValue =~ "Success"
| where OperationNameValue has_any ("list", "/read")
| summarize EnumStart=min(TimeGenerated), OpCount=count(), ResourceTypes=dcount(tostring(split(ResourceId,"/")[3]))
    by Caller, CallerIpAddress;
RiskySignins
| join kind=inner PostSigninEnum on $left.UserPrincipalName == $right.Caller
| where EnumStart > SigninTime and EnumStart < datetime_add('minute', 60, SigninTime)
| where ResourceTypes >= 3
| project SigninTime, EnumStart, UserPrincipalName, IPAddress, RiskLevel, Location, OpCount, ResourceTypes
| sort by ResourceTypes desc
Hunting — SPL
spl
index=azure sourcetype="azure:signin" riskLevelDuringSignIn IN ("medium", "high") OR isRisky=true
| eval signin_time=_time
| eval signin_user=userPrincipalName
| eval signin_ip=ipAddress
| table signin_time, signin_user, signin_ip, riskLevelDuringSignIn, location
| join type=inner signin_user [
    search index=azure sourcetype="azure:activity" status=Succeeded
        (lower(operationName) LIKE "%list%" OR lower(operationName) LIKE "%/read")
    | eval resource_type=mvindex(split(resourceId, "/"), 3)
    | stats min(_time) as enum_start, count as op_count, dc(resource_type) as resource_types
        by caller
    | rename caller as signin_user
]
| where enum_start > signin_time AND enum_start < (signin_time + 3600)
| where resource_types >= 3
| table signin_time, enum_start, signin_user, signin_ip, riskLevelDuringSignIn, op_count, resource_types
| sort - resource_types

Hunt for identities (especially service principals) with no or minimal prior Azure Activity history that suddenly perform broad cloud service enumeration. New identities performing wide discovery are characteristic of newly obtained tokens or freshly created attacker-controlled service principals performing initial reconnaissance.

Hunting — KQL
kql
// Hunt for service principals with no prior activity suddenly performing broad enumeration
let BaselinePeriod = 30d;
let DetectionWindow = 24h;
let EstablishedCallers = AzureActivity
| where TimeGenerated between (ago(BaselinePeriod) .. ago(DetectionWindow))
| summarize HistoricalOps=count() by Caller
| where HistoricalOps > 10;
AzureActivity
| where TimeGenerated > ago(DetectionWindow)
| where ActivityStatusValue =~ "Success"
| where OperationNameValue has_any ("list", "/read", "/get")
| summarize
    RecentOps=count(),
    ResourceTypes=dcount(tostring(split(ResourceId,"/")[3])),
    Subscriptions=dcount(SubscriptionId),
    Operations=make_set(OperationNameValue, 15)
    by Caller, CallerIpAddress
| where ResourceTypes >= 5
| join kind=leftanti EstablishedCallers on Caller
| project Caller, CallerIpAddress, RecentOps, ResourceTypes, Subscriptions, Operations
| sort by ResourceTypes desc
Hunting — SPL
spl
index=azure sourcetype="azure:activity" status=Succeeded
    (lower(operationName) LIKE "%list%" OR lower(operationName) LIKE "%/read" OR lower(operationName) LIKE "%/get%")
| eval resource_type=mvindex(split(resourceId, "/"), 3)
| eval is_recent=if(_time >= relative_time(now(), "-24h"), 1, 0)
| eval is_baseline=if(_time < relative_time(now(), "-24h") AND _time >= relative_time(now(), "-30d"), 1, 0)
| stats sum(is_recent) as recent_ops, sum(is_baseline) as baseline_ops,
    dc(eval(if(is_recent=1, resource_type, null()))) as recent_resource_types
    by caller, callerIpAddress
| where recent_resource_types >= 5 AND baseline_ops < 5
| sort - recent_resource_types

Atomic Red Team Tests

Test 1 Azure Resource Enumeration via Azure CLI
windows

Enumerates Azure resources and services across a subscription using standard Azure CLI commands. This simulates an attacker who has obtained valid Azure credentials and is performing initial cloud service discovery to understand the environment. The commands list resource types, compute resources, network infrastructure, and storage accounts — breadth of enumeration matches the detection logic.

Command

powershell
az login --use-device-code
az account list --output table
az resource list --output table
az vm list --output table
az network vnet list --output table
az storage account list --output table
az keyvault list --output table
az webapp list --output table
az functionapp list --output table
az aks list --output table
az security auto-provisioning-setting list --output table

Cleanup

powershell
az logout

Expected Telemetry

AzureActivity log entries for each az CLI command with OperationNameValue containing Microsoft.Compute/virtualMachines/read, Microsoft.Network/virtualNetworks/read, Microsoft.Storage/storageAccounts/read, Microsoft.KeyVault/vaults/read, Microsoft.Web/sites/read, Microsoft.ContainerService/managedClusters/read, Microsoft.Security/autoProvisioningSettings/read. Caller will be the authenticated user or service principal. CallerIpAddress will reflect the source machine's IP.

Expected Detection

AzureActivity query fires: DistinctResourceTypes >= 8 across Microsoft.Compute, Microsoft.Network, Microsoft.Storage, Microsoft.KeyVault, Microsoft.Web, Microsoft.ContainerService, Microsoft.Security within the 10-minute window. RiskScore = High or Critical depending on breadth.

Test 2 Entra ID Enumeration via AADInternals PowerShell Module
windows

Uses the AADInternals PowerShell module (a publicly available tool used by red teamers and threat actors including those affiliated with Storm-0501) to enumerate Entra ID tenant information, domains, OpenID configuration, and service principals. This directly simulates the procedure examples documented in MITRE ATT&CK for T1526.

Command

powershell
Install-Module -Name AADInternals -Force -Scope CurrentUser
Import-Module AADInternals
Get-AADIntLoginInformation -Domain target-tenant.onmicrosoft.com
Get-AADIntOpenIDConfiguration -Domain target-tenant.onmicrosoft.com
$token = Get-AADIntAccessTokenForMSGraph
Get-AADIntTenantDetails -AccessToken $token
Get-AADIntDomains -AccessToken $token

Cleanup

powershell
Remove-Module AADInternals -ErrorAction SilentlyContinue

Expected Telemetry

AuditLogs entries with Category='Core Directory' for tenant and domain read operations. User-Agent field in AdditionalDetails will contain 'AADInternals'. SigninLogs will show authentication events for the token acquisition. MicrosoftGraphActivityLogs (if enabled) will show HTTP GET requests to /v1.0/organization, /v1.0/domains, /v1.0/servicePrincipals with User-Agent='AADInternals'.

Expected Detection

Hunting query for tool User-Agent signatures fires immediately on 'AADInternals' in AdditionalDetails. AuditLogs Graph API enumeration query fires on Graph API operations if volume threshold is reached. The User-Agent hunt query is highest confidence for this test.

Test 3 Microsoft Graph API Service Principal Enumeration via PowerShell
windows

Enumerates Entra ID objects via Microsoft Graph API using PowerShell with the Microsoft.Graph module. Lists service principals, applications, directory roles, and assigned role members — the sequence of calls matches ROADTools and manual attacker reconnaissance patterns against Azure AD / Entra ID. Requires an authenticated user or app with Directory.Read.All permissions.

Command

powershell
Install-Module -Name Microsoft.Graph -Force -Scope CurrentUser
Connect-MgGraph -Scopes "Directory.Read.All","Application.Read.All","RoleManagement.Read.Directory"
Get-MgServicePrincipal -All | Select-Object DisplayName, AppId, ServicePrincipalType | Out-File $env:TEMP\sp_enum.txt
Get-MgApplication -All | Select-Object DisplayName, AppId, CreatedDateTime | Out-File $env:TEMP\app_enum.txt
Get-MgDirectoryRole -All | Select-Object DisplayName, RoleTemplateId | Out-File $env:TEMP\roles_enum.txt
Get-MgOrganization | Select-Object DisplayName, Id, TenantType | Out-File $env:TEMP\org_enum.txt
Get-MgPolicyAuthorizationPolicy | Out-File $env:TEMP\policy_enum.txt

Cleanup

powershell
Remove-Item $env:TEMP\sp_enum.txt,$env:TEMP\app_enum.txt,$env:TEMP\roles_enum.txt,$env:TEMP\org_enum.txt,$env:TEMP\policy_enum.txt -ErrorAction SilentlyContinue
Disconnect-MgGraph

Expected Telemetry

AuditLogs entries: OperationName='List servicePrincipals', 'List applications', 'List directoryRoles', 'Get organization', 'Get policy' with Category='Core Directory' and 'ApplicationManagement'. InitiatedBy will reflect the authenticated user. MicrosoftGraphActivityLogs will show GET requests to /v1.0/servicePrincipals, /v1.0/applications, /v1.0/directoryRoles, /v1.0/organization, /v1.0/policies/authorizationPolicy.

Expected Detection

AuditLogs Graph API enumeration query fires: DistinctOps >= 5 across service principal, application, role, organization, and policy reads within 10 minutes. Hunting query for new identities performing broad enumeration fires if this identity has minimal historical activity.

Test 4 AWS Cloud Service Discovery via Pacu Framework
linux

Uses the Pacu AWS exploitation framework (documented in MITRE ATT&CK T1526 procedure examples) to enumerate AWS services including CloudTrail logging configuration, GuardDuty detector status, IAM roles, and EC2 instances. Simulates attacker reconnaissance of both operational resources and security/logging services to identify what defenses are in place.

Command

bash
pip3 install pacu --quiet
python3 -c "
import subprocess
# Simulating Pacu-style AWS CLI enumeration without full Pacu install
subprocess.run(['aws', 'cloudtrail', 'describe-trails'], capture_output=False)
subprocess.run(['aws', 'guardduty', 'list-detectors'], capture_output=False)
subprocess.run(['aws', 'ec2', 'describe-instances', '--query', 'Reservations[].Instances[].{ID:InstanceId,State:State.Name}'], capture_output=False)
subprocess.run(['aws', 'iam', 'list-roles', '--query', 'Roles[].{Name:RoleName,ARN:Arn}'], capture_output=False)
subprocess.run(['aws', 's3', 'ls'], capture_output=False)
subprocess.run(['aws', 'lambda', 'list-functions', '--query', 'Functions[].FunctionName'], capture_output=False)
subprocess.run(['aws', 'ecs', 'list-clusters'], capture_output=False)
subprocess.run(['aws', 'secretsmanager', 'list-secrets', '--query', 'SecretList[].Name'], capture_output=False)
"

Expected Telemetry

AWS CloudTrail logs: DescribeTrails, ListDetectors, DescribeInstances, ListRoles, ListBuckets, ListFunctions, ListClusters, ListSecrets events with eventSource matching cloudtrail.amazonaws.com, guardduty.amazonaws.com, ec2.amazonaws.com, iam.amazonaws.com, s3.amazonaws.com, lambda.amazonaws.com, ecs.amazonaws.com, secretsmanager.amazonaws.com. userAgent will contain 'aws-cli'. sourceIPAddress will reflect the caller's IP.

Expected Detection

AWS GuardDuty Finding: Recon:IAMUser/ResourcePermissions or Discovery:IAMUser/AnomalousBehavior may fire. For Splunk environments ingesting CloudTrail, a similar enumeration-breadth query counting distinct eventSources from the same userIdentity.arn within a 10-minute window should fire with distinct_service_count >= 6.

Related Detections

Tactic Hub