Cloud Service Dashboard
An adversary may use a cloud service dashboard GUI with stolen credentials to gain useful information from an operational cloud environment, such as specific services, resources, and features. Cloud service dashboards (AWS Management Console, Azure Portal, GCP Cloud Console) provide rich graphical interfaces that may expose more configuration details than programmatic API calls, allowing adversaries to enumerate running instances, storage buckets, IAM roles, network configurations, and security findings. Because dashboard access uses standard web browser sessions, it may blend into legitimate user activity and bypass controls focused on API-level telemetry. Scattered Spider, for example, abused AWS Systems Manager Inventory after gaining console access to identify lateral movement targets.
What is T1538 Cloud Service Dashboard?
Cloud Service Dashboard (T1538) 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 Dashboard, covering the data sources and telemetry it touches: Logon Session: Logon Session Creation, Cloud Service: Cloud Service Metadata, Azure AD Sign-In Logs, AWS CloudTrail ConsoleLogin Events. 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
- T1538 Cloud Service Dashboard
- Canonical reference
- https://attack.mitre.org/techniques/T1538/
// Detection 1: Suspicious Azure Portal sign-in patterns
let HighRiskCountryCodes = dynamic(["CN", "RU", "KP", "IR", "BY", "CU", "SY"]);
let CloudDashboardApps = dynamic(["Azure Portal", "Microsoft Azure Portal", "Azure Active Directory Portal", "Microsoft 365 admin center", "Azure DevOps"]);
SigninLogs
| where TimeGenerated > ago(24h)
| where AppDisplayName in (CloudDashboardApps)
| extend CountryCode = tostring(LocationDetails.countryOrRegion)
| extend City = tostring(LocationDetails.city)
| extend Latitude = toreal(LocationDetails.geoCoordinates.latitude)
| extend Longitude = toreal(LocationDetails.geoCoordinates.longitude)
| extend IsHighRiskCountry = CountryCode in (HighRiskCountryCodes)
| extend IsRiskySignIn = RiskLevelDuringSignIn in ("high", "medium")
| extend IsFailed = ResultType != 0
| extend IsNoMFA = AuthenticationRequirement == "singleFactorAuthentication"
| extend SuspicionScore = toint(IsHighRiskCountry) + toint(IsRiskySignIn) + toint(IsNoMFA)
| where SuspicionScore > 0 or IsFailed
| project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress,
CountryCode, City, RiskLevelDuringSignIn, RiskLevelAggregated,
ResultType, ResultDescription, ConditionalAccessStatus,
AuthenticationRequirement, IsHighRiskCountry, IsRiskySignIn, IsNoMFA,
SuspicionScore, UserAgent
| sort by TimeGenerated desc
// ---
// Detection 2: AWS Management Console login events via AWS CloudTrail connector
// (Requires AWS CloudTrail ingestion into Microsoft Sentinel via AWS S3 connector)
// AWSCloudTrail
// | where TimeGenerated > ago(24h)
// | where EventName == "ConsoleLogin"
// | extend AdditionalData = parse_json(AdditionalEventData)
// | extend MFAUsed = tostring(AdditionalData.MFAUsed)
// | extend ConsoleLoginResult = tostring(parse_json(ResponseElements).ConsoleLogin)
// | extend UserType = tostring(parse_json(UserIdentity).type)
// | extend IsRoot = UserType == "Root"
// | extend IsNoMFA = MFAUsed == "No"
// | extend IsFailedLogin = ConsoleLoginResult == "Failure"
// | where IsRoot or IsNoMFA or IsFailedLogin
// | project TimeGenerated, UserIdentityArn, SourceIpAddress, UserAgent,
// MFAUsed, UserType, AWSRegion, ConsoleLoginResult, IsRoot, IsNoMFA
// | sort by TimeGenerated desc Primary detection targets Azure Portal sign-in events from SigninLogs, identifying suspicious console access patterns including sign-ins from high-risk country codes, identity risk signals from Azure AD Identity Protection, and single-factor authentication to cloud dashboards. A suspicion score aggregates multiple indicators. A secondary commented query targets AWS Management Console ConsoleLogin events via the AWSCloudTrail table (requires AWS CloudTrail connector). Focus on root account console logins, console access without MFA, and failed login attempts that precede successful access.
Data Sources
Required Tables
False Positives
- Legitimate system administrators accessing cloud dashboards from travel locations or home offices with VPN egress IPs in unexpected geographic regions
- Security operations teams conducting cloud configuration audits or compliance reviews using personal accounts that trigger risk signals
- Automated monitoring tools that use service accounts to access Azure Portal for health-check dashboards, generating sign-in log entries
- Cloud contractors or third-party vendors accessing client environments from their own corporate IP ranges, which may appear anomalous to the tenant
- Azure AD Identity Protection false positives on risk scoring for users with atypical but legitimate travel or remote work patterns
Sigma rule & cross-platform mapping
The detection logic for Cloud Service Dashboard (T1538) 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:
Platform-specific guides for T1538
References (7)
- https://attack.mitre.org/techniques/T1538/
- https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-aws-console-sign-in-events.html
- https://cloud.google.com/security-command-center/docs/quickstart-scc-dashboard
- https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a
- https://learn.microsoft.com/en-us/azure/active-directory/reports-monitoring/reference-azure-monitor-sign-ins-log-schema
- https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/azureactivity
- https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-inventory-about.html
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.
- Test 1AWS Console Sign-In URL Generation via STS (Federated Access Simulation)
Expected signal: AWS CloudTrail: GetFederationToken event from userIdentity of the caller IAM user, with requestParameters showing the policy document. The ConsoleLogin event in CloudTrail (eventSource: signin.amazonaws.com) fires when the generated URL is clicked in a browser, with additionalEventData.MFAUsed=No and userIdentity.type=FederatedUser.
- Test 2AWS Systems Manager Inventory Enumeration Post-Console-Access (Scattered Spider TTP)
Expected signal: AWS CloudTrail: DescribeInstanceInformation (eventName), ListInventoryEntries, and ListDocuments events under eventSource=ssm.amazonaws.com. All events carry the caller's IAM identity, source IP, userAgent (aws-cli or browser), and requestParameters. If called from a browser console session, the userIdentity.sessionContext will reference the console session.
- Test 3Azure Portal Resource Enumeration via Azure CLI (Stolen Token Simulation)
Expected signal: AzureActivity table in Sentinel: Microsoft.Resources/subscriptions/read, Microsoft.Resources/resourceGroups/read, Microsoft.Compute/virtualMachines/read, Microsoft.Storage/storageAccounts/read events with Caller matching the authenticated user principal. AADSignInLogs: service principal or user sign-in event for Azure CLI app (appId: 04b07795-8ddb-461a-bbee-02f9e1bf7b46). All events carry the source IP of the machine running the CLI.
- Test 4GCP Cloud Console Asset Enumeration via gcloud CLI
Expected signal: GCP Cloud Audit Logs: cloudresourcemanager.googleapis.com/projects.list, compute.instances.list, storage.buckets.list, iam.projects.getIamPolicy, and securitycenter.findings.list data access events. All entries include principalEmail (the caller), callerIp, userAgent (cloud-sdk/gcloud), and methodName. These logs appear in Cloud Audit Logs — Data Access log type and can be exported to Splunk via Pub/Sub or to Sentinel via the GCP connector.
Response Playbook
Triage
- Identify the user account involved — is it a human user, service account, or root/break-glass account? Pull account creation date, recent password changes, and MFA enrollment status from the identity provider
- Geolocate the source IP using threat intelligence. Check if the IP is associated with residential ISPs, commercial VPNs, Tor exit nodes, or cloud hosting providers (which adversaries use as jump points). Compare to the user's usual sign-in geography
- Review the timeline of events immediately before and after the dashboard access — did the user recently authenticate to their email (potential phishing), reset their password, or have MFA disabled? Look for AAD audit events 30–60 minutes before the sign-in
- Check what the user did after logging into the console — in AWS, pull subsequent CloudTrail events by the same user session (sessionId); in Azure, query AzureActivity for resource enumeration events correlated by user principal
- Assess the authentication strength — was this a single-factor login? Was it a new device? Does the user have a Conditional Access policy that should have blocked this access from the source IP or country?
- Contact the user out-of-band (phone or Slack, not email — email may be compromised) to confirm whether they initiated this session. Do NOT rely solely on email confirmation if credentials were potentially stolen
Containment
- If the session is confirmed unauthorized: immediately revoke all active sessions in Azure AD (User > Revoke sessions) or AWS (IAM > Security credentials > Sign-in sessions invalidate), and disable the account pending investigation
- For AWS root account compromise: contact AWS Support immediately, change the root account password and MFA, and rotate all access keys. Root compromise requires immediate escalation to CISO
- If the adversary accessed AWS Systems Manager or similar inventory services (Scattered Spider TTP): isolate potentially enumerated EC2 instances by moving them to a restricted security group that blocks outbound traffic
- Rotate all credentials associated with the compromised identity — passwords, API keys, OAuth tokens, and service principal secrets
- Enable or strengthen Conditional Access / Service Control Policies to block access from the source IP range, ASN, or country for the duration of the investigation
- Preserve the CloudTrail / Azure audit logs for the relevant session before any log retention windows expire — export to a separate storage account that the compromised identity cannot access
Evidence Collection
- AWS CloudTrail: filter by userIdentity.arn and sessionContext.sessionIssuer for the full session — eventTime, eventName, requestParameters, responseElements for all API calls made during the console session
- Azure AD SigninLogs: collect IPAddress, LocationDetails, DeviceDetail, MfaDetail, AuthenticationDetails, ConditionalAccessPolicies for the specific sign-in event (correlate on CorrelationId)
- Azure Activity Log: query AzureActivity filtered by Caller == <compromised UPN> and TimeGenerated in the session window — shows all resource reads, writes, and enumerations made through the portal
- AWS CloudTrail GetConsoleURL and AssumeRole events: identify if the adversary used role chaining or console URL generation via the API before browser-based access
- Browser fingerprint artifacts from the dashboard session — UserAgent string, screen resolution, timezone offset (available in Azure AD SigninLogs DeviceDetail and AWS CloudTrail userAgent fields)
- Identity Protection risk events in Azure AD — pull all risk events for the user (sign-in risk, user risk, risky workloads) for correlation with the incident timeline
- DNS and proxy logs for the dashboard domains (console.aws.amazon.com, portal.azure.com, console.cloud.google.com) from the corporate network during the session window to identify if access originated from within or outside the perimeter
Escalation Criteria
- ! Root/global administrator account used to access the cloud dashboard — any unauthorized root account console access is an automatic P1 incident requiring immediate CISO notification
- ! Evidence of post-access enumeration: IAM policy reads, user/role listing, S3/storage account enumeration, EC2/VM inventory queries, or security service configuration reads within the same session
- ! AWS Systems Manager Inventory or equivalent cloud asset enumeration APIs called after console login — this is a confirmed Scattered Spider TTP used to identify lateral movement targets
- ! Console access followed immediately by resource modification: security group changes, new IAM user creation, storage access policy modifications, or MFA device removal for other accounts
- ! Multiple geographic locations for the same user within an impossible travel timeframe (sign-in from US and EU within minutes)
- ! Adversary accesses cloud console from a known malicious IP or ASN confirmed by threat intelligence — treat as confirmed compromise regardless of whether post-access activity is visible
Investigation Guide
Forensic Artifacts
- >
AWS CloudTrail: ConsoleLogin event with eventSource=signin.amazonaws.com, MFAUsed field, and sourceIPAddress — authoritative record of all AWS Console sign-ins - >
AWS CloudTrail: GetConsoleURL event (eventName=GetConsoleURL) — generated when federated users or roles create a console sign-in URL via STS, often used by adversaries with temporary credentials - >
Azure AD SigninLogs table: CorrelationId field links all authentication events in a single sign-in flow; AuthenticationDetails shows each authentication step and whether MFA was satisfied - >
Azure AD AuditLogs table: track password resets, MFA configuration changes, and role assignments made by or to the compromised account in the period surrounding the incident - >
Azure Activity Log (AzureActivity table): every resource operation performed via Azure Portal leaves an entry with Caller (UPN), OperationNameValue, ResourceGroup, and clientIpAddress - >
AWS CloudTrail session events: all API calls made during the console session share a userIdentity.sessionContext.sessionIssuer value, allowing complete session reconstruction - >
Browser cache artifacts on the adversary's device: cloud console sessions generate substantial browser history, cookies, and cached credentials — relevant if endpoint forensics are possible - >
Identity Protection risk detections: Azure AD generates risk events (anonymizedIPAddress, unfamiliarFeatures, impossibleTravel, maliciousIPAddress) that may have been suppressed by the adversary timing the attack to avoid risky sign-in thresholds
Tuning Guidance
Cloud dashboard access is inherently noisy because it is a legitimate administrative interface. Start tuning by building an allowlist of expected source IP ranges (corporate VPN egress, named remote work IPs for key administrators) and excluding these from geographic anomaly alerts. For AWS, distinguish between human console logins and federated SSO logins (the latter often appear as AssumedRole events with a SAML issuer, not direct password auth) — the detection focus should be on direct password-based console logins without MFA. For Azure, leverage Conditional Access Named Locations to encode approved geographies and exclude them from the SigninLogs query, then alert only on logins from outside all named locations. The enumeration correlation hunting query will produce false positives for any administrator who performs routine infrastructure checks after logging in — tune by adding a minimum threshold of 10+ distinct enumeration operations and/or requiring the source IP to be outside the corporate IP range. In environments with heavy root account usage for billing access, consider a separate lower-severity alert for root logins that only escalates to high severity when root console login is followed by IAM or security configuration changes.
Hunting Queries
Hunt for accounts accessing cloud dashboards from multiple countries, multiple source IPs, or with repeated failed login attempts over a 7-day window. Legitimate users rarely sign into cloud portals from 3+ countries or 5+ distinct IPs in a week without a known travel or VPN explanation. High failure counts preceding a successful login indicate credential spraying or brute-force activity.
SigninLogs
| where TimeGenerated > ago(7d)
| where AppDisplayName has "Portal" or AppDisplayName has "Console"
| summarize
SignInCount = count(),
UniqueIPs = dcount(IPAddress),
Countries = make_set(tostring(LocationDetails.countryOrRegion)),
CountryCount = dcount(tostring(LocationDetails.countryOrRegion)),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
FailedCount = countif(ResultType != 0)
by UserPrincipalName, AppDisplayName
| where CountryCount >= 3 or UniqueIPs >= 5 or FailedCount > 5
| sort by CountryCount desc, UniqueIPs desc index=aws sourcetype=aws:cloudtrail eventName=ConsoleLogin
| bucket _time span=7d
| stats count as TotalLogins, dc(sourceIPAddress) as UniqueIPs, values(sourceIPAddress) as SourceIPs, values(awsRegion) as Regions, sum(eval(if('responseElements.ConsoleLogin'=="Failure",1,0))) as FailedLogins by 'userIdentity.arn'
| where UniqueIPs >= 3 OR FailedLogins > 5
| sort - UniqueIPs Hunt for successful cloud dashboard logins occurring outside normal business hours (before 06:00 or after 22:00 UTC). While some legitimate administrators work off-hours, repeated successful console logins at unusual times — especially combined with unfamiliar source IPs — are a strong indicator of unauthorized access using stolen credentials. Focus on accounts with 2+ off-hours logins on the same calendar day.
let CloudDashboardApps = dynamic(["Azure Portal", "Microsoft Azure Portal", "Azure Active Directory Portal"]);
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName in (CloudDashboardApps)
| where ResultType == 0
| extend Hour = hourofday(TimeGenerated)
| extend IsOffHours = Hour < 6 or Hour > 22
| where IsOffHours
| summarize
OffHoursLogins = count(),
UniqueSourceIPs = dcount(IPAddress),
Countries = make_set(tostring(LocationDetails.countryOrRegion)),
Sessions = make_set(CorrelationId)
by UserPrincipalName, bin(TimeGenerated, 1d)
| where OffHoursLogins >= 2
| sort by OffHoursLogins desc index=aws sourcetype=aws:cloudtrail eventName=ConsoleLogin 'responseElements.ConsoleLogin'=Success
| eval hour=strftime(_time, "%H")
| eval is_off_hours=if(hour < "06" OR hour > "22", 1, 0)
| where is_off_hours=1
| stats count as OffHoursLogins, dc(sourceIPAddress) as UniqueIPs, values(sourceIPAddress) as SourceIPs, values(awsRegion) as Regions by 'userIdentity.arn', date_mday, date_month
| where OffHoursLogins >= 2
| sort - OffHoursLogins Hunt for cloud resource enumeration activity occurring within 30 minutes of a successful console login. This pattern — login followed immediately by broad read/list API calls — is consistent with adversary reconnaissance using stolen credentials. In AWS, focus on ListBuckets, DescribeInstances, ListRoles, ListUsers, and ListInventoryEntries (the Scattered Spider SSM TTP). In Azure, focus on resource reads across multiple ResourceGroups and storage account key listing.
// Hunt for enumeration activity immediately following cloud console login
let PortalSignIns = SigninLogs
| where TimeGenerated > ago(7d)
| where AppDisplayName has "Portal"
| where ResultType == 0
| project SignInTime = TimeGenerated, UserPrincipalName, IPAddress, CorrelationId;
AzureActivity
| where TimeGenerated > ago(7d)
| where OperationNameValue has_any ("list", "read", "get", "Microsoft.Resources/subscriptions/resources/read",
"Microsoft.Authorization/roleAssignments/read", "Microsoft.Storage/storageAccounts/listKeys/action",
"Microsoft.Compute/virtualMachines/read", "Microsoft.Network/virtualNetworks/read")
| project ActivityTime = TimeGenerated, Caller, OperationNameValue, ResourceGroup, clientIpAddress
| join kind=inner PortalSignIns on $left.Caller == $right.UserPrincipalName
| where ActivityTime between (SignInTime .. (SignInTime + 30min))
| summarize
EnumerationOps = count(),
OpsPerformed = make_set(OperationNameValue),
ResourceGroups = make_set(ResourceGroup)
by Caller, SignInTime, IPAddress
| where EnumerationOps >= 5
| sort by EnumerationOps desc index=aws sourcetype=aws:cloudtrail eventName=ConsoleLogin 'responseElements.ConsoleLogin'=Success
| eval login_time=_time
| eval username=coalesce('userIdentity.userName', 'userIdentity.arn')
| table login_time, username, sourceIPAddress
| join type=inner username
[search index=aws sourcetype=aws:cloudtrail (eventName=ListBuckets OR eventName=DescribeInstances OR eventName=ListRoles OR eventName=ListUsers OR eventName=GetCallerIdentity OR eventName=DescribeVpcs OR eventName=DescribeSecurityGroups OR eventName=ListInventoryEntries)
| eval event_time=_time
| eval username=coalesce('userIdentity.userName', 'userIdentity.arn')
| table event_time, username, eventName, awsRegion]
| where event_time > login_time AND event_time < (login_time + 1800)
| stats count as EnumerationOps, values(eventName) as OpsPerformed, values(awsRegion) as Regions by username, login_time, sourceIPAddress
| where EnumerationOps >= 3
| sort - EnumerationOps Atomic Red Team Tests
Simulates how adversaries with stolen IAM access keys generate a console sign-in URL to access the AWS Management Console via a browser without the original account password. Uses STS GetFederationToken to produce a temporary credential set and constructs the ConsoleURL endpoint. This is a documented technique used by adversaries to convert stolen API credentials into interactive console sessions. Requires valid AWS credentials in the environment.
Command
# Step 1: Get caller identity to confirm credential validity
aws sts get-caller-identity --output json
# Step 2: Generate federation token (simulates adversary obtaining console access from API keys)
aws sts get-federation-token \
--name "recon-session" \
--duration-seconds 3600 \
--policy '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"sts:GetFederationToken","Resource":"*"}]}' \
--output json > /tmp/fed_token.json
# Step 3: Extract credentials and construct console sign-in URL
ACCESS_KEY=$(jq -r '.Credentials.AccessKeyId' /tmp/fed_token.json)
SECRET_KEY=$(jq -r '.Credentials.SecretAccessKey' /tmp/fed_token.json)
SESSION_TOKEN=$(jq -r '.Credentials.SessionToken' /tmp/fed_token.json)
SESSION_JSON=$(python3 -c "import json; print(json.dumps({'sessionId': '$ACCESS_KEY', 'sessionKey': '$SECRET_KEY', 'sessionToken': '$SESSION_TOKEN'}))")
# This URL, when opened in a browser, provides Management Console access
SIGNIN_TOKEN=$(curl -s "https://signin.aws.amazon.com/federation?Action=getSigninToken&Session=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$SESSION_JSON")" | jq -r '.SigninToken')
echo "Console URL: https://signin.aws.amazon.com/federation?Action=login&Issuer=example&Destination=https%3A%2F%2Fconsole.aws.amazon.com%2F&SigninToken=$SIGNIN_TOKEN" Cleanup
rm -f /tmp/fed_token.json Expected Telemetry
AWS CloudTrail: GetFederationToken event from userIdentity of the caller IAM user, with requestParameters showing the policy document. The ConsoleLogin event in CloudTrail (eventSource: signin.amazonaws.com) fires when the generated URL is clicked in a browser, with additionalEventData.MFAUsed=No and userIdentity.type=FederatedUser.
Expected Detection
KQL AWSCloudTrail query triggers on ConsoleLogin with MFAUsed=No. Hunting query detects console login followed by enumeration activity. The GetFederationToken event is also a useful hunting signal (eventName=GetFederationToken from a non-CI/CD context).
Replicates the specific AWS Systems Manager Inventory abuse documented for the Scattered Spider threat group. After obtaining cloud console access, adversaries used SSM Inventory to enumerate managed instances across the environment, identifying targets for lateral movement without needing direct network access to those systems. Requires AWS CLI with SSM read permissions.
Command
# Enumerate all managed instances visible via SSM (Scattered Spider lateral movement recon)
aws ssm describe-instance-information \
--output json \
--query 'InstanceInformationList[*].{InstanceId:InstanceId,PlatformName:PlatformName,IPAddress:IPAddress,ComputerName:ComputerName,PingStatus:PingStatus}'
# List SSM inventory for installed applications on all managed instances
aws ssm list-inventory-entries \
--instance-id $(aws ssm describe-instance-information --query 'InstanceInformationList[0].InstanceId' --output text 2>/dev/null || echo "i-00000000000000000") \
--type-name AWS:Application \
--output json 2>/dev/null || echo "No instances available in test environment"
# Enumerate all SSM documents (may reveal automation playbooks with embedded credentials)
aws ssm list-documents \
--filters Key=Owner,Values=Self \
--output json \
--query 'DocumentIdentifiers[*].{Name:Name,DocumentType:DocumentType,PlatformTypes:PlatformTypes}' Expected Telemetry
AWS CloudTrail: DescribeInstanceInformation (eventName), ListInventoryEntries, and ListDocuments events under eventSource=ssm.amazonaws.com. All events carry the caller's IAM identity, source IP, userAgent (aws-cli or browser), and requestParameters. If called from a browser console session, the userIdentity.sessionContext will reference the console session.
Expected Detection
Hunting query (KQL/SPL) for ListInventoryEntries and DescribeInstanceInformation within 30 minutes of a ConsoleLogin event triggers on this sequence. High volume of ssm: describe/list API calls from a new source IP or unusual account is a strong signal for post-console enumeration.
Simulates adversary behavior after obtaining Azure access tokens or credentials: uses Azure CLI to enumerate subscriptions, resource groups, virtual machines, and storage accounts — the same information visible in the Azure Portal dashboard. This represents the programmatic equivalent of clicking through Azure Portal tabs, and generates AzureActivity and AAD audit log entries identical to portal-based browsing.
Command
# Authenticate with Azure CLI (in a real attack, adversary uses stolen token or credentials)
# az login # Skipped in test — assumes prior authentication
# Enumerate subscriptions (top-level Azure Portal view)
az account list --output table
# Enumerate all resource groups (equivalent to Resource Groups dashboard tab)
az group list --output table --query '[*].{Name:name,Location:location,State:properties.provisioningState}'
# Enumerate virtual machines across all resource groups (equivalent to Virtual Machines dashboard)
az vm list --output table --query '[*].{Name:name,ResourceGroup:resourceGroup,Location:location,PowerState:powerState}'
# Enumerate storage accounts (equivalent to Storage Accounts dashboard tab)
az storage account list --output table --query '[*].{Name:name,ResourceGroup:resourceGroup,Location:location,Kind:kind}'
# Enumerate Azure AD users (equivalent to Users blade in Azure AD portal)
az ad user list --output table --query '[*].{UPN:userPrincipalName,DisplayName:displayName,AccountEnabled:accountEnabled}' 2>/dev/null || echo "Requires Azure AD Graph permissions" Expected Telemetry
AzureActivity table in Sentinel: Microsoft.Resources/subscriptions/read, Microsoft.Resources/resourceGroups/read, Microsoft.Compute/virtualMachines/read, Microsoft.Storage/storageAccounts/read events with Caller matching the authenticated user principal. AADSignInLogs: service principal or user sign-in event for Azure CLI app (appId: 04b07795-8ddb-461a-bbee-02f9e1bf7b46). All events carry the source IP of the machine running the CLI.
Expected Detection
Hunting query (Azure enumeration after portal sign-in) correlates these read operations with a preceding SigninLogs event for the Azure Portal or Azure CLI. KQL detection in AzureActivity for rapid sequential resource reads across multiple resource groups within a short window triggers on this pattern.
Simulates GCP Security Command Center and Cloud Asset Inventory enumeration — the GCP equivalent of the AWS SSM and Azure Portal enumeration techniques above. Adversaries with stolen GCP credentials use gcloud to replicate the asset discovery capabilities of the GCP Console, including listing all projects, compute instances, storage buckets, and IAM bindings — information that would be visible in the GCP Cloud Console dashboard.
Command
# Requires gcloud CLI installed and prior authentication with stolen credentials
# gcloud auth login # Skipped — assumes prior credential compromise
# List accessible GCP projects (top-level console navigation)
gcloud projects list --format='table(projectId, name, projectNumber)' 2>/dev/null || echo "Requires gcloud authentication"
# Enumerate compute instances (Compute Engine dashboard view)
gcloud compute instances list --format='table(name, zone, machineType, status, networkInterfaces[0].accessConfigs[0].natIP)' 2>/dev/null || echo "No compute access or not authenticated"
# Enumerate storage buckets (Cloud Storage dashboard view)
gsutil ls -L -b 2>/dev/null | grep -E '(gs://|Location|Storage class)' || echo "No storage access or not authenticated"
# Enumerate IAM bindings at project level (IAM & Admin dashboard view)
gcloud projects get-iam-policy $(gcloud config get-value project 2>/dev/null) --format=json 2>/dev/null || echo "No IAM read access or not authenticated"
# Query Security Command Center findings (mirrors GCP Command Center dashboard)
gcloud scc findings list $(gcloud projects describe $(gcloud config get-value project 2>/dev/null) --format='value(name)' 2>/dev/null) --format=table 2>/dev/null || echo "Requires Security Command Center API access" Expected Telemetry
GCP Cloud Audit Logs: cloudresourcemanager.googleapis.com/projects.list, compute.instances.list, storage.buckets.list, iam.projects.getIamPolicy, and securitycenter.findings.list data access events. All entries include principalEmail (the caller), callerIp, userAgent (cloud-sdk/gcloud), and methodName. These logs appear in Cloud Audit Logs — Data Access log type and can be exported to Splunk via Pub/Sub or to Sentinel via the GCP connector.
Expected Detection
Cloud Audit Log analysis for sequential list/enumeration API calls across multiple GCP services within a short time window from an unusual source IP. Correlation of GCP console login events (authenticationInfo.principalEmail, requestMetadata.callerIp) with subsequent API enumeration calls within the same session window.