InfiniteTechSolutions recently experienced suspicious activity in their Azure environment. Using Microsoft Sentinel, the security team detected unusual login patterns, unauthorized service installations, and anomalous API calls targeting their Microsoft Graph endpoint. Multiple user accounts appear to have been compromised, and there are signs of privilege escalation and persistent access mechanisms being established. The incident occurred in July 2025, with activities spanning across various systems including workstations and cloud services.

This lab spans five log sources ingested as custom tables into a Log Analytics workspace: Event_CL (Windows Security events), SigninLogs_CL (Azure AD interactive sign-ins), AADNonInteractiveUserSignInLogs_CL (token-based silent auths), MicrosoftGraphActivityLogs_CL (Graph API calls), and AzureDiagnostics_CL / AuditLogs_CL (Azure resource and directory audit events). All tables use the custom log _CL suffix, meaning native Sentinel field names don’t apply — schema needs to be confirmed via getschema before querying, and familiar fields like TimeGenerated may not reflect the actual event time. CreatedDateTime is the authoritative timestamp in the Azure AD log tables.
The first pivot is Windows event logs. Querying Event_CL for EventID 4625 (failed logon) across all machines reveals the breadth of the initial brute force — four distinct computers with login failures, with ITWS01 taking the heaviest volume at 17 failed attempts.
Event_CL
| where EventID == "4625"
| summarize dcount(Computer)

Event_CL
| where EventID == "4625"
| summarize FailedAttempts = count() by Computer
| order by FailedAttempts desc

The failed logon events on ITWS01 contain TargetUserName: ADMINISTRATOR sourced from 80.94.95.75 via NTLM LogonType 3 — a network brute force against the local administrator account from an external IP. Successful 4624 events from the same IP confirm the attacker eventually got in.
With the ADMINISTRATOR account compromised, the attacker used PsExec-style execution to establish a foothold on ITWS01. EventID 7045 (service installed) surfaces a service named c316a11 — a random hex string characteristic of PsExec’s auto-generated service naming. The image path \\127.0.0.1\ADMIN$\c316a11.exe confirms execution via the admin share against the local machine, indicating the attacker was running PsExec from an already-authenticated session.
Event_CL
| where EventID == "7045"
| where Computer == "ITWS01"
| extend ServiceName = extract(@"Service Name:\s*(.*?)(\.|$)", 1, RenderedDescription)
| project TimeGenerated, Computer, ServiceName, RenderedDescription
| order by TimeGenerated asc

The 7045 event only records LocalSystem as the service run-as account — it does not capture who issued the install. The installing account lives in the UserName field on the raw event record, not inside the EventData XML. Expanding the full row for the c316a11 event reveals ITWS01/infinitetechadmin as the account that ran the PsExec session.
Event_CL
| where EventID == "7045"
| where Computer == "ITWS01"
| where EventData contains "c316a11"

With a foothold established on the endpoint, the attacker pivoted to Azure AD. Querying SigninLogs_CL for ResultType == "50126" (invalid username or password) gives the total spray volume — 63 failed authentication attempts across the tenant.
SigninLogs_CL
| where ResultType == "50126"
| summarize count()

Two accounts hit the lockout threshold during the spray. ResultType == "50053" (account locked out) identifies them as Sarah Miles and Alice Jones — both generating lockout events that signal the spray was running fast enough to exceed the tenant’s lockout policy.
SigninLogs_CL
| where ResultType == "50053"
| summarize by Identity

To count the unique IPs involved in the spray, the correct approach is to exclude successes rather than enumerate specific failure codes. Using ResultType != 0 captures 50126 failures and 50053 lockout events in a single pass — the same logic as EventID != 4624 in Windows logon monitoring. Filtering only on 50126 undercounts by missing the lockout IPs, which is how we ended up one short repeatedly before flipping the logic. The correct count is 52 unique IPs.
SigninLogs_CL
| where ResultType != 0
| summarize dcount(IPAddress)

Filtering SigninLogs_CL for ResultType == "0" and ordering by CreatedDateTime ascending identifies the first successful authentication. Maya Wilson authenticated from 35.158.160.255 at 2025-07-01 19:21. Alice Jones also shows successful logins but from the 3.72.100.x range — consistent with the corporate subnet visible throughout the logs. The attacker IP 35.158.160.255 is the distinguishing factor.
SigninLogs_CL
| where ResultType == "0"
| project TimeGenerated, CreatedDateTime, Identity, IPAddress, UserPrincipalName
| order by CreatedDateTime asc
| take 10

Filtering specifically on Maya Wilson and projecting UserAgent alongside CreatedDateTime confirms the timestamp and surfaces the attacker’s browser fingerprint — a modern Edge build on Windows 10.
SigninLogs_CL
| where ResultType == "0"
| where Identity == "Maya Wilson"
| project TimeGenerated, CreatedDateTime, Identity, IPAddress, UserAgent
| order by CreatedDateTime asc
| take 1

