Detection rules › Kusto

Kusto rules: 0i

Cloudflare - Unexpected URI

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

'Detects client requests to unusual URI.'

MITRE ATT&CK Coverage

Rule Body

id: dcb797cd-a4cd-4306-897b-7991f71d7e28
name: Cloudflare - Unexpected URI
description: |
  'Detects client requests to unusual URI.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CloudflareDataConnector
    dataTypes:
      - Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
  - T1133
query: |
  Cloudflare
  | where HttpRequestMethod =~ 'GET'
  | where DstBytes != 0 or SrcBytes != 0
  | where ClientRequestURI matches regex @'(127\.\d{1,3}\.\d{1,3}\.\d{1,3})|(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.1[6-9]\.\d{1,3}\.\d{1,3})|(172\.2[0-9]\.\d{1,3}\.\d{1,3})|(172\.3[0-1]\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3})'
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 =~ 'GET'

Stage 3: where

| where DstBytes != 0 or SrcBytes != 0

Stage 4: where

| where ClientRequestURI matches regex @'(127\.\d{1,3}\.\d{1,3}\.\d{1,3})|(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.1[6-9]\.\d{1,3}\.\d{1,3})|(172\.2[0-9]\.\d{1,3}\.\d{1,3})|(172\.3[0-1]\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3})'

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ClientRequestURIregex_match
    • (127.\d{1,3}.\d{1,3}.\d{1,3})
    • (10.\d{1,3}.\d{1,3}.\d{1,3})
    • (172.1[6-9].\d{1,3}.\d{1,3})
    • (172.2[0-9].\d{1,3}.\d{1,3})
    • (172.3[0-1].\d{1,3}.\d{1,3})
    • (192.168.\d{1,3}.\d{1,3})
field:"ClientRequestURI" kind:regex_match
DstBytesne
  • 0
field:"DstBytes" kind:ne value:"0"
HttpRequestMethodeq
  • GET
field:"HttpRequestMethod" kind:eq value:"GET"
SrcBytesne
  • 0
field:"SrcBytes" kind:ne value:"0"

CTE - Rule Capacity High (90%)

#
Severity
medium
Time window
2d
Source
github.com/Azure/Azure-Sentinel

Tenant-wide blocking-rule/IOC capacity has reached 90%. Emitted by the daily tenant health-check job (api/src/functions/tenantHealthChecks.js) when usage newly crosses this threshold, and re-emitted at most once per ~30 days while it remains above threshold, so this stays a periodic reminder rather than a daily repeat.

Rule Body

id: b3eb26f4-affb-4a61-8b54-8780d234008e
name: CTE - Rule Capacity High (90%)
description: |
  Tenant-wide blocking-rule/IOC capacity has reached 90%. Emitted by the daily tenant health-check job (api/src/functions/tenantHealthChecks.js) when usage newly crosses this threshold, and re-emitted at most once per ~30 days while it remains above threshold, so this stays a periodic reminder rather than a daily repeat.
severity: Medium
requiredDataConnectors:
  - connectorId: cybereinforce_cte
    dataTypes:
      - CybereinforceCTE_CL
queryFrequency: 24h
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
tactics:
  []
relevantTechniques: []
query: |
  CybereinforceCTE_CL
  | where EventType == "rule.capacity_high"
  | extend d = parse_json(Details)
  | extend AppName = "Cybereinforce", RuleCount = toint(d.ruleCount), RuleLimit = toint(d.ruleLimit), UsedPct = todouble(d.usedPct)
  | project TimeGenerated, TenantId, AppName, RuleCount, RuleLimit, UsedPct, Severity, Category, Source
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: AppName
      - identifier: InstanceName
        columnName: TenantId
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CybereinforceCTE_CL

Stage 2: where

| where EventType == "rule.capacity_high"

Stage 3: extend

| extend d = parse_json(Details)

Stage 4: extend

| extend AppName = "Cybereinforce", RuleCount = toint(d.ruleCount), RuleLimit = toint(d.ruleLimit), UsedPct = todouble(d.usedPct)

Stage 5: project

| project TimeGenerated, TenantId, AppName, RuleCount, RuleLimit, UsedPct, Severity, Category, Source

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventTypeeq
  • rule.capacity_high
field:"EventType" kind:eq value:"rule.capacity_high"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AppNameproject
Categoryproject
RuleCountproject
RuleLimitproject
Severityproject
Sourceproject
TenantIdproject
TimeGeneratedproject
UsedPctproject

CyberArkEPM - Unexpected executable extension

#
Severity
medium
Time window
30m
Source
github.com/Azure/Azure-Sentinel

'Detects Windows executable with unexpected extension.'

MITRE ATT&CK Coverage

TacticTechniques
Execution
Stealth

Rule Body

id: 911d5b75-a1ce-4f13-a839-9c2474768696
name: CyberArkEPM - Unexpected executable extension
description: |
  'Detects Windows executable with unexpected extension.'
severity: Medium
requiredDataConnectors:
  - connectorId: CyberArkEPM
    dataTypes:
      - CyberArkEPM
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - DefenseEvasion
relevantTechniques:
  - T1204
  - T1036
query: | 
  CyberArkEPM
  | where EventSubType != 'AttackAttempt'
  | where ActingProcessName has @'\'
  | where ActingProcessName !endswith '.exe'
  | project EventEndTime, EventMessage, ActorUsername, ActingProcessFileInternalName
  | extend AccountCustomEntity = ActorUsername
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CyberArkEPM

Stage 2: where

| where EventSubType != 'AttackAttempt'

Stage 3: where

| where ActingProcessName has @'\'

Stage 4: where

| where ActingProcessName !endswith '.exe'

Stage 5: project

| project EventEndTime, EventMessage, ActorUsername, ActingProcessFileInternalName

Stage 6: extend

| extend AccountCustomEntity = ActorUsername

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
ActingProcessNameends_with.exeexcludes:ActingProcessName field:"ActingProcessName" value:".exe"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActingProcessFileInternalNameproject
ActorUsernameproject
EventEndTimeproject
EventMessageproject
AccountCustomEntityextend

Cyble Vision Alerts Darkweb Ransomware Leak

#

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

'A ransomware threat actor has posted victim data on the dark web. This alert includes leaked documents, threat actor name, victim organization, timestamps, and extracted text content for SOC triage.'

MITRE ATT&CK Coverage

Rule Body

id: 6deaf986-a25b-47b4-afbe-667901aa313b
name: Cyble Vision Alerts Darkweb Ransomware Leak
description: |
  'A ransomware threat actor has posted victim data on the dark web. This alert includes leaked documents, threat actor name, victim organization, timestamps, and extracted text content for SOC triage.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
  dataTypes:
  - CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
- Impact
- Exfiltration
- Reconnaissance
relevantTechniques:
- T1486
- T1657
query: |
  Alerts_darkweb_ransomware
  | where Service == "darkweb_ransomware"
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
alertDetailsOverride:
  alertDisplayNameFormat: Darkweb Ransomware Leak {{Victim}} by {{ThreatActor}}
  alertDescriptionFormat: |
    A ransomware group has posted data belonging on the dark web. A document leak has been detected, including text extracted from the file.
    **Victim:** {{Victim}}
    **Threat Actor:** {{ThreatActor}}
    **Leaked File:** {{FileName}}
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  Victim: DW_Victim_Nested
  ThreatActor: DW_ThreatActor_Nested
  CompanyLeak: DW_CompanyLeak
  DocumentYear: DW_DocumentYear
  FileName: DW_FileName
  OriginalFileName: DW_OriginalFileName
  ContentText: DW_Text
  UpdatedDate: DW_UpdatedDate_Nested
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: Name
    columnName: DW_Victim_Nested
  - identifier: FullName
    columnName: DW_ThreatActor_Nested
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_darkweb_ransomware

Stage 2: where

| where Service == "darkweb_ransomware"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

Cyble Vision Alerts Website Defacement URL

#

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

'Detects suspicious or unexpected changes to monitored URLs which may indicate website tampering or defacement.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 1dabe566-a0f1-4c27-8307-aea5a79eb5e9
name: Cyble Vision Alerts Website Defacement URL
description: |
  'Detects suspicious or unexpected changes to monitored URLs which may indicate website tampering or defacement.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
  dataTypes:
  - CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
- Impact
relevantTechniques:
- T1491
query: |
  Alerts_defacement_url  
  | where Service == "defacement_url" 
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
  alertDetailsOverride:
  alertDisplayNameFormat: URL Content Change Detected {{DU_DomainName}}
  alertDescriptionFormat: |
    A monitored URL for domain {{DU_DomainName}} has changed {{DU_Description}}. This may indicate unauthorized modification consistent with website defacement activity.
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  DU_description: DU_Description
  DU_DomainName: DU_DomainName
  DU_Keywords: DU_Keywords
  DU_Type: DU_Type
  DU_RecordID: DU_RecordID
entityMappings:
- entityType: Url
  fieldMappings:
  - identifier: Url
    columnName: DU_Description
- entityType: Host
  fieldMappings:
  - identifier: HostName
    columnName: DU_DomainName
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_defacement_url

Stage 2: where

| where Service == "defacement_url"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

CYFIRMA - High severity Malicious Network Indicators with Monitor Action Rule

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

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

"This rule detects high-confidence network-based indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Monitor' and no specified role. These indicators may be associated with infrastructure used in phishing campaigns, malware, or initial access via watering hole or drive-by compromise techniques."

MITRE ATT&CK Coverage

Rule Body

