Detection rules › Kusto

Kusto rules: 1h

[Deprecated] - Zinc Actor IOCs domains hashes IPs and useragent - October 2022

#
Status
available
Severity
high
Time window
6h
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
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.

FieldKindValuesSearch
Categoryeq
  • AzureFirewallApplicationRule
  • AzureFirewallDnsProxy
field:"Category" kind:eq
ClientIPmatch
  • IPList transforms: term
field:"ClientIP" kind:match value:"IPList"
DestinationHostmatch
  • domains transforms: term
field:"DestinationHost" kind:match value:"domains"
DestinationHostNamematch
  • domains transforms: term
field:"DestinationHostName" kind:match value:"domains"
DestinationIPmatch
  • IPList transforms: term
field:"DestinationIP" kind:match value:"IPList"
DestinationIpmatch
  • IPList transforms: term
field:"dest_ip" kind:match value:"IPList"
EventIDeq
  • 1 corpus 12 (kusto 11, elastic 1)
  • 3 corpus 5 (kusto 3, chronicle 2)
field:"EventID" kind:eq
IPAddressesmatch
  • IPList transforms: term
field:"IPAddresses" kind:match value:"IPList"
InitiatingProcessSHA256in
  • sha256Hashes
field:"InitiatingProcessSHA256" kind:in value:"sha256Hashes"
InitiatingProcessSHA256match
  • sha256Hashes transforms: term
field:"InitiatingProcessSHA256" kind:match value:"sha256Hashes"
Messagematch
  • domains transforms: term
field:"Message" kind:match value:"domains"
Namein
  • domains
field:"Name" kind:in value:"domains"
RemoteDnsCanonicalNamesmatch
  • domains transforms: term
field:"RemoteDnsCanonicalNames" kind:match value:"domains"
RemoteIPmatch
  • IPList transforms: term
field:"DestinationIp" kind:match value:"IPList"
RemoteUrlmatch
  • domains transforms: term
field:"DestinationHostname" kind:match value:"domains"
RequestURLmatch
  • domains transforms: term
field:"RequestURL" kind:match value:"domains"
Request_Namematch
  • domains transforms: term
field:"Request_Name" kind:match value:"domains"
ResourceTypeeq
  • AZUREFIREWALLS
field:"ResourceType" kind:eq value:"AZUREFIREWALLS"
SHA256match
  • sha256Hashes transforms: term
field:"sha256" kind:match value:"sha256Hashes"
SourceHostmatch
  • IPList transforms: term
field:"SourceHost" kind:match value:"IPList"
SourceIPmatch
  • IPList transforms: term
field:"SourceIP" kind:match value:"IPList"
SourceIpmatch
  • IPList transforms: term
field:"src_ip" kind:match value:"IPList"
UserAgentmatch
  • useragents transforms: term
field:"aws::userAgent" kind:match value:"useragents"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientIPproject
Operationproject
RecordTypeproject
TimeGeneratedproject
Typeproject
UserAgentproject
UserIdproject
AccountEntityextend
IPEntityextend
timestampextend
DnsDomainextend
HostNameextend
Nameextend
UPNSuffixextend

Anomalous User Agent connection attempt

#
Severity
low
Time window
1d
Group by
Computer, cIP, csMethod, csUriStem, csUserAgent, csUserAgent_size, csUserName, sIP, sPort, sSiteName, scStatus, scSubStatus, scWin32Status
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

'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

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Computersummarize
ConnectionCountsummarize
EndTimeUtcsummarize
StartTimeUtcsummarize
cIPsummarize
csMethodsummarize
csUriStemsummarize
csUserAgentsummarize
csUserAgent_sizesummarize
csUserNamesummarize
sIPsummarize
sPortsummarize
sSiteNamesummarize
scStatussummarize
scSubStatussummarize
scWin32Statussummarize
DomainIndexextend
HostNameextend
HostNameDomainextend
AccountNameextend
AccountUPNSuffixextend

Azure DevOps Retention Reduced

#
Status
available
Severity
low
Time window
1d
Source
github.com/Azure/Azure-Sentinel

'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

TacticTechniques
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.

FieldKindValuesSearch
Data.NewValueeq
  • 1 transforms: kusto_dynamic_path
field:"Data.NewValue" kind:eq value:"1"
Data.SettingNamein
  • PurgeArtifacts transforms: kusto_dynamic_path
  • PurgeRuns transforms: kusto_dynamic_path
field:"Data.SettingName" kind:in
OperationNameeq
  • Pipelines.PipelineRetentionSettingChanged
field:"OperationName" kind:eq value:"Pipelines.PipelineRetentionSettingChanged"

