Detection rules › Kusto

Kusto rules: 1f

Application Group Deleted

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

Detects when an application group is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.

Rule Body

id: fa8d692d-5b00-4a6c-99b3-30b4710efa59
name: Application Group Deleted
description: Detects when an application group is deleted from Veeam Backup & Replication.
  This might indicate unauthorized removal of critical components.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 30500\n| extend Name =  extract(\"\
  Name=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n    Date = format_datetime(TimeGenerated,\
  \ 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n    EventId = instanceId,\n\
  \    UserName = user,\n    [\"Application Group Name\"] = Name,\n    MessageDetails\
  \ = Description,\n    Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 30500

Stage 3: extend

| extend Name =  extract("Name=\"([^\"]*)\"", 1, SyslogMessage)

Stage 4: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    UserName = user,
    ["Application Group Name"] = Name,
    MessageDetails = Description,
    Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Application Group Nameproject
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
UserNameproject

Cisco Duo - Multiple user login failures

#
Status
available
Severity
high
Time window
10m
Group by
UserName
Source
github.com/Azure/Azure-Sentinel

'Detects when multiple user login failures occurs.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 034f62b6-df51-49f3-831f-1e4cfd3c40d2
name: Cisco Duo - Multiple user login failures
description: |
  'Detects when multiple user login failures occurs.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CiscoDuoAuthConnectorDefinition
    dataTypes:
      - DuoAuthentication_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let threshold = 10;
  DuoAuthentication_CL
  | where Result in~ ('denied', 'failure')
  | summarize count() by UserName, bin(TimeGenerated, 10m)
  | where count_ > threshold
  | extend AccountCustomEntity = UserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 10;

Stage 1: source

DuoAuthentication_CL

Stage 2: where

| where Result in~ ('denied', 'failure')

Stage 3: summarize

| summarize count() by UserName, bin(TimeGenerated, 10m)

Stage 4: where

| where count_ > threshold

Stage 5: extend

| extend AccountCustomEntity = UserName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Resultin
  • denied
  • failure
field:"Result" kind:in
count_gt
  • 10 transforms: number
field:"count_" kind:gt value:"10"

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserNamesummarize
AccountCustomEntityextend

Claroty - Suspicious file transfer

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

Detects Claroty events where EventOriginalType or EventType contains Suspicious File Transfer and maps the destination IP as the primary entity for triage.

MITRE ATT&CK Coverage

Rule Body

id: 5cf35bad-677f-4c23-8927-1611e7ff6f28
name: Claroty - Suspicious file transfer
description: 'Detects Claroty events where EventOriginalType or EventType contains Suspicious File Transfer and maps the destination
  IP as the primary entity for triage.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
  - Exfiltration
relevantTechniques:
  - T1018
  - T1020
query: |
  ClarotyEvent
  | where EventOriginalType has 'Suspicious File Transfer' or EventType has 'Suspicious File Transfer'
  | project TimeGenerated, DstIpAddr, EventOriginalType, EventType
  | extend IPCustomEntity = DstIpAddr
  | project TimeGenerated, DstIpAddr, EventOriginalType, EventType, IPCustomEntity
entityMappings:
- entityType: IP
  fieldMappings:
    - identifier: Address
      columnName: IPCustomEntity
alertDetailsOverride:
  alertDisplayNameFormat: Claroty suspicious file transfer to {{DstIpAddr}}
  alertDescriptionFormat: Claroty flagged suspicious file transfer activity to {{DstIpAddr}} using event type {{EventType}}
customDetails:
  EventOriginalType: EventOriginalType
  EventType: EventType
version: 1.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

ClarotyEvent

Stage 2: where

| where EventOriginalType has 'Suspicious File Transfer' or EventType has 'Suspicious File Transfer'

Stage 3: project

| project TimeGenerated, DstIpAddr, EventOriginalType, EventType

Stage 4: extend

| extend IPCustomEntity = DstIpAddr

Stage 5: project

| project TimeGenerated, DstIpAddr, EventOriginalType, EventType, IPCustomEntity

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventOriginalTypematch
  • Suspicious File Transfer transforms: term
field:"EventOriginalType" kind:match value:"Suspicious File Transfer"
EventTypematch
  • Suspicious File Transfer transforms: term
field:"EventType" kind:match value:"Suspicious File Transfer"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstIpAddrproject
EventOriginalTypeproject
EventTypeproject
IPCustomEntityproject
TimeGeneratedproject

Cloudflare - Unexpected POST requests

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

'Detects post requests to unusual extensions.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Command & Control

Rule Body

id: 7313352a-09f6-4a84-88bd-6f17f1cbeb88
name: Cloudflare - Unexpected POST requests
description: |
  'Detects post requests to unusual extensions.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CloudflareDataConnector
    dataTypes:
      - Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - CommandAndControl
relevantTechniques:
  - T1505
  - T1071
query: |
  Cloudflare
  | where HttpRequestMethod in~ ('POST', 'PUT')
  | where tostring(HttpStatusCode) startswith '2'
  | where DstBytes != 0 or SrcBytes != 0
  | extend fe = extract(@'.*(\.\w+)$', 1, ClientRequestURI)
  | where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

Cloudflare

Stage 2: where

| where HttpRequestMethod in~ ('POST', 'PUT')

Stage 3: where

| where tostring(HttpStatusCode) startswith '2'

Stage 4: where

| where DstBytes != 0 or SrcBytes != 0

Stage 5: extend

| extend fe = extract(@'.*(\.\w+)$', 1, ClientRequestURI)

Stage 6: where

| where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DstBytesne
  • 0
field:"DstBytes" kind:ne value:"0"
HttpRequestMethodin
  • POST
  • PUT
field:"HttpRequestMethod" kind:in
HttpStatusCodestarts_with
  • 2 transforms: tostring
field:"HttpStatusCode" kind:starts_with value:"2"
SrcBytesne
  • 0
field:"SrcBytes" kind:ne value:"0"
fein
  • .doc
  • .docx
  • .gif
  • .ico
  • .icon
  • .jpeg
  • .jpg
  • .mov
  • .pdf
  • .png
  • .ppt
  • .pttx
  • .rtf
  • .svg
  • .swf
  • .tif
  • .xml
  • .zip
field:"fe" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
feextend

CloudNGFW By Palo Alto Networks - Threat signatures from Unusual IP addresses

#
Status
available
Severity
medium
Time window
7d
Group by
FirewallName_s, src_ip
Source
github.com/Azure/Azure-Sentinel

'Identifies Palo Alto Threat signatures from unusual IP addresses which are not historically seen. This detection is also leveraged and required for MDE and PAN Fusion scenario https://docs.microsoft.com/Azure/sentinel/fusion-scenario-reference#network-request-to-tor-anonymization-service-followed-by-anomalous-traffic-flagged-by-palo-alto-networks-firewall'

MITRE ATT&CK Coverage

Rule Body

id: 89a86f70-615f-4a79-9621-6f68c50f365f
name: CloudNGFW By Palo Alto Networks - Threat signatures from Unusual IP addresses 
description: |
  'Identifies Palo Alto Threat signatures from unusual IP addresses which are not historically seen. 
  This detection is also leveraged and required for MDE and PAN Fusion scenario
  https://docs.microsoft.com/Azure/sentinel/fusion-scenario-reference#network-request-to-tor-anonymization-service-followed-by-anomalous-traffic-flagged-by-palo-alto-networks-firewall'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureCloudNGFWByPaloAltoNetworks
    dataTypes:
      - fluentbit_CL
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
  - Exfiltration
  - CommandAndControl
relevantTechniques:
  - T1046
  - T1030
  - T1071.001
tags:
  - Fusion
query: |
  let starttime = 7d;
  let endtime = 1d;
  let timeframe = 1h;
  let HistThreshold = 25; 
  let CurrThreshold = 10; 
  let HistoricalThreats = fluentbit_CL
  | where ident_s == "THREAT"
  | extend message = parse_json(Message)
  | where isnotempty(message.src_ip)
  | where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
  | where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
  | extend src_ip = tostring(message.src_ip)
  | summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s;
  let CurrentHourThreats = fluentbit_CL
  | where ident_s == "THREAT"
  | extend message = parse_json(Message)
  | where isnotempty(message.src_ip)
  | where TimeGenerated > ago(timeframe)
  | where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
  | extend src_ip = tostring(message.src_ip)
  | summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s;
  CurrentHourThreats 
  | where TotalEvents < CurrThreshold
  | join kind = leftanti (HistoricalThreats 
  | where TotalEvents > HistThreshold) on src_ip
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: src_ip
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let starttime = 7d;
let endtime = 1d;
let timeframe = 1h;
let HistThreshold = 25;
let CurrThreshold = 10;

let CurrentHourThreats is inlined into the numbered stages below.

Let Binding: HistoricalThreats used in Stage 10

let HistoricalThreats = fluentbit_CL
| where ident_s == "THREAT"
| extend message = parse_json(Message)
| where isnotempty(message.src_ip)
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
| where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
| extend src_ip = tostring(message.src_ip)
| summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s;

Stages 1 to 8 define let CurrentHourThreats as the rule's main pipeline source. Stages 9 to 10 use it.

Stage 1: source

fluentbit_CL

Stage 2: where

| where ident_s == "THREAT"

Stage 3: extend

| extend message = parse_json(Message)

Stage 4: where

| where isnotempty(message.src_ip)

Stage 5: where

| where TimeGenerated > ago(timeframe)

Stage 6: where

| where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')

Stage 7: extend

| extend src_ip = tostring(message.src_ip)

Stage 8: summarize

| summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s

Stage 9: where

CurrentHourThreats
| where TotalEvents < CurrThreshold

Stage 10: join (negated)

join kind=leftanti (HistoricalThreats) on src_ip

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
TotalEventslt
  • 10 transforms: number
field:"TotalEvents" kind:lt value:"10"
ident_seq
  • THREAT
field:"ident_s" kind:eq value:"THREAT"
message.src_ipis_not_null
  • (no value, null check)
field:"message.src_ip" kind:is_not_null
message.sub_typein
  • file transforms: kusto_dynamic_path
  • flood transforms: kusto_dynamic_path
  • packet transforms: kusto_dynamic_path
  • scan transforms: kusto_dynamic_path
  • spyware transforms: kusto_dynamic_path
  • virus transforms: kusto_dynamic_path
  • vulnerability transforms: kusto_dynamic_path
  • wildfire transforms: kusto_dynamic_path
  • wildfire-virus transforms: kusto_dynamic_path
field:"message.sub_type" kind:in

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
TotalEventsgt25excludes:TotalEvents field:"TotalEvents" value:"25"
ident_seqTHREATexcludes:ident_s field:"ident_s" value:"THREAT"
message.src_ipis_not_null(no value, null check)excludes:message.src_ip
message.sub_typeinfile, flood, packet, scan, spyware, virus, vulnerability, wildfire, wildfire-virusexcludes:message.sub_type

Output Fields

These fields are emitted when the rule matches.

FieldSource
DestinationIpListsummarize
FirewallName_ssummarize
FirstSeensummarize
LastSeensummarize
ThreatTypessummarize
TotalEventssummarize
src_ipsummarize

Corelight - Multiple files sent over HTTP with abnormal requests

#
Status
available
Severity
medium
Time window
15m
Group by
id_orig_h, uri
Source
github.com/Azure/Azure-Sentinel

'Detects sources sending multiple compressed files greater than 10MBs sent over HTTP in a short amount of time.'

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Rule Body

id: 7226d37b-50ee-4e3b-9f80-5b74080d8f2c
name: Corelight - Multiple files sent over HTTP with abnormal requests
description: |
  'Detects sources sending multiple compressed files greater than 10MBs sent over HTTP in a short amount of time.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Corelight
    dataTypes:
      - corelight_http
      - corelight_http_red
      - corelight_http2
      - corelight_http_agg
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1030
query: |
  let threshold = 25;
  let fl_size_threshold = 10000000;
  union isfuzzy=true corelight_http, corelight_http_agg
  | where method in~ ('POST', 'PUT')
  | where isempty(referrer)
  | where tolong(request_body_len) > fl_size_threshold
  | summarize count() by uri, id_orig_h, bin(TimeGenerated, 15m)
  | where count_ > threshold
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: id_orig_h
version: 2.2.0
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 25;
let fl_size_threshold = 10000000;

Union isfuzzy=true (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: corelight_http, corelight_http_agg

Leg 1: corelight_http

Leg 2: corelight_http_agg

Applied to the Combined Result

| where method in~ ('POST', 'PUT') | where isempty(referrer) | where tolong(request_body_len) > fl_size_threshold | summarize count() by uri, id_orig_h, bin(TimeGenerated, 15m) | where count_ > threshold

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
id_orig_hsummarize
urisummarize

Cyble Vision Alerts Discovered Subdomain

#

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
low
Time window
30m
Source
github.com/Azure/Azure-Sentinel

'Triggers when a new subdomain is detected for a monitored keyword/domain.'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance

Rule Body

id: 7a0f79cc-8d28-44b5-ac1e-6176565bb7b8
name: Cyble Vision Alerts Discovered Subdomain
description: |
  'Triggers when a new subdomain is detected for a monitored keyword/domain.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
  dataTypes:
  - CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
- Reconnaissance
relevantTechniques:
- T1595
query: |
  Alerts_subdomains  
  | where Service == "subdomains" 
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
  alertDetailsOverride:
  alertDisplayNameFormat: New Subdomain Detected {{SD_Subdomain}}
  alertDescriptionFormat: |
    A new subdomain was discovered for monitored domain/keyword. Keyword {{SD_KeywordName}} Bucket {{SD_BucketName}} Severity {{MappedSeverity}} This may represent new attack surface exposure or infrastructure expansion.
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  SD_Subdomain: SD_Subdomain
  SD_KeywordName: KeywordName
  SD_EntityID: EntityID
  SD_BucketName: BucketName
entityMappings:
- entityType: DNS
  fieldMappings:
  - identifier: DomainName
    columnName: SD_Subdomain
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_subdomains

Stage 2: where

| where Service == "subdomains"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Serviceeq
  • subdomains
field:"Service" kind:eq value:"subdomains"

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

Dev-0530 File Extension Rename

#
Severity
high
Time window
1d
Group by
AccountDomain, AccountName, ActorUsername, AlgorithmCustomEntity, Commandline, DeviceName, DvcDomain, DvcHostname, DvcId, EventType, FileHashCustomEntity, FileName, FolderPath, Hash, InitiatingProcessFileName, InitiatingProcessId, InitiatingProcessSHA256, TargetFilePath, Type
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Dev-0530 actors are known to encrypt the contents of the victims device as well as renaming the file extensions. This query looks for the creation of files with .h0lyenc extension or presence of ransom note.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rule Body

id: d82eb796-d1eb-43c8-a813-325ce3417cef
name: Dev-0530 File Extension Rename
description: |
  'Dev-0530 actors are known to encrypt the contents of the victims device as well as renaming the file extensions. This query looks for the creation of files with .h0lyenc extension or presence of ransom note.'
severity: High
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceFileEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1486
tags:
  - Dev-0530
  - Schema: ASIMFileEvent
    SchemaVersion: 0.1.0
query: |
  union isfuzzy=true
      (DeviceFileEvents
      | where ActionType == "FileCreated"
      | where FileName endswith ".h0lyenc" or FolderPath == "C:\\FOR_DECRYPT.html"
      | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated)
          by
          AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain,
          DeviceName,
          Type,
          InitiatingProcessId,
          FileName,
          FolderPath,
          EventType = ActionType,
          Commandline = InitiatingProcessCommandLine,
          InitiatingProcessFileName,
          InitiatingProcessSHA256,
          FileHashCustomEntity = SHA256,
          AlgorithmCustomEntity = "SHA256"
      | extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
      | extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
      ),
      (imFileEvent
      | where EventType == "FileCreated"
      | where TargetFilePath endswith ".h0lyenc" or TargetFilePath == "C:\\FOR_DECRYPT.html"
      | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated)
          by
          ActorUsername,
          DvcHostname,
          DvcDomain,
          DvcId,
          Type,
          EventType,
          FileHashCustomEntity = TargetFileSHA256,
          Hash,
          TargetFilePath,
          Commandline = ActingProcessCommandLine,
          AlgorithmCustomEntity = "SHA256"
      | extend AccountName = tostring(split(ActorUsername, @'\')[1]), AccountDomain = tostring(split(ActorUsername, @'\')[0])
      | extend HostName = DvcHostname, HostNameDomain = DvcDomain
      | extend DeviceName = strcat(DvcHostname, ".", DvcDomain )
      )
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ActorUserName
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: AlgorithmCustomEntity
      - identifier: Value
        columnName: FileHashCustomEntity
version: 1.1.1
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Union isfuzzy=true (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: DeviceFileEvents, imFileEvent

Leg 1: DeviceFileEvents

DeviceFileEvents
    | where ActionType == "FileCreated"
    | where FileName endswith ".h0lyenc" or FolderPath == "C:\\FOR_DECRYPT.html"
    | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated)
        by
        AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain,
        DeviceName,
        Type,
        InitiatingProcessId,
        FileName,
        FolderPath,
        EventType = ActionType,
        Commandline = InitiatingProcessCommandLine,
        InitiatingProcessFileName,
        InitiatingProcessSHA256,
        FileHashCustomEntity = SHA256,
        AlgorithmCustomEntity = "SHA256"
    | extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
    | extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)

Leg 2: imFileEvent

imFileEvent
    | where EventType == "FileCreated"
    | where TargetFilePath endswith ".h0lyenc" or TargetFilePath == "C:\\FOR_DECRYPT.html"
    | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated)
        by
        ActorUsername,
        DvcHostname,
        DvcDomain,
        DvcId,
        Type,
        EventType,
        FileHashCustomEntity = TargetFileSHA256,
        Hash,
        TargetFilePath,
        Commandline = ActingProcessCommandLine,
        AlgorithmCustomEntity = "SHA256"
    | extend AccountName = tostring(split(ActorUsername, @'\')[1]), AccountDomain = tostring(split(ActorUsername, @'\')[0])
    | extend HostName = DvcHostname, HostNameDomain = DvcDomain
    | extend DeviceName = strcat(DvcHostname, ".", DvcDomain )

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActorUsernamesummarize
AlgorithmCustomEntitysummarize
Commandlinesummarize
DvcDomainsummarize
DvcHostnamesummarize
DvcIdsummarize
EndTimesummarize
EventTypesummarize
FileHashCustomEntitysummarize
Hashsummarize
StartTimesummarize
TargetFilePathsummarize
Typesummarize
AccountDomainextend
AccountNameextend
HostNameextend
HostNameDomainextend
DeviceNameextend

DLL Hijacking - HijackLibs

#
Group by
$left.DLLDir, $right.ExpectedLocation, DLLName, Process
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

Below query detects DLL Hijacking that involves the DLLs and Processes shared by Wietze in the HijackLibs repo. False Positives(especially zoom.exe) and some false negatives might happen since the query is prepared in a semi-automated way. NOTE: The query uses the HijakLibs data as of 2022/09/06 19:20. It will be updated on a regular basis.

MITRE ATT&CK Coverage

TacticTechniques
StealthNo specific technique

References

Telemetry Coverage

Rule Body

// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Query parameters:
let HijackLibs = externaldata(DLLName:string, ExpectedLocation:string, Process: string )
[@"https://github.com/Cyb3r-Monk/Threat-Hunting-and-Detection/raw/main/Defense%20Evasion/HijackLibs.csv"]
with (format="csv", ignoreFirstRecord=True);
//
HijackLibs
| join hint.strategy=shuffle kind=rightsemi(
    DeviceImageLoadEvents
    | extend DLLDir =  case(FolderPath startswith "C:\\windows\\system32\\driverstore\\filerepository\\prnms002.inf", replace_regex(tolower(FolderPath), @'c:\\windows\\system32\\driverstore\\filerepository\\prnms002\.inf_.*\\amd64', @'%SYSTEM32%\\driverstore\\filerepository\\prnms002.inf_%VERSION%\\amd64'),
                            FolderPath has_all ('Windows Kits', 'bin', 'x86'), '%PROGRAMFILES%\\windows kits\\10\\bin\\%VERSION%\\x86',
                            FolderPath has_all ('Windows Kits', 'bin', 'x64'), '%PROGRAMFILES%\\windows kits\\10\\bin\\%VERSION%\\x64',
                            FolderPath has_all ('Windows Kits', 'bin', 'arm64'), '%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\arm64',
                            FolderPath has_all ('Program Files', 'Edge', 'Application'),  tolower('%PROGRAMFILES%\\microsoft\\edge\\application\\%VERSION%'),
                            FolderPath has_all ('Program Files', 'Microsoft Office', 'root'),  tolower('%PROGRAMFILES%\\microsoft office\\root\\office%VERSION%'),
                            FolderPath contains '\\Microsoft Office\\Office',  tolower('%PROGRAMFILES%\\Microsoft Office\\OFFICE%VERSION%'),
                            FolderPath has_all ('ProgramData', 'Microsoft', 'Windows Defender', 'Platform'), tolower('%PROGRAMDATA%\\Microsoft\\Windows Defender\\Platform\\%VERSION%'),
                            FolderPath startswith "C:\\Program Files (x86)", tolower(replace_string(tolower(FolderPath), 'c:\\program files (x86)', '%programfiles%')),
                            FolderPath startswith "C:\\Program Files", tolower(replace_string(tolower(FolderPath), 'c:\\program files', '%programfiles%')),
                            FolderPath startswith "C:\\WINDOWS\\SysWow64", tolower(replace_string(tolower(FolderPath), 'c:\\windows\\syswow64', '%syswow64%')),
                            FolderPath startswith "C:\\WINDOWS\\System32", tolower(replace_string(tolower(FolderPath), 'c:\\windows\\system32', '%system32%')),
                            FolderPath startswith "C:\\ProgramData", tolower(replace_string(tolower(FolderPath), 'c:\\programdata', '%programdata%')),
                            FolderPath has "AppData\\Local", tolower(replace_regex(tolower(FolderPath), @'c:\\users\\.*\\appdata\\local', '%localappdata%')),
                            FolderPath has "AppData\\Roaming", tolower(replace_regex(tolower(FolderPath), @'c:\\users\\.*\\appdata\\roaming', '%appdata%')),
                            tolower(FolderPath)),
            Process = case( InitiatingProcessFolderPath has_all ('WindowsApps', 'msteams.exe'), tolower('%PROGRAMFILES%\\WindowsApps\\MicrosoftTeams%VERSION%\\msteams.exe'),
                            InitiatingProcessFolderPath has_all ('Windows Kits', 'x86', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\x86\\oleview.exe'),
                            InitiatingProcessFolderPath has_all ('Windows Kits', 'x64', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\x64\\oleview.exe'),
                            InitiatingProcessFolderPath has_all ('Windows Kits', 'arm64', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\arm64\\oleview.exe'),
                            InitiatingProcessFolderPath has_all ('Program Files', 'trend micro', 'pwmsvc.exe'), tolower('%PROGRAMFILES%\\trend micro\\passwordmanager\\pwmsvc.exe'),
                            InitiatingProcessFolderPath has_all ('Program Files', 'trend micro', 'coreserviceshell.exe'), tolower('%PROGRAMFILES%\\trend micro\\amsp\\coreserviceshell.exe'),
                            InitiatingProcessFolderPath has_all ('Program Files', 'EdgeWebView', 'Application', 'msedgewebview2.exe'),  tolower('%PROGRAMFILES%\\Microsoft\\EdgeWebView\\Application\\%VERSION%\\msedgewebview2.exe'),
                            InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft Office', 'root'),  tolower(replace_regex(InitiatingProcessFolderPath, @'.*?\\Microsoft Office\\root\\Office.*\\(.*)', @'%PROGRAMFILES%\\Microsoft Office\\root\\Office%VERSION%\\\1')),
                            InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft Office', 'outlook.exe'),  tolower('%PROGRAMFILES%\\Microsoft Office\\OFFICE%VERSION%\\outlook.exe'),
                            InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft', 'Windows Defender', 'MsMpEng'),   tolower('%PROGRAMDATA%\\Microsoft\\Windows Defender\\Platform\\%VERSION%\\MsMpEng.exe'),
                            InitiatingProcessFolderPath startswith "C:\\Program Files (x86)", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\program files (x86)', '%programfiles%')),
                            InitiatingProcessFolderPath startswith "C:\\Program Files", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\program files', '%programfiles%')),
                            InitiatingProcessFolderPath startswith "C:\\WINDOWS", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\windows', '%windir%')),
                            InitiatingProcessFolderPath startswith "C:\\WINDOWS\\System32", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\windows\\system32', '%system32%')),
                            InitiatingProcessFolderPath startswith "C:\\ProgramData", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\programdata', '%programdata%')),
                            InitiatingProcessFolderPath has "AppData\\Local", tolower(replace_regex(tolower(InitiatingProcessFolderPath), @'c:\\users\\.*\\appdata\\local', '%localappdata%')),
                            InitiatingProcessFolderPath has "AppData\\Roaming", tolower(replace_regex(tolower(InitiatingProcessFolderPath), @'c:\\users\\.*\\appdata\\roaming', '%appdata%')),
                            tolower(InitiatingProcessFolderPath)),
            DLLName = tolower(FileName)
    )
    on Process, DLLName
    | extend DLLDir = tostring(parse_path(DLLDir).DirectoryPath)
    | join kind=leftanti HijackLibs on Process, DLLName, $left.DLLDir==$right.ExpectedLocation

Stages and Predicates

Let Binding: HijackLibs used in Stage 4

let HijackLibs = externaldata(DLLName:string, ExpectedLocation:string, Process: string )
[@"https://github.com/Cyb3r-Monk/Threat-Hunting-and-Detection/raw/main/Defense%20Evasion/HijackLibs.csv"]
with (format="csv", ignoreFirstRecord=True);

Stage 1: source

externaldata(...)

Stage 2: join

| join hint.strategy=shuffle kind=rightsemi(
    DeviceImageLoadEvents
    | extend DLLDir =  case(FolderPath startswith "C:\\windows\\system32\\driverstore\\filerepository\\prnms002.inf", replace_regex(tolower(FolderPath), @'c:\\windows\\system32\\driverstore\\filerepository\\prnms002\.inf_.*\\amd64', @'%SYSTEM32%\\driverstore\\filerepository\\prnms002.inf_%VERSION%\\amd64'),
                            FolderPath has_all ('Windows Kits', 'bin', 'x86'), '%PROGRAMFILES%\\windows kits\\10\\bin\\%VERSION%\\x86',
                            FolderPath has_all ('Windows Kits', 'bin', 'x64'), '%PROGRAMFILES%\\windows kits\\10\\bin\\%VERSION%\\x64',
                            FolderPath has_all ('Windows Kits', 'bin', 'arm64'), '%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\arm64',
                            FolderPath has_all ('Program Files', 'Edge', 'Application'),  tolower('%PROGRAMFILES%\\microsoft\\edge\\application\\%VERSION%'),
                            FolderPath has_all ('Program Files', 'Microsoft Office', 'root'),  tolower('%PROGRAMFILES%\\microsoft office\\root\\office%VERSION%'),
                            FolderPath contains '\\Microsoft Office\\Office',  tolower('%PROGRAMFILES%\\Microsoft Office\\OFFICE%VERSION%'),
                            FolderPath has_all ('ProgramData', 'Microsoft', 'Windows Defender', 'Platform'), tolower('%PROGRAMDATA%\\Microsoft\\Windows Defender\\Platform\\%VERSION%'),
                            FolderPath startswith "C:\\Program Files (x86)", tolower(replace_string(tolower(FolderPath), 'c:\\program files (x86)', '%programfiles%')),
                            FolderPath startswith "C:\\Program Files", tolower(replace_string(tolower(FolderPath), 'c:\\program files', '%programfiles%')),
                            FolderPath startswith "C:\\WINDOWS\\SysWow64", tolower(replace_string(tolower(FolderPath), 'c:\\windows\\syswow64', '%syswow64%')),
                            FolderPath startswith "C:\\WINDOWS\\System32", tolower(replace_string(tolower(FolderPath), 'c:\\windows\\system32', '%system32%')),
                            FolderPath startswith "C:\\ProgramData", tolower(replace_string(tolower(FolderPath), 'c:\\programdata', '%programdata%')),
                            FolderPath has "AppData\\Local", tolower(replace_regex(tolower(FolderPath), @'c:\\users\\.*\\appdata\\local', '%localappdata%')),
                            FolderPath has "AppData\\Roaming", tolower(replace_regex(tolower(FolderPath), @'c:\\users\\.*\\appdata\\roaming', '%appdata%')),
                            tolower(FolderPath)),
            Process = case( InitiatingProcessFolderPath has_all ('WindowsApps', 'msteams.exe'), tolower('%PROGRAMFILES%\\WindowsApps\\MicrosoftTeams%VERSION%\\msteams.exe'),
                            InitiatingProcessFolderPath has_all ('Windows Kits', 'x86', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\x86\\oleview.exe'),
                            InitiatingProcessFolderPath has_all ('Windows Kits', 'x64', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\x64\\oleview.exe'),
                            InitiatingProcessFolderPath has_all ('Windows Kits', 'arm64', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\arm64\\oleview.exe'),
                            InitiatingProcessFolderPath has_all ('Program Files', 'trend micro', 'pwmsvc.exe'), tolower('%PROGRAMFILES%\\trend micro\\passwordmanager\\pwmsvc.exe'),
                            InitiatingProcessFolderPath has_all ('Program Files', 'trend micro', 'coreserviceshell.exe'), tolower('%PROGRAMFILES%\\trend micro\\amsp\\coreserviceshell.exe'),
                            InitiatingProcessFolderPath has_all ('Program Files', 'EdgeWebView', 'Application', 'msedgewebview2.exe'),  tolower('%PROGRAMFILES%\\Microsoft\\EdgeWebView\\Application\\%VERSION%\\msedgewebview2.exe'),
                            InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft Office', 'root'),  tolower(replace_regex(InitiatingProcessFolderPath, @'.*?\\Microsoft Office\\root\\Office.*\\(.*)', @'%PROGRAMFILES%\\Microsoft Office\\root\\Office%VERSION%\\\1')),
                            InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft Office', 'outlook.exe'),  tolower('%PROGRAMFILES%\\Microsoft Office\\OFFICE%VERSION%\\outlook.exe'),
                            InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft', 'Windows Defender', 'MsMpEng'),   tolower('%PROGRAMDATA%\\Microsoft\\Windows Defender\\Platform\\%VERSION%\\MsMpEng.exe'),
                            InitiatingProcessFolderPath startswith "C:\\Program Files (x86)", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\program files (x86)', '%programfiles%')),
                            InitiatingProcessFolderPath startswith "C:\\Program Files", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\program files', '%programfiles%')),
                            InitiatingProcessFolderPath startswith "C:\\WINDOWS", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\windows', '%windir%')),
                            InitiatingProcessFolderPath startswith "C:\\WINDOWS\\System32", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\windows\\system32', '%system32%')),
                            InitiatingProcessFolderPath startswith "C:\\ProgramData", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\programdata', '%programdata%')),
                            InitiatingProcessFolderPath has "AppData\\Local", tolower(replace_regex(tolower(InitiatingProcessFolderPath), @'c:\\users\\.*\\appdata\\local', '%localappdata%')),
                            InitiatingProcessFolderPath has "AppData\\Roaming", tolower(replace_regex(tolower(InitiatingProcessFolderPath), @'c:\\users\\.*\\appdata\\roaming', '%appdata%')),
                            tolower(InitiatingProcessFolderPath)),
            DLLName = tolower(FileName)
    )
    on Process, DLLName

Stage 3: extend

extend DLLDir

Stage 4: join (negated)

join kind=leftanti (HijackLibs) on Process, DLLName, $left.DLLDir, $right.ExpectedLocation

Output Fields

These fields are emitted when the rule matches.

FieldSource
DLLDirextend

External Fabric Module XFM1 is unhealthy

#
Severity
high
Source
github.com/Azure/Azure-Sentinel

External Fabric Module XFM1 is unhealthy

MITRE ATT&CK Coverage

TacticTechniques
Execution

Rule Body

id: a8130dcc-3617-41c0-a7ac-5f352bcfffaf
name: External Fabric Module XFM1 is unhealthy
version: 1.0.0
kind: NRT
description: External Fabric Module XFM1 is unhealthy
severity: High
tactics:
- Execution
relevantTechniques:
- T0871
query: |2-
  Syslog
  | where SyslogMessage has "purity.alert"
  | extend Message = replace_string(SyslogMessage, "#012", "\n")
  | extend UTCTime = extract(@"UTC Time:\s*(\d{4}\s\w{3}\s\d{1,2}\s\d{2}:\d{2}:\d{2})\sUTC", 1, SyslogMessage)
  | extend PureAlertID = extract(@"Alert ID: ([\w-]+)", 1, SyslogMessage)
  | extend PureMessage = extract(@"\(Alert ID: [\w-]+\)\s(.*?)\s\[\d+\]", 1, SyslogMessage)
  | extend PureSeverity = extract(@"\s(\w+)\s", 1, SyslogMessage)
  | extend PureAlertState = extract(@"purity\.alert:\s\w+\s(\w+)", 1, SyslogMessage)
  | extend PureObjectName = extract(@"\s(\S+):", 1, SyslogMessage)
  | extend PureProcessID = extract(@"\[(\d+)\]", 1, SyslogMessage)
  | extend PureAction = extract(@"Suggested Action:\s*(.*?)(?:\s*Knowledge Base Article:|$)", 1, SyslogMessage)
  | extend PureUrl = extract(@"Knowledge Base Article:\s*(.*)", 1, SyslogMessage)
  | project  PureMessage, TimeGenerated, PureProcessID, HostIP, Computer, PureObjectName, PureSeverity, PureAlertID, PureAlertState, PureAction, PureUrl
  | where PureMessage matches regex @"(External Fabric Module XFM1 is unhealthy)"
entityMappings:
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: HostIP
suppressionEnabled: false
suppressionDuration: 5h
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: SingleAlert

Stages and Predicates

Stage 1: source

Syslog

Stage 2: where

| where SyslogMessage has "purity.alert"

Stage 3: extend (10 consecutive steps)

| extend Message = replace_string(SyslogMessage, "#012", "\n")
| extend UTCTime = extract(@"UTC Time:\s*(\d{4}\s\w{3}\s\d{1,2}\s\d{2}:\d{2}:\d{2})\sUTC", 1, SyslogMessage)
| extend PureAlertID = extract(@"Alert ID: ([\w-]+)", 1, SyslogMessage)
| extend PureMessage = extract(@"\(Alert ID: [\w-]+\)\s(.*?)\s\[\d+\]", 1, SyslogMessage)
| extend PureSeverity = extract(@"\s(\w+)\s", 1, SyslogMessage)
| extend PureAlertState = extract(@"purity\.alert:\s\w+\s(\w+)", 1, SyslogMessage)
| extend PureObjectName = extract(@"\s(\S+):", 1, SyslogMessage)
| extend PureProcessID = extract(@"\[(\d+)\]", 1, SyslogMessage)
| extend PureAction = extract(@"Suggested Action:\s*(.*?)(?:\s*Knowledge Base Article:|$)", 1, SyslogMessage)
| extend PureUrl = extract(@"Knowledge Base Article:\s*(.*)", 1, SyslogMessage)

Stage 4: project

| project  PureMessage, TimeGenerated, PureProcessID, HostIP, Computer, PureObjectName, PureSeverity, PureAlertID, PureAlertState, PureAction, PureUrl

Stage 5: where

| where PureMessage matches regex @"(External Fabric Module XFM1 is unhealthy)"

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Computerproject
HostIPproject
PureActionproject
PureAlertIDproject
PureAlertStateproject
PureMessageproject
PureObjectNameproject
PureProcessIDproject
PureSeverityproject
PureUrlproject
TimeGeneratedproject

GCP IAM - Service Account Enumeration

#
Status
available
Severity
low
Time window
1h
Group by
PayloadAuthenticationinfoPrincipalemail
Source
github.com/Azure/Azure-Sentinel

Detects possible service account enumeration.

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Telemetry Coverage

Rule Body

id: 50e0437e-912d-4cd0-ac19-fef0aebdd3d7
name: GCP IAM - Service Account Enumeration
description: |
  'Detects possible service account enumeration.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: GCPIAMDataConnector
    dataTypes:
      - GCP_IAM
queryFrequency: 12h
queryPeriod: 12h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1087
query: |
  let threshold = 5;
  GCP_IAM
  | where PayloadMethodname =~ 'google.iam.admin.v1.ListServiceAccounts'
  | summarize count() by PayloadAuthenticationinfoPrincipalemail, bin(TimeGenerated, 1h)
  | where count_ > threshold
  | extend AccountCustomEntity = PayloadAuthenticationinfoPrincipalemail
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 5;

Stage 1: source

GCP_IAM

Stage 2: where

| where PayloadMethodname =~ 'google.iam.admin.v1.ListServiceAccounts'

Stage 3: summarize

| summarize count() by PayloadAuthenticationinfoPrincipalemail, bin(TimeGenerated, 1h)

Stage 4: where

| where count_ > threshold

Stage 5: extend

| extend AccountCustomEntity = PayloadAuthenticationinfoPrincipalemail

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
PayloadAuthenticationinfoPrincipalemailsummarize
AccountCustomEntityextend

Global Network Traffic Rules Deleted

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

Detects when a global network traffic rule is deleted in Veeam Backup & Replication. This might indicate unauthorized removal of critical components.

Rule Body

id: 0995ccd8-16d4-4764-83b2-511235ca4e4b
name: Global Network Traffic Rules Deleted
description: Detects when a global network traffic rule is deleted in Veeam Backup
  & Replication. This might indicate unauthorized removal of critical components.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 32400\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    [\"User Name\"] = user,\n  MessageDetails = Description,\n\
  \   Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 32400

Stage 3: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    ["User Name"] = user,
  MessageDetails = Description,
   Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
User Nameproject

Hunt for AI Agent activity with Agent Info

#
Group by
$left.ExtractedObservabilityID, $right.ObservabilityID, AgentId
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

This query allows you to find the activity an AI Agent performed via the CloudAppEvents table using the AgentsInfo table.

References

Telemetry Coverage

Rule Body

// Fill in agent name or one of the Agent IDs you have found
let agent_name = "";
let some_agent_id = "";
AgentsInfo 
| where TimeGenerated > ago(1d)
| summarize arg_max(TimeGenerated, *) by AgentId
| where (isempty(some_agent_id) and Name =~ agent_name) or (isempty(agent_name) and * has some_agent_id)
// Skip local AI Agents
| where Platform != "LocalAgents"
| extend ExtractedObservabilityID = iff(
    ObservabilityID matches regex @"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})",
    extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, ObservabilityID),
    ObservabilityID
)
// Add fallback on titleId if ObservabilityID is empty
| extend ExtractedObservabilityID = iff(ExtractedObservabilityID == "", extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, tostring(parse_json(RawAgentInfo).titleId)), ExtractedObservabilityID)
| project Name, Platform, ExtractedObservabilityID
| join kind=inner (
    CloudAppEvents
    | where TimeGenerated > ago(7d)
    | where ActionType in ("InvokeAgent","InferenceCall","ExecuteToolBySDK","ExecuteToolByGateway","ExecuteToolByMCPServer")
    // Extract the platformIDs and ObservabilityID
    | extend PlatformAgentId = tostring(parse_json(RawEventData)["PlatformAgentId"]), 
        PlatformTargetAgentId = tostring(parse_json(RawEventData)["PlatformTargetAgentId"])
    | extend PlatformId = iff(isempty(PlatformAgentId) and isnotempty(PlatformTargetAgentId), PlatformTargetAgentId, PlatformAgentId)
    | extend ObservabilityID = iff(
        PlatformId matches regex @"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 
        extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, PlatformId),
        PlatformId
    )
) on $left.ExtractedObservabilityID == $right.ObservabilityID

Stages and Predicates

Parameters

let agent_name = "";
let some_agent_id = "";

Stage 1: source

AgentsInfo

Stage 2: where

| where TimeGenerated > ago(1d)

Stage 3: summarize

| summarize arg_max(TimeGenerated, *) by AgentId

Stage 4: where

| where (isempty(some_agent_id) and Name =~ agent_name) or (isempty(agent_name) and * has some_agent_id)

Stage 5: where

| where Platform != "LocalAgents"

Stage 6: extend

| extend ExtractedObservabilityID = iff(
    ObservabilityID matches regex @"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})",
    extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, ObservabilityID),
    ObservabilityID
)
ExtractedObservabilityID =
ifObservabilityID matches regex @"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})"extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, ObservabilityID)
elseObservabilityID

Stage 7: extend

| extend ExtractedObservabilityID = iff(ExtractedObservabilityID == "", extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, tostring(parse_json(RawAgentInfo).titleId)), ExtractedObservabilityID)
ExtractedObservabilityID =
ifExtractedObservabilityID == ""extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, tostring(parse_json(RawAgentInfo).titleId))
elseExtractedObservabilityID

Stage 8: project

| project Name, Platform, ExtractedObservabilityID

Stage 9: join

| join kind=inner (
    CloudAppEvents
    | where TimeGenerated > ago(7d)
    | where ActionType in ("InvokeAgent","InferenceCall","ExecuteToolBySDK","ExecuteToolByGateway","ExecuteToolByMCPServer")
    | extend PlatformAgentId = tostring(parse_json(RawEventData)["PlatformAgentId"]), 
        PlatformTargetAgentId = tostring(parse_json(RawEventData)["PlatformTargetAgentId"])
    | extend PlatformId = iff(isempty(PlatformAgentId) and isnotempty(PlatformTargetAgentId), PlatformTargetAgentId, PlatformAgentId)
    | extend ObservabilityID = iff(
        PlatformId matches regex @"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 
        extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, PlatformId),
        PlatformId
    )
) on $left.ExtractedObservabilityID == $right.ObservabilityID

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypein
  • ExecuteToolByGateway
  • ExecuteToolByMCPServer
  • ExecuteToolBySDK
  • InferenceCall
  • InvokeAgent
field:"ActionType" kind:in
Platformne
  • LocalAgents
field:"Platform" kind:ne value:"LocalAgents"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ExtractedObservabilityIDproject
Nameproject
Platformproject

McAfee ePO - Multiple threats on same host

#
Status
available
Severity
medium
Time window
1h
Group by
DvcIpAddr
Source
github.com/Azure/Azure-Sentinel

'Rule fires when multiple threat events were detected on the same host.'

MITRE ATT&CK Coverage

Rule Body

id: f53e5168-afdb-4fad-b29a-bb9cb71ec460
name: McAfee ePO - Multiple threats on same host
description: |
  'Rule fires when multiple threat events were detected on the same host.'
severity: Medium
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
  - Persistence
  - DefenseEvasion
  - PrivilegeEscalation
relevantTechniques:
  - T1562
  - T1070
  - T1189
  - T1195
  - T1543
  - T1055
query: |
  McAfeeEPOEvent
  | where isnotempty(ThreatName)
  | where ThreatName != '_'
  | summarize th_cnt = dcount(ThreatName), th_list = makeset(ThreatName) by DvcIpAddr
  | where th_cnt > 1
  | extend IPCustomEntity = DvcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

McAfeeEPOEvent

Stage 2: where

| where isnotempty(ThreatName)

Stage 3: where

| where ThreatName != '_'

Stage 4: summarize

| summarize th_cnt = dcount(ThreatName), th_list = makeset(ThreatName) by DvcIpAddr

Stage 5: where

| where th_cnt > 1

Stage 6: extend

| extend IPCustomEntity = DvcIpAddr

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DvcIpAddrsummarize
th_cntsummarize
th_listsummarize
IPCustomEntityextend

NetClean ProActive Incidents

#

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
10m
Source
github.com/Azure/Azure-Sentinel

'NetClean Incident'

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: 77548170-5c60-42e5-bdac-b0360d0779bb
name: NetClean ProActive Incidents
description: |
  'NetClean Incident'
severity: High
requiredDataConnectors:
  - connectorId: Netclean_ProActive_Incidents
    dataTypes:
      - Netclean_ProActive_Incidents
status: Available 
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
tactics:
  - Discovery
relevantTechniques:
  - T1083
query: |
  Netclean_Incidents_CL |  where value_incidentVersion_d == 1
entityMappings:
  - entityType: FileHash
    fieldMappings:
      - identifier: Value
        columnName: value_file_calculatedHashes_sha1_s
      - identifier: Algorithm
        columnName: value_detectionHashType_s
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: value_file_owner_computerUser_domain_s
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: value_device_machineName_s
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: value_device_networkInterfaces_s
alertDetailsOverride:
  alertDisplayNameFormat: NetClean {{value_agent_type_s}} {{type_s}}
  alertDescriptionFormat: A new NetClean {{value_agent_type_s}} {{key_type_s}} has been Created {{TimeGenerated}}
version: 1.0.2
kind: Scheduled



Stages and Predicates

Stage 1: source

Netclean_Incidents_CL

Stage 2: where

| where value_incidentVersion_d == 1

Indicators

These rows show field, operator, and value matches.

Palo Alto Prisma Cloud - IAM Group with Administrator Access Permissions

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

'Detects IAM Groups with Administrator Access Permissions.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: ac76d9c0-17a3-4aaa-a341-48f4c0b1c882
name: Palo Alto Prisma Cloud - IAM Group with Administrator Access Permissions
description: |
  'Detects IAM Groups with Administrator Access Permissions.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: PaloAltoPrismaCloud
    dataTypes:
      - PaloAltoPrismaCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  PaloAltoPrismaCloud
  | where Reason =~ 'NEW_ALERT'
  | where Status =~ 'open'
  | where AlertMessage has 'IAM Groups with Administrator Access Permissions'
  | extend AccountCustomEntity = UserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

PaloAltoPrismaCloud

Stage 2: where

| where Reason =~ 'NEW_ALERT'

Stage 3: where

| where Status =~ 'open'

Stage 4: where

| where AlertMessage has 'IAM Groups with Administrator Access Permissions'

Stage 5: extend

| extend AccountCustomEntity = UserName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

Pathlock TDnR - STRUST PSE Certificate Changes

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

Detects changes to STRUST PSE (Personal Security Environment) certificate files in SAP, forwarded by Pathlock Threat Detection and Response. PSE modifications may indicate certificate tampering, introduction of rogue trusted certificates, or preparation for man-in-the-middle attacks against SAP communication channels.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment
Credential Access

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c42
name: Pathlock TDnR - STRUST PSE Certificate Changes
kind: Scheduled
description: >-
  Detects changes to STRUST PSE (Personal Security Environment) certificate files in SAP,
  forwarded by Pathlock Threat Detection and Response. PSE modifications may indicate certificate
  tampering, introduction of rogue trusted certificates, or preparation for man-in-the-middle attacks
  against SAP communication channels.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - DefenseEvasion
relevantTechniques:
  - T1552
  - T1553
query: |
  Pathlock_TDnR_CL
  | where DataSource == "PSE_CHANGES"
  | 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 == "PSE_CHANGES"

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

RecordedFuture Threat Hunting Url All Actors

#
Severity
medium
Time window
1d
Group by
ObservableValue, lowerUrl
Source
github.com/Azure/Azure-Sentinel

'Recorded Future Threat Hunting Url correlation for all actors.'

MITRE ATT&CK Coverage

Rule Body

id: 3f6f0d1a-f2f9-4e01-881a-c55a4a71905b
name: RecordedFuture Threat Hunting Url All Actors
description: |
  'Recorded Future Threat Hunting Url correlation for all actors.'
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligenceUploadIndicatorsAPI
    dataTypes:
      - ThreatIntelIndicators
queryFrequency: 15m
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
  - DefenseEvasion
relevantTechniques:
  - T1098
  - T1078
customDetails:
  ActorInformation: RecordedFuturePortalLink
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: AllEntities
alertDetailsOverride:
  alertDisplayNameFormat: '{{Description}}'
  alertDescriptionFormat: '*{{Description}}**\n\nCorrelation found on {{Url}} from the {{Type}} table.\n'
  alertDynamicProperties:
    - alertProperty: AlertLink
      value: RecordedFuturePortalLink
query: |
  let ioc_lookBack = 1d;
  // The source table (_Im_WebSession) is a ASIM parser table, but can be replaced by any infrastructure table containing Url data.
  // The following workbook: Recorded Future - Url Correlation will help researching available data and selecting tables and columns
  _Im_WebSession
  | where isnotempty(Url)
  | extend lowerUrl=tolower(Url)
  | join kind=inner (
  ThreatIntelIndicators
  // Only look for IOCs
  | where ObservableKey == 'url:value'
  | where isnotempty(ObservableValue)
  // Only look at Recorded Future Threat Hunt Indicators.
  | where Data.description startswith "Recorded Future - Threat Hunt"
  // Only work with the latest indicators
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
  | where IsActive == true and ValidUntil > now()
  | extend lowerUrl=tolower(ObservableValue)
  ) on lowerUrl
  // select column from the source table to match with Recorded Future ThreatIntelIndicators $left.Url
  | mv-expand Label=Data.labels
  | extend RecordedFuturePortalLink = parse_json(tostring(Label)).RecordedFuturePortalLink
   | where isnotempty(RecordedFuturePortalLink)
   | summarize arg_max(TimeGenerated, *) by ObservableValue
  | project Url=ObservableValue, Description=Data.description, Type, TimeGenerated, RecordedFuturePortalLink
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.1.1
kind: Scheduled

Stages and Predicates

Parameters

let ioc_lookBack = 1d;

Stage 1: source

_Im_WebSession

Stage 2: where

| where isnotempty(Url)

Stage 3: extend

| extend lowerUrl=tolower(Url)

Stage 4: join

| join kind=inner (
ThreatIntelIndicators
| where ObservableKey == 'url:value'
| where isnotempty(ObservableValue)
| where Data.description startswith "Recorded Future - Threat Hunt"
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where IsActive == true and ValidUntil > now()
| extend lowerUrl=tolower(ObservableValue)
) on lowerUrl

Stage 5: mv-expand

mv-expand Label

Stage 6: extend

extend RecordedFuturePortalLink

Stage 7: where

where isnotempty(RecordedFuturePortalLink)

Stage 8: summarize

summarize by ObservableValue

Stage 9: project

project Description, RecordedFuturePortalLink, TimeGenerated, Type, Url

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Descriptionproject
RecordedFuturePortalLinkproject
TimeGeneratedproject
Typeproject
Urlproject

Sdelete deployed via GPO and run recursively (ASIM Version)

#
Severity
medium
Time window
1d
Group by
$left.ActingProcessId, $right.ParentProcessId, Dvc, timekey
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

This query looks for the Sdelete process being run recursively after being deployed to a host via GPO. Attackers could use this technique to deploy Sdelete to multiple host and delete data on them. This query uses the Advanced Security Information Model. Parsers will need to be deployed before use: https://docs.microsoft.com/azure/sentinel/normalization

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rule Body

id: 30c8b802-ace1-4408-bc29-4c5c5afb49e1
name: Sdelete deployed via GPO and run recursively (ASIM Version)
description: |
  'This query looks for the Sdelete process being run recursively after being deployed to a host via GPO. Attackers could use this technique to deploy Sdelete to multiple host and delete data on them.
    This query uses the Advanced Security Information Model. Parsers will need to be deployed before use: https://docs.microsoft.com/azure/sentinel/normalization'
severity: Medium
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1485
tags:
  -
query: |
  _Im_ProcessEvent
  | where EventType =~ "ProcessCreated"
  | where Process endswith "svchost.exe"
  | where CommandLine has "-k GPSvcGroup" or CommandLine has "-s gpsvc"
  | extend timekey = bin(TimeGenerated, 1m)
  | project timekey, ActingProcessId, Dvc
  | join kind=inner (
    _Im_ProcessEvent
    | where EventType =~ "ProcessCreated"
    | where Process =~ "sdelete.exe" or CommandLine has "sdelete"
    | where ActingProcessName endswith "svchost.exe"
    | where CommandLine has_all ("-s", "-r")
    | extend timekey = bin(TimeGenerated, 1m)
    ) 
    on $left.ActingProcessId == $right.ParentProcessId, timekey, Dvc
  | extend AccountName = tostring(split(ActorUsername, @'\')[1]), AccountNTDomain = tostring(split(ActorUsername, @'\')[0])
  | extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ActorUsername
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Dvc
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: DvcIpAddr
version: 1.0.6
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection" ]

Stages and Predicates

Stage 1: source

_Im_ProcessEvent

Stage 2: where

| where EventType =~ "ProcessCreated"

Stage 3: where

| where Process endswith "svchost.exe"

Stage 4: where

| where CommandLine has "-k GPSvcGroup" or CommandLine has "-s gpsvc"

Stage 5: extend

| extend timekey = bin(TimeGenerated, 1m)

Stage 6: project

| project timekey, ActingProcessId, Dvc

Stage 7: join

| join kind=inner (
  _Im_ProcessEvent
  | where EventType =~ "ProcessCreated"
  | where Process =~ "sdelete.exe" or CommandLine has "sdelete"
  | where ActingProcessName endswith "svchost.exe"
  | where CommandLine has_all ("-s", "-r")
  | extend timekey = bin(TimeGenerated, 1m)
  ) 
  on $left.ActingProcessId == $right.ParentProcessId, timekey, Dvc

Stage 8: extend (3 consecutive steps)

extend AccountNTDomain, AccountName, DomainIndex, HostName, HostNameDomain

Stage 9: project-away

project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActingProcessNameends_with
  • svchost.exe
field:"ActingProcessName" kind:ends_with value:"svchost.exe"
CommandLinematch
  • -k GPSvcGroup transforms: term corpus 2 (kusto 2)
  • -r transforms: term
  • -s transforms: term
  • -s gpsvc transforms: term corpus 2 (kusto 2)
  • sdelete transforms: term
field:"CommandLine" kind:match
EventTypeeq
  • ProcessCreated
field:"EventType" kind:eq value:"ProcessCreated"
Processends_with
  • svchost.exe
field:"Process" kind:ends_with value:"svchost.exe"
Processeq
  • sdelete.exe
field:"Process" kind:eq value:"sdelete.exe"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActingProcessIdproject
Dvcproject
timekeyproject
AccountNTDomainextend
AccountNameextend
HostNameextend
HostNameDomainextend

StealthTalk - Password brute force

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

Identifies a brute-force authentication attempt against a single StealthTalk user account. The StealthTalk anti-brute-force subsystem emits a MultiFailLogin event when consecutive failed attempts trigger an automatic account lockout. This rule fires when the reported number of consecutive failures is 9 or more, distinguishing a credential-guessing attack from an isolated user error. The lockout duration (LoginBlockingSeconds) is surfaced as a custom detail so the SOC analyst can prioritise by attack persistence.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: b8e5f3a2-9c4d-4d1f-8a7b-3c2d1e0f9a8b
name: StealthTalk - Password brute force
description: |
  Identifies a brute-force authentication attempt against a single StealthTalk user account. The StealthTalk anti-brute-force subsystem emits a `MultiFailLogin` event when consecutive failed attempts trigger an automatic account lockout. This rule fires when the reported number of consecutive failures is 9 or more, distinguishing a credential-guessing attack from an isolated user error. The lockout duration (`LoginBlockingSeconds`) is surfaced as a custom detail so the SOC analyst can prioritise by attack persistence.
severity: High
requiredDataConnectors:
  - connectorId: StealthTalkAnomalousAuth
    dataTypes:
      - StealthTalkAnomalousAuth_CL
queryFrequency: 15m
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
  - InitialAccess
relevantTechniques:
  - T1110
query: |
  let LookbackPeriod    = 5h;
  let MinFailedAttempts = 9;
  StealthTalkAnomalousAuth_CL
  | where TimeGenerated >= ago(LookbackPeriod)
  | where EventType == "MultiFailLogin"
  | where PassedAttempts >= MinFailedAttempts
  | extend
      BlockingMinutes = LoginBlockingSeconds / 60,
      AlertName       = "PasswordBruteForce",
      AlertDetails    = strcat(
          "User ", UserId,
          " had ", PassedAttempts, " consecutive failed login attempts",
          " on device ", DeviceId, ".",
          " Account blocked for ", LoginBlockingSeconds, " seconds",
          " (", LoginBlockingSeconds / 60, " min)."
      )
  | project
      TimeGenerated, UserId, DeviceId,
      PassedAttempts, LoginBlockingSeconds, BlockingMinutes,
      AppVersion, RawEventId, AlertName, AlertDetails
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserId
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DeviceId
customDetails:
  PassedAttempts: PassedAttempts
  BlockingMinutes: BlockingMinutes
  LoginBlockingSeconds: LoginBlockingSeconds
  AppVersion: AppVersion
  EventReference: RawEventId
alertDetailsOverride:
  alertDisplayNameFormat: 'StealthTalk: Password Brute Force - {{UserId}} ({{PassedAttempts}} failed attempts)'
  alertDescriptionFormat: '{{AlertDetails}}'
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: Selected
    groupByEntities:
      - Account
suppressionEnabled: false
suppressionDuration: 5h
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let LookbackPeriod = 5h;
let MinFailedAttempts = 9;

Stage 1: source

StealthTalkAnomalousAuth_CL

Stage 2: where

| where TimeGenerated >= ago(LookbackPeriod)

Stage 3: where

| where EventType == "MultiFailLogin"

Stage 4: where

| where PassedAttempts >= MinFailedAttempts

Stage 5: extend

| extend
    BlockingMinutes = LoginBlockingSeconds / 60,
    AlertName       = "PasswordBruteForce",
    AlertDetails    = strcat(
        "User ", UserId,
        " had ", PassedAttempts, " consecutive failed login attempts",
        " on device ", DeviceId, ".",
        " Account blocked for ", LoginBlockingSeconds, " seconds",
        " (", LoginBlockingSeconds / 60, " min)."
    )

Stage 6: project

| project
    TimeGenerated, UserId, DeviceId,
    PassedAttempts, LoginBlockingSeconds, BlockingMinutes,
    AppVersion, RawEventId, AlertName, AlertDetails

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertDetailsproject
AlertNameproject
AppVersionproject
BlockingMinutesproject
DeviceIdproject
LoginBlockingSecondsproject
PassedAttemptsproject
RawEventIdproject
TimeGeneratedproject
UserIdproject

Suspicious linking of existing user to external User

#
Severity
medium
Time window
1d
Group by
CorrelationId
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

This query will detect when an attempt is made to update an existing user and link it to an guest or external identity. These activities are unusual and such linking of external identities should be investigated. In some cases you may see internal Entra ID sync accounts (Sync_) do this which may be benign

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

PlatformRecord / event type
AzureUpdate user

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 22a320c2-e1e5-4c74-a35b-39fc9cdcf859
name: Suspicious linking of existing user to external User
description: |
  ' This query will detect when an attempt is made to update an existing user and link it to an guest or external identity. These activities are unusual and such linking of external 
  identities should be investigated. In some cases you may see internal Entra ID sync accounts (Sync_) do this which may be benign'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1078.004
tags:
  - GuestorExternalIdentities
query: |
  AuditLogs
  | where OperationName=~ "Update user" 
  | where Result =~ "success" 
  | mv-expand TargetResources 
  | mv-expand TargetResources.modifiedProperties 
  | extend displayName = tostring(TargetResources_modifiedProperties.displayName), 
  TargetUPN_oldValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.oldValue))[0]), 
  TargetUPN_newValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue))[0])
  | where displayName == "UserPrincipalName" and TargetUPN_oldValue !has "#EXT" and TargetUPN_newValue has "#EXT"
  | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
  | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
  | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
  | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
  | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
  | extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName))
  | summarize arg_max(TimeGenerated, *) by CorrelationId
  | project-reorder TimeGenerated, InitiatedBy, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, TargetUPN_oldValue, TargetUPN_newValue
  | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
  | extend TargetAccountName = tostring(split(TargetUPN_oldValue, "@")[0]), TargetUPNSuffix = tostring(split(TargetUPN_oldValue, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: InitiatingAppName
      - identifier: AadUserId
        columnName: InitiatingAppServicePrincipalId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetUPN_oldValue
      - identifier: Name
        columnName: TargetAccountName
      - identifier: UPNSuffix
        columnName: TargetUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIPAddress
version: 1.1.0
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName=~ "Update user"

Stage 3: where

| where Result =~ "success"

Stage 4: mv-expand

| mv-expand TargetResources

Stage 5: mv-expand

| mv-expand TargetResources.modifiedProperties

Stage 6: extend

| extend displayName = tostring(TargetResources_modifiedProperties.displayName), 
TargetUPN_oldValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.oldValue))[0]), 
TargetUPN_newValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue))[0])