Four minutes after the initial interactive login, the attacker began non-interactive authentication — silent token reuse that leaves a much lighter footprint than repeated interactive logins. AADNonInteractiveUserSignInLogs_CL confirms the first silent auth from 35.158.160.255 at 2025-07-01T19:25:14Z. The token type leveraged is a refreshToken — obtained from the initial interactive session and reused silently across subsequent requests to Azure Resource Manager without triggering MFA or Conditional Access re-evaluation.
AADNonInteractiveUserSignInLogs_CL
| where UserDisplayName == "Maya Wilson"
| project TimeGenerated, CreatedDateTime, UserDisplayName, IPAddress, IncomingTokenType
| order by CreatedDateTime asc
| take 5

Pivoting to non-interactive auths after the compromise timestamp and excluding Maya Wilson surfaces a second compromised account — Tom Clarkson, with 12 silent auth events from the same attacker IP. This indicates the attacker either compromised Tom’s credentials separately or pivoted to his account via token theft.
AADNonInteractiveUserSignInLogs_CL
| where ResultType == "0"
| where CreatedDateTime > datetime(2025-07-01T19:21:19Z)
| where UserDisplayName != "Maya Wilson"
| summarize count() by UserDisplayName, IPAddress
| order by count_ desc

With valid tokens in hand, the attacker queried Microsoft Graph for directory intelligence across two separate IPs. From 48.211.64.27, the delta sync endpoint was called — https://graph.microsoft.com/beta/users/microsoft.graph.delta(). The delta sync pattern is particularly efficient for directory harvesting: a single call returns the full user list, and the returned deltatoken enables incremental syncs to track any subsequent changes without re-pulling the entire directory.
MicrosoftGraphActivityLogs_CL
| where RequestUri contains "users"
| summarize count() by IPAddress, RequestUri
| order by count_ desc

From a separate IP, 51.11.96.191, the attacker queried the organization endpoint — pulling tenant-level configuration including verified domains and tenant ID, useful for downstream phishing infrastructure and identifying trust relationships.
MicrosoftGraphActivityLogs_CL
| where IPAddress != "48.211.64.27"
| where IPAddress !startswith "3.72.100"
| summarize count() by IPAddress, RequestUri
| order by count_ desc

The attacker compromised the DAILYCHECKER Azure Automation account and created a runbook named UsersReminders, linked to a schedule named DailyUsersReminder. Unlike endpoint-based persistence mechanisms, Automation runbooks survive reimaging, password resets, and most IR playbooks — they execute in Azure’s compute infrastructure independently of any on-prem endpoint state.
AzureDiagnostics_CL
| where ResourceType contains "AUTOMATION" or OperationName contains "automation"
| project TimeGenerated, OperationName, Resource, ResourceGroup
| take 20

AzureDiagnostics_CL
| where Resource == "DAILYCHECKER"
| where targetResourcesResources == "Runbook"

AzureDiagnostics_CL
| where Resource == "DAILYCHECKER"
| order by TimeGenerated asc
| take 20

Several hours post-compromise, Tom Clarkson’s account executed an Add owner to application operation against InfinivaultApp (application ID 9999-8888). Adding ownership to an Azure AD application grants full control over its credentials and API permissions — the owner can add new client secrets or certificates, effectively inheriting the application’s full permission scope. This is a low-noise escalation path compared to direct role assignments, appearing only in AuditLogs as a single event.
AuditLogs_CL
| where OperationName == "Add owner to application"
| project TimeGenerated, OperationName, InitiatedBy, TargetResources
| order by TimeGenerated asc

With elevated application permissions secured, the attacker returned to DAILYCHECKER and updated the UsersReminders runbook — embedding new payload or credentials reflecting the escalated privilege level. Tom Clarkson’s non-interactive auth sessions from 35.158.160.255 confirm that IP as the source of the automation updates.
AzureDiagnostics_CL
| where Resource == "DAILYCHECKER"
| where OperationName == "Update"
| project TimeGenerated, OperationName, targetResourcesResources, ResultDescription
| order by TimeGenerated asc

AADNonInteractiveUserSignInLogs_CL
| where UserDisplayName == "Tom Clarkson"
| where ResultType == "0"
| project TimeGenerated, CreatedDateTime, IPAddress, UserDisplayName
| order by CreatedDateTime asc

The final phase targets secrets storage. AzureDiagnostics_CL filtered for Key Vault operations reveals three vaults accessed: CORP-KV-PROD, INFRA-BACKUP-KV, and FINANCE-KV-EU — spanning production credentials, infrastructure backup keys, and finance-scoped secrets. The breadth of vaults accessed suggests the attacker had mapped the tenant’s Key Vault inventory during the Graph API recon phase and targeted them systematically.
AzureDiagnostics_CL
| where OperationName contains "Secret" or OperationName contains "vault"
| summarize count() by Resource