id: 8317de44-09e4-4a04-8fae-c38c1b72064b
name: CYFIRMA - High severity Malicious Network Indicators with Monitor Action Rule
description:  |
  "This rule detects high-confidence network-based indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Monitor' and no specified role. 
  These indicators may be associated with infrastructure used in phishing campaigns, malware, or initial access via watering hole or drive-by compromise techniques."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  //Network Indicators with Monitor Action 
  let timeFrame= 5m;
  CyfirmaIndicators_CL 
  | where ConfidenceScore >= 80
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and (isempty(Roles) or not(Roles has_any ('Command & Control', 'Malware', 'TOR', 'Trojan', 'Phishing')))
  | extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
  | extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
  | extend parsed = parse_json(extensions)
  | extend extensionKeys = bag_keys(parsed)
  | mv-expand extensionKeys
  | extend extensionKeyStr = tostring(extensionKeys)
  | extend ext = parsed[extensionKeyStr]
  | extend props = ext.properties
  | extend 
      extension_id = extensionKeyStr,
      ASN_Owner = props.asn_owner,
      ASN = props.asn,
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project
      IPv4,
      IPv6,
      URL,
      Domain,
      ThreatActors,
      RecommendedActions,
      Sources,
      Roles,
      Country,
      IPAbuse,
      name,
      Description,
      ConfidenceScore,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      SecurityVendors,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - InitialAccess
  - Execution
  - Reconnaissance
  - Impact
relevantTechniques:
  - T1566
  - T1189
  - T1204
  - T1595
  - T1498
  - T1566.002
  - T1204.001
  - T1595.002
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence Malicious Network Indicators with Monitor Action Rule - {{name}} "
  alertDescriptionFormat: "{{Description}} - {{name}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
customDetails:
  ThreatActors: ThreatActors
  Sources: Sources
  RecommendedActions: RecommendedActions
  Roles: Roles
  Country: Country
  Description: Description
  ConfidenceScore: ConfidenceScore
  SecurityVendors: SecurityVendors
  IndicatorID: IndicatorID
  Created: created
  Modified: modified
  ValidFrom: valid_from
  Tags: Tags
  ThreatType: ThreatType
  TimeGenerated: TimeGenerated
  IPAbuse: IPAbuse
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv4
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv6
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URL
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Parameters

let timeFrame = 5m;

Stage 1: source

CyfirmaIndicators_CL

Stage 2: where

| where ConfidenceScore >= 80
    and TimeGenerated between (ago(timeFrame) .. now())
    and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and (isempty(Roles) or not(Roles has_any ('Command & Control', 'Malware', 'TOR', 'Trojan', 'Phishing')))

Stage 3: extend (6 consecutive steps)

| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)

Stage 4: mv-expand

| mv-expand extensionKeys

Stage 5: extend (4 consecutive steps)

| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend 
    extension_id = extensionKeyStr,
    ASN_Owner = props.asn_owner,
    ASN = props.asn,
    ProviderName = 'CYFIRMA',
    ProductName = 'DeCYFIR/DeTCT'

Stage 6: project

| project
    IPv4,
    IPv6,
    URL,
    Domain,
    ThreatActors,
    RecommendedActions,
    Sources,
    Roles,
    Country,
    IPAbuse,
    name,
    Description,
    ConfidenceScore,
    IndicatorID,
    created,
    modified,
    valid_from,
    Tags,
    ThreatType,
    TimeGenerated,
    SecurityVendors,
    ProductName,
    ProviderName

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
patterncontainsfile:hashesexcludes:pattern field:"pattern" value:"file:hashes"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ConfidenceScoreproject
Countryproject
Descriptionproject
Domainproject
IPAbuseproject
IPv4project
IPv6project
IndicatorIDproject
ProductNameproject
ProviderNameproject
RecommendedActionsproject
Rolesproject
SecurityVendorsproject
Sourcesproject
Tagsproject
ThreatActorsproject
ThreatTypeproject
TimeGeneratedproject
URLproject
createdproject
modifiedproject
nameproject
valid_fromproject

D3 Smart SOAR - High or critical severity incident detected

#

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
1h
Source
github.com/Azure/Azure-Sentinel

Identifies when a D3 Smart SOAR incident with High or Critical severity is ingested. This helps security teams prioritize response to the most impactful incidents reported by D3 Smart SOAR.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 48ef0be4-8240-4a03-bbb9-320b562d6ce4
name: D3 Smart SOAR - High or critical severity incident detected
description: |
  Identifies when a D3 Smart SOAR incident with High or Critical severity is ingested. This helps security teams prioritize response to the most impactful incidents reported by D3 Smart SOAR.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: D3SOARConnectorDefinition
    dataTypes:
      - D3SOARIncidents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1499
query: |
  D3SOARIncidents_CL
  | where TimeGenerated > ago(1h)
  | where IncidentSeverity in ("High", "Critical")
  | project
      TimeGenerated,
      IncidentNumber,
      IncidentTitle,
      IncidentSeverity,
      IncidentStatus,
      IncidentType,
      IncidentPriority,
      IncidentOwner,
      IncidentCreator,
      IncidentDescription,
      IncidentStage
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: IncidentOwner
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

D3SOARIncidents_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where IncidentSeverity in ("High", "Critical")

Stage 4: project

| project
    TimeGenerated,
    IncidentNumber,
    IncidentTitle,
    IncidentSeverity,
    IncidentStatus,
    IncidentType,
    IncidentPriority,
    IncidentOwner,
    IncidentCreator,
    IncidentDescription,
    IncidentStage

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
IncidentSeverityin
  • Critical
  • High
field:"IncidentSeverity" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
IncidentCreatorproject
IncidentDescriptionproject
IncidentNumberproject
IncidentOwnerproject
IncidentPriorityproject
IncidentSeverityproject
IncidentStageproject
IncidentStatusproject
IncidentTitleproject
IncidentTypeproject
TimeGeneratedproject

Dataverse - Suspicious use of TDS endpoint

#
Status
available
Severity
low
Time window
14d
Group by
$left.ClientIp, $right.IPAddress, AccountName, ClientIp, FirstEvent, InstanceUrl, UPNSuffix, UserId
Source
github.com/Azure/Azure-Sentinel

Identifies Dataverse TDS (Tabular Data Stream) protocol based queries where the source user or IP address has recent security alerts and the TDS protocol has not been used previously in the target environment.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: d875af10-6bb9-4d6a-a6e4-78439a98bf4b
kind: Scheduled
name: Dataverse - Suspicious use of TDS endpoint
description: Identifies Dataverse TDS (Tabular Data Stream) protocol based queries
  where the source user or IP address has recent security alerts and the TDS protocol
  has not been used previously in the target environment.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
  - connectorId: AzureActiveDirectoryIdentityProtection
    dataTypes:
      - SecurityAlert
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - InitialAccess
relevantTechniques:
  - T1048
  - T1190
query: |
  let query_frequency = 1h;
  let query_lookback = 14d;
  DataverseActivity
  | where TimeGenerated >= ago(query_frequency)
  | where Message == 'ExecutePowerBISql'
  | summarize FirstEvent = min(TimeGenerated) by UserId, ClientIp, InstanceUrl
  | join kind=inner(
      DataverseActivity
      | where TimeGenerated >= ago(query_lookback)
      | where Message == 'ExecutePowerBISql'
      | summarize UniqueUsers = dcount(UserId, 4) by InstanceUrl)
      on InstanceUrl
  | where UniqueUsers == 1
  | join kind=inner (
      SecurityAlert
      | where Entities has ('"Type":"ip"')
      | project AlertName, SystemAlertId, Entities
      | mv-expand todynamic(Entities)
      | where Entities.Type == "ip"
      | extend IPAddress = tostring(Entities.Address)
      | summarize SystemAlerts = make_set(SystemAlertId, 100), Alerts = make_set(AlertName, 100) by IPAddress)
      on $left.ClientIp == $right.IPAddress
  | extend
      CloudAppId = int(32780),
      AccountName = tostring(split(UserId, '@')[0]),
      UPNSuffix = tostring(split(UserId, '@')[1])
  | join kind = inner (
      SecurityAlert
      | where Entities has ('Type":"account"')
      | project AlertName, SystemAlertId, Entities
      | mv-expand todynamic(Entities)
      | where Entities.Type == "account"
      | extend
          UPNSuffix = tostring(Entities.UPNSuffix),
          AccountName = tostring(Entities.Name)
      | summarize SystemAlerts = make_set(SystemAlertId, 100), Alerts = make_set(AlertName, 100) by AccountName, UPNSuffix
      | where isnotempty(AccountName) and isnotempty(UPNSuffix))
      on AccountName, UPNSuffix
  | summarize SystemAlerts = make_set(SystemAlerts, 100), Alerts = make_set(Alerts, 100) by FirstEvent, UserId, ClientIp, InstanceUrl, AccountName, UPNSuffix
  | extend CloudAppId = int(32780)
  | project
      FirstEvent,
      UserId,
      ClientIp,
      InstanceUrl,
      Alerts,
      SystemAlerts,
      CloudAppId,
      AccountName,
      UPNSuffix
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
alertDetailsOverride:
  alertDisplayNameFormat: 'Dataverse - Suspicious use of TDS endpoint in {{InstanceUrl}} '
  alertDescriptionFormat: 'The TDS endpoint was used to query Dataverse instance {{InstanceUrl}}
    . The use of this protocol was not seen previously and the following alerts were
    associated with the caller: {{Alerts}}'
version: 3.2.0

Stages and Predicates

Parameters

let query_frequency = 1h;
let query_lookback = 14d;

Stage 1: source

DataverseActivity

Stage 2: where

| where TimeGenerated >= ago(query_frequency)

Stage 3: where

| where Message == 'ExecutePowerBISql'

Stage 4: summarize

| summarize FirstEvent = min(TimeGenerated) by UserId, ClientIp, InstanceUrl

Stage 5: join

| join kind=inner(
    DataverseActivity
    | where TimeGenerated >= ago(query_lookback)
    | where Message == 'ExecutePowerBISql'
    | summarize UniqueUsers = dcount(UserId, 4) by InstanceUrl)
    on InstanceUrl