Stage 7: where

| where displayName == "UserPrincipalName" and TargetUPN_oldValue !has "#EXT" and TargetUPN_newValue has "#EXT"

Stage 8: extend (6 consecutive steps)

| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName))

Stage 9: summarize

| summarize arg_max(TimeGenerated, *) by CorrelationId

Stage 10: project-reorder

| project-reorder TimeGenerated, InitiatedBy, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, TargetUPN_oldValue, TargetUPN_newValue

Stage 11: extend

| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])

Stage 12: extend

| extend TargetAccountName = tostring(split(TargetUPN_oldValue, "@")[0]), TargetUPNSuffix = tostring(split(TargetUPN_oldValue, "@")[1])

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
TargetUPN_oldValuematch#EXTexcludes:TargetUPN_oldValue field:"TargetUPN_oldValue" value:"#EXT"

Output Fields

These fields are emitted when the rule matches.

FieldSource
CorrelationIdsummarize
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend
TargetAccountNameextend
TargetUPNSuffixextend

Suspicious use of CPL file

#
Group by
SHA1
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

This query identifies .cpl files being loaded and verifies if the corresponding file is suspicious by looking at the signature and global prevalence.

Known False Positives

  • Legitimate custom software might create a control panel item that is unsigned or has a low global prevalence.