Output Fields

These fields are emitted when the rule matches.

FieldSource
timestampextend
AccountNameextend
AccountUPNSuffixextend

Azure secure score block legacy authentication

#
Status
available
Severity
high
Time window
6h
Source
github.com/Azure/Azure-Sentinel

'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

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.

FieldKindValuesSearch
ControlName_seq
  • AzureSecureScoreBlockLegacyAuthentication
field:"ControlName_s" kind:eq value:"AzureSecureScoreBlockLegacyAuthentication"

Cisco Duo - Admin password reset

#
Status
available
Severity
high
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'Detects when admin's password was reset.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

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.

Status
available
Severity
high
Time window
5m
Group by
uid
Source
github.com/Azure/Azure-Sentinel

"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

TacticTechniques
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.

FieldKindValuesSearch
Categorymatch
  • Customer Accounts Leaks transforms: term
field:"Category" kind:match value:"Customer Accounts Leaks"

Output Fields

These fields are emitted when the rule matches.

FieldSource
uidsummarize

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.

Severity
medium
Time window
5m
Group by
name
Source
github.com/Azure/Azure-Sentinel

"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

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
NetworkIPsproject
ProductNameproject
ProviderNameproject
TimeGeneratedproject
UIDproject
attack_complexityproject
attack_vectorproject
confidence_scoreproject
confidentiality_impactproject
createdproject
cvss_scoreproject
cvss_vectorproject
cvss_versionproject
descriptionproject
exploitability_scoreproject
impact_scoreproject
integrity_impactproject
modifiedproject
nameproject
privileges_requiredproject
productsproject
scopeproject
serversproject
technologiesproject
user_interactionproject
vendorsproject
vulnerability_categoryproject
vulnerability_typeproject

Dataverse - Audit logging disabled

#
Status
available
Severity
low
Time window
14d
Source
github.com/Azure/Azure-Sentinel

Identifies a change in system audit configuration whereby audit logging is turned off.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Telemetry Coverage

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.

FieldKindValuesSearch
AuditValuein
  • isauditenabled
  • isreadauditenabled
  • isuseraccessauditenabled
field:"AuditValue" kind:in
Fieldsmatch
  • isauditenabled transforms: term
  • isreadauditenabled transforms: term
  • isuseraccessauditenabled transforms: term
field:"Fields" kind:match
Messagein
  • Update
  • UpdateAuditSettings
field:"Message" kind:in

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
AuditEnabledeqtrueexcludes:AuditEnabled field:"AuditEnabled" value:"true"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
AuditEnabledproject
AuditValueproject
ClientIpproject
CloudAppIdproject
InstanceUrlproject
OriginalObjectIdproject
TimeGeneratedproject
UPNSuffixproject
UserIdproject

Detect instances of multiple client errors occurring within a brief period of time (ASIM Web Session)

#
Status
available
Severity
medium
Time window
5m
Group by
SrcHostname, SrcIpAddr, SrcUsername
Source
github.com/Azure/Azure-Sentinel

'This detection mechanism identifies situations where multiple client errors originate from a single source within a limited time frame.'

MITRE ATT&CK Coverage

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.

FieldSource
EventEndTimesummarize
EventResultDetailsSetsummarize
EventStartTimesummarize
SrcHostnamesummarize
SrcIpAddrsummarize
SrcUsernamesummarize
TotalErrorCountsummarize
URLssummarize
Nameextend
Thresholdextend
UPNSuffixextend

Detect Malicious Teams Message

#
Group by
TeamsMessageId
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

This detection rule detects Microsoft Teams messages where MDO detected a threat in the message.

MITRE ATT&CK Coverage

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

#
Status
available
Severity
low
Time window
1d
Source
github.com/Azure/Azure-Sentinel

Detects new service account key creation.

MITRE ATT&CK Coverage

TacticTechniques
Lateral Movement

Telemetry Coverage

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
service_accountextend
AccountNameextend
AccountUPNSuffixextend

Google DNS - CVE-2021-40444 exploitation

#
Severity
high
Time window
15m
Source
github.com/Azure/Azure-Sentinel

'Detects CVE-2021-40444 exploitation.'

MITRE ATT&CK Coverage

TacticTechniques
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.

FieldKindValuesSearch
Querymatch
  • dodefoh.com transforms: term
  • hidusi.com transforms: term
  • joxinu.com transforms: term
field:"Query" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
DNSCustomEntityextend
IPCustomEntityextend

Pathlock TDnR - TMS Transport and Import Events