Stage 6: where

where UniqueUsers == 1

Stage 7: join

| join kind=inner (
    SecurityAlert
    | where Entities has ('"Type":"ip"')
    | project AlertName, SystemAlertId, Entities
    | mv-expand todynamic(Entities)
    | where Entities.Type == "ip"
    | extend IPAddress = tostring(Entities.Address)
    | summarize SystemAlerts = make_set(SystemAlertId, 100), Alerts = make_set(AlertName, 100) by IPAddress)
    on $left.ClientIp == $right.IPAddress

Stage 8: extend

extend AccountName, CloudAppId, UPNSuffix

Stage 9: join

| join kind = inner (
    SecurityAlert
    | where Entities has ('Type":"account"')
    | project AlertName, SystemAlertId, Entities
    | mv-expand todynamic(Entities)
    | where Entities.Type == "account"
    | extend
        UPNSuffix = tostring(Entities.UPNSuffix),
        AccountName = tostring(Entities.Name)
    | summarize SystemAlerts = make_set(SystemAlertId, 100), Alerts = make_set(AlertName, 100) by AccountName, UPNSuffix
    | where isnotempty(AccountName) and isnotempty(UPNSuffix))
    on AccountName, UPNSuffix

Stage 10: summarize

summarize Alerts, SystemAlerts by FirstEvent, UserId, ClientIp, InstanceUrl, AccountName, UPNSuffix

Stage 11: extend

extend CloudAppId

Stage 12: project

project AccountName, Alerts, ClientIp, CloudAppId, FirstEvent, InstanceUrl, SystemAlerts, UPNSuffix, UserId

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AccountNameis_not_null
  • (no value, null check)
field:"AccountName" kind:is_not_null
Entitiesmatch
  • "Type":"ip" transforms: term
  • Type":"account" transforms: term
field:"Entities" kind:match
Entities.Typeeq
  • account transforms: kusto_dynamic_path
  • ip transforms: kusto_dynamic_path
field:"Entities.Type" kind:eq
Messageeq
  • ExecutePowerBISql
field:"Message" kind:eq value:"ExecutePowerBISql"
UPNSuffixis_not_null
  • (no value, null check)
field:"UPNSuffix" kind:is_not_null
UniqueUserseq
  • 1
field:"UniqueUsers" kind:eq value:"1"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
Alertsproject
ClientIpproject
CloudAppIdproject
FirstEventproject
InstanceUrlproject
SystemAlertsproject
UPNSuffixproject
UserIdproject

Deletion of data on multiple drives using cipher exe

#
Status
available
Severity
medium
Time window
1m
Group by
DeviceId, DeviceName
Source
github.com/Azure/Azure-Sentinel

This query checks for attempts to delete data on multiple drives using cipher.exe. This activity is typically done by ransomware to prevent recovery of data after encryption.

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rule Body

id: 03caa992-477f-4b19-8e2a-8cd58f8f9652
name: Deletion of data on multiple drives using cipher exe
description: |
  This query checks for attempts to delete data on multiple drives using cipher.exe. This activity is typically done by ransomware to prevent recovery of data after encryption.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1485
query: |
  // Look for cipher.exe deleting data from multiple drives
  DeviceProcessEvents
  | where FileName =~ "cipher.exe" 
  // cipher.exe /w flag used for deleting data 
  | where ProcessCommandLine has "/w" 
  | summarize CipherCount = dcount(ProcessCommandLine), CipherList = make_set(ProcessCommandLine, 1000) by DeviceId, DeviceName, bin(TimeGenerated, 1m) 
  // cipher.exe accessing multiple drives in a short timeframe  
  | where CipherCount > 1
  | extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
  | extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

DeviceProcessEvents

Stage 2: where

| where FileName =~ "cipher.exe"

Stage 3: where

| where ProcessCommandLine has "/w"

Stage 4: summarize

| summarize CipherCount = dcount(ProcessCommandLine), CipherList = make_set(ProcessCommandLine, 1000) by DeviceId, DeviceName, bin(TimeGenerated, 1m)

Stage 5: where

| where CipherCount > 1

Stage 6: extend

| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 7: extend

| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =
ifDeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))
else""

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CipherCountgt
  • 1 transforms: number
field:"CipherCount" kind:gt value:"1"
FileNameeq
  • cipher.exe corpus 3 (elastic 1, splunk 1, kusto 1)
field:"process_basename" kind:eq value:"cipher.exe"
ProcessCommandLinematch
  • /w transforms: term
field:"CommandLine" kind:match value:"/w"

Output Fields

These fields are emitted when the rule matches.

FieldSource
CipherCountsummarize
CipherListsummarize
DeviceIdsummarize
DeviceNamesummarize
HostNameextend
DnsDomainextend

GTI Relevance System Alert - Incident by Alert ID

#
Status
available
Severity
medium
Time window
5m
Source
github.com/Azure/Azure-Sentinel

Creates a Microsoft Sentinel incident for each unique Google Threat Intelligence (GTI) Alert ID. Multiple ingestion records sharing the same Alert ID (e.g. updated snapshots of the same alert) are grouped into a single incident. The rule surfaces the most recent snapshot of each alert and maps severity from the GTI severity analysis field.

MITRE ATT&CK Coverage

Rule Body

id: a1b2c3d4-e5f6-7890-abcd-ef1234567891
name: GTI Relevance System Alert - Incident by Alert ID
description: |
  Creates a Microsoft Sentinel incident for each unique Google Threat Intelligence (GTI) Alert ID.
  Multiple ingestion records sharing the same Alert ID (e.g. updated snapshots of the same alert) are
  grouped into a single incident. The rule surfaces the most recent snapshot of each alert and maps
  severity from the GTI severity analysis field.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: GoogleThreatIntelligenceRelevanceSystemAlertsAPI
    dataTypes:
      - RelevanceSystemAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Reconnaissance
  - Impact
  - CredentialAccess
relevantTechniques:
  - T1566
  - T1078
  - T1552
  - T1486
  - T1595
query: |
  GTIRelevanceSystemAlerts
  | extend
      SentinelSeverity = case(
          SeverityLevel == "CRITICAL", "High",
          SeverityLevel == "HIGH",     "High",
          SeverityLevel == "MEDIUM",   "Medium",
          SeverityLevel == "LOW",      "Low",
          "Informational"
      ),
      DetailSeverity = coalesce(DataLeakSeverity, InsiderThreatSeverity, InitialAccessBrokerSeverity, "")
  | project
      TimeGenerated,
      AlertId,
      AlertName        = DisplayName,
      AlertState       = State,
      DetailType,
      DetailSeverity,
      SeverityLevel,
      SentinelSeverity,
      SeverityConfidence,
      SeverityReasoning,
      PriorityLevel,
      PriorityReasoning,
      RelevanceLevel,
      RelevanceConfidence,
      RelevanceReasoning,
      FindingCount,
      AiSummary,
      AuditCreateTime,
      AuditUpdateTime
alertDetailsOverride:
  alertDisplayNameFormat: 'GTI Alert: {{AlertName}} [{{AlertState}}]'
  alertDescriptionFormat: |
    Alert ID: {{AlertId}}
    Type: {{DetailType}}

    Summary: {{AiSummary}}
  alertSeverityColumnName: SentinelSeverity
customDetails:
  AlertId: AlertId
  AlertState: AlertState
  DetailType: DetailType
  DetailSeverity: DetailSeverity
  PriorityLevel: PriorityLevel
  RelevanceLevel: RelevanceLevel
  FindingCount: FindingCount
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P5D
    matchingMethod: Selected
    groupByCustomDetails:
      - AlertId
eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GTIRelevanceSystemAlerts

Stage 2: extend

| extend
    SentinelSeverity = case(
        SeverityLevel == "CRITICAL", "High",
        SeverityLevel == "HIGH",     "High",
        SeverityLevel == "MEDIUM",   "Medium",
        SeverityLevel == "LOW",      "Low",
        "Informational"
    ),
    DetailSeverity = coalesce(DataLeakSeverity, InsiderThreatSeverity, InitialAccessBrokerSeverity, "")
SentinelSeverity =
ifSeverityLevel == "CRITICAL""High"
elifSeverityLevel == "HIGH""High"
elifSeverityLevel == "MEDIUM""Medium"
elifSeverityLevel == "LOW""Low"
else"Informational"

Stage 3: project

| project
    TimeGenerated,
    AlertId,
    AlertName        = DisplayName,
    AlertState       = State,
    DetailType,
    DetailSeverity,
    SeverityLevel,
    SentinelSeverity,
    SeverityConfidence,
    SeverityReasoning,
    PriorityLevel,
    PriorityReasoning,
    RelevanceLevel,
    RelevanceConfidence,
    RelevanceReasoning,
    FindingCount,
    AiSummary,
    AuditCreateTime,
    AuditUpdateTime

Output Fields

These fields are emitted when the rule matches.

FieldSource
AiSummaryproject
AlertIdproject
AlertNameproject
AlertStateproject
AuditCreateTimeproject
AuditUpdateTimeproject
DetailSeverityproject
DetailTypeproject
FindingCountproject
PriorityLevelproject
PriorityReasoningproject
RelevanceConfidenceproject
RelevanceLevelproject
RelevanceReasoningproject
SentinelSeverityproject
SeverityConfidenceproject
SeverityLevelproject
SeverityReasoningproject
TimeGeneratedproject

Guardian- Regex Policy Violation Detection

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

'This alert creates an incident when Regex Policy Violation detected from the Guardian.'

Rule Body

id: d9ad323f-6115-4f19-9e81-feabceeb6730
name: Guardian- Regex Policy Violation Detection
description: |
  'This alert creates an incident when Regex Policy Violation detected from the Guardian.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: BoschAIShield
    dataTypes:
      - Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  Guardian
  | where PolicyViolatedControlFeature =~ 'Regex'
  | where Severity =~ 'Low'
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: Guardian- Regex Policy Violation detection
  alertDescriptionFormat: |
    This query detects Regex Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
  alertTacticsColumnName: null
  alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Guardian