MITRE ATT&CK Coverage

References

Telemetry Coverage

Rule Body

let timeframe = 2*1h;
let default_global_prevalence = 0;
let suspiciousCPLs = DeviceImageLoadEvents
    | where ingestion_time() >= ago(timeframe)
    // Begin environment-specific filter.
    // End environment-specific filter.
    | where FileName endswith ".cpl"
    | summarize by SHA1
    // FileProfile is case-sensitive and works on lower-case hashes.
    | extend SHA1=tolower(SHA1)
    | invoke FileProfile(SHA1, 1000)
    | where not(ProfileAvailability =~ "Error")
    // Begin environment-specific filter.
    // End environment-specific filter.
    | where ((isempty(Signer) or not(IsCertificateValid==1)) and coalesce(GlobalPrevalence,default_global_prevalence) < 100) or coalesce(GlobalPrevalence,default_global_prevalence) < 50;
let loadedDlls=DeviceImageLoadEvents
    | where ingestion_time() >= ago(timeframe)
    // FileProfile is case-sensitive and works on lower-case hashes.
    | extend SHA1=tolower(SHA1)
    | where SHA1 in~ ((suspiciousCPLs|project SHA1)) and ActionType =~ "ImageLoaded"
    // Begin environment-specific filter.
    // End environment-specific filter.
    ;
