☁️ Cloud Threat Hunting

Cloud Threat Hunting Queries

KQL (Microsoft Sentinel) and SPL (Splunk) queries for AWS, Azure, and GCP threat hunting. Covers privilege escalation, lateral movement, exfiltration, and persistence.

☁ AWS CloudTrail — via Sentinel

Requires AWS CloudTrail ingested into Sentinel via AWS S3 connector or directly.
Privilege Escalation — New IAM Admin Created High T1078.004 CloudTrail
Detects when a new IAM user is created and immediately granted AdministratorAccess or a wildcard policy — classic privilege escalation or persistence.
AWSCloudTrail | where EventName in ("CreateUser","AttachUserPolicy","PutUserPolicy") | where RequestParameters has_any ("AdministratorAccess","arn:aws:iam::aws:policy/AdministratorAccess","\"Effect\":\"Allow\",\"Action\":\"*\"") | summarize Events=make_list(EventName), Policies=make_set(RequestParameters), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by UserIdentityArn, SourceIPAddress, AWSRegion | where array_length(Events) > 1 | project TimeRange=strcat(FirstSeen," → ",LastSeen), Actor=UserIdentityArn, SourceIP=SourceIPAddress, Region=AWSRegion, Events, Policies | sort by FirstSeen desc
Data source: AWSCloudTrail table in Sentinel (AWS S3 connector)
CloudTrail Logging Disabled High T1562.008 CloudTrail
Detects when someone disables CloudTrail logging — a common attacker technique to eliminate audit trail before performing destructive or exfiltration actions.
AWSCloudTrail | where EventName in ("StopLogging","DeleteTrail","UpdateTrail") | project TimeGenerated, Actor=UserIdentityArn, EventName, TrailArn=tostring(parse_json(RequestParameters).trailARN), SourceIP=SourceIPAddress, Region=AWSRegion | sort by TimeGenerated desc
Data source: AWSCloudTrail table in Sentinel (AWS S3 connector)
Credential Exfiltration — IAM Access Key Creation for Non-Operator High T1552.001 CloudTrail
Detects creation of new IAM access keys, especially when created by an account that is not a known credential management service or break-glass account.
AWSCloudTrail | where EventName == "CreateAccessKey" | extend TargetUser = tostring(parse_json(RequestParameters).userName) | extend CreatedBy = tostring(parse_json(UserIdentity).arn) | where TargetUser != "" // creating keys for another user (not self) is more suspicious | where CreatedBy !contains "terraform" and CreatedBy !contains "automation" | project TimeGenerated, CreatedBy, TargetUser, SourceIP=SourceIPAddress, Region=AWSRegion | sort by TimeGenerated desc
Data source: AWSCloudTrail table in Sentinel (AWS S3 connector)
S3 Bucket Policy Made Public High T1530 CloudTrail
Detects when an S3 bucket policy is modified to include public access (allUsers or allAuthenticatedUsers). Often the first step in data exfiltration staging.
AWSCloudTrail | where EventName in ("PutBucketPolicy","PutBucketAcl","PutPublicAccessBlock") | where RequestParameters has_any ("AllUsers","AuthenticatedUsers","\"Effect\":\"Allow\"") | extend BucketName = tostring(parse_json(RequestParameters).bucketName) | project TimeGenerated, Actor=UserIdentityArn, EventName, BucketName, SourceIP=SourceIPAddress | sort by TimeGenerated desc
Data source: AWSCloudTrail table in Sentinel (AWS S3 connector)
EC2 Instance Launched with Unusual User Data (Possible Crypto Mining) Medium T1496 CloudTrail
Detects EC2 instances launched with suspicious user data scripts, often used to install crypto miners or establish backdoors automatically on boot.
AWSCloudTrail | where EventName == "RunInstances" | extend UserData = tostring(parse_json(RequestParameters).userData) | where UserData != "" | extend DecodedUD = base64_decode_tostring(UserData) | where DecodedUD has_any ("curl","wget","bash -i","nc -e","python3 -c","crypto","miner","xmrig") | project TimeGenerated, Actor=UserIdentityArn, InstanceType=tostring(parse_json(RequestParameters).instanceType), Region=AWSRegion, SuspiciousScript=DecodedUD, SourceIP=SourceIPAddress | sort by TimeGenerated desc
Data source: AWSCloudTrail table in Sentinel (AWS S3 connector)
Mass S3 Data Download — Exfiltration Indicator High T1530 CloudTrail
Detects unusual volume of S3 GetObject calls from a single IP or identity — potential data exfiltration staging.
AWSCloudTrail | where EventName == "GetObject" | where isnotempty(RequestParameters) | summarize DownloadCount=count(), Buckets=make_set(tostring(parse_json(RequestParameters).bucketName)), DataSizeApprox="unknown" // Add actual size if available via S3 access logs by UserIdentityArn, SourceIPAddress, bin(TimeGenerated,1h) | where DownloadCount > 500 // Tune threshold to your baseline | sort by DownloadCount desc
Data source: AWSCloudTrail table in Sentinel (AWS S3 connector)