Stage 2: where

| where PolicyViolatedControlFeature =~ 'Regex'

Stage 3: where

| where Severity =~ 'Low'

Indicators

These rows show field, operator, and value matches.

Lateral Movement Risk - Role Chain Length

#

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

The policy detects chains of more than 3 roles in the account, this is a misconfiguration that can enable lateral movement.

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Privilege Escalation

Rule Body

id: 25bef734-4399-4c55-9579-4ebabd9cccf6
name: Lateral Movement Risk - Role Chain Length
kind: Scheduled
description: The policy detects chains of more than 3 roles in the account, this is a misconfiguration that can enable lateral movement.
severity: Informational
status: Available
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
  - Persistence
relevantTechniques:
  - T1098
query: |-
  Authomize_v2_CL
  | where ingestion_time() >= ago(30m)
  | extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s
  | where Policy has "Chain of 3 or more roles"
  | project  EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URL
requiredDataConnectors:
- connectorId: Authomize
  dataTypes: [ "Authomize_v2_CL" ]
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertnameFormat: Alert from Authomize - Account can elevate privileges by assuming a role
  alertDescriptionFormat: Account can elevate privileges by assuming a role. The policy detects chains of more than 3 roles in the account, this is a misconfiguration that can enable lateral movement.
  alertSeverity: Severity
  alertTactics: Tactics  
  alertDynamicProperties:
  - alertProperty: AlertLink
    value: URL
customDetails:
  AuthomizeEventID: EventID
  EventName: Policy
  EventDescription: Description
  EventRecommendation: Recommendation
  ReferencedURL: URL
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.3

Stages and Predicates

Stage 1: source

Authomize_v2_CL

Stage 2: where

| where ingestion_time() >= ago(30m)

Stage 3: extend

| extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s

Stage 4: where

| where Policy has "Chain of 3 or more roles"

Stage 5: project

| project  EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Policymatch
  • Chain of 3 or more roles transforms: term
field:"policy_name_s" kind:match value:"Chain of 3 or more roles"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Categoryproject
Descriptionproject
EventIDproject
Policyproject
Recommendationproject
Severityproject
Tacticsproject
URLproject

Missing Domain Controller Heartbeat

#
Severity
high
Time window
2h
Group by
Computer
Author
Jose Sebastian Canos
Source
github.com/Azure/Azure-Sentinel

'This detection will go over the heartbeats received from the agents of Domain Controllers over the last hour, and will create alerts if the last heartbeats were received an hour ago.'

MITRE ATT&CK Coverage

Rule Body

id: b8b8ba09-1e89-45a1-8bd7-691cd23bfa32
name: Missing Domain Controller Heartbeat
description: |
  'This detection will go over the heartbeats received from the agents of Domain Controllers over the last hour, and will create alerts if the last heartbeats were received an hour ago.'
severity: High
requiredDataConnectors: []
queryFrequency: 15m
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
  - DefenseEvasion
relevantTechniques:
  - T1499
  - T1564
query: |
  let query_frequency = 15m;
  let missing_period = 1h;
  //Enter a reference list of hostnames for your DC servers
  let DCServersList = dynamic (["DC01.simulandlabs.com","DC02.simulandlabs.com"]);
  //Alternatively, a Watchlist can be used
  //let DCServersList = _GetWatchlist('HostName-DomainControllers') | project HostName;
  Heartbeat
  | summarize arg_max(TimeGenerated, *) by Computer
  | where Computer in (DCServersList)
  //You may specify the OS type of your Domain Controllers
  //| where OSType == 'Windows'
  | where TimeGenerated between (ago(query_frequency + missing_period) .. ago(missing_period))
  | project TimeGenerated, Computer, OSType, Version, ComputerEnvironment, Type, Solutions
  | sort by TimeGenerated asc
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Computer
version: 1.0.5
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Jose Sebastian Canos
    support:
        tier: Community
    providers: Microsoft
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let query_frequency = 15m;
let missing_period = 1h;
let DCServersList = dynamic (["DC01.simulandlabs.com","DC02.simulandlabs.com"]);

Stage 1: source

Heartbeat

Stage 2: summarize

| summarize arg_max(TimeGenerated, *) by Computer

Stage 3: where

| where Computer in (DCServersList)

Stage 4: where

| where TimeGenerated between (ago(query_frequency + missing_period) .. ago(missing_period))

Stage 5: project

| project TimeGenerated, Computer, OSType, Version, ComputerEnvironment, Type, Solutions

Stage 6: sort

| sort by TimeGenerated asc

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Computerin
  • DC01.simulandlabs.com
  • DC02.simulandlabs.com
field:"Computer" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
Computerproject
ComputerEnvironmentproject
OSTypeproject
Solutionsproject
TimeGeneratedproject
Typeproject
Versionproject

Objects Added to Malware Detection Exclusions

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

Detects when an object is added to malware detection exclusions.

Rule Body