loadedDlls
    | join kind=leftouter suspiciousCPLs on SHA1
    // Begin environment-specific filter.
    // End environment-specific filter.

Stages and Predicates

Parameters

let timeframe = 2*1h;
let default_global_prevalence = 0;

let loadedDlls is inlined into the numbered stages below.

Let Binding: suspiciousCPLs used in Stages 1, 7

let suspiciousCPLs = DeviceImageLoadEvents
    | where ingestion_time() >= ago(timeframe)
    | where FileName endswith ".cpl"
    | summarize by SHA1
    | extend SHA1=tolower(SHA1)
    | invoke FileProfile(SHA1, 1000)
    | where not(ProfileAvailability =~ "Error")
    | where ((isempty(Signer) or not(IsCertificateValid==1)) and coalesce(GlobalPrevalence,default_global_prevalence) < 100) or coalesce(GlobalPrevalence,default_global_prevalence) < 50;

Stage 1: source

let suspiciousCPLs

Stage 2: source

let loadedDlls

Stage 3: source

DeviceImageLoadEvents

Stage 4: where

where ...

Stage 5: extend

extend SHA1

Stage 6: where

where ActionType =~ "ImageLoaded"

Stage 7: join

join kind=leftouter (suspiciousCPLs) on SHA1

Stage 8: summarize aggregation inside the join branch

