Detection rules › Kusto
Kusto rules: 1h
[Deprecated] - Zinc Actor IOCs domains hashes IPs and useragent - October 2022
#Use Microsoft's up-to-date Threat Intelligence solution from the Content Hub to replace the deprecated query with outdated IoCs. Install it from: https://learn.microsoft.com/azure/sentinel/sentinel-solutions-deploy
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
Rule Body
id: 95543d6d-f00d-4193-a63f-4edeefb7ec36
name: "[Deprecated] - Zinc Actor IOCs domains hashes IPs and useragent - October 2022"
description: |
'Use Microsoft's up-to-date Threat Intelligence solution from the Content Hub to replace the deprecated query with outdated IoCs. Install it from: https://learn.microsoft.com/azure/sentinel/sentinel-solutions-deploy'
severity: High
status: Available
requiredDataConnectors:
- connectorId: DNS
dataTypes:
- DnsEvents
- connectorId: AzureMonitor(VMInsights)
dataTypes:
- VMConnection
- connectorId: F5
dataTypes:
- CommonSecurityLog
- connectorId: CiscoASA
dataTypes:
- CommonSecurityLog
- connectorId: CiscoAsaAma
dataTypes:
- CommonSecurityLog
- connectorId: PaloAltoNetworks
dataTypes:
- CommonSecurityLog
- connectorId: Fortinet
dataTypes:
- CommonSecurityLog
- connectorId: CheckPoint
dataTypes:
- CommonSecurityLog
- connectorId: CEF
dataTypes:
- CommonSecurityLog
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceNetworkEvents
- DeviceFileEvents
- DeviceEvents
- DeviceProcessEvents
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: Office365
dataTypes:
- OfficeActivity
- connectorId: AzureFirewall
dataTypes:
- AzureDiagnostics
- connectorId: WindowsFirewall
dataTypes:
- WindowsFirewall
- connectorId: WindowsFirewallAma
dataTypes:
- WindowsFirewall
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1546
tags:
- Zinc
query: |
let iocs = externaldata(DateAdded:string,IoC:string,Type:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/ZincOctober2022IOCs.csv"] with (format="csv", ignoreFirstRecord=True);
let domains = (iocs | where Type =~ "domainname"| project IoC);
let IPList = (iocs | where Type =~ "ip"| project IoC);
let sha256Hashes = (iocs | where Type =~ "sha256" | project IoC);
let useragents = (iocs | where Type =~ "useragent" | project IoC);
(union isfuzzy=true
(CommonSecurityLog
| where DestinationHostName has_any (domains) or RequestURL has_any (domains) or Message has_any (domains) or SourceIP has_any (IPList) or DestinationIP has_any (IPList)
| parse Message with * '(' DNSName ')' *
| project TimeGenerated, Message, SourceUserID, RequestURL, DestinationHostName, Type, SourceIP, DestinationIP, DNSName
| extend timestamp = TimeGenerated, AccountEntity = SourceUserID, UrlEntity = RequestURL , IPEntity = DestinationIP, DNSCustomEntity = DNSName
),
(DnsEvents
| where Name in~ (domains) or IPAddresses has_any (IPList)
| project TimeGenerated, Computer, IPAddresses, Name, ClientIP, Type
| extend DNSName = Name, Host = Computer
| extend timestamp = TimeGenerated, HostEntity = Host, DNSCustomEntity = DNSName, IPEntity = IPAddresses
),
(VMConnection
| where RemoteDnsCanonicalNames has_any (domains) or SourceIp has_any (IPList) or DestinationIp has_any (IPList)
| parse RemoteDnsCanonicalNames with * '["' DNSName '"]' *
| project TimeGenerated, Computer, Direction, RemoteDnsCanonicalNames, ProcessName, SourceIp, DestinationIp, DestinationPort, DNSName,BytesSent, BytesReceived, RemoteCountry, Type
| extend timestamp = TimeGenerated, IPEntity = DestinationIp, HostEntity = Computer, ProcessEntity = ProcessName, DNSCustomEntity = DNSName
),
(Event
| where Source =~ "Microsoft-Windows-Sysmon"
| where EventID == 3
| extend EvData = parse_xml(EventData)
| extend EventDetail = EvData.DataItem.EventData.Data
| extend SourceIP = tostring(EventDetail.[9].["#text"]), DestinationIP = tostring(EventDetail.[14].["#text"]), Image = EventDetail.[4].["#text"]
| where SourceIP has_any (IPList) or DestinationIP has_any (IPList)
| project TimeGenerated, SourceIP, DestinationIP, Image, UserName, Computer, EventDetail, Type
| extend timestamp = TimeGenerated, AccountEntity = UserName, ProcessEntity = tostring(split(Image, '\\', -1)[-1]), HostEntity = Computer , IPEntity = DestinationIP
),
(DeviceNetworkEvents
| where RemoteUrl has_any (domains) or RemoteIP has_any (IPList) or InitiatingProcessSHA256 in (sha256Hashes)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, RemoteIP, RemoteUrl, LocalIP, Type
| extend timestamp = TimeGenerated, IPEntity = RemoteIP, HostEntity = DeviceName, UrlEntity =RemoteUrl
),
(AzureDiagnostics
| where ResourceType =~ "AZUREFIREWALLS"
| where Category =~ "AzureFirewallDnsProxy"
| project TimeGenerated,Resource, msg_s, Type
| parse msg_s with "DNS Request: " ClientIP ":" ClientPort " - " QueryID " " Request_Type " " Request_Class " " Request_Name ". " Request_Protocol " " Request_Size " " EDNSO_DO " " EDNS0_Buffersize " " Responce_Code " " Responce_Flags " " Responce_Size " " Response_Duration
| where Request_Name has_any (domains) or ClientIP has_any (IPList)
| extend timestamp = TimeGenerated, DNSName = Request_Name, IPEntity = ClientIP
),
(AzureDiagnostics
| where ResourceType =~ "AZUREFIREWALLS"
| where Category =~ "AzureFirewallApplicationRule"
| project TimeGenerated,Resource, msg_s
| parse msg_s with Protocol 'request from ' SourceHost ':' SourcePort 'to ' DestinationHost ':' DestinationPort '. Action:' Action
| where DestinationHost has_any (domains) or SourceHost has_any (IPList)
| extend timestamp = TimeGenerated, DNSName = DestinationHost, IPEntity = SourceHost
),
(Event
| where Source =~ "Microsoft-Windows-Sysmon"
| where EventID == 1
| extend EvData = parse_xml(EventData)
| extend EventDetail = EvData.DataItem.EventData.Data
| parse EventDetail with * 'SHA256=' SHA256 '",' *
| extend Image = EventDetail.[4].["#text"], CommandLine = EventDetail.[10].["#text"]
| where SHA256 has_any (sha256Hashes)
| project TimeGenerated, EventDetail, UserName, Computer, Type, Source, SHA256, CommandLine, Image
| extend Type = strcat(Type, ": ", Source)
| extend timestamp = TimeGenerated, HostEntity = Computer , AccountEntity = UserName, ProcessEntity = tostring(split(Image, '\\', -1)[-1]), AlgorithmEntity = "SHA256", FileHashEntity = SHA256
),
(DeviceProcessEvents
| where InitiatingProcessSHA256 has_any (sha256Hashes)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessSHA256, FolderPath, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = InitiatingProcessAccountName, ProcessEntity = InitiatingProcessFileName, AlgorithmEntity = "SHA256", FileHashEntity = InitiatingProcessSHA256
),
(DeviceFileEvents
| where InitiatingProcessSHA256 has_any (sha256Hashes)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, RequestAccountName, RequestSourceIP, InitiatingProcessSHA256, FolderPath, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = RequestAccountName, ProcessEntity = InitiatingProcessFileName, AlgorithmEntity = "SHA256", FileHashEntity = InitiatingProcessSHA256
),
(DeviceEvents
| where InitiatingProcessSHA256 has_any (sha256Hashes)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessSHA256, FolderPath, Type
| extend CommandLine = InitiatingProcessCommandLine
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = InitiatingProcessAccountName, ProcessEntity = InitiatingProcessFileName, AlgorithmEntity = "SHA256", FileHashEntity = InitiatingProcessSHA256
),
(OfficeActivity
| where ClientIP has_any (IPList) or UserAgent has_any (useragents)
| project TimeGenerated, UserAgent, Operation, RecordType, UserId, ClientIP, Type
| extend timestamp = TimeGenerated, IPEntity = ClientIP, AccountEntity = UserId
)
)
| extend HostName = tostring(split(HostEntity, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(HostEntity, '.'), 1, -1), '.'))
| extend Name = tostring(split(AccountEntity, '@', 0)[0]), UPNSuffix = tostring(split(AccountEntity, '@', 1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPEntity
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ProcessEntity
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: AlgorithmEntity
- identifier: Value
columnName: FileHashEntity
version: 1.0.4
kind: Scheduled
Stages and Predicates
Let Binding: iocs
let iocs = externaldata(DateAdded:string,IoC:string,Type:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/ZincOctober2022IOCs.csv"] with (format="csv", ignoreFirstRecord=True);
Let Binding: domains
let domains = (iocs | where Type =~ "domainname"| project IoC);
Let Binding: IPList
let IPList = (iocs | where Type =~ "ip"| project IoC);
Let Binding: sha256Hashes
let sha256Hashes = (iocs | where Type =~ "sha256" | project IoC);
Let Binding: useragents
let useragents = (iocs | where Type =~ "useragent" | project IoC);
Union isfuzzy=true (12 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: CommonSecurityLog, DnsEvents, VMConnection, Event, DeviceNetworkEvents, AzureDiagnostics, AzureDiagnostics, Event, DeviceProcessEvents, DeviceFileEvents, DeviceEvents, OfficeActivity
Leg 1: CommonSecurityLog
CommonSecurityLog
| where DestinationHostName has_any (domains) or RequestURL has_any (domains) or Message has_any (domains) or SourceIP has_any (IPList) or DestinationIP has_any (IPList)
| parse Message with * '(' DNSName ')' *
| project TimeGenerated, Message, SourceUserID, RequestURL, DestinationHostName, Type, SourceIP, DestinationIP, DNSName
| extend timestamp = TimeGenerated, AccountEntity = SourceUserID, UrlEntity = RequestURL , IPEntity = DestinationIP, DNSCustomEntity = DNSName
Leg 2: DnsEvents
DnsEvents
| where Name in~ (domains) or IPAddresses has_any (IPList)
| project TimeGenerated, Computer, IPAddresses, Name, ClientIP, Type
| extend DNSName = Name, Host = Computer
| extend timestamp = TimeGenerated, HostEntity = Host, DNSCustomEntity = DNSName, IPEntity = IPAddresses
Leg 3: VMConnection
VMConnection
| where RemoteDnsCanonicalNames has_any (domains) or SourceIp has_any (IPList) or DestinationIp has_any (IPList)
| parse RemoteDnsCanonicalNames with * '["' DNSName '"]' *
| project TimeGenerated, Computer, Direction, RemoteDnsCanonicalNames, ProcessName, SourceIp, DestinationIp, DestinationPort, DNSName,BytesSent, BytesReceived, RemoteCountry, Type
| extend timestamp = TimeGenerated, IPEntity = DestinationIp, HostEntity = Computer, ProcessEntity = ProcessName, DNSCustomEntity = DNSName
Leg 4: Event
Event
| where Source =~ "Microsoft-Windows-Sysmon"
| where EventID == 3
| extend EvData = parse_xml(EventData)
| extend EventDetail = EvData.DataItem.EventData.Data
| extend SourceIP = tostring(EventDetail.[9].["#text"]), DestinationIP = tostring(EventDetail.[14].["#text"]), Image = EventDetail.[4].["#text"]
| where SourceIP has_any (IPList) or DestinationIP has_any (IPList)
| project TimeGenerated, SourceIP, DestinationIP, Image, UserName, Computer, EventDetail, Type
| extend timestamp = TimeGenerated, AccountEntity = UserName, ProcessEntity = tostring(split(Image, '\\', -1)[-1]), HostEntity = Computer , IPEntity = DestinationIP
Leg 5: DeviceNetworkEvents
DeviceNetworkEvents
| where RemoteUrl has_any (domains) or RemoteIP has_any (IPList) or InitiatingProcessSHA256 in (sha256Hashes)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, RemoteIP, RemoteUrl, LocalIP, Type
| extend timestamp = TimeGenerated, IPEntity = RemoteIP, HostEntity = DeviceName, UrlEntity =RemoteUrl
Leg 6: AzureDiagnostics
AzureDiagnostics
| where ResourceType =~ "AZUREFIREWALLS"
| where Category =~ "AzureFirewallDnsProxy"
| project TimeGenerated,Resource, msg_s, Type
| parse msg_s with "DNS Request: " ClientIP ":" ClientPort " - " QueryID " " Request_Type " " Request_Class " " Request_Name ". " Request_Protocol " " Request_Size " " EDNSO_DO " " EDNS0_Buffersize " " Responce_Code " " Responce_Flags " " Responce_Size " " Response_Duration
| where Request_Name has_any (domains) or ClientIP has_any (IPList)
| extend timestamp = TimeGenerated, DNSName = Request_Name, IPEntity = ClientIP
Leg 7: AzureDiagnostics
AzureDiagnostics
| where ResourceType =~ "AZUREFIREWALLS"
| where Category =~ "AzureFirewallApplicationRule"
| project TimeGenerated,Resource, msg_s
| parse msg_s with Protocol 'request from ' SourceHost ':' SourcePort 'to ' DestinationHost ':' DestinationPort '. Action:' Action
| where DestinationHost has_any (domains) or SourceHost has_any (IPList)
| extend timestamp = TimeGenerated, DNSName = DestinationHost, IPEntity = SourceHost
Leg 8: Event
Event
| where Source =~ "Microsoft-Windows-Sysmon"
| where EventID == 1
| extend EvData = parse_xml(EventData)
| extend EventDetail = EvData.DataItem.EventData.Data
| parse EventDetail with * 'SHA256=' SHA256 '",' *
| extend Image = EventDetail.[4].["#text"], CommandLine = EventDetail.[10].["#text"]
| where SHA256 has_any (sha256Hashes)
| project TimeGenerated, EventDetail, UserName, Computer, Type, Source, SHA256, CommandLine, Image
| extend Type = strcat(Type, ": ", Source)
| extend timestamp = TimeGenerated, HostEntity = Computer , AccountEntity = UserName, ProcessEntity = tostring(split(Image, '\\', -1)[-1]), AlgorithmEntity = "SHA256", FileHashEntity = SHA256
Leg 9: DeviceProcessEvents
DeviceProcessEvents
| where InitiatingProcessSHA256 has_any (sha256Hashes)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessSHA256, FolderPath, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = InitiatingProcessAccountName, ProcessEntity = InitiatingProcessFileName, AlgorithmEntity = "SHA256", FileHashEntity = InitiatingProcessSHA256
Leg 10: DeviceFileEvents
DeviceFileEvents
| where InitiatingProcessSHA256 has_any (sha256Hashes)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, RequestAccountName, RequestSourceIP, InitiatingProcessSHA256, FolderPath, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = RequestAccountName, ProcessEntity = InitiatingProcessFileName, AlgorithmEntity = "SHA256", FileHashEntity = InitiatingProcessSHA256
Leg 11: DeviceEvents
DeviceEvents
| where InitiatingProcessSHA256 has_any (sha256Hashes)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessSHA256, FolderPath, Type
| extend CommandLine = InitiatingProcessCommandLine
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = InitiatingProcessAccountName, ProcessEntity = InitiatingProcessFileName, AlgorithmEntity = "SHA256", FileHashEntity = InitiatingProcessSHA256
Leg 12: OfficeActivity
OfficeActivity
| where ClientIP has_any (IPList) or UserAgent has_any (useragents)
| project TimeGenerated, UserAgent, Operation, RecordType, UserId, ClientIP, Type
| extend timestamp = TimeGenerated, IPEntity = ClientIP, AccountEntity = UserId
Applied to the Combined Result
| extend HostName = tostring(split(HostEntity, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(HostEntity, '.'), 1, -1), '.'))
| extend Name = tostring(split(AccountEntity, '@', 0)[0]), UPNSuffix = tostring(split(AccountEntity, '@', 1)[0])
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ClientIP | project |
Operation | project |
RecordType | project |
TimeGenerated | project |
Type | project |
UserAgent | project |
UserId | project |
AccountEntity | extend |
IPEntity | extend |
timestamp | extend |
DnsDomain | extend |
HostName | extend |
Name | extend |
UPNSuffix | extend |
Anomalous User Agent connection attempt
#'Identifies connection attempts (success or fail) from clients with very short or very long User Agent strings and with less than 100 connection attempts.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: f845881e-2500-44dc-8ed7-b372af3e1e25
name: Anomalous User Agent connection attempt
description: |
'Identifies connection attempts (success or fail) from clients with very short or very long User Agent strings and with less than 100 connection attempts.'
severity: Low
requiredDataConnectors:
- connectorId: AzureMonitor(IIS)
dataTypes:
- W3CIISLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
query: |
let short_uaLength = 5;
let long_uaLength = 1000;
let c_threshold = 100;
W3CIISLog
// Exclude local IPs as these create noise
| where cIP !startswith "192.168." and cIP != "::1"
| where isnotempty(csUserAgent) and csUserAgent !in~ ("-", "MSRPC") and (string_size(csUserAgent) <= short_uaLength or string_size(csUserAgent) >= long_uaLength)
| extend csUserAgent_size = string_size(csUserAgent)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ConnectionCount = count() by Computer, sSiteName, sPort, csUserAgent, csUserAgent_size, csUserName , csMethod, csUriStem, sIP, cIP, scStatus, scSubStatus, scWin32Status
| where ConnectionCount < c_threshold
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(csUserName, "@")[0]), AccountUPNSuffix = tostring(split(csUserName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: csUserName
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: HostNameDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: cIP
version: 1.0.3
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Threat Protection" ]
Stages and Predicates
Parameters
let short_uaLength = 5;
let long_uaLength = 1000;
let c_threshold = 100;
Stage 1: source
W3CIISLog
Stage 2: where
| where cIP !startswith "192.168." and cIP != "::1"
Stage 3: where
| where isnotempty(csUserAgent) and csUserAgent !in~ ("-", "MSRPC") and (string_size(csUserAgent) <= short_uaLength or string_size(csUserAgent) >= long_uaLength)
Stage 4: extend
| extend csUserAgent_size = string_size(csUserAgent)
Stage 5: summarize
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ConnectionCount = count() by Computer, sSiteName, sPort, csUserAgent, csUserAgent_size, csUserName , csMethod, csUriStem, sIP, cIP, scStatus, scSubStatus, scWin32Status
Stage 6: where
| where ConnectionCount < c_threshold
Stage 7: extend (3 consecutive steps)
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(csUserName, "@")[0]), AccountUPNSuffix = tostring(split(csUserName, "@")[1])
Indicators
These rows show field, operator, and value matches.
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
cIP | starts_with | 192.168. | excludes:cIP field:"cIP" value:"192.168." |
csUserAgent | in | -, MSRPC | excludes:csUserAgent field:"csUserAgent" value:"-" field:"csUserAgent" value:"MSRPC" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | summarize |
ConnectionCount | summarize |
EndTimeUtc | summarize |
StartTimeUtc | summarize |
cIP | summarize |
csMethod | summarize |
csUriStem | summarize |
csUserAgent | summarize |
csUserAgent_size | summarize |
csUserName | summarize |
sIP | summarize |
sPort | summarize |
sSiteName | summarize |
scStatus | summarize |
scSubStatus | summarize |
scWin32Status | summarize |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
AccountName | extend |
AccountUPNSuffix | extend |
Azure DevOps Retention Reduced
#'AzureDevOps retains items such as run records and produced artifacts for a configurable amount of time. An attacker looking to reduce the footprint left by their malicious activity may look to reduce the retention time for artifacts and runs. This query will look for where retention has been reduced to the minimum level - 1, or reduced by more than half.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Rule Body
id: 71d374e0-1cf8-4e50-aecd-ab6c519795c2
name: Azure DevOps Retention Reduced
description: |
'AzureDevOps retains items such as run records and produced artifacts for a configurable amount of time. An attacker looking to reduce the footprint left by their malicious activity may look to reduce the retention time for artifacts and runs.
This query will look for where retention has been reduced to the minimum level - 1, or reduced by more than half.'
severity: Low
status: Available
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1564
query: |
ADOAuditLogs
| where OperationName =~ "Pipelines.PipelineRetentionSettingChanged"
| where Data.SettingName in ("PurgeArtifacts", "PurgeRuns")
| where Data.NewValue == 1 or Data.NewValue < Data.OldValue/2
| project-reorder TimeGenerated, OperationName, ActorUPN, IpAddress, UserAgent, Data
| extend timestamp = TimeGenerated
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUPN
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IpAddress
version: 1.0.5
kind: Scheduled
Stages and Predicates
Stage 1: source
ADOAuditLogs
Stage 2: where
| where OperationName =~ "Pipelines.PipelineRetentionSettingChanged"
Stage 3: where
| where Data.SettingName in ("PurgeArtifacts", "PurgeRuns")
Stage 4: where
| where Data.NewValue == 1 or Data.NewValue < Data.OldValue/2
Stage 5: project-reorder
| project-reorder TimeGenerated, OperationName, ActorUPN, IpAddress, UserAgent, Data
Stage 6: extend
| extend timestamp = TimeGenerated
Stage 7: extend
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Data.NewValue | eq |
| field:"Data.NewValue" kind:eq value:"1" |
Data.SettingName | in |
| field:"Data.SettingName" kind:in |
OperationName | eq |
| field:"OperationName" kind:eq value:"Pipelines.PipelineRetentionSettingChanged" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
timestamp | extend |
AccountName | extend |
AccountUPNSuffix | extend |
Azure secure score block legacy authentication
#'This query searches for most compromising sign-in attempts come from legacy authentication. Older office clients such as Office 2010 do not support modern authentication and use legacy protocols such as IMAP, SMTP, and POP3. Legacy authentication does not support multi-factor authentication (MFA). Even if an MFA policy is configured in your environment, bad actors can bypass these enforcements through legacy protocols.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: C27BB559-28C5-4924-A7DA-3BF04CD02C8F
name: Azure secure score block legacy authentication
description: |
'This query searches for most compromising sign-in attempts come from legacy authentication.
Older office clients such as Office 2010 do not support modern authentication and use legacy protocols such as IMAP, SMTP, and POP3.
Legacy authentication does not support multi-factor authentication (MFA).
Even if an MFA policy is configured in your environment, bad actors can bypass these enforcements through legacy protocols.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SenservaPro
dataTypes:
- SenservaPro_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1212
- T1556
query: |
SenservaPro_CL
| where ControlName_s == 'AzureSecureScoreBlockLegacyAuthentication'
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: ControlName_s
- identifier: AadTenantId
columnName: TenantId
- identifier: DisplayName
columnName: TenantDisplayName_s
- entityType: SecurityGroup
fieldMappings:
- identifier: DistinguishedName
columnName: Group_s
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: SourceSystem
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
SenservaPro_CL
Stage 2: where
| where ControlName_s == 'AzureSecureScoreBlockLegacyAuthentication'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ControlName_s | eq |
| field:"ControlName_s" kind:eq value:"AzureSecureScoreBlockLegacyAuthentication" |
Cisco Duo - Admin password reset
#'Detects when admin's password was reset.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: 413e49a5-b107-4698-8428-46b89308bd22
name: Cisco Duo - Admin password reset
description: |
'Detects when admin's password was reset.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CiscoDuoActivityConnectorDefinition
dataTypes:
- DuoActivity_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1078
query: |
DuoActivity_CL
| where ActionName =~ "admin_reset_password"
| extend AccountCustomEntity = ActorName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
DuoActivity_CL
Stage 2: where
| where ActionName =~ "admin_reset_password"
Stage 3: extend
| extend AccountCustomEntity = ActorName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionName | eq |
| field:"ActionName" kind:eq value:"admin_reset_password" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
CYFIRMA - Customer Accounts Leaks Detection Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"Detects recent leaks of customer account credentials based on CYFIRMA's threat intelligence. This rule surfaces the latest credential exposures, including email, username, and breach metadata. It enables security teams to quickly identify and investigate leaked customer data from third-party breaches, dark web listings, or public repositories."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access |
Rule Body
id: ebd1bf8d-aa18-4e66-9cad-555b71a290f1
name: CYFIRMA - Customer Accounts Leaks Detection Rule
description: |
"Detects recent leaks of customer account credentials based on CYFIRMA's threat intelligence.
This rule surfaces the latest credential exposures, including email, username, and breach metadata.
It enables security teams to quickly identify and investigate leaked customer data from third-party breaches, dark web listings, or public repositories."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
- connectorId: CyfirmaCompromisedAccountsDataConnector
dataTypes:
- CyfirmaCompromisedAccounts_CL
queryFrequency: 5m
queryPeriod: 5m
suppressionDuration: 6h
suppressionEnabled: true
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CredentialAccess
- InitialAccess
relevantTechniques:
- T1552
- T1078
query: |
// Customer Accounts Leaks - Latest per UID
let timeFrame = 5m;
CyfirmaCompromisedAccounts_CL
| where TimeGenerated between (ago(timeFrame) .. now())
and Category has "Customer Accounts Leaks"
| extend
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| summarize arg_max(TimeGenerated,
user_name,
email,
url,
password,
breach_date,
first_seen,
last_seen,
impact,
recommendations,
description,
source,
ProductName,
ProviderName
) by uid
| sort by TimeGenerated desc
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: user_name
- identifier: UPNSuffix
columnName: email
- entityType: URL
fieldMappings:
- identifier: Url
columnName: url
customDetails:
UID: uid
TimeGenerated: TimeGenerated
BreachDate: breach_date
FirstSeen: first_seen
LastSeen: last_seen
Impact: impact
Recommendations: recommendations
Source: source
Description: description
alertDetailsOverride:
alertDisplayNameFormat: "Customer Leak - {{user_name}} - {{email}}"
alertDescriptionFormat: "{{description}}"
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaCompromisedAccounts_CL
Stage 2: where
| where TimeGenerated between (ago(timeFrame) .. now())
and Category has "Customer Accounts Leaks"
Stage 3: extend
| extend
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 4: summarize
| summarize arg_max(TimeGenerated,
user_name,
email,
url,
password,
breach_date,
first_seen,
last_seen,
impact,
recommendations,
description,
source,
ProductName,
ProviderName
) by uid
Stage 5: sort
| sort by TimeGenerated desc
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | match |
| field:"Category" kind:match value:"Customer Accounts Leaks" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
uid | summarize |
CYFIRMA - Medium Severity Asset based Vulnerabilities Rule Alert
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This rule detects medium severity asset-based vulnerabilities from CYFIRMA's vulnerability intelligence data. It identifies vulnerabilities with a confidence score of 50 or higher, excluding those categorized as 'ATTACK_SURFACE_VULNERABILITY', and generates alerts for assets that may be at risk."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Privilege Escalation | |
| Defense Impairment | |
| Credential Access | |
| Lateral Movement |
Rule Body
id: 6306f2d9-34a3-409a-850d-175b7bdd1ab1
name: CYFIRMA - Medium Severity Asset based Vulnerabilities Rule Alert
description: |
"This rule detects medium severity asset-based vulnerabilities from CYFIRMA's vulnerability intelligence data.
It identifies vulnerabilities with a confidence score of 50 or higher, excluding those categorized as 'ATTACK_SURFACE_VULNERABILITY', and generates alerts for assets that may be at risk."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaVulnerabilitiesIntelDC
dataTypes:
- CyfirmaVulnerabilities_CL
query: |
// Medium severity - Asset based Vulnerabilities
let timeFrame= 5m;
CyfirmaVulnerabilities_CL
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
| mv-expand extensionKeys
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
attack_complexity = tostring(props.attack_complexity),
cvss_score = toreal(props.cvss_score),
integrity_impact = tostring(props.integrity_impact),
impact_score = tostring(props.impact_score),
attack_vector = tostring(props.attack_vector),
privileges_required = tostring(props.privileges_required),
cvss_version = tostring(props.cvss_version),
user_interaction = tostring(props.user_interaction),
cvss_vector = tostring(props.cvss_vector),
scope = tostring(props.scope),
confidentiality_impact = tostring(props.confidentiality_impact),
exploitability_score = toreal(props.exploitability_score),
products = tostring(props.products),
technologies = tostring(props.technologies),
vendors = tostring(props.vendors),
confidence_score = toint(confidence),
servers = tostring(props.servers),
vulnerability_type = tostring(props.vulnerability_type),
vulnerability_category = tostring(props.vulnerability_category),
NetworkIPs = tostring(props.ips),
ProviderName ='CYFIRMA',
ProductName ='DeCYFIR/DeTCT'
| summarize arg_max(
integrity_impact,
TimeGenerated,
id,
description,
confidence_score,
created,
modified,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
)
by name
| where confidence_score >= 60 and vulnerability_category != 'ATTACK_SURFACE_VULNERABILITY' and TimeGenerated between (ago(timeFrame) .. now())
| project
TimeGenerated,
name,
confidence_score,
integrity_impact,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
UID = id,
description,
created,
modified,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: false
tactics:
- Execution
- LateralMovement
- PrivilegeEscalation
- InitialAccess
- CredentialAccess
- DefenseEvasion
relevantTechniques:
- T1059
- T1203
- T1210
- T1068
- T1190
- T1133
- T1003
- T1553
- T1548.002
- T1021.002
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - Medium Severity Asset based Vulnerability Identified - {{name}} "
alertDescriptionFormat: "{{description}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
TimeGenerated: TimeGenerated
CVE: name
ConfidenceScore: confidence_score
IntegrityImpact: integrity_impact
AttackComplexity: attack_complexity
CVSSScore: cvss_score
ImpactScore: impact_score
AttackVector: attack_vector
ConfidentialImpact: confidentiality_impact
PrivilegesRequired: privileges_required
CVSSVersion: cvss_version
UserInteraction: user_interaction
CVSSVector: cvss_vector
scope: scope
ExploitabilityScore: exploitability_score
Modified: modified
Products: products
Vendors: vendors
Technologies: technologies
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaVulnerabilities_CL
Stage 2: extend
| extend parsed = parse_json(extensions)
Stage 3: extend
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
attack_complexity = tostring(props.attack_complexity),
cvss_score = toreal(props.cvss_score),
integrity_impact = tostring(props.integrity_impact),
impact_score = tostring(props.impact_score),
attack_vector = tostring(props.attack_vector),
privileges_required = tostring(props.privileges_required),
cvss_version = tostring(props.cvss_version),
user_interaction = tostring(props.user_interaction),
cvss_vector = tostring(props.cvss_vector),
scope = tostring(props.scope),
confidentiality_impact = tostring(props.confidentiality_impact),
exploitability_score = toreal(props.exploitability_score),
products = tostring(props.products),
technologies = tostring(props.technologies),
vendors = tostring(props.vendors),
confidence_score = toint(confidence),
servers = tostring(props.servers),
vulnerability_type = tostring(props.vulnerability_type),
vulnerability_category = tostring(props.vulnerability_category),
NetworkIPs = tostring(props.ips),
ProviderName ='CYFIRMA',
ProductName ='DeCYFIR/DeTCT'
Stage 6: summarize
| summarize arg_max(
integrity_impact,
TimeGenerated,
id,
description,
confidence_score,
created,
modified,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
)
by name
Stage 7: where
| where confidence_score >= 60 and vulnerability_category != 'ATTACK_SURFACE_VULNERABILITY' and TimeGenerated between (ago(timeFrame) .. now())
Stage 8: project
| project
TimeGenerated,
name,
confidence_score,
integrity_impact,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
UID = id,
description,
created,
modified,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
confidence_score | ge |
| field:"confidence_score" kind:ge value:"60" |
vulnerability_category | ne |
| field:"vulnerability_category" kind:ne value:"ATTACK_SURFACE_VULNERABILITY" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
NetworkIPs | project |
ProductName | project |
ProviderName | project |
TimeGenerated | project |
UID | project |
attack_complexity | project |
attack_vector | project |
confidence_score | project |
confidentiality_impact | project |
created | project |
cvss_score | project |
cvss_vector | project |
cvss_version | project |
description | project |
exploitability_score | project |
impact_score | project |
integrity_impact | project |
modified | project |
name | project |
privileges_required | project |
products | project |
scope | project |
servers | project |
technologies | project |
user_interaction | project |
vendors | project |
vulnerability_category | project |
vulnerability_type | project |
Dataverse - Audit logging disabled
#Identifies a change in system audit configuration whereby audit logging is turned off.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Dataverse and model-driven apps audit (catch-all) |
Rule Body
id: ea07523b-e6b8-469b-9e25-cdef1ae6fb45
kind: Scheduled
name: Dataverse - Audit logging disabled
description: Identifies a change in system audit configuration whereby audit logging
is turned off.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: Dataverse
dataTypes:
- DataverseActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
let query_frequency = 1h;
let audit_properties = dynamic(['isauditenabled', 'isuseraccessauditenabled','isreadauditenabled']);
DataverseActivity
| where TimeGenerated >= ago(query_frequency)
| where Message in~ ("Update", "UpdateAuditSettings")
| where Fields has_any (audit_properties)
| mv-expand Fields
| extend AuditValue = Fields.Name, AuditEnabled = tobool(Fields.Value)
| where AuditValue in~ (audit_properties) and not (AuditEnabled)
| extend
CloudAppId = int(32780),
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1])
| project
TimeGenerated,
UserId,
ClientIp,
OriginalObjectId,
AuditValue,
AuditEnabled,
InstanceUrl,
CloudAppId,
AccountName,
UPNSuffix
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: CloudAppId
- identifier: InstanceName
columnName: InstanceUrl
alertDetailsOverride:
alertDisplayNameFormat: 'Dataverse - Audit logging was disabled in {{InstanceUrl}} '
alertDescriptionFormat: 'Audit settings changes were detected in {{InstanceUrl}}.
{{AuditValue}} enabled: was set to {{AuditEnabled}}.'
version: 3.2.1
Stages and Predicates
Parameters
let query_frequency = 1h;
let audit_properties = dynamic(['isauditenabled', 'isuseraccessauditenabled','isreadauditenabled']);
Stage 1: source
DataverseActivity
Stage 2: where
| where TimeGenerated >= ago(query_frequency)
Stage 3: where
| where Message in~ ("Update", "UpdateAuditSettings")
Stage 4: where
| where Fields has_any (audit_properties)
Stage 5: mv-expand
| mv-expand Fields
Stage 6: extend
| extend AuditValue = Fields.Name, AuditEnabled = tobool(Fields.Value)
Stage 7: where
| where AuditValue in~ (audit_properties) and not (AuditEnabled)
Stage 8: extend
| extend
CloudAppId = int(32780),
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1])
Stage 9: project
| project
TimeGenerated,
UserId,
ClientIp,
OriginalObjectId,
AuditValue,
AuditEnabled,
InstanceUrl,
CloudAppId,
AccountName,
UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AuditValue | in |
| field:"AuditValue" kind:in |
Fields | match |
| field:"Fields" kind:match |
Message | in |
| field:"Message" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
AuditEnabled | eq | true | excludes:AuditEnabled field:"AuditEnabled" value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
AuditEnabled | project |
AuditValue | project |
ClientIp | project |
CloudAppId | project |
InstanceUrl | project |
OriginalObjectId | project |
TimeGenerated | project |
UPNSuffix | project |
UserId | project |
Detect instances of multiple client errors occurring within a brief period of time (ASIM Web Session)
#'This detection mechanism identifies situations where multiple client errors originate from a single source within a limited time frame.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Command & Control |
Rule Body
id: faa40333-1e8b-40cc-a003-51ae41fa886f
name: Detect instances of multiple client errors occurring within a brief period of time (ASIM Web Session)
description: |
'This detection mechanism identifies situations where multiple client errors originate from a single source within a limited time frame.'
severity: Medium
status: Available
tags:
- Schema: WebSession
SchemaVersion: 0.2.6
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CommandAndControl
relevantTechniques:
- T1190
- T1133
- T1071
query: |
// HTTP response status codes indicate whether a specific HTTP request has been successfully completed.
// Please refer this for more details: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
let threshold = 100; // You can set threshold value that suits your environment
_Im_WebSession(starttime=ago(1h))
| where toint(EventResultDetails) between (400 .. 499)
| summarize
TotalErrorCount = count(),
EventStartTime = min(TimeGenerated),
EventEndTime = max(TimeGenerated),
URLs=make_set(Url, 100),
EventResultDetailsSet=make_set(EventResultDetails,10)
by SrcIpAddr, bin(TimeGenerated, 5m), SrcUsername, SrcHostname
| where TotalErrorCount > threshold
| extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),""), Threshold=threshold
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SrcHostname
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
TotalErrorCount: TotalErrorCount
ErrorThreshold: Threshold
RequestURLs: URLs
EventResultSet: EventResultDetailsSet
EventStartTime: EventStartTime
EventEndTime: EventEndTime
alertDetailsOverride:
alertDisplayNameFormat: "High number of client errors originated by user '{{SrcUsername}}' from IP address '{{SrcIpAddr}}'"
alertDescriptionFormat: "The client has made a total of '{{TotalErrorCount}}' requests to URLs '{{URLs}}',
which have resulted in client errors. A sudden surge in HTTP code errors, especially in the form of client-side errors like 400 or 401, could indicate malicious activity, such as attackers attempting to exploit vulnerabilities or perform unauthorized actions. For detailed information regarding the specific errors encountered, please refer to the following link:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status."
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 100;
Stage 1: source
_Im_WebSession(starttime=ago(1h))
Stage 2: where
| where toint(EventResultDetails) between (400 .. 499)
Stage 3: summarize
| summarize
TotalErrorCount = count(),
EventStartTime = min(TimeGenerated),
EventEndTime = max(TimeGenerated),
URLs=make_set(Url, 100),
EventResultDetailsSet=make_set(EventResultDetails,10)
by SrcIpAddr, bin(TimeGenerated, 5m), SrcUsername, SrcHostname
Stage 4: where
| where TotalErrorCount > threshold
Stage 5: extend
| extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),""), Threshold=threshold
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventEndTime | summarize |
EventResultDetailsSet | summarize |
EventStartTime | summarize |
SrcHostname | summarize |
SrcIpAddr | summarize |
SrcUsername | summarize |
TotalErrorCount | summarize |
URLs | summarize |
Name | extend |
Threshold | extend |
UPNSuffix | extend |
Detect Malicious Teams Message
#This detection rule detects Microsoft Teams messages where MDO detected a threat in the message.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution |
References
Telemetry Coverage
Rule Body
// Malicious messages detection
MessageEvents
| where ThreatTypes != ""
| join kind=leftouter MessageUrlInfo on TeamsMessageId
Stages and Predicates
Stage 1: source
MessageEvents
Stage 2: where
| where ThreatTypes != ""
Stage 3: join
| join kind=leftouter MessageUrlInfo on TeamsMessageId
GCP IAM - New Service Account Key
#Detects new service account key creation.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GCP | google.iam.admin.CreateServiceAccountKey: Create service account key |
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: fc135860-8773-4ead-b5be-9789af1ff8ff
name: GCP IAM - New Service Account Key
description: |
'Detects new service account key creation.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: GCPIAMDataConnector
dataTypes:
- GCP_IAM
queryFrequency: 24h
queryPeriod: 24h
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
relevantTechniques:
- T1550
query: |
GCP_IAM
| where PayloadMethodname =~ 'google.iam.admin.v1.CreateServiceAccountKey'
| extend result = parse_json(todynamic(PayloadAuthorizationinfo))[0]['granted']
| where result =~ 'true'
| extend service_account = extract(@'serviceAccounts\/(.*?)@', 1, PayloadResponseName)
| project-away result
| extend AccountName = tostring(split(service_account, "@")[0]), AccountUPNSuffix = tostring(split(service_account, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: service_account
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
GCP_IAM
Stage 2: where
| where PayloadMethodname =~ 'google.iam.admin.v1.CreateServiceAccountKey'
Stage 3: extend
| extend result = parse_json(todynamic(PayloadAuthorizationinfo))[0]['granted']
Stage 4: where
| where result =~ 'true'
Stage 5: extend
| extend service_account = extract(@'serviceAccounts\/(.*?)@', 1, PayloadResponseName)
Stage 6: project-away
| project-away result
Stage 7: extend
| extend AccountName = tostring(split(service_account, "@")[0]), AccountUPNSuffix = tostring(split(service_account, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PayloadMethodname | eq |
| field:"gcp::method_name" kind:eq value:"google.iam.admin.v1.CreateServiceAccountKey" |
result | eq |
| field:"result" kind:eq value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
service_account | extend |
AccountName | extend |
AccountUPNSuffix | extend |
Google DNS - CVE-2021-40444 exploitation
#'Detects CVE-2021-40444 exploitation.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Rule Body
id: 6758c671-e9ee-495d-b6b0-92ffd08a8c3b
name: Google DNS - CVE-2021-40444 exploitation
description: |
'Detects CVE-2021-40444 exploitation.'
severity: High
requiredDataConnectors:
- connectorId: GCPDNSDataConnector
dataTypes:
- GCPCloudDNS
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1068
query: |
GCPCloudDNS
| where Query has_any ('hidusi.com', 'dodefoh.com', 'joxinu.com')
| extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DNSCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GCPCloudDNS
Stage 2: where
| where Query has_any ('hidusi.com', 'dodefoh.com', 'joxinu.com')
Stage 3: extend
| extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Query | match |
| field:"Query" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DNSCustomEntity | extend |
IPCustomEntity | extend |
Pathlock TDnR - TMS Transport and Import Events
#Detects SAP Transport Management System (TMS) transport and import events, forwarded by Pathlock Threat Detection and Response. Unauthorized transports may introduce malicious code changes, configuration weaknesses, or backdoors into production SAP systems, bypassing normal change management and approval processes.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Persistence |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c71
name: Pathlock TDnR - TMS Transport and Import Events
kind: Scheduled
description: >-
Detects SAP Transport Management System (TMS) transport and import events, forwarded by Pathlock
Threat Detection and Response. Unauthorized transports may introduce malicious code changes,
configuration weaknesses, or backdoors into production SAP systems, bypassing normal
change management and approval processes.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- Execution
relevantTechniques:
- T1059
- T1543
query: |
Pathlock_TDnR_CL
| where DataSource == "TRANSPORT_LOG"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "TRANSPORT_LOG"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"TRANSPORT_LOG" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Potential Lateral Movement via MSI ODBC Driver Install over DCOM
#Detects Potential Lateral Movement via MSI Custom Actions to install ODBC Driver over DCOM remotely.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement | No specific technique |
References
Telemetry Coverage
Rule Body
// Description: Detect Potential Lateral Movement via MSI ODBC Driver Installer over DCOM
// Author: Cyb3rMonk(https://x.com/Cyb3rMonk)
// Website: https://academy.bluraven.io
// Reference: https://specterops.io/blog/2025/09/29/dcom-again-installing-trouble-lateral-movement-bof/
//
DeviceProcessEvents
| where InitiatingProcessParentFileName == "services.exe"
| where InitiatingProcessFileName == "msiexec.exe"
| where FileName == "msiexec.exe"
| project DeviceId, DeviceName, ProcessUniqueId
| join kind=inner (
DeviceNetworkEvents
| where ActionType == "InboundConnectionAccepted"
| where InitiatingProcessFileName == "msiexec.exe"
| distinct DeviceId
) on DeviceId
| join kind=inner
DeviceImageLoadEvents
on DeviceId, DeviceName, $left.ProcessUniqueId == $right.InitiatingProcessUniqueId
| project-away DeviceId1, DeviceName1, DeviceId2
| project-reorder TimeGenerated, ActionType, DeviceId, DeviceName, FolderPath, InitiatingProcessFileName
Stages and Predicates
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where InitiatingProcessParentFileName == "services.exe"
Stage 3: where
| where InitiatingProcessFileName == "msiexec.exe"
Stage 4: where
| where FileName == "msiexec.exe"
Stage 5: project
| project DeviceId, DeviceName, ProcessUniqueId
Stage 6: join
| join kind=inner (
DeviceNetworkEvents
| where ActionType == "InboundConnectionAccepted"
| where InitiatingProcessFileName == "msiexec.exe"
| distinct DeviceId
) on DeviceId
Stage 7: join
| join kind=inner
DeviceImageLoadEvents
on DeviceId, DeviceName, $left.ProcessUniqueId == $right.InitiatingProcessUniqueId
Stage 8: project-away
project-away DeviceId1, DeviceId2, DeviceName1
Stage 9: project-reorder
project-reorder
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"InboundConnectionAccepted" |
FileName | eq |
| field:"process_basename" kind:eq value:"msiexec.exe" |
InitiatingProcessFileName | eq |
| field:"parent_process_basename" kind:eq |
InitiatingProcessParentFileName | eq |
| field:"grandparent_process_basename" kind:eq value:"services.exe" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceId | project |
DeviceName | project |
ProcessUniqueId | project |
Rare application consent
#This will alert when the "Consent to application" operation occurs by a user that has not done this operation before or rarely does this. This could indicate that permissions to access the listed Azure App were provided to a malicious actor. Consent to application, Add service principal and Add OAuth2PermissionGrant should typically be rare events. This may help detect the Oauth2 attack that can be initiated by this publicly available tool - https://github.com/fireeye/PwnAuth For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | any: Entra ID audit event (any operation) |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- [Entra ID] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles (Kusto)
- Added Credentials to Existing Application (Sigma)
- Added Owner To Application (Sigma)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Granted Microsoft Permissions (Sigma)
- App Granted Privileged Delegated Or App Permissions (Sigma)
- Application AppID Uri Configuration Changes (Sigma)
Rule Body
id: 83ba3057-9ea3-4759-bf6a-933f2e5bc7ee
name: Rare application consent
description: |
'This will alert when the "Consent to application" operation occurs by a user that has not done this operation before or rarely does this.
This could indicate that permissions to access the listed Azure App were provided to a malicious actor.
Consent to application, Add service principal and Add OAuth2PermissionGrant should typically be rare events.
This may help detect the Oauth2 attack that can be initiated by this publicly available tool - https://github.com/fireeye/PwnAuth
For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 3
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1136
- T1068
query: |
let current = 1d;
let auditLookback = 7d;
// Setting threshold to 3 as a default, change as needed.
// Any operation that has been initiated by a user or app more than 3 times in the past 7 days will be excluded
let threshold = 3;
// Gather initial data from lookback period, excluding current, adjust current to more than a single day if no results
let AuditTrail = AuditLogs | where TimeGenerated >= ago(auditLookback) and TimeGenerated < ago(current)
// 2 other operations that can be part of malicious activity in this situation are
// "Add OAuth2PermissionGrant" and "Add service principal", extend the filter below to capture these too
| where OperationName has "Consent to application"
| extend InitiatedBy = iff(isnotempty(tostring(InitiatedBy.user.userPrincipalName)),
tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend TargetResourceName = tolower(tostring(TargetResource.displayName))
)
| summarize max(TimeGenerated), OperationCount = count() by OperationName, InitiatedBy, TargetResourceName
// only including operations initiated by a user or app that is above the threshold so we produce only rare and has not occurred in last 7 days
| where OperationCount > threshold;
// Gather current period of audit data
let RecentConsent = AuditLogs | where TimeGenerated >= ago(current)
| where OperationName has "Consent to application"
| extend IpAddress = case(
isnotempty(tostring(InitiatedBy.user.ipAddress)) and tostring(InitiatedBy.user.ipAddress) != 'null', tostring(InitiatedBy.user.ipAddress),
isnotempty(tostring(InitiatedBy.app.ipAddress)) and tostring(InitiatedBy.app.ipAddress) != 'null', tostring(InitiatedBy.app.ipAddress),
'Not Available')
| extend InitiatedBy = iff(isnotempty(tostring(InitiatedBy.user.userPrincipalName)),
tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend TargetResourceName = tolower(tostring(TargetResource.displayName)),
props = TargetResource.modifiedProperties
)
| parse props with * "ConsentType: " ConsentType "]" *
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
| project TimeGenerated, InitiatedBy, IpAddress, TargetResourceName, Category, OperationName, ConsentType, UserAgent, CorrelationId, Type;
// Exclude previously seen audit activity for "Consent to application" that was seen in the lookback period
// First for rare InitiatedBy
let RareConsentBy = RecentConsent | join kind= leftanti AuditTrail on OperationName, InitiatedBy
| extend Reason = "Previously unseen user consenting";
// Second for rare TargetResourceName
let RareConsentApp = RecentConsent | join kind= leftanti AuditTrail on OperationName, TargetResourceName
| extend Reason = "Previously unseen app granted consent";
RareConsentBy | union RareConsentApp
| summarize Reason = make_set(Reason,100) by TimeGenerated, InitiatedBy, IpAddress, TargetResourceName, Category, OperationName, ConsentType, UserAgent, CorrelationId, Type
| extend timestamp = TimeGenerated, Name = tolower(tostring(split(InitiatedBy,'@',0)[0])), UPNSuffix = tolower(tostring(split(InitiatedBy,'@',1)[0]))
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatedBy
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: TargetResourceName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IpAddress
version: 1.1.5
kind: Scheduled
Stages and Predicates
Parameters
let current = 1d;
let auditLookback = 7d;
let threshold = 3;
let RecentConsent, let RareConsentBy and let RareConsentApp are inlined into the numbered stages below.
Let Binding: AuditTrail
let AuditTrail = AuditLogs | where TimeGenerated >= ago(auditLookback) and TimeGenerated < ago(current)
| where OperationName has "Consent to application"
| extend InitiatedBy = iff(isnotempty(tostring(InitiatedBy.user.userPrincipalName)),
tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend TargetResourceName = tolower(tostring(TargetResource.displayName))
)
| summarize max(TimeGenerated), OperationCount = count() by OperationName, InitiatedBy, TargetResourceName
| where OperationCount > threshold;
Stages 1 to 11 define let RareConsentBy as the rule's main pipeline source. Stages 12 to 25 combine it with further branches.
Stage 1: source
AuditLogs
Stage 2: where
| where TimeGenerated >= ago(current)
Stage 3: where
| where OperationName has "Consent to application"
Stage 4: extend
| extend IpAddress = case(
isnotempty(tostring(InitiatedBy.user.ipAddress)) and tostring(InitiatedBy.user.ipAddress) != 'null', tostring(InitiatedBy.user.ipAddress),
isnotempty(tostring(InitiatedBy.app.ipAddress)) and tostring(InitiatedBy.app.ipAddress) != 'null', tostring(InitiatedBy.app.ipAddress),
'Not Available')
IpAddress =isnotempty(tostring(InitiatedBy.user.ipAddress)) and InitiatedBy.user.ipAddress != "null"tostring(InitiatedBy.user.ipAddress)isnotempty(tostring(InitiatedBy.app.ipAddress)) and InitiatedBy.app.ipAddress != "null"tostring(InitiatedBy.app.ipAddress)'Not Available'Stage 5: extend
| extend InitiatedBy = iff(isnotempty(tostring(InitiatedBy.user.userPrincipalName)),
tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
InitiatedBy =isnotempty(tostring(InitiatedBy.user.userPrincipalName))tostring(InitiatedBy.user.userPrincipalName)tostring(InitiatedBy.app.displayName)Stage 6: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend TargetResourceName = tolower(tostring(TargetResource.displayName)),
props = TargetResource.modifiedProperties
)
Stage 7: parse
| parse props with * "ConsentType: " ConsentType "]" *
Stage 8: kusto:mv-apply
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
Stage 9: project
| project TimeGenerated, InitiatedBy, IpAddress, TargetResourceName, Category, OperationName, ConsentType, UserAgent, CorrelationId, Type
Stage 10: join (negated)
| join kind= leftanti AuditTrail on OperationName, InitiatedBy
Stage 11: extend
| extend Reason = "Previously unseen user consenting"
Stage 12: union
RareConsentBy
| union RareConsentApp
Stage 13: source
AuditLogs
Stage 14: where
| where TimeGenerated >= ago(current)
Stage 15: where
| where OperationName has "Consent to application"
Stage 16: extend
| extend IpAddress = case(
isnotempty(tostring(InitiatedBy.user.ipAddress)) and tostring(InitiatedBy.user.ipAddress) != 'null', tostring(InitiatedBy.user.ipAddress),
isnotempty(tostring(InitiatedBy.app.ipAddress)) and tostring(InitiatedBy.app.ipAddress) != 'null', tostring(InitiatedBy.app.ipAddress),
'Not Available')
IpAddress =isnotempty(tostring(InitiatedBy.user.ipAddress)) and InitiatedBy.user.ipAddress != "null"tostring(InitiatedBy.user.ipAddress)isnotempty(tostring(InitiatedBy.app.ipAddress)) and InitiatedBy.app.ipAddress != "null"tostring(InitiatedBy.app.ipAddress)'Not Available'Stage 17: extend
| extend InitiatedBy = iff(isnotempty(tostring(InitiatedBy.user.userPrincipalName)),
tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
InitiatedBy =isnotempty(tostring(InitiatedBy.user.userPrincipalName))tostring(InitiatedBy.user.userPrincipalName)tostring(InitiatedBy.app.displayName)Stage 18: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend TargetResourceName = tolower(tostring(TargetResource.displayName)),
props = TargetResource.modifiedProperties
)
Stage 19: parse
| parse props with * "ConsentType: " ConsentType "]" *
Stage 20: kusto:mv-apply
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
Stage 21: project
| project TimeGenerated, InitiatedBy, IpAddress, TargetResourceName, Category, OperationName, ConsentType, UserAgent, CorrelationId, Type
Stage 22: join (negated)
| join kind= leftanti AuditTrail on OperationName, TargetResourceName
Stage 23: extend
| extend Reason = "Previously unseen app granted consent"
Stage 24: summarize
| summarize Reason = make_set(Reason,100) by TimeGenerated, InitiatedBy, IpAddress, TargetResourceName, Category, OperationName, ConsentType, UserAgent, CorrelationId, Type
Stage 25: extend
| extend timestamp = TimeGenerated, Name = tolower(tostring(split(InitiatedBy,'@',0)[0])), UPNSuffix = tolower(tostring(split(InitiatedBy,'@',1)[0]))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AdditionalDetail.key | eq |
| field:"AdditionalDetail.key" kind:eq value:"User-Agent" |
OperationName | match |
| field:"OperationName" kind:match value:"Consent to application" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"ServicePrincipal" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
OperationCount | gt | 3 | excludes:OperationCount field:"OperationCount" value:"3" |
OperationName | match | Consent to application | excludes:OperationName field:"OperationName" value:"Consent to application" |
TargetResource.type | eq | ServicePrincipal | excludes:TargetResource.type field:"TargetResource.type" value:"ServicePrincipal" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Category | summarize |
ConsentType | summarize |
CorrelationId | summarize |
InitiatedBy | summarize |
IpAddress | summarize |
OperationName | summarize |
Reason | summarize |
TargetResourceName | summarize |
TimeGenerated | summarize |
Type | summarize |
UserAgent | summarize |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
SAP ETD - Synch investigations
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
Synch investigations coming in from SAP Enterprise Threat Detection into Microsoft Sentinel (one way)
Rule Body
id: 5096db53-fad3-4844-a264-246f7b7e6e06
kind: Scheduled
name: SAP ETD - Synch investigations
description: Synch investigations coming in from SAP Enterprise Threat Detection into Microsoft Sentinel (one way)
severity: High
status: Available
requiredDataConnectors:
- connectorId: SAPETDAlerts
dataTypes:
- SAPETDInvestigations_CL
queryFrequency: 1h
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
let AuditTimeAgo = 2d;
let _severity= dynamic(["HIGH","VERY HIGH"]);
SAPETDInvestigations_CL
| where TimeGenerated > ago(AuditTimeAgo)
| where Severity in (_severity)
| mv-expand Users
| extend
UserAccountName = tostring(Users.UserAccountName),
UserEmail = tostring(Users.EmailAddresses[0])
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: UserAccountName
- entityType: Mailbox
fieldMappings:
- identifier: MailboxPrimaryAddress
columnName: UserEmail
alertDetailsOverride:
alertDisplayNameFormat: 'SAP ETD - {{Description}} '
alertDescriptionFormat: 'Description: {{Description}}. Processed by {{Processor}}. Severity: {{Severity}}.'
customDetails:
ETD_InvestNumber: InvestigationId
SAP_UserAccount: UserAccountName
SAP_UserEmail: UserEmail
version: 1.0.1
Stages and Predicates
Parameters
let AuditTimeAgo = 2d;
let _severity = dynamic(["HIGH","VERY HIGH"]);
Stage 1: source
SAPETDInvestigations_CL
Stage 2: where
| where TimeGenerated > ago(AuditTimeAgo)
Stage 3: where
| where Severity in (_severity)
Stage 4: mv-expand
| mv-expand Users
Stage 5: extend
| extend
UserAccountName = tostring(Users.UserAccountName),
UserEmail = tostring(Users.EmailAddresses[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Severity | in |
| field:"Severity" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserAccountName | extend |
UserEmail | extend |
SenservaPro AD Applications Not Using Client Credentials
#'Searches for logs of AD Applications without Client Credentials (Key or Secret)'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 56910d7b-aae7-452c-a3ed-89f72ef59234
name: SenservaPro AD Applications Not Using Client Credentials
description: |
'Searches for logs of AD Applications without Client Credentials (Key or Secret)'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SenservaPro
dataTypes:
- SenservaPro_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1529
- T1485
query: |
SenservaPro_CL
| where ControlName_s == 'ApplicationNotUsingClientCredentials'
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: ControlName_s
- identifier: AadTenantId
columnName: TenantId
- identifier: DisplayName
columnName: TenantDisplayName_s
- entityType: SecurityGroup
fieldMappings:
- identifier: DistinguishedName
columnName: Group_s
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: SourceSystem
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SenservaPro_CL
Stage 2: where
| where ControlName_s == 'ApplicationNotUsingClientCredentials'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ControlName_s | eq |
| field:"ControlName_s" kind:eq value:"ApplicationNotUsingClientCredentials" |
SpyCloud Enterprise Breach Detection
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
'This alert creates an incident when an malware record is detected in the SpyCloud watchlist data'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: cb410ad5-6e9d-4278-b963-1e3af205d680
name: SpyCloud Enterprise Breach Detection
description: |
'This alert creates an incident when an malware record is detected in the SpyCloud watchlist data'
severity: High
requiredDataConnectors: []
status: Available
queryFrequency: 12h
queryPeriod: 12h
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
tactics:
- CredentialAccess
relevantTechniques:
- T1555
query: |
SpyCloudBreachDataWatchlist_CL
| where Severity_s == '20'
| project TimeGenerated, Document_Id_g, Source_Id_s, SpyCloud_Publish_Date_t, Email_s, Domain_s, Password_s, Password_Plaintext_s, Username_s, IP_Address_s
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 12h
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride: null
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Email_s
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Username_s
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IP_Address_s
customDetails:
Document_Id: Document_Id_g
Password: Password_s
Password_Plaintext: Password_Plaintext_s
Source_Id: Source_Id_s
Domain: Domain_s
PublishDate: SpyCloud_Publish_Date_t
sentinelEntitiesMappings: null
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
SpyCloudBreachDataWatchlist_CL
Stage 2: where
| where Severity_s == '20'
Stage 3: project
| project TimeGenerated, Document_Id_g, Source_Id_s, SpyCloud_Publish_Date_t, Email_s, Domain_s, Password_s, Password_Plaintext_s, Username_s, IP_Address_s
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Severity_s | eq |
| field:"Severity_s" kind:eq value:"20" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Document_Id_g | project |
Domain_s | project |
Email_s | project |
IP_Address_s | project |
Password_Plaintext_s | project |
Password_s | project |
Source_Id_s | project |
SpyCloud_Publish_Date_t | project |
TimeGenerated | project |
Username_s | project |