id: a8ebf22b-a050-434c-8095-2267f206257a
name: Objects Added to Malware Detection Exclusions
description: Detects when an object is added to malware detection exclusions.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 42260\n| extend AddedObjectNames\
  \ = extract(\"AddedObjectNames=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n\
  \    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource\
  \ = original_host,\n    EventId = instanceId,\n    [\"Object Names\"] = AddedObjectNames,\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 == 42260

Stage 3: extend

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

Stage 4: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    ["Object Names"] = AddedObjectNames,
    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
Object Namesproject
Severityproject

Palo Alto Threat signatures from Unusual IP addresses

#
Status
available
Severity
medium
Time window
7d
Group by
DeviceAction, DeviceProduct, DeviceVendor, SourceIP
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: Palo Alto 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: CefAma
    dataTypes:
      - CommonSecurityLog
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 = CommonSecurityLog
  | where isnotempty(SourceIP)
  | where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
  | where DeviceVendor =~ "Palo Alto Networks"
  | where Activity =~ "THREAT" and SimplifiedDeviceAction =~ "alert" 
  | where DeviceEventClassID in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
  | summarize TotalEvents = count(), ThreatTypes = make_set(DeviceEventClassID), DestinationIpList = make_set(DestinationIP), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by SourceIP, DeviceAction, DeviceVendor;
  let CurrentHourThreats =  CommonSecurityLog
  | where isnotempty(SourceIP)
  | where TimeGenerated > ago(timeframe)
  | where DeviceVendor =~ "Palo Alto Networks"
  | where Activity =~ "THREAT" and SimplifiedDeviceAction =~ "alert" 
  | where DeviceEventClassID in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
  | summarize TotalEvents = count(), ThreatTypes = make_set(DeviceEventClassID), DestinationIpList = make_set(DestinationIP), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by SourceIP, DeviceAction, DeviceProduct, DeviceVendor;
  CurrentHourThreats 
  | where TotalEvents < CurrThreshold
  | join kind = leftanti (HistoricalThreats 
  | where TotalEvents > HistThreshold) on SourceIP
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
version: 1.0.3
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 9

let HistoricalThreats = CommonSecurityLog
| where isnotempty(SourceIP)
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
| where DeviceVendor =~ "Palo Alto Networks"
| where Activity =~ "THREAT" and SimplifiedDeviceAction =~ "alert" 
| where DeviceEventClassID in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
| summarize TotalEvents = count(), ThreatTypes = make_set(DeviceEventClassID), DestinationIpList = make_set(DestinationIP), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by SourceIP, DeviceAction, DeviceVendor;

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

Stage 1: source

CommonSecurityLog

Stage 2: where

| where isnotempty(SourceIP)

Stage 3: where

| where TimeGenerated > ago(timeframe)

Stage 4: where

| where DeviceVendor =~ "Palo Alto Networks"

Stage 5: where

| where Activity =~ "THREAT" and SimplifiedDeviceAction =~ "alert"

Stage 6: where

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

Stage 7: summarize

| summarize TotalEvents = count(), ThreatTypes = make_set(DeviceEventClassID), DestinationIpList = make_set(DestinationIP), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by SourceIP, DeviceAction, DeviceProduct, DeviceVendor

Stage 8: where

CurrentHourThreats
| where TotalEvents < CurrThreshold

Stage 9: join (negated)

join kind=leftanti (HistoricalThreats) on SourceIP

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
ActivityeqTHREATexcludes:Activity field:"Activity" value:"THREAT"
DeviceEventClassIDinfile, flood, packet, scan, spyware, virus, vulnerability, wildfire, wildfire-virusexcludes:DeviceEventClassID
DeviceVendoreqPalo Alto Networksexcludes:DeviceVendor field:"DeviceVendor" value:"Palo Alto Networks"
SimplifiedDeviceActioneqalertexcludes:SimplifiedDeviceAction field:"SimplifiedDeviceAction" value:"alert"
SourceIPis_not_null(no value, null check)excludes:SourceIP
TotalEventsgt25excludes:TotalEvents field:"TotalEvents" value:"25"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DestinationIpListsummarize
DeviceActionsummarize
DeviceProductsummarize
DeviceVendorsummarize
FirstSeensummarize
LastSeensummarize
SourceIPsummarize
ThreatTypessummarize
TotalEventssummarize

Potential Build Process Compromise

#
Severity
medium
Time window
5m
Group by
BuildAccount, BuildCommand, BuildParentProcess, BuildProcess, BuildProcessTime, Computer, FileEditAccount, FileEditParentProcess, FileEditProcess, FileEditTime, FileEdited, timekey
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

The query looks for source code files being modified immediately after a build process is started. The purpose of this is to look for malicious code injection during the build process. More details: https://techcommunity.microsoft.com/t5/azure-sentinel/monitoring-the-software-supply-chain-with-azure-sentinel/ba-p/2176463

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rule Body

id: 5ef06767-b37c-4818-b035-47de950d0046
name: Potential Build Process Compromise
description: |
  'The query looks for source code files being modified immediately after a build process is started. The purpose of this is to look for malicious code injection during the build process.
  More details: https://techcommunity.microsoft.com/t5/azure-sentinel/monitoring-the-software-supply-chain-with-azure-sentinel/ba-p/2176463'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes: 
      - SecurityEvents 
  - connectorId: WindowsForwardedEvents
    dataTypes: 
      - WindowsEvent 
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1554
tags:
  - Solorigate
  - NOBELIUM
query: |
  // How far back to look for events from
  let timeframe = 1d;
  // How close together build events and file modifications should occur to alert (make this smaller to reduce FPs)
  let time_window = 5m;
  // Edit this to include build processes used
  let build_processes = dynamic(["MSBuild.exe", "dotnet.exe", "VBCSCompiler.exe"]);
  // Include any processes that you want to allow to edit files during/around the build process
  let allow_list = dynamic([""]);
  (union isfuzzy=true
  (SecurityEvent
  | where TimeGenerated > ago(timeframe)
  // Look for build process starts
  | where EventID == 4688
  | where Process has_any (build_processes)
  | summarize by BuildParentProcess=ParentProcessName, BuildProcess=Process, BuildAccount = Account, Computer, BuildCommand=CommandLine, timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
  | join kind=inner(
  SecurityEvent
  | where TimeGenerated > ago(timeframe)
  // Look for file modifications to code file
  | where EventID == 4663
  | where Process !in (allow_list)
  // Look for code files, edit this to include file extensions used in build.
  | where ObjectName endswith ".cs" or ObjectName endswith ".cpp"
  // 0x6 and 0x4 for file append, 0x100 for file replacements
  | where AccessMask == "0x6"  or AccessMask == "0x4" or AccessMask == "0X100"
  | summarize by FileEditParentProcess=ParentProcessName, FileEditAccount = Account, Computer, FileEdited=ObjectName, FileEditProcess=ProcessName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
  // join where build processes and file modifications seen at same time on same host
  on timekey, Computer
  // Limit to only where the file edit happens after the build process starts
  | where BuildProcessTime <= FileEditTime
  | summarize make_set(FileEdited), make_set(FileEditProcess), make_set(FileEditAccount) by timekey, Computer, BuildParentProcess, BuildProcess
  ),
  (WindowsEvent
  | where TimeGenerated > ago(timeframe)
  // Look for build process starts
  | where EventID == 4688 and EventData has_any (build_processes)
  | extend NewProcessName = tostring(EventData.NewProcessName)
  | extend Process=tostring(split(NewProcessName, '\\')[-1])
  | where Process has_any (build_processes)
  | extend ParentProcessName = tostring(EventData.ParentProcessName)
  | extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
  | extend CommandLine = tostring(EventData.CommandLine) 
  | summarize by BuildParentProcess=ParentProcessName, BuildProcess=Process, BuildAccount = Account, Computer, BuildCommand=CommandLine, timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
  | join kind=inner(
  WindowsEvent
  | where TimeGenerated > ago(timeframe)
  // Look for file modifications to code file
  | where EventID == 4663 and EventData has_any ("0x6", "0x4", "0X100") and EventData has_any (".cs", ".cpp")
  | extend NewProcessName = tostring(EventData.NewProcessName)
  | extend Process=tostring(split(NewProcessName, '\\')[-1])
  | where Process !in (allow_list)
  // Look for code files, edit this to include file extensions used in build.
  | extend ObjectName = tostring(EventData.ObjectName)
  | where ObjectName endswith ".cs" or ObjectName endswith ".cpp"
  // 0x6 and 0x4 for file append, 0x100 for file replacements
  | extend AccessMask = tostring(EventData.AccessMask)  
  | where AccessMask == "0x6"  or AccessMask == "0x4" or AccessMask == "0X100"
  | extend ParentProcessName = tostring(EventData.ParentProcessName)
  | extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
  | extend ProcessName = tostring(EventData.ProcessName)
  | summarize by FileEditParentProcess=ParentProcessName, FileEditAccount = Account, Computer, FileEdited=ObjectName, FileEditProcess=ProcessName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
  // join where build processes and file modifications seen at same time on same host
  on timekey, Computer
  // Limit to only where the file edit happens after the build process starts
  | where BuildProcessTime <= FileEditTime
  | summarize make_set(FileEdited), make_set(FileEditProcess), make_set(FileEditAccount) by timekey, Computer, BuildParentProcess, BuildProcess
  ))
  | 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: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
version: 1.1.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let timeframe = 1d;
let time_window = 5m;
let build_processes = dynamic(["MSBuild.exe", "dotnet.exe", "VBCSCompiler.exe"]);
let allow_list = dynamic([""]);

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 TimeGenerated > ago(timeframe)
| where EventID == 4688
| where Process has_any (build_processes)
| summarize by BuildParentProcess=ParentProcessName, BuildProcess=Process, BuildAccount = Account, Computer, BuildCommand=CommandLine, timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
| join kind=inner(
SecurityEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4663
| where Process !in (allow_list)
| where ObjectName endswith ".cs" or ObjectName endswith ".cpp"
| where AccessMask == "0x6"  or AccessMask == "0x4" or AccessMask == "0X100"
| summarize by FileEditParentProcess=ParentProcessName, FileEditAccount = Account, Computer, FileEdited=ObjectName, FileEditProcess=ProcessName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
on timekey, Computer
| where BuildProcessTime <= FileEditTime
| summarize make_set(FileEdited), make_set(FileEditProcess), make_set(FileEditAccount) by timekey, Computer, BuildParentProcess, BuildProcess

Leg 2: WindowsEvent

WindowsEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4688 and EventData has_any (build_processes)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| where Process has_any (build_processes)
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend CommandLine = tostring(EventData.CommandLine) 
| summarize by BuildParentProcess=ParentProcessName, BuildProcess=Process, BuildAccount = Account, Computer, BuildCommand=CommandLine, timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
| join kind=inner(
WindowsEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4663 and EventData has_any ("0x6", "0x4", "0X100") and EventData has_any (".cs", ".cpp")
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| where Process !in (allow_list)
| extend ObjectName = tostring(EventData.ObjectName)
| where ObjectName endswith ".cs" or ObjectName endswith ".cpp"
| extend AccessMask = tostring(EventData.AccessMask)  
| where AccessMask == "0x6"  or AccessMask == "0x4" or AccessMask == "0X100"
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| extend Account =  strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend ProcessName = tostring(EventData.ProcessName)
| summarize by FileEditParentProcess=ParentProcessName, FileEditAccount = Account, Computer, FileEdited=ObjectName, FileEditProcess=ProcessName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
on timekey, Computer
| where BuildProcessTime <= FileEditTime
| summarize make_set(FileEdited), make_set(FileEditProcess), make_set(FileEditAccount) by timekey, Computer, BuildParentProcess, BuildProcess

Applied to the Combined Result

| 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
AccessMaskeq
  • 0X100
  • 0x4
  • 0x6
field:"AccessMask" kind:eq
EventDatamatch
  • .cpp transforms: term
  • .cs transforms: term
  • 0X100 transforms: term
  • 0x4 transforms: term
  • 0x6 transforms: term
  • MSBuild.exe transforms: term
  • VBCSCompiler.exe transforms: term
  • dotnet.exe transforms: term
field:"EventData" kind:match
EventIDeq
  • 4663
  • 4688 corpus 23 (kusto 23)
field:"EventID" kind:eq
ObjectNameends_with
  • .cpp
  • .cs
field:"ObjectName" kind:ends_with
Processmatch
  • MSBuild.exe transforms: term
  • VBCSCompiler.exe transforms: term
  • dotnet.exe transforms: term
field:"Process" kind:match

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
Processeqallow_listexcludes:Process field:"Process" value:"allow_list"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Computersummarize
FileEditAccountsummarize
FileEditParentProcesssummarize
FileEditProcesssummarize
FileEditTimesummarize
FileEditedsummarize
timekeysummarize

Potentially Relayed NTLM Authentication - Microsoft Sentinel

#
Group by
$left.TargetLogonId, $left.WorkstationName, $right.SubjectLogonId, $right.TargetUserName, Account, Computer, IpAddress, WorkstationName
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

The below query detects NTLM logons where Network Address in the logon event doesn't match the Workstation Name's IP. This indicates potentially relayed NTLM authentication. It analyzes only the logons with domain accounts having admin privileges.

MITRE ATT&CK Coverage

TacticTechniques
Credential AccessNo specific technique

Telemetry Coverage

Rule Body

// Author       : Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Link to original post:
// https://posts.bluraven.io/detecting-ntlm-relay-attacks-d92e99e68fb9
//
// Description: This query detects NTLM logons where Network Address in the NTLM logon event doesn't match the Workstation Name's IP. 
//				      This indicates potentially relayed NTLM authentication. The query analyzes only the logons with domain accounts having admin privileges. 
//
// Query parameters:
//
let ingestion_delay = 2h;
let rule_frequency = 2h;
let lookback = 1d;
// Specify domains in NETBIOS name and full domain format
let domains = dynamic(["PUT YOUR AD DOMAINS HERE!", "contoso","contoso.local"]);
// Exclude authentications coming from  device performing SNAT.
// Exclude devices that always perform NTLM authentication
let SNAT_Subnets = datatable (subnet: string) [
    "1.0.0.0/26", "1.1.1.1/32"
];
// Get NTLM relay candidates
let NTLMRelayCandidates = materialize ( 
    SecurityEvent
    | where TimeGenerated > ago(rule_frequency + ingestion_delay)
    | where EventID == 4624
    | where AccountType == "User"
    | where AuthenticationPackageName == "NTLM"
    | where LogonType == 3
    | where TargetDomainName in~ (domains)
    | where isnotempty(IpAddress) and IpAddress !in ('-', '::1', '127.0.0.1')
    | where isnotempty(WorkstationName) and WorkstationName <> '-'
    | where IpPort <> 0 and Computer !has WorkstationName
    | where ElevatedToken <> '%%1843'// exclude non-admin logon sessions
    | extend delay = ingestion_time() - TimeGenerated
    | summarize hint.strategy=shuffle arg_max(TimeGenerated, *) by Computer, Account, IpAddress, WorkstationName
    // Machine logon events have the IP address of the machine, exclude results where the IPAddress in the NTLM logon matches the IPAddress in Machine logon event
    | join hint.strategy=shuffle kind=leftanti 
        (
        SecurityEvent
        | where TimeGenerated > ago(lookback)
        | where EventID == 4624
        | where AccountType == "Machine"
        | where isnotempty(IpAddress) and IpAddress !in ('-', '::1', '127.0.0.1')
        | distinct TargetUserName, IpAddress
        | extend TargetUserName = toupper(replace(@'([A-z0-9-]+)\.?.*', @'\1', TargetUserName))
        )
        on $left.WorkstationName == $right.TargetUserName, IpAddress // filter condition
        // Filter out excluded IP subnets.
        | evaluate ipv4_lookup(SNAT_Subnets, IpAddress, subnet, return_unmatched = true)
        | where isempty(subnet) // remove results that matched a SNAT subnet.
    )
;
// Windows 2012 doesn't have elevated token info in NTLM logon events.
// Filter relayed authentications where the session has admin privileges
let Computers=
    NTLMRelayCandidates| summarize make_set(Computer);
//
let Accounts = 
    NTLMRelayCandidates| summarize make_set(TargetUserName);
// There must be a 4672 event for an admin logon with the same logon id
NTLMRelayCandidates
| join hint.strategy=shuffle kind=inner 
    (
    SecurityEvent
    | where TimeGenerated > ago(rule_frequency + ingestion_delay) 
    | where Computer in (Computers)
    | where SubjectUserName in (Accounts)
    | where EventID == 4672
    | where AccountType == "User"
    | project Computer, Account, SubjectLogonId, PrivilegeList
    )
    on $left.TargetLogonId==$right.SubjectLogonId, Account, Computer
| extend Origin = WorkstationName, RelayingDeviceIP = IpAddress, Target = Computer
| project-reorder TimeGenerated, Computer, Origin, RelayingDeviceIP, Target, Account, PrivilegeList1
// more filtering can be done based on the privilege list, specific computers or accounts.

Stages and Predicates

Parameters

let ingestion_delay = 2h;
let rule_frequency = 2h;
let lookback = 1d;
let domains = dynamic(["PUT YOUR AD DOMAINS HERE!", "contoso","contoso.local"]);

let NTLMRelayCandidates is inlined into the numbered stages below.

Let Binding: SNAT_Subnets

let SNAT_Subnets = datatable (subnet: string) [
    "1.0.0.0/26", "1.1.1.1/32"
];

Let Binding: Computers used in Stages 2, 20

let Computers = NTLMRelayCandidates| summarize make_set(Computer);

Let Binding: Accounts used in Stages 3, 20

let Accounts = NTLMRelayCandidates| summarize make_set(TargetUserName);

Stages 1 to 19 define let NTLMRelayCandidates as the rule's main pipeline source. Stages 20 to 22 use it.

Stage 1: source

let NTLMRelayCandidates

Stage 2: source

let Computers

Stage 3: source

let Accounts

Stage 4: source

SecurityEvent

Stage 5: where

where TimeGenerated > ago(14400s)

Stage 6: where

where EventID == 4624

Stage 7: where

where AccountType == "User"

Stage 8: where

where AuthenticationPackageName == "NTLM"

Stage 9: where

where LogonType == 3

Stage 10: where

where TargetDomainName in~ ("PUT YOUR AD DOMAINS HERE!", "contoso", "contoso.local")

Stage 11: where

where not (IpAddress in ("-", "127.0.0.1", "::1")) and isnotempty(IpAddress)

Stage 12: where

where isnotempty(WorkstationName) and WorkstationName != "-"

Stage 13: where

where Computer =~ WorkstationName and IpPort != 0

Stage 14: where

where ElevatedToken != "%%1843"

Stage 15: extend

extend delay

Stage 16: summarize

summarize by Computer, Account, IpAddress, WorkstationName

Stage 17: join (negated)

join kind=leftanti (SecurityEvent) on $left.WorkstationName, $right.TargetUserName, IpAddress

Stage 18: evaluate

evaluate

Stage 19: where

where isempty(subnet)

Stage 20: join

NTLMRelayCandidates
| join hint.strategy=shuffle kind=inner 
    (
    SecurityEvent
    | where TimeGenerated > ago(rule_frequency + ingestion_delay) 
    | where Computer in (Computers)
    | where SubjectUserName in (Accounts)
    | where EventID == 4672
    | where AccountType == "User"
    | project Computer, Account, SubjectLogonId, PrivilegeList
    )
    on $left.TargetLogonId==$right.SubjectLogonId, Account, Computer

Stage 21: extend

extend Origin, RelayingDeviceIP, Target

Stage 22: project-reorder

project-reorder

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountsummarize
Computersummarize
IpAddresssummarize
WorkstationNamesummarize
Originextend
RelayingDeviceIPextend
Targetextend

Progress MOVEIt File transfer folder count above threshold

#
Severity
medium
Time window
15m
Group by
Computer, ipaddress, userdisplayname, username
Source
github.com/Azure/Azure-Sentinel

Identifies Progress MOVEIt File Transfers with distinct folder count above certain threshold in a 15min time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Telemetry Coverage

Rule Body

id: 26a993ca-0a96-45a0-8405-05a210fb98f8
name: Progress MOVEIt File transfer folder count above threshold
description: |
  'Identifies Progress MOVEIt File Transfers  with distinct folder count above certain threshold in a 15min time period.
   Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.'
severity: Medium
requiredDataConnectors:
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1020
query: |
  let threshold = 500;
  Event
  | where EventID == 0 and Source == "MOVEit DMZ Audit" and EventLog == "Application"
  | where RenderedDescription has_any ("Downloaded", "Uploaded")
  | parse RenderedDescription with * "User '" userdisplayname "' (" *
  | parse RenderedDescription with * "IPAddress: " ipaddress " FileID:" * " FileName: " filename " FolderID: " folderid " FolderPath: " folderpath " Username: " username " AgentBrand: " *
  | summarize count_distinct_folderpath=dcount(folderpath), dirlist=make_set(folderpath) by Computer,userdisplayname,username,ipaddress,bin(TimeGenerated, 15m)
  | where count_distinct_folderpath >= threshold
  | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField","_", tostring(hash(tostring(dirlist)))))
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: userdisplayname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ipaddress
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Computer
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: DirSample
customDetails:
  TransferCount: count_distinct_folderpath
  FilesList: dirlist
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: Selected
    groupByEntities:
    - Account
    - Host
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 500;

Stage 1: source

Event

Stage 2: where

| where EventID == 0 and Source == "MOVEit DMZ Audit" and EventLog == "Application"

Stage 3: where

| where RenderedDescription has_any ("Downloaded", "Uploaded")

Stage 4: parse

| parse RenderedDescription with * "User '" userdisplayname "' (" *

Stage 5: parse

| parse RenderedDescription with * "IPAddress: " ipaddress " FileID:" * " FileName: " filename " FolderID: " folderid " FolderPath: " folderpath " Username: " username " AgentBrand: " *

Stage 6: summarize

| summarize count_distinct_folderpath=dcount(folderpath), dirlist=make_set(folderpath) by Computer,userdisplayname,username,ipaddress,bin(TimeGenerated, 15m)

Stage 7: where

| where count_distinct_folderpath >= threshold

Stage 8: extend

| extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField","_", tostring(hash(tostring(dirlist)))))
DirSample =
ifdirlist == 1tostring(dirlist[0])
elsestrcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventIDeq
  • 0 corpus 2 (kusto 2)
field:"EventID" kind:eq value:"0"
EventLogeq
  • Application corpus 2 (kusto 2)
field:"EventLog" kind:eq value:"Application"
RenderedDescriptionmatch
  • Downloaded transforms: term corpus 2 (kusto 2)
  • Uploaded transforms: term corpus 2 (kusto 2)
field:"RenderedDescription" kind:match
count_distinct_folderpathge
  • 500 transforms: number
field:"count_distinct_folderpath" kind:ge value:"500"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Computersummarize
count_distinct_folderpathsummarize
dirlistsummarize
ipaddresssummarize
userdisplaynamesummarize
usernamesummarize
DirSampleextend

Radiflow - Policy Violation Detected

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

'Generates an incident when an unauthorized session or action is detected either by Radiflow's iSID.'

MITRE ATT&CK Coverage

Rule Body

id: a3f4cc3e-2403-4570-8d21-1dedd5632958
name: Radiflow - Policy Violation Detected
description: |
  'Generates an incident when an unauthorized session or action is detected either by Radiflow's iSID.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: RadiflowIsid
    dataTypes:
      - RadiflowEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
- ImpairProcessControl
- Execution
- Collection
- Persistence
relevantTechniques:
- T0886
- T0855
- T0858
- T0845
- T0889
- T0843
query: |
  RadiflowEvent
  | where DeviceProduct =~ 'iSID'
  | where EventClassID in (5, 156, 161, 169, 171, 182, 183, 184, 185)
  | extend EventMessage = iif(EventMessage == ' ', 'Policy Violation', EventMessage)
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: Policy Violation Detected
  alertDescriptionFormat: Recent activity which violates the security policy has been detected in the network. Please check the details for additional information.
  alertSeverityColumnName: EventSeverity
  alertDynamicProperties: []
suppressionEnabled: false
suppressionDuration: 5h
customDetails:
  SourceMAC: SourceMACAddress
  SourceType: SourceType
  SourceVendor: SourceVendor
  DestinationMAC: DestinationMACAddress
  DestinationType: DestinationType
  DestinationVendor: DestinationVendor
  Port: Port
  Protocol: Protocol
  SourceVLAN: SourceVLAN
  SourceHostName: SourceHostName
  SourceIP: SourceIP
  DestinationHostName: DestinationHostName
  DestinationIP: DestinationIP
entityMappings:
- entityType: Host
  fieldMappings:
  - identifier: NetBiosName
    columnName: SourceHostName
  - identifier: HostName
    columnName: SourceHostName
- entityType: Host
  fieldMappings:
  - identifier: NetBiosName
    columnName: DestinationHostName
  - identifier: HostName
    columnName: DestinationHostName
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: SourceIP
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: DestinationIP
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

RadiflowEvent

Stage 2: where

| where DeviceProduct =~ 'iSID'

Stage 3: where

| where EventClassID in (5, 156, 161, 169, 171, 182, 183, 184, 185)

Stage 4: extend

| extend EventMessage = iif(EventMessage == ' ', 'Policy Violation', EventMessage)

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EventMessageextend

Shadow Copy Deletions

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

This rule detects when Shadow Copies are being deleted. This is a know actions that is performed by threat actors. This query detects know commands that have been used by the ransomware actors. Some information from Mitre Attack: https://attack.mitre.org/techniques/T1490/

MITRE ATT&CK Coverage

TacticTechniques
Impact

Telemetry Coverage

Rule Body

id: 28c63a44-2d35-48b7-831b-3ed24af17c7e
name: Shadow Copy Deletions
description: |
  This rule detects when Shadow Copies are being deleted. This is a know actions that is performed by threat actors.
  This query detects know commands that have been used by the ransomware actors.
  Some information from Mitre Attack: 
  https://attack.mitre.org/techniques/T1490/
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1490
query: |
  let CommonRansomwareExecutionCommands = dynamic([@'vssadmin.exe delete shadows /all /quiet', 
  @'wmic.exe shadowcopy delete', @'wbadmin delete catalog -quiet', 
  @'Get-WmiObject Win32_Shadowcopy | ForEach-Object {$_.Delete();}',
  @'del /s /f /q c:\*.VHD c:\*.bac c:\*.bak c:\*.wbcat c:\*.bkf c:\Backup*.* c:\backup*.* c:\*.set c:\*.win c:\*.dsk', 
  @'wbadmin delete systemstatebackup -keepVersions:0', 
  @'schtasks.exe /Change /TN "\Microsoft\Windows\SystemRestore\SR" /disable', 
  @'schtasks.exe /Change /TN "\Microsoft\Windows\SystemRestore\SR" /enable >nul 2>&1', 
  @'reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f', 
  @'reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f', 
  @'reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f', 
  @'reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f', 
  @'reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /f >nul 2>&1', 
  @'reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /f >nul 2>&1', 
  @'reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /f >nul 2>&1', 
  @'reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /f >nul 2>&1']);
  DeviceProcessEvents
  | where ProcessCommandLine has_any (CommonRansomwareExecutionCommands)
  | project-reorder TimeGenerated, ProcessCommandLine, DeviceName, AccountName
  | extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
  | extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountUpn
      - identifier: Name
        columnName: AccountName
      - identifier: DnsDomain
        columnName: AccountDomain
  - entityType: Process
    fieldMappings:
      - identifier: ProcessId
        columnName: ProcessId
      - identifier: CommandLine
        columnName: ProcessCommandLine
version: 1.0.0
kind: Scheduled

Stages and Predicates

Let Binding: CommonRansomwareExecutionCommands used in Stage 2

let CommonRansomwareExecutionCommands = dynamic([@'vssadmin.exe delete shadows /all /quiet', 
@'wmic.exe shadowcopy delete', @'wbadmin delete catalog -quiet', 
@'Get-WmiObject Win32_Shadowcopy | ForEach-Object {$_.Delete();}',
@'del /s /f /q c:\*.VHD c:\*.bac c:\*.bak c:\*.wbcat c:\*.bkf c:\Backup*.* c:\backup*.* c:\*.set c:\*.win c:\*.dsk', 
@'wbadmin delete systemstatebackup -keepVersions:0', 
@'schtasks.exe /Change /TN "\Microsoft\Windows\SystemRestore\SR" /disable', 
@'schtasks.exe /Change /TN "\Microsoft\Windows\SystemRestore\SR" /enable >nul 2>&1', 
@'reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f', 
@'reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f', 
@'reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f', 
@'reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f', 
@'reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /f >nul 2>&1', 
@'reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /f >nul 2>&1', 
@'reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /f >nul 2>&1', 
@'reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /f >nul 2>&1']);

Stage 1: source

DeviceProcessEvents

Stage 2: where

| where ProcessCommandLine has_any (CommonRansomwareExecutionCommands)

Stage 3: project-reorder

| project-reorder TimeGenerated, ProcessCommandLine, DeviceName, AccountName

Stage 4: extend

| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 5: extend

| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =
ifDeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))
else""

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ProcessCommandLinematch
  • Get-WmiObject Win32_Shadowcopy | ForEach-Object {$_.Delete();} transforms: term
  • del /s /f /q c:\*.VHD c:\*.bac c:\*.bak c:\*.wbcat c:\*.bkf c:\Backup*.* c:\backup*.* c:\*.set c:\*.win c:\*.dsk transforms: term
  • reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f transforms: term
  • reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f transforms: term
  • reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f transforms: term
  • reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f transforms: term
  • reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /f >nul 2>&1 transforms: term
  • reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /f >nul 2>&1 transforms: term
  • reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /f >nul 2>&1 transforms: term
  • reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /f >nul 2>&1 transforms: term
  • schtasks.exe /Change /TN "\Microsoft\Windows\SystemRestore\SR" /disable transforms: term
  • schtasks.exe /Change /TN "\Microsoft\Windows\SystemRestore\SR" /enable >nul 2>&1 transforms: term
  • vssadmin.exe delete shadows /all /quiet transforms: term
  • wbadmin delete catalog -quiet transforms: term
  • wbadmin delete systemstatebackup -keepVersions:0 transforms: term
  • wmic.exe shadowcopy delete transforms: term
field:"CommandLine" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostNameextend
DnsDomainextend

Silent Enabling of Chrome DevTools Protocol

#
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

CDP-Enable-BOF enables Chrome DevTools Protocol by injecting code into the main browser process that hasn't started with the --remote-debugging-port. This enables access to the current user's cookies and credential data without triggering any EDR detections that often monitors the --remote-debugging-port command line parameter.
The query below detects when a Chrome/Edge process that doesn't have the --remote-debugging-port parameter in its command line creates a listening port (remote debugging port). This behavior is normal when chromedriver/msedgedriver starts Chrome/Edge (used in automation workflows).

MITRE ATT&CK Coverage

TacticTechniques
Credential AccessNo specific technique

References

Telemetry Coverage

Rule Body

// Description: detects when a Chrome/Edge process that doesn't have the `--remote-debugging-port` parameter in its command line creates a listening port (remote debugging port)
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://academy.bluraven.io)
//
// Query Parameters
DeviceNetworkEvents
| where ActionType == "ListeningConnectionCreated"
| where InitiatingProcessFileName in~ ("chrome.exe", "msedge.exe") and InitiatingProcessCommandLine !has "--remote-debugging-port"
| where InitiatingProcessParentFileName !in~ ("chromedriver.exe", "msedgedriver.exe")

Stages and Predicates

Stage 1: source

DeviceNetworkEvents

Stage 2: where

| where ActionType == "ListeningConnectionCreated"

Stage 3: where

| where InitiatingProcessFileName in~ ("chrome.exe", "msedge.exe") and InitiatingProcessCommandLine !has "--remote-debugging-port"

Stage 4: where

| where InitiatingProcessParentFileName !in~ ("chromedriver.exe", "msedgedriver.exe")

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypeeq
  • ListeningConnectionCreated
field:"ActionType" kind:eq value:"ListeningConnectionCreated"
InitiatingProcessFileNamein
  • chrome.exe corpus 13 (splunk 9, elastic 3, kusto 1)
  • msedge.exe corpus 12 (splunk 8, elastic 3, kusto 1)
field:"process_basename" kind:in

Exclusions

The rule actively suppresses these predicates.

SlackAudit - User login after deactivated.

#
Status
available
Severity
medium
Time window
14d
Group by
EntityUserEmail, EntityUserId
Source
github.com/Azure/Azure-Sentinel

'Detects when a Slack user account was deactivated and the same user identity later authenticated again within the detection window, which may indicate account reactivation, unauthorized access, or use of a deactivated account. Analyst triage should review the deactivation time, subsequent login time, EntityUserEmail, and EntityUserId to determine whether the login was expected. This rule uses the SlackAuditAPI connector and SlackAudit_CL data type.'

MITRE ATT&CK Coverage

Rule Body

id: e6e99dcb-4dff-48d2-8012-206ca166b36b
name: SlackAudit - User login after deactivated.
description: |
  'Detects when a Slack user account was deactivated and the same user identity later authenticated again within
  the detection window, which may indicate account reactivation, unauthorized access, or use of a deactivated account. Analyst
  triage should review the deactivation time, subsequent login time, EntityUserEmail, and EntityUserId to determine whether
  the login was expected. This rule uses the SlackAuditAPI connector and SlackAudit_CL data type.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SlackAuditAPI
    dataTypes:
      - SlackAudit_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1078
  - T1078.004
query: |
  let lbperiod_max_d = 14d;
  let lbperiod_min_d = 1d;
  let lb_time_max_h = 24h;
  SlackAudit
  | where TimeGenerated between (ago(lbperiod_max_d) .. (lbperiod_min_d))
  | where Action =~ 'user_deactivated'
  | summarize deactivation_time = max(TimeGenerated) by EntityUserEmail, EntityUserId
  | project deactivation_time, EntityUserEmail, EntityUserId
  | join (SlackAudit
        | where TimeGenerated > ago(lb_time_max_h)
        | where Action =~ 'user_login'
        | summarize new_login_time = max(TimeGenerated) by SrcUserEmail, SrcUserIdentity
        | project new_login_time, SrcUserEmail, EntityUserId = SrcUserIdentity) on EntityUserId
  | where EntityUserEmail == SrcUserEmail
  | where deactivation_time < new_login_time
  | extend AccountCustomEntity = SrcUserEmail
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
alertDetailsOverride:
  alertDisplayNameFormat: 'Slack user login after deactivation: {{AccountCustomEntity}}'
  alertDescriptionFormat: User {{AccountCustomEntity}} logged in after deactivation; review deactivation_time {{deactivation_time}}
    and new_login_time {{new_login_time}}.
customDetails:
  DeactivationTime: deactivation_time
  NewLoginTime: new_login_time
  SlackUserEmail: EntityUserEmail
  SlackUserId: EntityUserId
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let lbperiod_max_d = 14d;
let lbperiod_min_d = 1d;
let lb_time_max_h = 24h;

Stage 1: source

SlackAudit

Stage 2: where

| where TimeGenerated between (ago(lbperiod_max_d) .. (lbperiod_min_d))

Stage 3: where

| where Action =~ 'user_deactivated'

Stage 4: summarize

| summarize deactivation_time = max(TimeGenerated) by EntityUserEmail, EntityUserId

Stage 5: project

| project deactivation_time, EntityUserEmail, EntityUserId

Stage 6: join

| join (SlackAudit
      | where TimeGenerated > ago(lb_time_max_h)
      | where Action =~ 'user_login'
      | summarize new_login_time = max(TimeGenerated) by SrcUserEmail, SrcUserIdentity
      | project new_login_time, SrcUserEmail, EntityUserId = SrcUserIdentity) on EntityUserId

Stage 7: where

where EntityUserEmail =~ SrcUserEmail

Stage 8: where

where deactivation_time < new_login_time

Stage 9: extend

extend AccountCustomEntity

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • user_deactivated
  • user_login
field:"Action" kind:eq

Output Fields

These fields are emitted when the rule matches.

FieldSource
EntityUserEmailproject
EntityUserIdproject
deactivation_timeproject
AccountCustomEntityextend

Snowflake - Multiple failed queries

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

'Detects multiple failed queries in short timeframe.'

MITRE ATT&CK Coverage

Rule Body

id: 5f8a81d9-7d27-4ff5-a0ce-4285ee02c2c8
name: Snowflake - Multiple failed queries
description: |
  'Detects multiple failed queries in short timeframe.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Snowflake
    dataTypes:
      - Snowflake
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1518
  - T1082
query: |
  let threshold = 50;
  Snowflake
  | where isnotempty(QueryExecutionStatus)
  | where QueryExecutionStatus !~ 'SUCCESS'
  | summarize count() by TargetUsername, bin(TimeGenerated, 5m)
  | where count_ > threshold
  | extend AccountCustomEntity = TargetUsername
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 50;

Stage 1: source

Snowflake

Stage 2: where

| where isnotempty(QueryExecutionStatus)

Stage 3: where

| where QueryExecutionStatus !~ 'SUCCESS'

Stage 4: summarize

| summarize count() by TargetUsername, bin(TimeGenerated, 5m)

Stage 5: where

| where count_ > threshold

Stage 6: extend

| extend AccountCustomEntity = TargetUsername

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
TargetUsernamesummarize
AccountCustomEntityextend

TI Map Domain entity to Syslog

#
Severity
medium
Time window
14d
Group by
$left.DomainName, $right.domain, Id, IndicatorId, ObservableValue, domain
Source
github.com/Azure/Azure-Sentinel

Identifies a match in Syslog table from any Domain IOC from TI

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: cd19434e-10f2-4e2f-b3c1-ce6f08ac5357
name: TI Map Domain entity to Syslog
description: |
  Identifies a match in Syslog table from any Domain IOC from TI
severity: Medium
requiredDataConnectors:
  - connectorId: Syslog
    dataTypes:
      - Syslog
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
   let dt_lookBack = 1h;  // Define the time range to look back for syslog data (1 hour)
   let ioc_lookBack = 14d;  // Define the time range to look back for threat intelligence indicators (14 days)
   // Create a list of top-level domains (TLDs) from the threat feed for later validation
   let list_tlds = ThreatIntelIndicators
    | where TimeGenerated > ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
    | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
    | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
    | where IndicatorType == "domain-name"
    | extend DomainName = tolower(ObservableValue)
    | extend parts = split(DomainName, '.')
    | extend tld = parts[(array_length(parts)-1)]
    | summarize count() by tostring(tld)
    | summarize make_list(tld);
   // Fetch the latest active domain indicators from the threat intelligence data within the specified time range
   let Domain_Indicators = ThreatIntelIndicators
   | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
   | where IndicatorType == "domain-name"
   | extend DomainName = tolower(ObservableValue)
   | extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
    | where TimeGenerated >= ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
    | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
    | extend IndicatorId = tostring(split(Id, "--")[2])
    | extend Url = iff(ObservableKey == "url:value", ObservableValue, "");
   // Join the threat intelligence indicators with syslog data on matching domain entities
   Domain_Indicators
   | project-reorder *, IsActive, Tags, TrafficLightProtocolLevel, DomainName, Type
    | join kind=innerunique (
      Syslog
      | where TimeGenerated > ago(dt_lookBack)
      // Extract domain patterns from syslog messages
      | extend domain = extract("(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})",1, tolower(SyslogMessage))
      | where isnotempty(domain)
      | extend parts = split(domain, '.')
      // Split out the top-level domain (TLD)
      | extend tld = parts[(array_length(parts)-1)]
      // Validate parsed domain by checking if the TLD is in the list of TLDs in our threat feed
      | where tld in~ (list_tlds)
      | extend Syslog_TimeGenerated = TimeGenerated
    ) on $left.DomainName==$right.domain
    | where Syslog_TimeGenerated < ValidUntil
    // Retrieve the latest syslog timestamp for each indicator and domain combination
    | summarize Syslog_TimeGenerated = arg_max(Syslog_TimeGenerated, *) by IndicatorId, domain
    // Select the desired columns for the final result set
    | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
    | extend Description = tostring(parse_json(Data).description)
    | project Syslog_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence, SyslogMessage, Computer, ProcessName, domain, HostIP, Type, DomainName, Url
    // Extract the hostname from the Computer field
    | extend HostName = tostring(split(Computer, '.', 0)[0])
    // Extract the DNS domain from the Computer field
    | extend DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
    // Assign the Syslog_TimeGenerated value to the timestamp field
    | extend timestamp = Syslog_TimeGenerated
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: HostIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.4.7
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let Domain_Indicators is inlined into the numbered stages below.

Let Binding: list_tlds used in Stage 12

let list_tlds = ThreatIntelIndicators
 | where TimeGenerated > ago(ioc_lookBack)
 | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
 | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
 | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
 | where IndicatorType == "domain-name"
 | extend DomainName = tolower(ObservableValue)
 | extend parts = split(DomainName, '.')
 | extend tld = parts[(array_length(parts)-1)]
 | summarize count() by tostring(tld)
 | summarize make_list(tld);

Stages 1 to 10 define let Domain_Indicators as the rule's main pipeline source. Stages 11 to 18 use it.

Stage 1: source

ThreatIntelIndicators

Stage 2: extend

| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))

Stage 3: where

| where IndicatorType == "domain-name"

Stage 4: extend

| extend DomainName = tolower(ObservableValue)

Stage 5: extend

| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)