summarize by SHA1

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypeeq
  • ImageLoaded
field:"ActionType" kind:eq value:"ImageLoaded"
FileNameends_with
  • .cpl
field:"FileName" kind:ends_with value:".cpl"
GlobalPrevalencelt
  • 100 transforms: coalesce_default:0
  • 50 transforms: coalesce_default:0
field:"GlobalPrevalence" kind:lt
Signeris_null
  • (no value, null check)
field:"Signer" kind:is_null

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
ProfileAvailabilityeqErrorexcludes:ProfileAvailability field:"ProfileAvailability" value:"Error"

Output Fields

These fields are emitted when the rule matches.

FieldSource
SHA1summarize

Tape Library Deleted

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

Detects when a tape library is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.

Rule Body

id: ecf98b4a-3986-4739-879d-dd446e839153
name: Tape Library Deleted
description: Detects when a tape library is deleted from Veeam Backup & Replication.
  This might indicate unauthorized removal of critical components.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 23633\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    [\"User Name\"] = user,\n  MessageDetails = Description,\n\
  \   Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 23633

Stage 3: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    ["User Name"] = user,
  MessageDetails = Description,
   Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
User Nameproject

User account added to built in domain local or global group

#
Severity
low
Time window
1d
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Identifies when a user account has been added to a privileged built in domain local group or global group such as the Enterprise Admins, Cert Publishers or DnsAdmins. Be sure to verify this is an expected addition.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: a35f2c18-1b97-458f-ad26-e033af18eb99
name: User account added to built in domain local or global group
description: |
  'Identifies when a user account has been added to a privileged built in domain local group or global group such as the Enterprise Admins, Cert Publishers or DnsAdmins. Be sure to verify this is an expected addition.'
severity: Low
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1098
  - T1078
query: |
  // For AD SID mappings - https://docs.microsoft.com/windows/security/identity-protection/access-control/active-directory-security-groups
  let WellKnownLocalSID = "S-1-5-32-5[0-9][0-9]$";
  let WellKnownGroupSID = "S-1-5-21-[0-9]*-[0-9]*-[0-9]*-5[0-9][0-9]$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1102$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1103$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-498$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1000$";
  union isfuzzy=true
  (
  SecurityEvent
  // 4728 - A member was added to a security-enabled global group
  // 4732 - A member was added to a security-enabled local group
  // 4756 - A member was added to a security-enabled universal group
  | where EventID in (4728, 4732, 4756)
  | where AccountType == "User"
  | where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
  // Exclude Remote Desktop Users group: S-1-5-32-555
  | where TargetSid !in ("S-1-5-32-555")
  | extend SimpleMemberName = iff(MemberName == "-", MemberName, substring(MemberName, 3, indexof_regex(MemberName, @",OU|,CN") - 3))
  | project TimeGenerated, EventID, Activity, Computer, SimpleMemberName, MemberName, MemberSid, TargetAccount, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName, 
  SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid
  ),
  (
  WindowsEvent
  // 4728 - A member was added to a security-enabled global group
  // 4732 - A member was added to a security-enabled local group
  // 4756 - A member was added to a security-enabled universal group
  | where EventID in (4728, 4732, 4756) and not(EventData has "S-1-5-32-555")
  | extend SubjectUserSid = tostring(EventData.SubjectUserSid)
  | extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
  | extend AccountType=case(Account endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
  | extend MemberName = tostring(EventData.MemberName)
  | where AccountType == "User"
  | extend TargetSid = tostring(EventData.TargetSid)
  | where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
  // Exclude Remote Desktop Users group: S-1-5-32-555
  | where TargetSid !in ("S-1-5-32-555")
  | extend SimpleMemberName = iff(MemberName == "-", MemberName, substring(MemberName, 3, indexof_regex(MemberName, @",OU|,CN") - 3))
  | extend MemberSid = tostring(EventData.MemberSid)
  | extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName), 
  TargetAccount = strcat(tostring(EventData.TargetDomainName),"\\", tostring(EventData.TargetUserName))
  | extend UserPrincipalName = tostring(EventData.UserPrincipalName)
  | extend SubjectUserName = tostring(EventData.SubjectUserName), SubjectDomainName = tostring(EventData.SubjectDomainName), 
  SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
  | project TimeGenerated, EventID, Computer, SimpleMemberName, MemberName, MemberSid, TargetAccount, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName, 
  SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid
  )
  | extend GroupAddedMemberTo = TargetAccount, AddedByAccount = SubjectAccount, AddedByAccountName = SubjectUserName, AddedByAccountDomainName = SubjectDomainName, 
  AddedByAccountSid = SubjectUserSid, AddedMemberName = SimpleMemberName, AddedMemberSid = MemberSid
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: SubjectAccount
      - identifier: Name
        columnName: SubjectUserName
      - identifier: NTDomain
        columnName: SubjectDomainName
  - entityType: Account
    fieldMappings:
      - identifier: Sid
        columnName: SubjectUserSid
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AddedMemberName
      - identifier: Sid
        columnName: AddedMemberSid
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
version: 1.3.8
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Parameters

let WellKnownLocalSID = "S-1-5-32-5[0-9][0-9]$";

Let Binding: WellKnownGroupSID

let WellKnownGroupSID = "S-1-5-21-[0-9]*-[0-9]*-[0-9]*-5[0-9][0-9]$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1102$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1103$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-498$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1000$";

Union isfuzzy=true (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, WindowsEvent

Leg 1: SecurityEvent

SecurityEvent
| where EventID in (4728, 4732, 4756)
| where AccountType == "User"
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| where TargetSid !in ("S-1-5-32-555")
| extend SimpleMemberName = iff(MemberName == "-", MemberName, substring(MemberName, 3, indexof_regex(MemberName, @",OU|,CN") - 3))
| project TimeGenerated, EventID, Activity, Computer, SimpleMemberName, MemberName, MemberSid, TargetAccount, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName, 
SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid

Leg 2: WindowsEvent

WindowsEvent
| where EventID in (4728, 4732, 4756) and not(EventData has "S-1-5-32-555")
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend AccountType=case(Account endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| extend MemberName = tostring(EventData.MemberName)
| where AccountType == "User"
| extend TargetSid = tostring(EventData.TargetSid)
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| where TargetSid !in ("S-1-5-32-555")
| extend SimpleMemberName = iff(MemberName == "-", MemberName, substring(MemberName, 3, indexof_regex(MemberName, @",OU|,CN") - 3))
| extend MemberSid = tostring(EventData.MemberSid)
| extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName), 
TargetAccount = strcat(tostring(EventData.TargetDomainName),"\\", tostring(EventData.TargetUserName))
| extend UserPrincipalName = tostring(EventData.UserPrincipalName)
| extend SubjectUserName = tostring(EventData.SubjectUserName), SubjectDomainName = tostring(EventData.SubjectDomainName), 
SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| project TimeGenerated, EventID, Computer, SimpleMemberName, MemberName, MemberSid, TargetAccount, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName, 
SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid

Applied to the Combined Result

| extend GroupAddedMemberTo = TargetAccount, AddedByAccount = SubjectAccount, AddedByAccountName = SubjectUserName, AddedByAccountDomainName = SubjectDomainName, 
AddedByAccountSid = SubjectUserSid, AddedMemberName = SimpleMemberName, AddedMemberSid = MemberSid
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AccountTypeeq
  • User corpus 4 (kusto 4)
field:"AccountType" kind:eq value:"User"
EventIDin
  • 4728
  • 4732
  • 4756
field:"EventID" kind:in
TargetSidregex_match
    • S-1-5-21-[0-9]*-[0-9]*-[0-9]*-5[0-9][0-9]$
    • S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1102$
    • S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1103$
    • S-1-5-21-[0-9]*-[0-9]*-[0-9]*-498$
    • S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1000$
  • S-1-5-32-5[0-9][0-9]$
field:"TargetSid" kind:regex_match

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
TargetSideqS-1-5-32-555excludes:TargetSid field:"TargetSid" value:"S-1-5-32-555"
EventDatamatchS-1-5-32-555excludes:EventData field:"EventData" value:"S-1-5-32-555"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Computerproject
EventIDproject
MemberNameproject
MemberSidproject
SimpleMemberNameproject
SubjectAccountproject
SubjectDomainNameproject
SubjectUserNameproject
SubjectUserSidproject
TargetAccountproject
TargetDomainNameproject
TargetSidproject
TargetUserNameproject
TimeGeneratedproject
UserPrincipalNameproject
AddedByAccountextend
AddedByAccountDomainNameextend
AddedByAccountNameextend
AddedByAccountSidextend
AddedMemberNameextend
AddedMemberSidextend
GroupAddedMemberToextend
HostNameextend
HostNameDomainextend

Vectra Create Incident Based on Tag for Accounts

#
Status
available
Severity
high
Time window
10m
Source
github.com/Azure/Azure-Sentinel

Create an incident when the account entity presents a specific tag. If the tag is present, an incident should be created and marked with highest priority.

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Rule Body

id: 87325835-dd8c-41e7-b686-fd5adbbd0aee
name: Vectra Create Incident Based on Tag for Accounts
version: 1.1.0
kind: Scheduled
description: Create an incident when the account entity presents a specific tag. If the tag is present, an incident should be created and marked with highest priority.
status: Available
requiredDataConnectors:
  - connectorId: VectraXDR
    dataTypes:
      - Entities_Data_CL
severity: High
queryFrequency: 10m
queryPeriod: 10m
suppressionDuration: PT1H
suppressionEnabled: false
triggerOperator: GreaterThan
triggerThreshold: 0
tactics: 
  - Persistence
relevantTechniques:
  - T1546
query: |
  Entities_Data_CL
  | where entity_type == "account"
  | extend Tags = todynamic(tags)
  | where set_has_element(Tags, "MDR - Customer Escalation")
  | summarize arg_max(['last_modified_timestamp'], *) by ['name']
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: Name
    columnName: name
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P7D
    matchingMethod: AllEntities
    groupByEntities: [Account]
    groupByAlertDetails: [DisplayName]
customDetails:
  entity_id: id
  entity_type: entity_type
  tags: tags
  attack_profile: attack_profile
  ip_address: ip
alertDetailsOverride:
  alertDisplayNameFormat: Vectra AI Incident- {{name}} 
  alertDescriptionFormat: An incident has been escalated for Vectra AI entity {{name}} that is presenting an urgency score of {{urgency_score}}
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Stage 1: source

Entities_Data_CL

Stage 2: where

| where entity_type == "account"

Stage 3: extend

| extend Tags = todynamic(tags)

Stage 4: where

| where set_has_element(Tags, "MDR - Customer Escalation")

Stage 5: summarize

| summarize arg_max(['last_modified_timestamp'], *) by ['name']

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Tagsin
  • MDR - Customer Escalation transforms: set_has_element
field:"Tags" kind:in value:"MDR - Customer Escalation"
entity_typeeq
  • account
field:"entity_type" kind:eq value:"account"

Veeam ONE Unusual Job Duration

#

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
medium
Time window
3h
Source
github.com/Azure/Azure-Sentinel

Detects Veeam ONE unusual job duration alerts.

Rule Body

id: 04530ba4-b26e-4a83-8057-a4fc286de848
name: Veeam ONE Unusual Job Duration
description: Detects Veeam ONE unusual job duration alerts.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: VeeamCustomTablesDataConnector
  dataTypes:
  - VeeamOneTriggeredAlarms_CL
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: VeeamOneTriggeredAlarms_CL | where PredefinedAlarmId == 369
version: 1.0.1
kind: Scheduled
customDetails:
  PredefinedAlarmId: PredefinedAlarmId
  TriggeredAlarmId: TriggeredAlarmId
  Description: Description
  Name: Name
  VoneHostName: VoneHostName
  TriggeredTime: TriggeredTime
  Status: Status
  Comment: Comment
  ObjectId: ObjectId
  ObjectName: ObjectName
  ObjectType: ObjectType

Stages and Predicates

Stage 1: source

VeeamOneTriggeredAlarms_CL

Stage 2: where

| where PredefinedAlarmId == 369

Indicators

These rows show field, operator, and value matches.

Back to all Kusto rules