| Phase | Action |
|---|---|
| Reconnaissance | NTLM brute force against ADMINISTRATOR from 80.94.95.75 across 4 workstations |
| Initial Access | ADMINISTRATOR account compromised on ITWS01 |
| Lateral Movement | PsExec-style service c316a11 installed via \\127.0.0.1\ADMIN$ using infinitetechadmin |
| Credential Access | Azure AD password spray — 63 failed attempts, 52 unique IPs, 2 accounts locked |
| Initial Cloud Compromise | Maya Wilson compromised at 2025-07-01 19:21 from 35.158.160.255 |
| Persistence (Token) | refreshToken reuse via non-interactive auth — Maya Wilson then Tom Clarkson |
| Discovery | Graph API delta sync from 48.211.64.27 — full user directory harvested |
| Discovery | Organization endpoint queried from 51.11.96.191 — tenant config enumerated |
| Persistence (Cloud) | DAILYCHECKER automation account — UsersReminders runbook + DailyUsersReminder schedule |
| Privilege Escalation | Tom Clarkson added as owner of InfinivaultApp (9999-8888) |
| Defense Evasion | Runbook updated post-escalation to embed elevated-privilege payload |
| Exfiltration | CORP-KV-PROD, INFRA-BACKUP-KV, FINANCE-KV-EU secrets accessed |
| Type | Value |
|---|---|
| IP (Brute Force Source) | 80[.]94[.]95[.]75 |
| IP (Cloud Attacker C2) | 35[.]158[.]160[.]255 |
| IP (Graph Recon — Users) | 48[.]211[.]64[.]27 |
| IP (Graph Recon — Org) | 51[.]11[.]96[.]191 |
| Account (On-Prem Compromised) | ITWS01/infinitetechadmin |
| Account (Cloud Compromised) | maya.wilson@infinitechsolutions[.]xyz |
| Account (Cloud Lateral) | tom.clarkson@infinitechsolutions[.]xyz |
| Service (Malicious) | c316a11 |
| Service Path | \127[.]0[.]0[.]1\ADMIN$\c316a11.exe |
| Automation Account | DAILYCHECKER |
| Runbook | UsersReminders |
| Schedule | DailyUsersReminder |
| Application | InfinivaultApp (9999-8888) |
| Key Vault | CORP-KV-PROD |
| Key Vault | INFRA-BACKUP-KV |
| Key Vault | FINANCE-KV-EU |
| Technique | ID | Description |
|---|---|---|
| Valid Accounts: Cloud Accounts | T1078.004 | Maya Wilson and Tom Clarkson cloud accounts compromised and abused |
| Brute Force: Password Spraying | T1110.003 | 52 unique IPs across 63 failed auth attempts against Azure AD |
| Create or Modify System Process: Windows Service | T1543.003 | c316a11 PsExec-style service installed on ITWS01 |
| Steal Application Access Token | T1528 | refreshToken reused for non-interactive silent authentication |
| Use Alternate Authentication Material: Application Access Token | T1550.001 | Token reuse across Maya Wilson and Tom Clarkson sessions |
| Create Cloud Instance | T1136.003 | UsersReminders runbook created in DAILYCHECKER automation account |
| Account Manipulation: Additional Cloud Roles | T1098.003 | Tom Clarkson added as owner of InfinivaultApp |
| Data from Cloud Storage | T1530 | CORP-KV-PROD, INFRA-BACKUP-KV, FINANCE-KV-EU Key Vaults accessed |
| Cloud Service Discovery | T1526 | Graph API delta sync and organization endpoint enumerated |
| Account Discovery: Cloud Account | T1087.004 | Graph delta endpoint used to harvest full Azure AD user directory |
Monitor AADNonInteractiveUserSignInLogs for token reuse from anomalous IPs. This is a log source many orgs don’t actively alert on. Silent auth events from unexpected geolocations or IPs appearing shortly after an interactive login from the same source are a strong signal of token theft. The 4-minute gap between Maya Wilson’s interactive login and first non-interactive auth is exactly the window a defender needs to catch — a Sentinel analytic rule correlating interactive and non-interactive sign-ins from the same IP within a short time window provides durable coverage.
Use ResultType != 0 for spray detection, not specific error codes. Enumerating failure codes like 50126 misses lockout events (50053) and other failure conditions, undercounting the true attacker IP footprint. The correct pattern excludes successes rather than enumerating failures — identical logic to excluding EventID 4624 in Windows logon monitoring. A single where ResultType != 0 is more complete and resilient to novel error codes than a maintained list of known failure types.
Graph API delta sync is a high-fidelity recon indicator. Legitimate applications using delta sync have consistent, predictable calling patterns tied to a registered app identity. A one-off delta query from an IP with no prior Graph activity — particularly using the /beta/ endpoint — is a strong indicator of manual directory harvesting. MicrosoftGraphActivityLogs should be ingested into Sentinel and alerted on for delta token usage outside known application identities.
Azure Automation runbooks are a durable persistence mechanism that most IR playbooks miss. Unlike scheduled tasks or registry run keys, runbooks survive endpoint reimaging and execute independently of on-prem infrastructure. Any new runbook creation or schedule linkage in a production Automation account warrants an alert, with particular scrutiny on the create-then-schedule pattern seen here — the two events together constitute the full persistence chain.
Application ownership is a silent privilege escalation path. The Add owner to application audit event is easy to miss without a dedicated detection rule. An application owner can add new credentials to the app and inherit its full API permission scope — equivalent to a role assignment but with far less visibility. Alert on this operation for any application holding sensitive API permissions, and review application ownership as part of regular Azure AD hygiene.