Stage 6: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 7: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue

Stage 8: where

| where IsActive and (ValidUntil > now() or isempty(ValidUntil))

Stage 9: extend

| extend IndicatorId = tostring(split(Id, "--")[2])

Stage 10: extend

| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
Url =
ifObservableKey == "url:value"ObservableValue
else""

Stage 11: project-reorder

Domain_Indicators
| project-reorder *, IsActive, Tags, TrafficLightProtocolLevel, DomainName, Type

Stage 12: join

| join kind=innerunique (
   Syslog
   | where TimeGenerated > ago(dt_lookBack)
   | extend domain = extract("(([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,})",1, tolower(SyslogMessage))
   | where isnotempty(domain)
   | extend parts = split(domain, '.')
   | extend tld = parts[(array_length(parts)-1)]
   | where tld in~ (list_tlds)
   | extend Syslog_TimeGenerated = TimeGenerated
 ) on $left.DomainName==$right.domain

Stage 13: where

where Syslog_TimeGenerated < ValidUntil

Stage 14: summarize

summarize Syslog_TimeGenerated by IndicatorId, domain

Stage 15: extend

extend ActivityGroupNames

Stage 16: extend

extend Description

Stage 17: project

project ActivityGroupNames, Computer, Confidence, Description, DomainName, HostIP, Id, ProcessName, SyslogMessage, Syslog_TimeGenerated, Type, Url, ValidUntil, domain

Stage 18: extend (3 consecutive steps)

extend DnsDomain, HostName, timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
Computerproject
Confidenceproject
Descriptionproject
DomainNameproject
HostIPproject
Idproject
ProcessNameproject
SyslogMessageproject
Syslog_TimeGeneratedproject
Typeproject
Urlproject
ValidUntilproject
domainproject
HostNameextend
DnsDomainextend
timestampextend

Back to all Kusto rules