Cloud Threat Hunting: CloudTrail, Entra ID and the M365 Audit Log

The control plane is the crime scene

On-premises hunting leans on endpoints: process creation, file writes, memory. In cloud environments the most important actions never touch an endpoint you own. Someone creates an access key, assigns a role, adds a mailbox forwarding rule, snapshots a disk and shares it with another account. Each of those is an API call, and the only record is a log line.

That changes the hunter's job in three ways:

  • Identity is the perimeter. Nearly every cloud intrusion starts with a valid credential or token. Hunting starts from "who is acting", not "what is running".
  • Logging is a configuration choice. If it was not enabled, retained and exported before the incident, it does not exist. There is no forensic recovery.
  • Volume is huge and mostly benign. Automation generates the overwhelming majority of events. Hunting is about knowing the small set of operations that matter and the baseline of who normally performs them.

If you have never run a structured hunt, start with your first threat hunt for the method. This article is the cloud-specific map.

Know your sources first

Platform Log What it covers Watch out for
AWS CloudTrail management events Control plane API calls in every service Event history is 90 days and per region; create a multi-region trail to S3 for real retention
AWS CloudTrail data events S3 object access, Lambda invoke, etc. Not enabled by default, extra cost, enable selectively on sensitive buckets
Azure Activity Log Azure Resource Manager operations (create, delete, role assignments) 90 days in the portal; export via diagnostic settings
Entra ID Sign-in logs Interactive, non-interactive, service principal, managed identity sign-ins Non-interactive logs are separate and high volume; you need them
Entra ID Audit logs Directory changes: users, groups, roles, apps, consents Retention depends on licence; export to a SIEM
M365 Unified Audit Log Exchange, SharePoint, OneDrive, Teams, admin actions Retention depends on licence tier; some events need Audit Premium

Check retention before you need it. Many cloud intrusions are discovered weeks or months after initial access. If your logs only live in the native console with default retention, the start of the story may already be gone. Export to storage you control.

AWS CloudTrail

High-value events

Group them by what the attacker is trying to achieve:

Goal Event names
Recon after credential theft GetCallerIdentity, ListUsers, ListRoles, ListBuckets, GetAccountAuthorizationDetails
Persistence CreateUser, CreateAccessKey, CreateLoginProfile, UpdateLoginProfile, UpdateAssumeRolePolicy
Privilege escalation AttachUserPolicy, PutUserPolicy, AttachRolePolicy, CreatePolicyVersion, PassRole via RunInstances or CreateFunction
Defense evasion StopLogging, DeleteTrail, UpdateTrail, PutEventSelectors, GuardDuty DeleteDetector, UpdateDetector
Exfiltration PutBucketPolicy, DeletePublicAccessBlock, ModifySnapshotAttribute, ModifyDBSnapshotAttribute, GetSecretValue bursts
Impact and abuse RunInstances in regions you never use (cryptomining), CreateKeyPair, AuthorizeSecurityGroupIngress to 0.0.0.0/0

GetCallerIdentity deserves a mention: it is the "whoami" of AWS, and almost every tool runs it first. On its own it means nothing. From a new IP, for a long-lived access key that normally only runs from your CI, it is an excellent first clue.

Example queries (Athena over CloudTrail in S3)

-- Anyone tampering with logging
SELECT eventtime, eventname, useridentity.arn, sourceipaddress, awsregion
FROM cloudtrail_logs
WHERE eventname IN ('StopLogging','DeleteTrail','UpdateTrail','PutEventSelectors','DeleteDetector')
  AND eventtime > to_iso8601(current_timestamp - interval '30' day)
ORDER BY eventtime DESC;
-- Access keys used from IPs never seen for that key in the prior 30 days
WITH recent AS (
  SELECT useridentity.accesskeyid AS key, sourceipaddress AS ip, min(eventtime) AS first_seen
  FROM cloudtrail_logs
  WHERE eventtime > to_iso8601(current_timestamp - interval '1' day)
    AND useridentity.accesskeyid LIKE 'AKIA%'   -- long-term IAM user keys
  GROUP BY 1, 2
),
history AS (
  SELECT DISTINCT useridentity.accesskeyid AS key, sourceipaddress AS ip
  FROM cloudtrail_logs
  WHERE eventtime BETWEEN to_iso8601(current_timestamp - interval '31' day)
                      AND to_iso8601(current_timestamp - interval '1' day)
)
SELECT r.* FROM recent r
LEFT JOIN history h ON r.key = h.key AND r.ip = h.ip
WHERE h.key IS NULL;
-- Failed API calls by principal: enumeration usually trips AccessDenied
SELECT useridentity.arn, count(*) AS denied, count(DISTINCT eventname) AS distinct_calls
FROM cloudtrail_logs
WHERE errorcode IN ('AccessDenied','Client.UnauthorizedOperation')
  AND eventtime > to_iso8601(current_timestamp - interval '1' day)
GROUP BY 1
HAVING count(DISTINCT eventname) > 15
ORDER BY distinct_calls DESC;

The last one is a favourite: legitimate automation fails on the same one or two calls repeatedly; an attacker exploring permissions fails on many different calls.

Azure and Entra ID

Sign-in logs