🔷 Azure Activity & Sign-In Logs

Built-in to Sentinel when Azure Activity and Azure AD logs are connected.
Azure AD — Impossible Travel Login High T1078.004 SigninLogs
Detects logins from two geographically distant locations within a time window that makes physical travel impossible — indicates stolen credentials in use.
SigninLogs | where ResultType == 0 // Successful sign-in only | where LocationDetails.countryOrRegion != "" | summarize Locations=make_set(strcat(tostring(LocationDetails.countryOrRegion),":",tostring(LocationDetails.city))), IPs=make_set(IPAddress), FirstLogin=min(TimeGenerated), LastLogin=max(TimeGenerated) by UserPrincipalName, bin(TimeGenerated, 4h) | where array_length(Locations) > 1 | extend Countries=make_set_if(tostring(split(l,":")[0]), l, l in (Locations)) | where array_length(Countries) > 1 // Different countries in 4-hour window | project TimeWindow=strcat(FirstLogin," to ",LastLogin), User=UserPrincipalName, Locations, IPs | sort by FirstLogin desc
Data source: SigninLogs table in Sentinel
Azure — New Global Admin or Privileged Role Assignment High T1078.004 AuditLogs
Detects assignment of highly privileged Azure AD roles — Global Administrator, Privileged Role Administrator, Security Administrator.
AuditLogs | where OperationName in ("Add member to role","Add eligible member to role") | extend Role = tostring(TargetResources[0].displayName) | extend Assignee = tostring(TargetResources[0].userPrincipalName) | extend Assigner = tostring(InitiatedBy.user.userPrincipalName) | where Role in ("Global Administrator","Privileged Role Administrator","Security Administrator", "User Administrator","Application Administrator","Cloud Application Administrator") | project TimeGenerated, Assigner, Assignee, Role, CorrelationId | sort by TimeGenerated desc
Data source: AuditLogs table in Sentinel
Azure — Key Vault Secret Access Outside Business Hours Medium T1555 AzureDiagnostics
Detects Key Vault secret reads outside normal business hours (IST 09:00-19:00) — unusual access patterns may indicate compromised service principal or insider threat.
AzureDiagnostics | where ResourceType == "VAULTS" and OperationName == "SecretGet" | extend HourIST = datetime_part("hour", TimeGenerated) + 5 // UTC+5:30 approximation | where HourIST < 9 or HourIST > 19 // Outside IST business hours | where ResultType == "Success" | summarize AccessCount=count(), Secrets=make_set(id_s) by CallerIPAddress, identity_claim_oid_g, bin(TimeGenerated,1h) | where AccessCount > 5 | project TimeGenerated, CallerIP=CallerIPAddress, Identity=identity_claim_oid_g, SecretAccessCount=AccessCount, Secrets | sort by AccessCount desc
Data source: AzureDiagnostics table in Sentinel
Azure — Resource Deletion Spike (Possible Destruction) High T1485 AzureActivity
Detects a high volume of Azure resource deletions in a short time — ransomware, wiper, or destructive attacker destroying cloud resources.
AzureActivity | where OperationNameValue has "DELETE" and ActivityStatusValue == "Success" | where Caller != "" and Caller !contains "serviceprincipals/automation" | summarize DeletionCount=count(), ResourceTypes=make_set(ResourceProviderValue), Resources=make_list(ResourceId, 20) by Caller, bin(TimeGenerated, 30m) | where DeletionCount > 10 // Tune to your baseline | project TimeWindow=TimeGenerated, Actor=Caller, DeletionCount, ResourceTypes, Resources | sort by DeletionCount desc
Data source: AzureActivity table in Sentinel
Azure — New Service Principal with Client Secret (Persistence) High T1136.003 AuditLogs
Detects creation of new service principals or addition of credentials to existing ones — common persistence technique after initial compromise.
AuditLogs | where OperationName in ("Add service principal","Add service principal credentials","Update application – Certificates and secrets management") | extend Actor = tostring(InitiatedBy.user.userPrincipalName) | extend AppName = tostring(TargetResources[0].displayName) | project TimeGenerated, Actor, AppName, OperationName, CorrelationId | sort by TimeGenerated desc
Data source: AuditLogs table in Sentinel

☁ AWS CloudTrail — Splunk