#
Status
available
Severity
high
Time window
1h
Source
github.com/Azure/Azure-Sentinel

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

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AffectedUserproject
Areaproject
Bnameproject
CentralTsproject
DataSourceproject
DestIpproject
Eventidproject
Hostnameproject
Instanceproject
LogLineproject
MessageV1project
MessageV2project
MessageV3project
MessageV4project
MsgIdproject
MsgNoproject
MsgTypeproject
Reportproject
SrcIpproject
Subidproject
Sysidproject
Tcodeproject
TimeGeneratedproject

Potential Lateral Movement via MSI ODBC Driver Install over DCOM

#
Group by
$left.ProcessUniqueId, $right.InitiatingProcessUniqueId, DeviceId, DeviceName
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

Detects Potential Lateral Movement via MSI Custom Actions to install ODBC Driver over DCOM remotely.

MITRE ATT&CK Coverage

TacticTechniques
Lateral MovementNo 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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceIdproject
DeviceNameproject
ProcessUniqueIdproject

Rare application consent

#
Status
available
Severity
medium
Time window
7d
Group by
Category, ConsentType, CorrelationId, InitiatedBy, IpAddress, OperationName, TargetResourceName, TimeGenerated, Type, UserAgent
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Persistence
Privilege Escalation

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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 used in Stages 10, 22

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 =
ifisnotempty(tostring(InitiatedBy.user.ipAddress)) and InitiatedBy.user.ipAddress != "null"tostring(InitiatedBy.user.ipAddress)
elifisnotempty(tostring(InitiatedBy.app.ipAddress)) and InitiatedBy.app.ipAddress != "null"tostring(InitiatedBy.app.ipAddress)
else'Not Available'

Stage 5: extend

| extend InitiatedBy = iff(isnotempty(tostring(InitiatedBy.user.userPrincipalName)),
                          tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
InitiatedBy =
ifisnotempty(tostring(InitiatedBy.user.userPrincipalName))tostring(InitiatedBy.user.userPrincipalName)
elsetostring(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 =
ifisnotempty(tostring(InitiatedBy.user.ipAddress)) and InitiatedBy.user.ipAddress != "null"tostring(InitiatedBy.user.ipAddress)
elifisnotempty(tostring(InitiatedBy.app.ipAddress)) and InitiatedBy.app.ipAddress != "null"tostring(InitiatedBy.app.ipAddress)
else'Not Available'

Stage 17: extend

| extend InitiatedBy = iff(isnotempty(tostring(InitiatedBy.user.userPrincipalName)),
                          tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
InitiatedBy =
ifisnotempty(tostring(InitiatedBy.user.userPrincipalName))tostring(InitiatedBy.user.userPrincipalName)
elsetostring(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.

FieldKindValuesSearch
AdditionalDetail.keyeq
  • User-Agent transforms: kusto_dynamic_path
field:"AdditionalDetail.key" kind:eq value:"User-Agent"
OperationNamematch
  • Consent to application transforms: term corpus 2 (kusto 2)
field:"OperationName" kind:match value:"Consent to application"
TargetResource.typeeq
  • ServicePrincipal transforms: kusto_dynamic_path
field:"TargetResource.type" kind:eq value:"ServicePrincipal"

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Categorysummarize
ConsentTypesummarize
CorrelationIdsummarize
InitiatedBysummarize
IpAddresssummarize
OperationNamesummarize
Reasonsummarize
TargetResourceNamesummarize
TimeGeneratedsummarize
Typesummarize
UserAgentsummarize
Nameextend
UPNSuffixextend
timestampextend

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.

Status
available
Severity
high
Time window
2d
Source
github.com/Azure/Azure-Sentinel

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.

FieldKindValuesSearch
Severityin
  • HIGH
  • VERY HIGH
field:"Severity" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserAccountNameextend
UserEmailextend

SenservaPro AD Applications Not Using Client Credentials

#
Status
available
Severity
medium
Time window
6h
Source
github.com/Azure/Azure-Sentinel

'Searches for logs of AD Applications without Client Credentials (Key or Secret)'

MITRE ATT&CK Coverage

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.

FieldKindValuesSearch
ControlName_seq
  • ApplicationNotUsingClientCredentials
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.

Status
available
Severity
high
Time window
12h
Source
github.com/Azure/Azure-Sentinel

'This alert creates an incident when an malware record is detected in the SpyCloud watchlist data'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Document_Id_gproject
Domain_sproject
Email_sproject
IP_Address_sproject
Password_Plaintext_sproject
Password_sproject
Source_Id_sproject
SpyCloud_Publish_Date_tproject
TimeGeneratedproject
Username_sproject

Back to all Kusto rules