Most Azure and M365 intrusions begin here. Useful dimensions: ResultType (0 is success, 50126 invalid credentials, 50053 locked account, 500121 strong authentication failed), AuthenticationProtocol, ClientAppUsed, IPAddress, AutonomousSystemNumber, ConditionalAccessStatus.

// Password spray: many users, few attempts each, from one source
SigninLogs
| where TimeGenerated > ago(1d) and ResultType == "50126"
| summarize Users = dcount(UserPrincipalName), Attempts = count() by IPAddress
| where Users > 20 and Attempts / Users < 3
// Successful sign-ins via legacy protocols (no MFA possible)
SigninLogs
| where ResultType == "0"
| where ClientAppUsed in ("IMAP4", "POP3", "Authenticated SMTP", "Exchange ActiveSync", "Other clients")
| project TimeGenerated, UserPrincipalName, ClientAppUsed, IPAddress

Do not forget AADNonInteractiveUserSignInLogs (refresh token use, where replayed tokens show up) and AADServicePrincipalSignInLogs (workload identities, increasingly the target).

Audit logs

High-value operations in Entra audit logs:

  • Add member to role and eligible or active PIM assignments, especially Global Administrator and Privileged Role Administrator.
  • Add service principal credentials and Update application – Certificates and secrets management: a secret added to an existing app is a classic, quiet backdoor.
  • Consent to application and Add delegated permission grant: covered in depth in our identity attacks article.
  • Set federation settings on domain and Set domain authentication: federation backdoors let an attacker mint tokens for any user.
  • User registered security info: a new MFA method right after a suspicious sign-in.
AuditLogs
| where OperationName in ("Add service principal credentials",
    "Update application – Certificates and secrets management",
    "Add member to role", "Set federation settings on domain")
| extend Actor = coalesce(tostring(InitiatedBy.user.userPrincipalName),
                          tostring(InitiatedBy.app.displayName))
| project TimeGenerated, OperationName, Actor, Target = tostring(TargetResources[0].displayName)

Azure Activity Log

For the resource plane, watch Microsoft.Authorization/roleAssignments/write (who got Owner on what), Microsoft.Compute/virtualMachines/runCommand/action (code execution on VMs from the control plane), Microsoft.Compute/disks/beginGetAccess/action (generating a download URL for a disk, a strong exfiltration signal) and changes to diagnostic settings that disable logging.

Microsoft 365 Unified Audit Log

Once an attacker owns a mailbox or a SharePoint identity, this is where their actions appear.

Operation Why it matters
New-InboxRule, Set-InboxRule, UpdateInboxRules Hide replies, auto-delete security alerts, forward invoices. BEC hallmark
Set-Mailbox with forwarding parameters Silent external forwarding
Add-MailboxPermission, Add-RecipientPermission Access to other mailboxes
New-TransportRule Tenant-wide mail manipulation, admin-level
MailItemsAccessed Which mail items a session actually read, key for breach scoping
FileDownloaded, FileSyncDownloadedFull Bulk SharePoint and OneDrive exfiltration
New-ComplianceSearch, New-ComplianceSearchAction Attackers with admin rights use eDiscovery to search and export everything
# Exchange Online PowerShell: inbox rules created in the last 7 days
Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) `
  -Operations New-InboxRule,Set-InboxRule,UpdateInboxRules -ResultSize 5000 |
  Select-Object CreationDate, UserIds, Operations, AuditData

Inside AuditData, look at the rule parameters. Rules that move messages containing words like "invoice", "payment", "phishing", "hacked" to RSS Feeds or Archive, or that delete them, are rarely created by humans for themselves.

Connecting the dots

Single events are leads. Sequences are findings. The patterns worth turning into hunts:

1. Sign-in from new ASN  ->  new MFA method registered  ->  inbox rule created        (mailbox takeover)
2. Access key from new IP ->  GetCallerIdentity ->  ListBuckets ->  GetObject burst   (credential leak to data theft)
3. App credential added   ->  service principal sign-in from new IP ->  Graph mail reads (app backdoor)
4. Role assignment to user ->  diagnostic settings deleted ->  runCommand on VMs       (escalation and evasion)

Enrich the IPs and domains along the way: an IP from a hosting provider or anonymising service deserves a different priority than one from the user's home ISP. Look them up on /search and record the pivots in a case so the reasoning survives the shift change.

Want to know what these events really look like in your tenant before an attacker generates them? The M365 red kit helps you build controlled scenarios around Microsoft 365 so you can validate that each query above actually returns something.

What to do Monday

  • [ ] Confirm a multi-region CloudTrail trail exists, writes to a separate account's S3 bucket, and has log file validation on.
  • [ ] Export Entra sign-in (all four types) and audit logs, and the Azure Activity Log, to your SIEM or storage you control.
  • [ ] Check your Unified Audit Log retention and whether MailItemsAccessed is available on your licence.
  • [ ] Turn the logging-tampering query into an alert today. There is no legitimate reason for it to fire unannounced.
  • [ ] Baseline who normally creates access keys, app secrets and role assignments; hunt for everyone else.
  • [ ] Pick one sequence from the list above and run it as this month's hypothesis-driven hunt.

Cloud attackers leave no files behind, but they cannot act without calling an API. Every call is a line in a log. Make sure you are keeping it, then go read it.