Requires Splunk Add-on for Amazon Web Services. Index: aws, sourcetype: aws:cloudtrail
IAM Admin Role Creation or Attachment High T1078.004 CloudTrail
Detects creation of new IAM users or attachment of admin policies — initial persistence or privilege escalation.
index=aws sourcetype="aws:cloudtrail" (eventName="CreateUser" OR eventName="AttachUserPolicy" OR eventName="CreateRole" OR eventName="AttachRolePolicy") (requestParameters.policyArn="*AdministratorAccess" OR requestParameters.policyDocument="*\"Action\":\"*\"*") | stats count by eventTime, userIdentity.arn, eventName, requestParameters.userName, requestParameters.policyArn, sourceIPAddress, awsRegion | sort -eventTime
index=aws sourcetype=aws:cloudtrail (Splunk Add-on for AWS required)
CloudTrail or GuardDuty Disabled High T1562.008 CloudTrail
Detects disabling of key detective controls — immediate investigation required.
index=aws sourcetype="aws:cloudtrail" (eventName="StopLogging" OR eventName="DeleteTrail" OR eventName="UpdateTrail" OR eventName="DeleteDetector" OR eventName="DisassociateFromMasterAccount") | table _time, userIdentity.arn, eventName, requestParameters, sourceIPAddress, awsRegion | sort -_time
index=aws sourcetype=aws:cloudtrail (Splunk Add-on for AWS required)
EC2 Security Group — SSH/RDP Opened to World High T1562.007 CloudTrail
Detects when a security group rule is added allowing SSH or RDP from 0.0.0.0/0.
index=aws sourcetype="aws:cloudtrail" eventName="AuthorizeSecurityGroupIngress" | spath input=requestParameters output=ipRanges path=ipPermissions.items{}.ipRanges.items{}.cidrIp | where ipRanges="0.0.0.0/0" | spath input=requestParameters output=ports path=ipPermissions.items{}.toPort | where ports IN ("22","3389") | table _time, userIdentity.arn, requestParameters.groupId, ports, sourceIPAddress, awsRegion | sort -_time
index=aws sourcetype=aws:cloudtrail (Splunk Add-on for AWS required)
Mass S3 GetObject — Potential Data Exfiltration High T1530 CloudTrail
Detects burst of S3 GetObject requests from a single identity in a short window.
index=aws sourcetype="aws:cloudtrail" eventName="GetObject" | bin _time span=1h | stats count as downloads, dc(requestParameters.bucketName) as bucket_count by _time, userIdentity.arn, sourceIPAddress | where downloads > 300 | sort -downloads | table _time, userIdentity.arn, sourceIPAddress, downloads, bucket_count
index=aws sourcetype=aws:cloudtrail (Splunk Add-on for AWS required)
New EC2 Instance with Suspicious User Data Medium T1496 CloudTrail
Detects crypto-mining or backdoor installation via EC2 user data on launch.
index=aws sourcetype="aws:cloudtrail" eventName="RunInstances" | spath input=requestParameters output=userData path=userData | eval decoded=urldecode(userData) | where match(decoded, "(?i)(xmrig|minerd|cryptonight|curl.*sh|wget.*sh|bash -i|nc -e)") | table _time, userIdentity.arn, sourceIPAddress, awsRegion, decoded | sort -_time
index=aws sourcetype=aws:cloudtrail (Splunk Add-on for AWS required)
Root Account Used High T1078.004 CloudTrail
Any use of the AWS root account should be investigated immediately.
index=aws sourcetype="aws:cloudtrail" userIdentity.type="Root" NOT eventName IN ("GetBucketPolicy","DescribeEventAggregates") | table _time, eventName, sourceIPAddress, userAgent, awsRegion, errorCode | sort -_time
index=aws sourcetype=aws:cloudtrail (Splunk Add-on for AWS required)

🔷 Azure Activity — Splunk

Requires Splunk Add-on for Microsoft Azure. Index: azure, sourcetype: azure:monitor:activity
Bulk Azure Resource Deletion High T1485 Azure Monitor
Detects mass resource deletion across an Azure subscription — ransomware or destructive attack.
index=azure sourcetype="azure:monitor:activity" operationName="*delete*" status="Succeeded" NOT caller="*automation*" | bin _time span=30m | stats count as deletions, values(resourceType) as types, values(resourceId) as resources by _time, caller | where deletions > 10 | sort -deletions
index=azure sourcetype=azure:monitor:activity or azure:aad:signin
New Privileged Role Assigned High T1078.004 Azure Monitor
Detects assignment of privileged Azure AD roles.
index=azure sourcetype="azure:aad:audit" operationName="Add member to role" OR operationName="Add eligible member to role" | spath output=role path=targetResources{}.displayName | where match(role, "(?i)(Global Administrator|Privileged Role|Security Admin|User Admin)") | table _time, initiatedBy.user.userPrincipalName, targetResources{}.userPrincipalName, role | sort -_time
index=azure sourcetype=azure:monitor:activity or azure:aad:signin
Azure Login from Unexpected Country High T1078 Azure Monitor
Detects Azure AD logins from countries not in your expected geo-list — useful for Indian orgs expecting primarily India-based logins.
index=azure sourcetype="azure:aad:signin" resultType=0 NOT location.countryOrRegion IN ("India","IN") | stats count by userPrincipalName, location.countryOrRegion, location.city, ipAddress | sort -count
index=azure sourcetype=azure:monitor:activity or azure:aad:signin