Detection rules › Kusto

Kusto rules: 21

ApexOne - Attack Discovery Detection

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

'Detects Attack Discovery Detection events.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 7a3193b8-67b7-11ec-90d6-0242ac120003
name: ApexOne - Attack Discovery Detection
description: |
  'Detects Attack Discovery Detection events.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
query: |
  TMApexOneEvent
  | where EventMessage has "Attack Discovery Detection"
  | extend IPCustomEntity = SrcIpAddr, AccountCustomEntity = DstUserName
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

TMApexOneEvent

Stage 2: where

| where EventMessage has "Attack Discovery Detection"

Stage 3: extend

| extend IPCustomEntity = SrcIpAddr, AccountCustomEntity = DstUserName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventMessagematch
  • Attack Discovery Detection transforms: term
field:"EventMessage" kind:match value:"Attack Discovery Detection"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend
IPCustomEntityextend

Azure VM Run Command operation executed during suspicious login window

#
Severity
high
Time window
2d
Group by
Authorization, Caller, CorrelationId, joinkey
Author
Microsoft Security Rearch
Source
github.com/Azure/Azure-Sentinel

Identifies when the Azure Run Command operation is executed by a UserPrincipalName and IP Address that has resulted in a recent user entity behaviour alert.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access
Lateral Movement

Telemetry Coverage

Rule Body

id: 11bda520-a965-4654-9a45-d09f372f71aa
name: Azure VM Run Command operation executed during suspicious login window
description: |
  'Identifies when the Azure Run Command operation is executed by a UserPrincipalName and IP Address that has resulted in a recent user entity behaviour alert.'
severity: High
requiredDataConnectors:
  - connectorId: AzureActivity
    dataTypes:
      - AzureActivity
  - connectorId: BehaviorAnalytics
    dataTypes:
      - BehaviorAnalytics
queryFrequency: 1d
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
  - CredentialAccess
relevantTechniques:
  - T1570
  - T1212
query: |
  AzureActivity
  // Isolate run command actions
  | where OperationNameValue =~ "MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION"
  // Confirm that the operation impacted a virtual machine
  | where Authorization has "virtualMachines"
  // Each runcommand operation consists of three events when successful, Started, Accepted (or Rejected), Successful (or Failed).
  | summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), max(CallerIpAddress), make_list(ActivityStatusValue) by CorrelationId, Authorization, Caller
  // Limit to Run Command executions that Succeeded
  | where list_ActivityStatusValue has_any ("Success", "Succeeded")
  // Extract data from the Authorization field
  | extend Authorization_d = parse_json(Authorization)
  | extend Scope = Authorization_d.scope
  | extend Scope_s = split(Scope, "/")
  | extend Subscription = tostring(Scope_s[2])
  | extend VirtualMachineName = tostring(Scope_s[-1])
  | project StartTime, EndTime, Subscription, VirtualMachineName, CorrelationId, Caller, CallerIpAddress=max_CallerIpAddress
  // Create a join key using  the Caller (UPN)
  | extend joinkey = tolower(Caller)
  // Join the Run Command actions to UEBA data
  | join kind = inner (
      BehaviorAnalytics
      // We are specifically interested in unusual logins
      | where EventSource == "Azure AD" and ActivityInsights.ActionUncommonlyPerformedByUser == "True"
      | project UEBAEventTime=TimeGenerated, UEBAActionType=ActionType, UserPrincipalName, UEBASourceIPLocation=SourceIPLocation, UEBAActivityInsights=ActivityInsights, UEBAUsersInsights=UsersInsights
      | where isnotempty(UserPrincipalName) and isnotempty(UEBASourceIPLocation)
      | extend joinkey = tolower(UserPrincipalName)
  ) on joinkey
  // Create a window around the UEBA event times, check to see if the Run Command action was performed within them
  | extend UEBAWindowStart = UEBAEventTime - 1h, UEBAWindowEnd = UEBAEventTime + 6h
  | where StartTime between (UEBAWindowStart .. UEBAWindowEnd)
  | project StartTime, EndTime, Subscription, VirtualMachineName, Caller, CallerIpAddress, UEBAEventTime, UEBAActionType, UEBASourceIPLocation, UEBAActivityInsights, UEBAUsersInsights
  | extend AccountName = tostring(split(Caller, "@")[0]), AccountUPNSuffix = tostring(split(Caller, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Caller
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: CallerIpAddress
version: 1.0.10
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Rearch
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Platform" ]

Stages and Predicates

Stage 1: source

AzureActivity

Stage 2: where

| where OperationNameValue =~ "MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION"

Stage 3: where

| where Authorization has "virtualMachines"

Stage 4: summarize

| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), max(CallerIpAddress), make_list(ActivityStatusValue) by CorrelationId, Authorization, Caller

Stage 5: where

| where list_ActivityStatusValue has_any ("Success", "Succeeded")

Stage 6: extend (5 consecutive steps)

| extend Authorization_d = parse_json(Authorization)
| extend Scope = Authorization_d.scope
| extend Scope_s = split(Scope, "/")
| extend Subscription = tostring(Scope_s[2])
| extend VirtualMachineName = tostring(Scope_s[-1])

Stage 7: project

| project StartTime, EndTime, Subscription, VirtualMachineName, CorrelationId, Caller, CallerIpAddress=max_CallerIpAddress

Stage 8: extend

| extend joinkey = tolower(Caller)

Stage 9: join

| join kind = inner (
    BehaviorAnalytics
    | where EventSource == "Azure AD" and ActivityInsights.ActionUncommonlyPerformedByUser == "True"
    | project UEBAEventTime=TimeGenerated, UEBAActionType=ActionType, UserPrincipalName, UEBASourceIPLocation=SourceIPLocation, UEBAActivityInsights=ActivityInsights, UEBAUsersInsights=UsersInsights
    | where isnotempty(UserPrincipalName) and isnotempty(UEBASourceIPLocation)
    | extend joinkey = tolower(UserPrincipalName)
) on joinkey

Stage 10: extend

extend UEBAWindowEnd, UEBAWindowStart

Stage 11: where

where StartTime >= "UEBAWindowStart" and StartTime <= "UEBAWindowEnd"

Stage 12: project

project Caller, CallerIpAddress, EndTime, StartTime, Subscription, UEBAActionType, UEBAActivityInsights, UEBAEventTime, UEBASourceIPLocation, UEBAUsersInsights, VirtualMachineName

Stage 13: extend

extend AccountName, AccountUPNSuffix

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Callerproject
CallerIpAddressproject
EndTimeproject
StartTimeproject
Subscriptionproject
UEBAActionTypeproject
UEBAActivityInsightsproject
UEBAEventTimeproject
UEBASourceIPLocationproject
UEBAUsersInsightsproject
VirtualMachineNameproject
AccountNameextend
AccountUPNSuffixextend

Cayosoft Guardian - Cloud Application Security Threats

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

Creates Microsoft Sentinel incidents for cloud application and Microsoft Entra ID service principal security threats reported by Cayosoft Guardian. Incident severity is determined dynamically based on the severity of the detected threat. This rule complements the Core Identity and Infrastructure Threats rule, which covers all other Cayosoft Guardian threat types not included in this rule. Alerts with the same Cayosoft ThreatId are grouped into a single incident within a 1-day lookback window. Each alert includes a link to the Cayosoft Threat Directory for guidance on remediation.

Rule Body

id: e6f6c71c-f1fd-473b-9129-249db5d7462c
name: Cayosoft Guardian - Cloud Application Security Threats
description: |
  Creates Microsoft Sentinel incidents for cloud application and Microsoft Entra ID service principal security threats reported by Cayosoft Guardian. Incident severity is determined dynamically based on the severity of the detected threat.

  This rule complements the Core Identity and Infrastructure Threats rule, which covers all other Cayosoft Guardian threat types not included in this rule.

  Alerts with the same Cayosoft ThreatId are grouped into a single incident within a 1-day lookback window. Each alert includes a link to the Cayosoft Threat Directory for guidance on remediation.
contentId: CayosoftGuardianCloudAppAlerts
status: Available
requiredDataConnectors:
- connectorId: CayosoftGuardianConnector
  dataTypes:
  - CayosoftThreatAlerts_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 1h
suppressionEnabled: false
severity: Medium
tactics: []
relevantTechniques: []
query: |
  // ATTENTION: ruleFrequency must match the "queryFrequency" value in the rule's scheduling settings!
  // ATTENTION: lookbackPeriod must match the "queryPeriod" value in the rule's scheduling settings!
  // If you change one, change both, in every location, or you will reintroduce duplicate alerts.
  // Pattern reference: https://learn.microsoft.com/en-us/azure/sentinel/ingestion-delay
  let ruleFrequency = 1h;
  let lookbackPeriod = 1d;
  let window_end = bin(now(), ruleFrequency);      
  let window_start = window_end - ruleFrequency;   
  CayosoftThreatAlerts_CL
  | where TimeGenerated >= ago(lookbackPeriod)
  | where ingestion_time() > window_start and ingestion_time() <= window_end
  | extend ParsedCategories = todynamic(Categories)
  | extend TargetObjectTypeNormalized = tostring(TargetObjectType)
  | extend TargetLocationNormalized = tostring(TargetLocation)
  | mv-apply Cat = ParsedCategories on (
      summarize Values = make_list(tostring(Cat.value)) by Key = iff(tostring(Cat.name) startswith 'MITRE ATT', 'MITRE ATT&CK', tostring(Cat.name))
  | summarize CleanCategories = make_bag(pack(Key, strcat_array(Values, ', ')))
      )
  | extend Tactics = iff(isnotempty(CleanCategories['Attack tactics']), split(replace_string(tostring(CleanCategories['Attack tactics']), ' ', ''), ','), dynamic([]))
  | extend Techniques = iff(isnotempty(CleanCategories['MITRE ATT&CK']), extract_all(@"(T\d{4})", tostring(CleanCategories['MITRE ATT&CK'])), dynamic([]))
  | extend ExtractedDomain = tostring(split(TargetLocationNormalized, '/')[0])
  | extend IsEntra = TargetObjectTypeNormalized startswith 'microsoft.graph'
  | extend IsAD = TargetObjectTypeNormalized startswith 'microsoft.ad' or TargetObjectTypeNormalized has 'ServiceAccount'
  | extend IsGMSA = TargetObjectTypeNormalized contains 'ManagedServiceAccount'
  | extend ParsedUser = case(
      TargetObjectTypeNormalized has 'user' or TargetObjectTypeNormalized has 'ServiceAccount' or TargetObjectTypeNormalized has 'inetOrgPerson', TargetObjectName,
      extract("member\\s+'([^']+)'", 1, Subject)
      )
  | extend TargetAccountAadUserId = iff(IsEntra and TargetObjectTypeNormalized has 'user', TargetObjectId, dynamic(null))
  | extend TargetAccountName = case(
      IsEntra and TargetObjectTypeNormalized has 'user', iff(ParsedUser contains '@', tostring(split(ParsedUser, '@')[0]), ParsedUser),
      IsAD and (TargetObjectTypeNormalized has 'user' or TargetObjectTypeNormalized has 'foreignSecurityPrincipal' or IsGMSA), TargetObjectName,
      IsAD and TargetObjectTypeNormalized has 'directoryObject', TargetObjectName,
      isnotempty(ParsedUser), iff(ParsedUser contains '@', tostring(split(ParsedUser, '@')[0]), ParsedUser),
      dynamic(null)
      )
  | extend TargetAccountObjectGuid = iff(IsAD and (TargetObjectTypeNormalized has 'user' or TargetObjectTypeNormalized has 'foreignSecurityPrincipal' or IsGMSA), TargetObjectId, dynamic(null))
  | extend TargetHostName = iff(TargetObjectTypeNormalized has 'computer' or TargetObjectTypeNormalized has 'device', TargetObjectName, dynamic(null))
  | extend TargetHostDomain = iff(IsAD and TargetObjectTypeNormalized has 'computer', ExtractedDomain, dynamic(null))
  | extend TargetHostAzureID = iff(IsEntra and TargetObjectTypeNormalized has 'device', TargetObjectId, dynamic(null))
  | extend RawPath = iff(TargetLocationNormalized contains '/', substring(TargetLocationNormalized, indexof(TargetLocationNormalized, '/') + 1), '')
  // Convert domain
  | extend DomainDN = strcat('DC=', replace_string(ExtractedDomain, '.', ',DC='))
  // Smart path processing
  | extend PathDN = case(
      isempty(RawPath), '',
      RawPath has_any ('Builtin', 'Users', 'Computers', 'ForeignSecurityPrincipals', 'Managed Service Accounts'), strcat('CN=', replace_string(RawPath, '/', ',CN=')),
      strcat('OU=', replace_string(RawPath, '/', ',OU='))
  )
  // Assemble the final DistinguishedName and Name for the group
  | extend TargetGroupName = iff(TargetObjectTypeNormalized has 'group', TargetObjectName, dynamic(null))
  | extend TargetGroupDistinguishedName = iff(
      TargetObjectTypeNormalized has 'group', 
      strcat('CN=', TargetObjectName, iff(isnotempty(PathDN), strcat(',', PathDN), ''), ',', DomainDN), 
      dynamic(null)
  )
  | extend TargetGroupObjectGuid = iff(TargetObjectTypeNormalized has 'group', TargetObjectId, dynamic(null))
  | extend TargetDNSName = case(
      IsAD and TargetObjectTypeNormalized has 'trustedDomain', TargetObjectName,
      isnotempty(TargetLocationNormalized), ExtractedDomain,
      dynamic(null)
      )
  | extend ThreatDocUrl = tolower( strcat('https://www.cayosoft.com/threat-directory/', ThreatId, '/'))
  | extend AlertSeverity = case(
      Severity == 'high', 'High',
      Severity == 'medium', 'Medium',
      Severity == 'low', 'Low',
      Severity == 'informational', 'Informational',
      'Informational'
      )
  | project-away Categories, ParsedCategories
  | project-rename Categories = CleanCategories 
  | where IsEntra and (TargetObjectType has 'application' or TargetObjectType has 'servicePrincipal')
  | extend TargetCloudAppName = TargetObjectName
  | extend TargetCloudAppId = TargetObjectId
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: Selected
    groupByCustomDetails:
    - ThreatId
alertDetailsOverride:
  alertDisplayNameFormat: "{{Subject}}"
  alertDescriptionFormat: "Details: {{ThreatDocUrl}}"
  alertTacticsColumnName: Tactics
  alertSeverityColumnName: AlertSeverity
  alertDynamicProperties:
  - alertProperty: AlertLink
    value: ThreatDocUrl
  - alertProperty: Techniques
    value: Techniques
customDetails:
  ThreatId: ThreatId
  Categories: Categories
  Evidence: Evidence
  Where: TargetLocation
  TargetName: TargetObjectName
  TargetType: TargetObjectType
  CreatedDateTime: CreatedDateTime
  RemediationComplex: RemediationComplexity
  ThreatDocUrl: ThreatDocUrl
entityMappings:
- entityType: CloudApplication
  fieldMappings:
  - identifier: Name
    columnName: TargetCloudAppName
- entityType: DNS
  fieldMappings:
  - identifier: DomainName
    columnName: TargetDNSName
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let ruleFrequency = 1h;
let lookbackPeriod = 1d;
let window_end = bin(now(), ruleFrequency);
let window_start = window_end - ruleFrequency;

Stage 1: source

CayosoftThreatAlerts_CL

Stage 2: where

| where TimeGenerated >= ago(lookbackPeriod)

Stage 3: where

| where ingestion_time() > window_start and ingestion_time() <= window_end

Stage 4: extend (3 consecutive steps)

| extend ParsedCategories = todynamic(Categories)
| extend TargetObjectTypeNormalized = tostring(TargetObjectType)
| extend TargetLocationNormalized = tostring(TargetLocation)

Stage 5: kusto:mv-apply

| mv-apply Cat = ParsedCategories on (
    summarize Values = make_list(tostring(Cat.value)) by Key = iff(tostring(Cat.name) startswith 'MITRE ATT', 'MITRE ATT&CK', tostring(Cat.name))
| summarize CleanCategories = make_bag(pack(Key, strcat_array(Values, ', ')))
    )

Stage 6: extend (22 consecutive steps)

| extend Tactics = iff(isnotempty(CleanCategories['Attack tactics']), split(replace_string(tostring(CleanCategories['Attack tactics']), ' ', ''), ','), dynamic([]))
| extend Techniques = iff(isnotempty(CleanCategories['MITRE ATT&CK']), extract_all(@"(T\d{4})", tostring(CleanCategories['MITRE ATT&CK'])), dynamic([]))
| extend ExtractedDomain = tostring(split(TargetLocationNormalized, '/')[0])
| extend IsEntra = TargetObjectTypeNormalized startswith 'microsoft.graph'
| extend IsAD = TargetObjectTypeNormalized startswith 'microsoft.ad' or TargetObjectTypeNormalized has 'ServiceAccount'
| extend IsGMSA = TargetObjectTypeNormalized contains 'ManagedServiceAccount'
| extend ParsedUser = case(
    TargetObjectTypeNormalized has 'user' or TargetObjectTypeNormalized has 'ServiceAccount' or TargetObjectTypeNormalized has 'inetOrgPerson', TargetObjectName,
    extract("member\\s+'([^']+)'", 1, Subject)
    )
| extend TargetAccountAadUserId = iff(IsEntra and TargetObjectTypeNormalized has 'user', TargetObjectId, dynamic(null))
| extend TargetAccountName = case(
    IsEntra and TargetObjectTypeNormalized has 'user', iff(ParsedUser contains '@', tostring(split(ParsedUser, '@')[0]), ParsedUser),
    IsAD and (TargetObjectTypeNormalized has 'user' or TargetObjectTypeNormalized has 'foreignSecurityPrincipal' or IsGMSA), TargetObjectName,
    IsAD and TargetObjectTypeNormalized has 'directoryObject', TargetObjectName,
    isnotempty(ParsedUser), iff(ParsedUser contains '@', tostring(split(ParsedUser, '@')[0]), ParsedUser),
    dynamic(null)
    )
| extend TargetAccountObjectGuid = iff(IsAD and (TargetObjectTypeNormalized has 'user' or TargetObjectTypeNormalized has 'foreignSecurityPrincipal' or IsGMSA), TargetObjectId, dynamic(null))
| extend TargetHostName = iff(TargetObjectTypeNormalized has 'computer' or TargetObjectTypeNormalized has 'device', TargetObjectName, dynamic(null))
| extend TargetHostDomain = iff(IsAD and TargetObjectTypeNormalized has 'computer', ExtractedDomain, dynamic(null))
| extend TargetHostAzureID = iff(IsEntra and TargetObjectTypeNormalized has 'device', TargetObjectId, dynamic(null))
| extend RawPath = iff(TargetLocationNormalized contains '/', substring(TargetLocationNormalized, indexof(TargetLocationNormalized, '/') + 1), '')
| extend DomainDN = strcat('DC=', replace_string(ExtractedDomain, '.', ',DC='))
| extend PathDN = case(
    isempty(RawPath), '',
    RawPath has_any ('Builtin', 'Users', 'Computers', 'ForeignSecurityPrincipals', 'Managed Service Accounts'), strcat('CN=', replace_string(RawPath, '/', ',CN=')),
    strcat('OU=', replace_string(RawPath, '/', ',OU='))
)
| extend TargetGroupName = iff(TargetObjectTypeNormalized has 'group', TargetObjectName, dynamic(null))
| extend TargetGroupDistinguishedName = iff(
    TargetObjectTypeNormalized has 'group', 
    strcat('CN=', TargetObjectName, iff(isnotempty(PathDN), strcat(',', PathDN), ''), ',', DomainDN), 
    dynamic(null)
)
| extend TargetGroupObjectGuid = iff(TargetObjectTypeNormalized has 'group', TargetObjectId, dynamic(null))
| extend TargetDNSName = case(
    IsAD and TargetObjectTypeNormalized has 'trustedDomain', TargetObjectName,
    isnotempty(TargetLocationNormalized), ExtractedDomain,
    dynamic(null)
    )
| extend ThreatDocUrl = tolower( strcat('https://www.cayosoft.com/threat-directory/', ThreatId, '/'))
| extend AlertSeverity = case(
    Severity == 'high', 'High',
    Severity == 'medium', 'Medium',
    Severity == 'low', 'Low',
    Severity == 'informational', 'Informational',
    'Informational'
    )
Tactics =
ifisnotempty(CleanCategories["Attack tactics"])split(replace_string(tostring(CleanCategories["Attack tactics"]), ' ', ''), ',')
elsedynamic([])

Stage 7: project-away

| project-away Categories, ParsedCategories

Stage 8: project-rename

| project-rename Categories = CleanCategories

Stage 9: where

| where IsEntra and (TargetObjectType has 'application' or TargetObjectType has 'servicePrincipal')

Stage 10: extend

| extend TargetCloudAppName = TargetObjectName

Stage 11: extend

| extend TargetCloudAppId = TargetObjectId

Stage 12: summarize aggregation inside the mv-apply branch

summarize by Key

Stage 13: summarize aggregation inside the mv-apply branch

summarize

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
IsEntraeq
  • true transforms: boolean
field:"IsEntra" kind:eq value:"true"
TargetObjectTypematch
  • application transforms: term
  • servicePrincipal transforms: term
field:"TargetObjectType" kind:match

Cortex XDR Incident - High

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

'A new incident was created in the Cortex XDR portal with a severity "High". Click on the events for incident details. '

Rule Body

id: 06263265-ff65-43ff-8b15-6ac82325a672
name: Cortex XDR Incident - High
description: |
  'A new incident was created in the Cortex XDR portal with a severity "High". Click on the events for incident details. '
severity: High
requiredDataConnectors:
  - connectorId: CortexXDRDataConnector
    dataTypes:
      - PaloAltoCortexXDR_Incidents_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics: []
relevantTechniques: []
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride: null
customDetails: null
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Users_s
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: XdrUrl
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hosts_s
sentinelEntitiesMappings: null
query: |
  PaloAltoCortexXDR_Incidents
  | where Severity contains "high"
  | where Status !contains "resolved_auto"
  // the below line filters for incidents that has at least one user or one host in it. Comment the below line if you want to generated incidents that have 0 user or 0 host as well
  | where HostCount > 0 or UserCount > 0
  | extend Hosts_s = tostring(Hosts), Users_s = tostring(Users)
  | project TimeGenerated, IncidentId, Status, Severity, IncidentSources, Description, XdrUrl, Hosts_s, Users_s, AlertCategories, HostCount, UserCount, AlertCount, CreationTime, ModificationTime
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

PaloAltoCortexXDR_Incidents

Stage 2: where

| where Severity contains "high"

Stage 3: where

| where Status !contains "resolved_auto"

Stage 4: where

| where HostCount > 0 or UserCount > 0

Stage 5: extend

| extend Hosts_s = tostring(Hosts), Users_s = tostring(Users)

Stage 6: project

| project TimeGenerated, IncidentId, Status, Severity, IncidentSources, Description, XdrUrl, Hosts_s, Users_s, AlertCategories, HostCount, UserCount, AlertCount, CreationTime, ModificationTime

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
Statuscontainsresolved_autoexcludes:Status field:"Status" value:"resolved_auto"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertCategoriesproject
AlertCountproject
CreationTimeproject
Descriptionproject
HostCountproject
Hosts_sproject
IncidentIdproject
IncidentSourcesproject
ModificationTimeproject
Severityproject
Statusproject
TimeGeneratedproject
UserCountproject
Users_sproject
XdrUrlproject

Credential Dumping Tools - Service Installation

#
Status
available
Severity
high
Time window
1h
Group by
AccountName, Computer, EventID, ImagePath, ServiceName
Source
github.com/Azure/Azure-Sentinel

This query detects the installation of a Windows service that contains artifacts from credential dumping tools such as Mimikatz.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: 4ebbb5c2-8802-11ec-a8a3-0242ac120002
name: Credential Dumping Tools - Service Installation
description: |
   'This query detects the installation of a Windows service that contains artifacts from credential dumping tools such as Mimikatz.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - Event
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - Event
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1003.001
query: |
  // Enter a reference list of decoy users (usernames) "Case Sensitive"
  let MaliciousServiceArtifacts = dynamic (["fgexec","cachedump","mimikatz","mimidrv","wceservice","pwdump"]);
  Event
  | where Source == "Service Control Manager" and EventID == 7045
  | parse EventData with * 'ServiceName">' ServiceName "<" * 'ImagePath">' ImagePath "<" *
  | where ServiceName has_any (MaliciousServiceArtifacts) or ImagePath has_any (MaliciousServiceArtifacts)
  | parse EventData with * 'AccountName">' AccountName "<" *
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventID, Computer, ServiceName, ImagePath, AccountName
  | extend HostName = split(Computer, '.', 0)[0], DnsDomain = strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')
entityMappings:
- entityType: Account
  fieldMappings:
    - identifier: Name
      columnName: AccountName
- entityType: File
  fieldMappings:
    - identifier: Name
      columnName: ImagePath
- entityType: Host
  fieldMappings:
    - identifier: HostName
      columnName: HostName
    - identifier: DnsDomain
      columnName: DnsDomain
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let MaliciousServiceArtifacts = dynamic (["fgexec","cachedump","mimikatz","mimidrv","wceservice","pwdump"]);

Stage 1: source

Event

Stage 2: where

| where Source == "Service Control Manager" and EventID == 7045

Stage 3: parse

| parse EventData with * 'ServiceName">' ServiceName "<" * 'ImagePath">' ImagePath "<" *

Stage 4: where

| where ServiceName has_any (MaliciousServiceArtifacts) or ImagePath has_any (MaliciousServiceArtifacts)

Stage 5: parse

| parse EventData with * 'AccountName">' AccountName "<" *

Stage 6: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventID, Computer, ServiceName, ImagePath, AccountName

Stage 7: extend

| extend HostName = split(Computer, '.', 0)[0], DnsDomain = strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventIDeq
  • 7045 corpus 2 (chronicle 1, kusto 1)
field:"EventID" kind:eq value:"7045"
ImagePathmatch
  • cachedump transforms: term
  • fgexec transforms: term
  • mimidrv transforms: term
  • mimikatz transforms: term
  • pwdump transforms: term
  • wceservice transforms: term
field:"ImagePath" kind:match
ServiceNamematch
  • cachedump transforms: term
  • fgexec transforms: term
  • mimidrv transforms: term
  • mimikatz transforms: term
  • pwdump transforms: term
  • wceservice transforms: term
field:"ServiceName" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNamesummarize
Computersummarize
EndTimesummarize
EventIDsummarize
ImagePathsummarize
ServiceNamesummarize
StartTimesummarize
DnsDomainextend
HostNameextend

CybleVision Alerts Stealer Logs

#

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 credential theft and information-stealer malware logs. Extracts stolen credentials, URLs, device info, IPs, domains, and metadata using the Alerts_StealerLogs parser.'

MITRE ATT&CK Coverage

Rule Body

id: e0bf55c2-35ef-47ab-8846-5087618ae805
name: CybleVision Alerts Stealer Logs
description: |
  'Detects credential theft and information-stealer malware logs. Extracts stolen credentials, URLs, device info, IPs, domains, and metadata using the Alerts_StealerLogs parser.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
  dataTypes:
  - CybleVisionAlerts_CL
enabled: true
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: PT5H
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
- CredentialAccess
- Collection
- Exfiltration
- Reconnaissance
- InitialAccess
relevantTechniques:
- T1555
- T1005
- T1041
- T1589
- T1189
subTechniques: []
query: |
  Alerts_stealer_logs 
  | where Service == "stealer_logs" 
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
alertDetailsOverride:
  alertDisplayNameFormat: CybleVision Stealer Log Credential Exposure {{SL_MainUsername}}
  alertDescriptionFormat: |
    Stolen credentials detected in stealer logs. Malware Family {{SL_MalwareFamily}} URL {{SL_MainURL}} IP {{SL_IP}}
  alertDynamicProperties: []
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  MalwareFamily: SL_MalwareFamily
  CompromisedDate: SL_CompromiseDate
  App: SL_App
  ContentURL: SL_URL_Content
  ContentUsername: SL_Username_Content
  Password: SL_Password
  HWID: SL_HWID
  IP: SL_IP
  Domain: SL_Domain
  Country: SL_Country
  FileName: SL_FileName
  FileSize: SL_FileSize
  FileCreated: SL_FileCreated
  FileModified: SL_FileModified
  Tags: SL_Tags
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: Name
    columnName: SL_MainUsername
- entityType: URL
  fieldMappings:
  - identifier: Url
    columnName: SL_MainURL
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: SL_IP
- entityType: DNS
  fieldMappings:
  - identifier: DomainName
    columnName: SL_Domain
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_stealer_logs

Stage 2: where

| where Service == "stealer_logs"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

CYFIRMA - Attack Surface - Malicious Domain/IP Reputation Medium Rule

#

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

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

"This alert is raised when CYFIRMA detects a critical reputation score for an IP address linked to your infrastructure. The IP has been previously associated with hacking activity and web application attacks. Denied outbound traffic to a foreign country from a known Microsoft data center IP suggests potential misuse or compromise of cloud infrastructure."

MITRE ATT&CK Coverage

Rule Body

id: 70f137e4-e4ef-4635-92de-10c4f5b0fcd0
name: CYFIRMA - Attack Surface - Malicious Domain/IP Reputation Medium Rule
description: |
  "This alert is raised when CYFIRMA detects a critical reputation score for an IP address linked to your infrastructure. 
  The IP has been previously associated with hacking activity and web application attacks. 
  Denied outbound traffic to a foreign country from a known Microsoft data center IP suggests potential misuse or compromise of cloud infrastructure."
version: 1.0.1
kind: Scheduled
severity: Medium
requiredDataConnectors:
  - connectorId: CyfirmaAttackSurfaceAlertsConnector
    dataTypes:
      - CyfirmaASDomainIPReputationAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
  - CommandAndControl
  - Reconnaissance
  - Impact
  - DefenseEvasion
  - Exfiltration
relevantTechniques:
  - T1566
  - T1071
  - T1090
  - T1595
  - T1499
  - T1036
  - T1041
query: |
  // Medium Severity - Malicious Domain/IP Reputation Detected
  let timeFrame = 5m;
  CyfirmaASDomainIPReputationAlerts_CL
  | where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
  | extend
      FirstSeen=first_seen,
      LastSeen=last_seen,
      RiskScore=risk_score,
      Domain=sub_domain,
      TopDomain=top_domain,
      NetworkIP=ip,
      AlertUID=alert_uid,
      UID=uid,
      Categories=categories,
      IPversion=ip_version,
      ISP=isp,
      ThreatActors=threat_actors,
      Country=country,
      LastUsersReported=last_users_reported,
      ProviderName='CYFIRMA',
      ProductName='DeCYFIR/DeTCT'
  | project
      TimeGenerated,
      Domain,
      TopDomain,
      RiskScore,
      FirstSeen,
      LastSeen,
      NetworkIP,
      AlertUID,
      UID,
      Categories,
      IPversion,
      ISP,
      ThreatActors,
      Country,
      LastUsersReported,
      ProviderName,
      ProductName
entityMappings:
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: TopDomain
      - identifier: DnsDomain
        columnName: Domain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: NetworkIP
customDetails:
  TimeGenerated: TimeGenerated
  RiskScore: RiskScore
  FirstSeen: FirstSeen
  LastSeen: LastSeen
  LastUsersReported: LastUsersReported
  AlertUID: AlertUID
  UID: UID
  Categories: Categories
  IPversion: IPversion
  ThreatActors: ThreatActors
  Country: Country
  ISP: ISP
alertDetailsOverride:
  alertDisplayNameFormat: "CYFIRMA - Medium Severity Malicious Domain/IP Reputation Alert - Domain: {{Domain}}, IP: {{NetworkIP}} "
  alertDescriptionFormat: "CYFIRMA - Medium Severity Malicious Domain/IP Reputation Alert - Domain: {{Domain}}, IP: {{NetworkIP}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Parameters

let timeFrame = 5m;

Stage 1: source

CyfirmaASDomainIPReputationAlerts_CL

Stage 2: where

| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())

Stage 3: extend

| extend
    FirstSeen=first_seen,
    LastSeen=last_seen,
    RiskScore=risk_score,
    Domain=sub_domain,
    TopDomain=top_domain,
    NetworkIP=ip,
    AlertUID=alert_uid,
    UID=uid,
    Categories=categories,
    IPversion=ip_version,
    ISP=isp,
    ThreatActors=threat_actors,
    Country=country,
    LastUsersReported=last_users_reported,
    ProviderName='CYFIRMA',
    ProductName='DeCYFIR/DeTCT'

Stage 4: project

| project
    TimeGenerated,
    Domain,
    TopDomain,
    RiskScore,
    FirstSeen,
    LastSeen,
    NetworkIP,
    AlertUID,
    UID,
    Categories,
    IPversion,
    ISP,
    ThreatActors,
    Country,
    LastUsersReported,
    ProviderName,
    ProductName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertUIDproject
Categoriesproject
Countryproject
Domainproject
FirstSeenproject
IPversionproject
ISPproject
LastSeenproject
LastUsersReportedproject
NetworkIPproject
ProductNameproject
ProviderNameproject
RiskScoreproject
ThreatActorsproject
TimeGeneratedproject
TopDomainproject
UIDproject

CYFIRMA - Data Breach and Web Monitoring - Dark Web High Rule

#

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

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

"Detects critical alerts from CYFIRMA related to sensitive data or credentials leaked on dark web forums. These events often indicate unauthorized access or compromise of enterprise systems, cloud environments, or identity platforms. Immediate investigation is required to assess breach scope and initiate mitigation, including credential resets, access reviews, and threat actor tracking."

MITRE ATT&CK Coverage

Rule Body

id: c3f1f55b-7e54-4416-8afc-7d7876b29b0f
name: CYFIRMA - Data Breach and Web Monitoring - Dark Web High Rule
description: |
  "Detects critical alerts from CYFIRMA related to sensitive data or credentials leaked on dark web forums. 
  These events often indicate unauthorized access or compromise of enterprise systems, cloud environments, or identity platforms. 
  Immediate investigation is required to assess breach scope and initiate mitigation, including credential resets, access reviews, and threat actor tracking."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
  - connectorId: CyfirmaDigitalRiskAlertsConnector
    dataTypes:
      - CyfirmaDBWMDarkWebAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
  - Collection
  - Exfiltration
  - Impact
relevantTechniques:
  - T1552.001
  - T1555.003
  - T1212
  - T1119
  - T1048
  - T1486
query: |
  // High severity - Data Breach and Web Monitoring - Dark Web
  let timeFrame = 5m;
  CyfirmaDBWMDarkWebAlerts_CL
  | where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
  | extend
      Description=description,
      FirstSeen=first_seen,
      LastSeen=last_seen,
      RiskScore=risk_score,
      AlertUID=alert_uid,
      UID=uid,
      AssetType=asset_type,
      AssetValue=signature,
      Impact=impact,
      Recommendation='',
      ProviderName='CYFIRMA',
      ProductName='DeCYFIR/DeTCT',
      AlertTitle=Alert_title
  | project
      TimeGenerated,
      Description,
      RiskScore,
      FirstSeen,
      LastSeen,
      AlertUID,
      UID,
      AssetType,
      AssetValue,
      Impact,
      ProductName,
      ProviderName,
      AlertTitle
customDetails:
  AssetType: AssetType
  AssetValue: AssetValue
  RiskScore: RiskScore
  Impact: Impact
  Description: Description
  FirstSeen: FirstSeen
  LastSeen: LastSeen
  AlertUID: AlertUID
  UID: UID
  TimeGenerated: TimeGenerated
alertDetailsOverride:
  alertDisplayNameFormat: "CYFIRMA - High Severity Alert: Critical Data Exposure of Enterprise SSO on Dark Web - {{AlertTitle}} "
  alertDescriptionFormat: "{{Description}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Parameters

let timeFrame = 5m;

Stage 1: source

CyfirmaDBWMDarkWebAlerts_CL

Stage 2: where

| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())

Stage 3: extend

| extend
    Description=description,
    FirstSeen=first_seen,
    LastSeen=last_seen,
    RiskScore=risk_score,
    AlertUID=alert_uid,
    UID=uid,
    AssetType=asset_type,
    AssetValue=signature,
    Impact=impact,
    Recommendation='',
    ProviderName='CYFIRMA',
    ProductName='DeCYFIR/DeTCT',
    AlertTitle=Alert_title

Stage 4: project

| project
    TimeGenerated,
    Description,
    RiskScore,
    FirstSeen,
    LastSeen,
    AlertUID,
    UID,
    AssetType,
    AssetValue,
    Impact,
    ProductName,
    ProviderName,
    AlertTitle

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertTitleproject
AlertUIDproject
AssetTypeproject
AssetValueproject
Descriptionproject
FirstSeenproject
Impactproject
LastSeenproject
ProductNameproject
ProviderNameproject
RiskScoreproject
TimeGeneratedproject
UIDproject

Failed AzureAD logons but success logon to AWS Console

#
Severity
medium
Time window
1d
Group by
AWSRegion, AccountName, AccountUPNSuffix, EventTypeName, LoginResult, MFAUsed, Reason, RecipientAccountId, SourceIpAddress, UserAgent, UserIdentityType
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Identifies a list of IP addresses with a minimum number (defualt of 5) of failed logon attempts to Microsoft Entra ID. Uses that list to identify any successful AWS Console logons from these IPs within the same timeframe.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Credential Access

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 643c2025-9604-47c5-833f-7b4b9378a1f5
name: Failed AzureAD logons but success logon to AWS Console
description: |
  'Identifies a list of IP addresses with a minimum number (defualt of 5) of failed logon attempts to Microsoft Entra ID.
  Uses that list to identify any successful AWS Console logons from these IPs within the same timeframe.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
     - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - CredentialAccess
relevantTechniques:
  - T1078
  - T1110
query: |
  //Adjust this threshold to fit your environment
  let signin_threshold = 5;
  //Make a list of IPs with AAD signin failures above our threshold
  let aadFunc = (tableName:string){
  let Suspicious_signins =
  table(tableName)
  | where ResultType !in ("0", "50125", "50140")
  | where IPAddress !in ("127.0.0.1", "::1")
  | summarize count() by IPAddress
  | where count_ >  signin_threshold
  | summarize make_set(IPAddress);
  Suspicious_signins
  };
  let aadSignin = aadFunc("SigninLogs");
  let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
  let Suspicious_signins =
  union isfuzzy=true aadSignin, aadNonInt
  | summarize make_set(set_IPAddress);
  //See if any of those IPs have sucessfully logged into the AWS console
  AWSCloudTrail
  | where EventName =~ "ConsoleLogin"
  | extend LoginResult = tostring(parse_json(ResponseElements).ConsoleLogin)
  | where LoginResult =~ "Success"
  | where SourceIpAddress in (Suspicious_signins)
  | extend Reason = "Multiple failed AAD logins from IP address"
  | extend MFAUsed = tostring(parse_json(AdditionalEventData).MFAUsed)
  | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
  | extend UserName = tostring(split(UserIdentityArn, '/')[-1])
  | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
  | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
    AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Reason, LoginResult, EventTypeName, UserIdentityType, RecipientAccountId, AccountName, AccountUPNSuffix, AWSRegion, SourceIpAddress, UserAgent, MFAUsed
  | extend timestamp = StartTime
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
version: 1.0.5
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Parameters

let signin_threshold = 5;
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");

Let Binding: aadFunc

let aadFunc = (tableName:string){
let Suspicious_signins =
table(tableName)
| where ResultType !in ("0", "50125", "50140")
| where IPAddress !in ("127.0.0.1", "::1")
| summarize count() by IPAddress
| where count_ >  signin_threshold
| summarize make_set(IPAddress);

Union isfuzzy=true (2 Sources)

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

Leg 1: aadSignin

Leg 2: aadNonInt

Applied to the Combined Result

| summarize make_set(set_IPAddress) | where EventName =~ "ConsoleLogin" | extend LoginResult = tostring(parse_json(ResponseElements).ConsoleLogin) | where LoginResult =~ "Success" | where SourceIpAddress in (Suspicious_signins) | extend Reason = "Multiple failed AAD logins from IP address" | extend MFAUsed = tostring(parse_json(AdditionalEventData).MFAUsed) | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn) | extend UserName = tostring(split(UserIdentityArn, '/')[-1]) | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName) | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
  AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "") | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Reason, LoginResult, EventTypeName, UserIdentityType, RecipientAccountId, AccountName, AccountUPNSuffix, AWSRegion, SourceIpAddress, UserAgent, MFAUsed | extend timestamp = StartTime

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AWSRegionsummarize
AccountNamesummarize
AccountUPNSuffixsummarize
EndTimesummarize
EventTypeNamesummarize
LoginResultsummarize
MFAUsedsummarize
Reasonsummarize
RecipientAccountIdsummarize
SourceIpAddresssummarize
StartTimesummarize
UserAgentsummarize
UserIdentityTypesummarize
timestampextend

File Server Settings Updated

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

Detects when file server settings are updated in Veeam Backup & Replication. This might indicate configuration changes that require review.

Rule Body

id: 62008da4-627e-47fe-b78a-62e1e23b3eb9
name: File Server Settings Updated
description: Detects when file server settings are updated in Veeam Backup & Replication.
  This might indicate configuration changes that require review.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 28940\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    UserName = user,\n  MessageDetails = Description,\n\
  \    Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 28940

Stage 3: project

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

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
UserNameproject

GCP IAM - Disable Data Access Logging

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

'Detects when Data Access Logging is disabled.'

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 2530a631-9605-404d-ae58-58ef1f91b17c
name: GCP IAM - Disable Data Access Logging
description: |
  'Detects when Data Access Logging is disabled.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: GCPIAMDataConnector
    dataTypes:
      - GCP_IAM
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
query: |
  GCP_IAM
  | where PayloadMethodname == 'SetIamPolicy'
  | extend s = parse_json(todynamic(PayloadRequestPolicyAuditconfigs))[0]['auditLogConfigs']
  | where isempty(s)
  | extend AccountName = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[0]), AccountUPNSuffix = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: PayloadAuthenticationinfoPrincipalemail
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

GCP_IAM

Stage 2: where

| where PayloadMethodname == 'SetIamPolicy'

Stage 3: extend

| extend s = parse_json(todynamic(PayloadRequestPolicyAuditconfigs))[0]['auditLogConfigs']

Stage 4: where

| where isempty(s)

Stage 5: extend

| extend AccountName = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[0]), AccountUPNSuffix = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[1])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
PayloadMethodnameeq
  • SetIamPolicy
field:"gcp::method_name" kind:eq value:"SetIamPolicy"
sis_null
  • (no value, null check)
field:"s" kind:is_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
sextend
AccountNameextend
AccountUPNSuffixextend

GCP IAM - Empty user agent

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

'Detects requests where user agent is empty.'

MITRE ATT&CK Coverage

TacticTechniques
Lateral Movement

Rule Body

id: 9e0d8632-d33d-4075-979e-c972674f77b3
name: GCP IAM - Empty user agent
description: |
  'Detects requests where user agent is empty.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: GCPIAMDataConnector
    dataTypes:
      - GCP_IAM
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1550
query: |
  GCP_IAM
  | where isempty(HttpUserAgentOriginal)
  | extend AccountName = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[0]), AccountUPNSuffix = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: PayloadAuthenticationinfoPrincipalemail
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

GCP_IAM

Stage 2: where

| where isempty(HttpUserAgentOriginal)

Stage 3: extend

| extend AccountName = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[0]), AccountUPNSuffix = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[1])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
HttpUserAgentOriginalis_null
  • (no value, null check)
field:"HttpUserAgentOriginal" kind:is_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameextend
AccountUPNSuffixextend

GreyNoise TI Map IP Entity to CommonSecurityLog

#
Severity
medium
Time window
14d
Group by
$left.TI_ipEntity, $right.CS_ipEntity, CS_ipEntity, IndicatorId
Source
github.com/Azure/Azure-Sentinel

This query maps GreyNoise IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in CommonSecurityLog.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: e50657d7-8bca-43ff-a647-d407fae440d6
name: GreyNoise TI Map IP Entity to CommonSecurityLog
description: |
  This query maps GreyNoise IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in CommonSecurityLog.
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: CEF
    dataTypes:
      - CommonSecurityLog
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
  - connectorId: GreyNoise2SentinelAPI
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 4h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let IPRegex = '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}';
  let dt_lookBack = 1h; // Look back 1 hour for CommonSecurityLog events
  let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
  // Fetch threat intelligence indicators related to IP addresses
  let IP_Indicators = ThreatIntelligenceIndicator
    | where TimeGenerated >= ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where Active == true and ExpirationDateTime > now()
    | where SourceSystem == 'GreyNoise'
    | where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
    | extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
    | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
    | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
    | where ipv4_is_private(TI_ipEntity) == false and  TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127.";
  // Perform a join between IP indicators and CommonSecurityLog events
  IP_Indicators
    // Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation
    | join kind=innerunique (
        CommonSecurityLog
        | where TimeGenerated >= ago(dt_lookBack)
        | extend MessageIP = extract(IPRegex, 0, Message)
        | extend CS_ipEntity = iff(isnotempty(SourceIP), SourceIP, DestinationIP)
        | extend CS_ipEntity = iff(isempty(CS_ipEntity) and isnotempty(MessageIP), MessageIP, CS_ipEntity)
        | extend CommonSecurityLog_TimeGenerated = TimeGenerated
    )
    on $left.TI_ipEntity == $right.CS_ipEntity
    // Filter out logs that occurred after the expiration of the corresponding indicator
    | where CommonSecurityLog_TimeGenerated < ExpirationDateTime
    // Group the results by IndicatorId and CS_ipEntity, and keep the log entry with the latest timestamp
    | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, CS_ipEntity
    // Select the desired output fields
    | project timestamp = CommonSecurityLog_TimeGenerated, SourceIP, DestinationIP, MessageIP, Message, DeviceVendor, DeviceProduct, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, TI_ipEntity, CS_ipEntity, LogSeverity, DeviceAction, Type
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: CS_ipEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let IPRegex = '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}';
let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let IP_Indicators is inlined into the numbered stages below.

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

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 4: where

| where Active == true and ExpirationDateTime > now()

Stage 5: where

| where SourceSystem == 'GreyNoise'

Stage 6: where

| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)

Stage 7: extend (3 consecutive steps)

| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
TI_ipEntity =
ifisnotempty(NetworkIP)NetworkIP
elseNetworkDestinationIP

Stage 8: where

| where ipv4_is_private(TI_ipEntity) == false and  TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."

Stage 9: join

IP_Indicators
| join kind=innerunique (
      CommonSecurityLog
      | where TimeGenerated >= ago(dt_lookBack)
      | extend MessageIP = extract(IPRegex, 0, Message)
      | extend CS_ipEntity = iff(isnotempty(SourceIP), SourceIP, DestinationIP)
      | extend CS_ipEntity = iff(isempty(CS_ipEntity) and isnotempty(MessageIP), MessageIP, CS_ipEntity)
      | extend CommonSecurityLog_TimeGenerated = TimeGenerated
  )
  on $left.TI_ipEntity == $right.CS_ipEntity

Stage 10: where

where CommonSecurityLog_TimeGenerated < ExpirationDateTime

Stage 11: summarize

summarize CommonSecurityLog_TimeGenerated by IndicatorId, CS_ipEntity

Stage 12: project

project CS_ipEntity, ConfidenceScore, DestinationIP, DeviceAction, DeviceProduct, DeviceVendor, ExpirationDateTime, IndicatorId, LogSeverity, Message, MessageIP, SourceIP, TI_ipEntity, ThreatType, Type, timestamp

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
TI_ipEntitycidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:TI_ipEntity
TI_ipEntitystarts_with127.excludes:TI_ipEntity field:"TI_ipEntity" value:"127."
TI_ipEntitystarts_with::excludes:TI_ipEntity field:"TI_ipEntity" value:"::"
TI_ipEntitystarts_withfe80excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80"

Output Fields

These fields are emitted when the rule matches.

FieldSource
CS_ipEntityproject
ConfidenceScoreproject
DestinationIPproject
DeviceActionproject
DeviceProductproject
DeviceVendorproject
ExpirationDateTimeproject
IndicatorIdproject
LogSeverityproject
Messageproject
MessageIPproject
SourceIPproject
TI_ipEntityproject
ThreatTypeproject
Typeproject
timestampproject

High count of failed attempts from same client IP

#
Severity
medium
Time window
1m
Group by
Computer, cIP, sIP
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

'Identifies when 20 or more failed attempts from a given client IP in 1 minute occur on the IIS server. This could be indicative of an attempted brute force. This could also simply indicate a misconfigured service or device. Recommendations: Validate that these are expected connections from the given Client IP. If the client IP is not recognized, potentially block these connections at the edge device. If these are expected connections, verify the credentials are properly configured on the system, service, application or device that is associated with the client IP. References: IIS status code mapping: https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0 Win32 Status code mapping: https://msdn.microsoft.com/library/cc231199.aspx'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 19e01883-15d8-4eb6-a7a5-3276cd668388
name: High count of failed attempts from same client IP
description: |
  'Identifies when 20 or more failed attempts from a given client IP in 1 minute occur on the IIS server.
  This could be indicative of an attempted brute force. This could also simply indicate a misconfigured service or device.
  Recommendations: Validate that these are expected connections from the given Client IP.  If the client IP is not recognized, potentially block these connections at the edge device.
  If these are expected connections, verify the credentials are properly configured on the system, service, application or device that is associated with the client IP.
  References:
  IIS status code mapping: https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0
  Win32 Status code mapping: https://msdn.microsoft.com/library/cc231199.aspx'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureMonitor(IIS)
    dataTypes:
      - W3CIISLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let timeBin = 1m;
  let failedThreshold = 20;
  W3CIISLog
  | where scStatus in ("401","403")
  | where csUserName != "-"
  | extend scStatusFull = strcat(scStatus, ".",scSubStatus)
  // Map common IIS codes
  | extend scStatusFull_Friendly = case(
  scStatusFull == "401.0", "Access denied.",
  scStatusFull == "401.1", "Logon failed.",
  scStatusFull == "401.2", "Logon failed due to server configuration.",
  scStatusFull == "401.3", "Unauthorized due to ACL on resource.",
  scStatusFull == "401.4", "Authorization failed by filter.",
  scStatusFull == "401.5", "Authorization failed by ISAPI/CGI application.",
  scStatusFull == "403.0", "Forbidden.",
  scStatusFull == "403.4", "SSL required.",
  "See - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0")
  // Mapping to Hex so can be mapped using website in comments above
  | extend scWin32Status_Hex = tohex(tolong(scWin32Status))
  // Map common win32 codes
  | extend scWin32Status_Friendly = case(
  scWin32Status_Hex =~ "775", "The referenced account is currently locked out and cannot be logged on to.",
  scWin32Status_Hex =~ "52e", "Logon failure: Unknown user name or bad password.",
  scWin32Status_Hex =~ "532", "Logon failure: The specified account password has expired.",
  scWin32Status_Hex =~ "533", "Logon failure: Account currently disabled.",
  scWin32Status_Hex =~ "2ee2", "The request has timed out.",
  scWin32Status_Hex =~ "0", "The operation completed successfully.",
  scWin32Status_Hex =~ "1", "Incorrect function.",
  scWin32Status_Hex =~ "2", "The system cannot find the file specified.",
  scWin32Status_Hex =~ "3", "The system cannot find the path specified.",
  scWin32Status_Hex =~ "4", "The system cannot open the file.",
  scWin32Status_Hex =~ "5", "Access is denied.",
  scWin32Status_Hex =~ "8009030e", "SEC_E_NO_CREDENTIALS",
  scWin32Status_Hex =~ "8009030C", "SEC_E_LOGON_DENIED",
  "See - https://msdn.microsoft.com/library/cc231199.aspx")
  // decode URI when available
  | extend decodedUriQuery = url_decode(csUriQuery)
  // Count of failed attempts from same client IP
  | summarize makeset(decodedUriQuery), makeset(csUserName), makeset(sSiteName), makeset(sPort), makeset(csUserAgent), makeset(csMethod), makeset(csUriQuery), makeset(scStatusFull), makeset(scStatusFull_Friendly), makeset(scWin32Status_Hex), makeset(scWin32Status_Friendly), FailedConnectionsCount = count() by bin(TimeGenerated, timeBin), cIP, Computer, sIP
  | where FailedConnectionsCount >= failedThreshold
  | project TimeGenerated, cIP, set_csUserName, set_decodedUriQuery, Computer, set_sSiteName, sIP, set_sPort, set_csUserAgent, set_csMethod, set_scStatusFull, set_scStatusFull_Friendly, set_scWin32Status_Hex, set_scWin32Status_Friendly, FailedConnectionsCount
  | order by FailedConnectionsCount
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: cIP
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Network", "Identity" ]

Stages and Predicates

Parameters

let timeBin = 1m;
let failedThreshold = 20;

Stage 1: source

W3CIISLog

Stage 2: where

| where scStatus in ("401","403")

Stage 3: where

| where csUserName != "-"

Stage 4: extend (5 consecutive steps)

| extend scStatusFull = strcat(scStatus, ".",scSubStatus)
| extend scStatusFull_Friendly = case(
scStatusFull == "401.0", "Access denied.",
scStatusFull == "401.1", "Logon failed.",
scStatusFull == "401.2", "Logon failed due to server configuration.",
scStatusFull == "401.3", "Unauthorized due to ACL on resource.",
scStatusFull == "401.4", "Authorization failed by filter.",
scStatusFull == "401.5", "Authorization failed by ISAPI/CGI application.",
scStatusFull == "403.0", "Forbidden.",
scStatusFull == "403.4", "SSL required.",
"See - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0")
| extend scWin32Status_Hex = tohex(tolong(scWin32Status))
| extend scWin32Status_Friendly = case(
scWin32Status_Hex =~ "775", "The referenced account is currently locked out and cannot be logged on to.",
scWin32Status_Hex =~ "52e", "Logon failure: Unknown user name or bad password.",
scWin32Status_Hex =~ "532", "Logon failure: The specified account password has expired.",
scWin32Status_Hex =~ "533", "Logon failure: Account currently disabled.",
scWin32Status_Hex =~ "2ee2", "The request has timed out.",
scWin32Status_Hex =~ "0", "The operation completed successfully.",
scWin32Status_Hex =~ "1", "Incorrect function.",
scWin32Status_Hex =~ "2", "The system cannot find the file specified.",
scWin32Status_Hex =~ "3", "The system cannot find the path specified.",
scWin32Status_Hex =~ "4", "The system cannot open the file.",
scWin32Status_Hex =~ "5", "Access is denied.",
scWin32Status_Hex =~ "8009030e", "SEC_E_NO_CREDENTIALS",
scWin32Status_Hex =~ "8009030C", "SEC_E_LOGON_DENIED",
"See - https://msdn.microsoft.com/library/cc231199.aspx")
| extend decodedUriQuery = url_decode(csUriQuery)

Stage 5: summarize

| summarize makeset(decodedUriQuery), makeset(csUserName), makeset(sSiteName), makeset(sPort), makeset(csUserAgent), makeset(csMethod), makeset(csUriQuery), makeset(scStatusFull), makeset(scStatusFull_Friendly), makeset(scWin32Status_Hex), makeset(scWin32Status_Friendly), FailedConnectionsCount = count() by bin(TimeGenerated, timeBin), cIP, Computer, sIP

Stage 6: where

| where FailedConnectionsCount >= failedThreshold

Stage 7: project

| project TimeGenerated, cIP, set_csUserName, set_decodedUriQuery, Computer, set_sSiteName, sIP, set_sPort, set_csUserAgent, set_csMethod, set_scStatusFull, set_scStatusFull_Friendly, set_scWin32Status_Hex, set_scWin32Status_Friendly, FailedConnectionsCount

Stage 8: sort

| order by FailedConnectionsCount

Stage 9: extend

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))

Stage 10: extend

| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
HostNameDomain =
ifDomainIndex != -1substring(Computer, (DomainIndex + 1))
elseComputer

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Computerproject
FailedConnectionsCountproject
TimeGeneratedproject
cIPproject
sIPproject
set_csMethodproject
set_csUserAgentproject
set_csUserNameproject
set_decodedUriQueryproject
set_sPortproject
set_sSiteNameproject
set_scStatusFullproject
set_scStatusFull_Friendlyproject
set_scWin32Status_Friendlyproject
set_scWin32Status_Hexproject
DomainIndexextend
HostNameextend
HostNameDomainextend

Hunt for Defender for Identity not installed but eligible

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

This query shows you which servers are eligible for Defender for identity but does not have the Defender for Identity agent installed. The query seach the eligible servers via Defender for Endpoint (requirement for this query to work), and is based on the server roles that MDE recongnizes.

Telemetry Coverage

Rule Body

let device_roles = dynamic(["EntraConnectServer", "AzureADConnectServer", "ActiveDirectoryCertificateServicesServer", "DomainController", "ADFS"]);
let mdi_servers = (
    DeviceTvmSoftwareInventory
    | where SoftwareName == "azure_advanced_threat_protection_sensor"
    | distinct MdiDeviceName=tolower(DeviceName)
);
let mdi_eligible_servers = (
    ExposureGraphNodes
    | extend DeviceRoles= parse_json(NodeProperties)["rawData"]["deviceRole"]
    | extend CriticalityRuleNames = parse_json(NodeProperties)["rawData"]["criticalityLevel"]["ruleNames"]
    | where DeviceRoles has_any (device_roles) or
        CriticalityRuleNames has_any (device_roles)
    | distinct NodeName=tolower(NodeName), tostring(DeviceRoles), tostring(CriticalityRuleNames)
);
mdi_servers
| join kind=rightouter mdi_eligible_servers on $left.MdiDeviceName == $right.NodeName
| extend Issue = iff(isempty(MdiDeviceName), "This server is eligible for MDI but does not have MDI installed", "None")
| where Issue != "None"

Stages and Predicates

Parameters

let device_roles = dynamic(["EntraConnectServer", "AzureADConnectServer", "ActiveDirectoryCertificateServicesServer", "DomainController", "ADFS"]);

let mdi_servers is inlined into the numbered stages below.

Let Binding: mdi_eligible_servers used in Stages 2, 6

let mdi_eligible_servers = (
    ExposureGraphNodes
    | extend DeviceRoles= parse_json(NodeProperties)["rawData"]["deviceRole"]
    | extend CriticalityRuleNames = parse_json(NodeProperties)["rawData"]["criticalityLevel"]["ruleNames"]
    | where DeviceRoles has_any (device_roles) or
        CriticalityRuleNames has_any (device_roles)
    | distinct NodeName=tolower(NodeName), tostring(DeviceRoles), tostring(CriticalityRuleNames)
);

Stage 1: source

let mdi_servers

Stage 2: source

let mdi_eligible_servers

Stage 3: source

DeviceTvmSoftwareInventory

Stage 4: where

where SoftwareName == "azure_advanced_threat_protection_sensor"

Stage 5: distinct

distinct

Stage 6: join

join kind=rightouter (mdi_eligible_servers) on $left.MdiDeviceName, $right.NodeName

Stage 7: extend

extend Issue
Issue =
ifisempty(MdiDeviceName)"This server is eligible for MDI but does not have MDI installed"
else"None"

Stage 8: where

where Issue != "None"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CriticalityRuleNamesmatch
  • ADFS transforms: term
  • ActiveDirectoryCertificateServicesServer transforms: term
  • AzureADConnectServer transforms: term
  • DomainController transforms: term
  • EntraConnectServer transforms: term
field:"CriticalityRuleNames" kind:match
DeviceRolesmatch
  • ADFS transforms: term
  • ActiveDirectoryCertificateServicesServer transforms: term
  • AzureADConnectServer transforms: term
  • DomainController transforms: term
  • EntraConnectServer transforms: term
field:"DeviceRoles" kind:match
Issuene
  • None
field:"Issue" kind:ne value:"None"
SoftwareNameeq
  • azure_advanced_threat_protection_sensor
field:"SoftwareName" kind:eq value:"azure_advanced_threat_protection_sensor"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Issueextend

Infoblox - TI - Syslog Match Found - URL

#
Status
available
Severity
medium
Time window
14d
Group by
$left.DomainName, $right.Url, IndicatorId
Source
github.com/Azure/Azure-Sentinel

'Syslog URL match found in your Infoblox TIDE Threat Intelligence. Customize query count, scheduling, responses and more. Modify data sources, types and threat properties as desired.'

MITRE ATT&CK Coverage

Rule Body

id: 28ee3c2b-eb4b-44de-a71e-e462843fea72
name: Infoblox - TI - Syslog Match Found - URL
description: |
  'Syslog URL match found in your Infoblox TIDE Threat Intelligence. Customize query count, scheduling, responses and more. Modify data sources, types and threat properties as desired.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Syslog
    dataTypes: 
      - Syslog
  - connectorId: ThreatIntelligence
    dataTypes: 
      - ThreatIntelligenceIndicator
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1498
  - T1565
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let TI = ThreatIntelligenceIndicator
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now()  
  | where Description has_cs "Infoblox - URL"
  | where isnotempty(DomainName)
  ;
  let Data = Syslog
  | extend HitTime = TimeGenerated
  | where TimeGenerated >= ago(dt_lookBack)
  //Extract URL patterns from syslog message
  | extend Url = extract("(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)", 1,SyslogMessage)
  | where isnotempty(Url)
  ;
  TI | join kind=innerunique Data on $left.DomainName == $right.Url
  | where HitTime >= TimeGenerated and HitTime < ExpirationDateTime
  | project LatestIndicatorTime, HitTime, SyslogMessage, Computer, ProcessName, Url, HostIP, 
  AdditionalInformation, Description, ThreatType, TrafficLightProtocolLevel, Type, ConfidenceScore, ExpirationDateTime, SourceSystem, Action, IndicatorId, ExternalIndicatorId, Tags
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: HostIP
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Computer
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Url
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

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

let TI is inlined into the numbered stages below.

Let Binding: Data used in Stage 7

let Data = Syslog
| extend HitTime = TimeGenerated
| where TimeGenerated >= ago(dt_lookBack)
| extend Url = extract("(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)", 1,SyslogMessage)
| where isnotempty(Url);

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

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 4: where

| where Active == true and ExpirationDateTime > now()

Stage 5: where

| where Description has_cs "Infoblox - URL"

Stage 6: where

| where isnotempty(DomainName)

Stage 7: join

TI
| join kind=innerunique Data on $left.DomainName == $right.Url

Stage 8: where

where HitTime >= TimeGenerated and HitTime < ExpirationDateTime

Stage 9: project

project Action, AdditionalInformation, Computer, ConfidenceScore, Description, ExpirationDateTime, ExternalIndicatorId, HitTime, HostIP, IndicatorId, LatestIndicatorTime, ProcessName, SourceSystem, SyslogMessage, Tags, ThreatType, TrafficLightProtocolLevel, Type, Url

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Activeeq
  • true
field:"Active" kind:eq value:"true"
Descriptionmatch
  • Infoblox - URL transforms: term
field:"Description" kind:match value:"Infoblox - URL"
DomainNameis_not_null
  • (no value, null check)
field:"DomainName" kind:is_not_null
Urlis_not_null
  • (no value, null check)
field:"Url" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionproject
AdditionalInformationproject
Computerproject
ConfidenceScoreproject
Descriptionproject
ExpirationDateTimeproject
ExternalIndicatorIdproject
HitTimeproject
HostIPproject
IndicatorIdproject
LatestIndicatorTimeproject
ProcessNameproject
SourceSystemproject
SyslogMessageproject
Tagsproject
ThreatTypeproject
TrafficLightProtocolLevelproject
Typeproject
Urlproject

McAfee ePO - Spam Email detected

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

Detects when email was marked as spam.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

id: ffc9052b-3658-4ad4-9003-0151515fde15
name: McAfee ePO - Spam Email detected
description: |
  'Detects when email was marked as spam.'
severity: Medium
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - InitialAccess
relevantTechniques:
  - T1566
query: |
  McAfeeEPOEvent
  | where EventId == '4650'
  | extend IPCustomEntity = DvcIpAddr, HostCustomEntity = DvcHostname
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: HostCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

McAfeeEPOEvent

Stage 2: where

| where EventId == '4650'

Stage 3: extend

| extend IPCustomEntity = DvcIpAddr, HostCustomEntity = DvcHostname

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostCustomEntityextend
IPCustomEntityextend

OracleDBAudit - Shutdown Server

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

'Detects when "SHUTDOWN" command was sent to server.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 27cc2cdc-ba67-4906-a6ef-ecbc9c284f4e
name: OracleDBAudit - Shutdown Server
description: |
  'Detects when "SHUTDOWN" command was sent to server.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1529
query: |
  OracleDatabaseAuditEvent
  | where isnotempty(SrcIpAddr)
  | where DbAction =~ 'SHUTDOWN'
  | project SrcIpAddr, DstUserName
  | extend AccountCustomEntity = DstUserName
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

OracleDatabaseAuditEvent

Stage 2: where

| where isnotempty(SrcIpAddr)

Stage 3: where

| where DbAction =~ 'SHUTDOWN'

Stage 4: project

| project SrcIpAddr, DstUserName

Stage 5: extend

| extend AccountCustomEntity = DstUserName

Stage 6: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DbActioneq
  • SHUTDOWN
field:"DbAction" kind:eq value:"SHUTDOWN"
SrcIpAddris_not_null
  • (no value, null check)
field:"src_ip" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstUserNameproject
SrcIpAddrproject
AccountCustomEntityextend
IPCustomEntityextend

Possible contact with a domain generated by a DGA

#
Severity
medium
Time window
6h
Group by
DGADomain, DataSource, DestinationIP, Name, SourceIP
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

'Identifies contacts with domains names in CommonSecurityLog that might have been generated by a Domain Generation Algorithm (DGA). DGAs can be used by malware to generate rendezvous points that are difficult to predict in advance. This detection uses the Alexa Top 1 million domain names to build a model of what normal domains look like. It uses this to identify domains that may have been randomly generated by an algorithm. The triThreshold is set to 500 - increase this to report on domains that are less likely to have been randomly generated, decrease it for more likely. The start time and end time look back over 6 hours of data and the dgaLengthThreshold is set to 8 - meaning domains whose length is 8 or more are reported. NOTE - The top1M csv zip file used in the query is dynamic and may produce different results over various time periods. It's important to cross-check the events against the entities involved in the incident.'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 4acd3a04-2fad-4efc-8a4b-51476594cec4
name: Possible contact with a domain generated by a DGA
description: |
  'Identifies contacts with domains names in CommonSecurityLog that might have been generated by a Domain Generation Algorithm (DGA). DGAs can be used by malware to generate rendezvous points that are difficult to predict in advance.
  This detection uses the Alexa Top 1 million domain names to build a model of what normal domains look like. It uses this to identify domains that may have been randomly generated by an algorithm.
  The triThreshold is set to 500 - increase this to report on domains that are less likely to have been randomly generated, decrease it for more likely.
  The start time and end time look back over 6 hours of data and the dgaLengthThreshold is set to 8 - meaning domains whose length is 8 or more are reported.
  NOTE - The top1M csv zip file used in the query is dynamic and may produce different results over various time periods. It's important to cross-check the events against the entities involved in the incident.'
severity: Medium
requiredDataConnectors:
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog
  - connectorId: Barracuda
    dataTypes:
      - CommonSecurityLog
  - connectorId: CEF
    dataTypes:
      - CommonSecurityLog
  - connectorId: CheckPoint
    dataTypes:
      - CommonSecurityLog
  - connectorId: CiscoASA
    dataTypes:
      - CommonSecurityLog
  - connectorId: F5
    dataTypes:
      - CommonSecurityLog
  - connectorId: Fortinet
    dataTypes:
      - CommonSecurityLog
  - connectorId: PaloAltoNetworks
    dataTypes:
      - CommonSecurityLog
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1568
query: |
    let triThreshold = 500;
    let startTime = 6h;
    let dgaLengthThreshold = 8;
    // fetch the alexa top 1M domains
    let top1M =  (externaldata (Position:int, Domain:string)   [@"http://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip"]  with (format="csv", zipPattern="*.csv"));
    // extract tri grams that are above our threshold - i.e. are common
    let triBaseline =   top1M
    | extend Domain = tolower(extract("([^.]*).{0,7}$", 1, Domain))
    | extend AllTriGrams = array_concat(extract_all("(...)", Domain), extract_all("(...)", substring(Domain, 1)), extract_all("(...)", substring(Domain, 2)))
    | mvexpand Trigram=AllTriGrams
    | summarize triCount=count() by tostring(Trigram)
    | sort by triCount desc
    | where triCount > triThreshold
    | distinct Trigram;
    // collect domain information from common security log, filter and extract the DGA candidate and its trigrams
    let allDataSummarized =   CommonSecurityLog
    | where TimeGenerated > ago(startTime)
    | where isnotempty(DestinationHostName)
    | extend Name = tolower(DestinationHostName)
    | distinct Name
    | where Name has "."
    | where Name !endswith ".home" and Name !endswith ".lan"
    // extract DGA candidate
    | extend DGADomain = extract("([^.]*).{0,7}$", 1, Name)
    | where strlen(DGADomain) > dgaLengthThreshold
    // throw out domains with number in them
    | where DGADomain matches regex "^[A-Za-z]{0,}$"
    // extract the tri grams from summarized data
    | extend AllTriGrams = array_concat(extract_all("(...)", DGADomain), extract_all("(...)", substring(DGADomain, 1)), extract_all("(...)", substring(DGADomain, 2)));
    // throw out domains that have repeating tri's and/or >=3 repeating letters
    let nonRepeatingTris =  allDataSummarized
    | join kind=leftanti
    (
        allDataSummarized
        | mvexpand AllTriGrams
        | summarize count() by tostring(AllTriGrams), DGADomain
        | where count_ > 1
        | distinct DGADomain
    )
    on DGADomain;
    // find domains that do not have a common tri in the baseline
    let dataWithRareTris =  nonRepeatingTris
    | join kind=leftanti
    (
        nonRepeatingTris
        | mvexpand AllTriGrams
        | extend Trigram = tostring(AllTriGrams)
        | distinct Trigram, DGADomain
        | join kind=inner
        (
            triBaseline
        )
        on Trigram
        | distinct DGADomain
    )
    on DGADomain;
    dataWithRareTris
    // join DGAs back on connection data
    | join kind=inner
    (
        CommonSecurityLog
        | where TimeGenerated > ago(startTime)
        | where isnotempty(DestinationHostName)
        | extend DestinationHostName = tolower(DestinationHostName)
        | project-rename Name=DestinationHostName, DataSource=DeviceVendor
        | summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated) by Name, SourceIP, DestinationIP, DataSource
    )
    on Name
    | project StartTime, EndTime, Name, DGADomain, SourceIP, DestinationIP, DataSource
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Name
version: 1.0.6
kind: Scheduled
metadata:
    source: 
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let triThreshold = 500;
let startTime = 6h;
let dgaLengthThreshold = 8;

let allDataSummarized, let nonRepeatingTris and let dataWithRareTris are inlined into the numbered stages below.

Let Binding: top1M

let top1M = (externaldata (Position:int, Domain:string)   [@"http://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip"]  with (format="csv", zipPattern="*.csv"));

Let Binding: triBaseline

let triBaseline = top1M
| extend Domain = tolower(extract("([^.]*).{0,7}$", 1, Domain))
| extend AllTriGrams = array_concat(extract_all("(...)", Domain), extract_all("(...)", substring(Domain, 1)), extract_all("(...)", substring(Domain, 2)))
| mvexpand Trigram=AllTriGrams
| summarize triCount=count() by tostring(Trigram)
| sort by triCount desc
| where triCount > triThreshold
| distinct Trigram;

Stages 1 to 13 define let dataWithRareTris as the rule's main pipeline source. Stages 14 to 19 use it.

Stage 1: source

CommonSecurityLog

Stage 2: where

where TimeGenerated > ago(21600s)

Stage 3: where

where isnotempty(DestinationHostName)

Stage 4: extend

extend Name

Stage 5: distinct

distinct Name

Stage 6: where

where Name has "."

Stage 7: where

where not (Name endswith ".home") and not (Name endswith ".lan")

Stage 8: extend

extend DGADomain

Stage 9: where

where DGADomain > 8

Stage 10: where

where DGADomain matches regex "^[A-Za-z]{0,}$"

Stage 11: extend

extend AllTriGrams

Stage 12: join (negated)

join kind=leftanti (allDataSummarized) on DGADomain

Stage 13: join (negated)

join kind=leftanti (nonRepeatingTris) on DGADomain

Stage 14: join

dataWithRareTris
| join kind=inner
(
    CommonSecurityLog
    | where TimeGenerated > ago(startTime)
    | where isnotempty(DestinationHostName)
    | extend DestinationHostName = tolower(DestinationHostName)
    | project-rename Name=DestinationHostName, DataSource=DeviceVendor
    | summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated) by Name, SourceIP, DestinationIP, DataSource
)
on Name

Stage 15: project

project DGADomain, DataSource, DestinationIP, EndTime, Name, SourceIP, StartTime

Stage 16: summarize aggregation inside the join branch

summarize by DGADomain

Stage 17: summarize aggregation inside the join branch

summarize by DGADomain

Stage 18: summarize aggregation inside the join branch

summarize

Stage 19: summarize aggregation inside the join branch

summarize by Name, SourceIP, DestinationIP, DataSource

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
DataSourcesummarize
DestinationIPsummarize
Namesummarize
SourceIPsummarize

Potential Fodhelper UAC Bypass

#
Status
available
Severity
medium
Time window
2h
Group by
Computer, TimeKey
Source
github.com/Azure/Azure-Sentinel

This detection looks for the steps required to conduct a UAC bypass using Fodhelper.exe. By default this detection looks for the setting of the required registry keys and the invoking of the process within 1 hour - this can be tweaked as required.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 56f3f35c-3aca-4437-a1fb-b7a84dc4af00
name: Potential Fodhelper UAC Bypass
description: |
  'This detection looks for the steps required to conduct a UAC bypass using Fodhelper.exe. By default this detection looks for the setting of the required registry keys and the invoking of the process within 1 hour - this can be tweaked as required.'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1548.002
query: |
  SecurityEvent
    | where EventID == 4657
    | parse ObjectName with "\\REGISTRY\\" KeyPrefix "\\" RegistryKey
    | project-reorder RegistryKey
    | where RegistryKey has "Software\\Classes\\ms-settings\\shell\\open\\command"
    | extend TimeKey = bin(TimeGenerated, 1h)
    | join (
    SecurityEvent
    | where EventID == 4688
    | where Process =~ "fodhelper.exe"
    | where ParentProcessName endswith "cmd.exe" or ParentProcessName endswith "powershell.exe" or ParentProcessName endswith "powershell_ise.exe"
    | extend TimeKey = bin(TimeGenerated, 1h)) on TimeKey, Computer
    | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
    | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
    | extend AccountName = tostring(split(TargetAccount, @'\')[1]), AccountNTDomain = tostring(split(TargetAccount, @'\')[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetAccount
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityEvent

Stage 2: where

| where EventID == 4657

Stage 3: parse

| parse ObjectName with "\\REGISTRY\\" KeyPrefix "\\" RegistryKey

Stage 4: project-reorder

| project-reorder RegistryKey

Stage 5: where

| where RegistryKey has "Software\\Classes\\ms-settings\\shell\\open\\command"

Stage 6: extend

| extend TimeKey = bin(TimeGenerated, 1h)

Stage 7: join

| join (
  SecurityEvent
  | where EventID == 4688
  | where Process =~ "fodhelper.exe"
  | where ParentProcessName endswith "cmd.exe" or ParentProcessName endswith "powershell.exe" or ParentProcessName endswith "powershell_ise.exe"
  | extend TimeKey = bin(TimeGenerated, 1h)) on TimeKey, Computer

Stage 8: extend (3 consecutive steps)

extend AccountNTDomain, AccountName, DomainIndex, HostName, HostNameDomain

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventIDeq
  • 4657 corpus 2 (kusto 2)
  • 4688
field:"EventID" kind:eq
ParentProcessNameends_with
  • cmd.exe
  • powershell.exe
  • powershell_ise.exe
field:"ParentProcessName" kind:ends_with
Processeq
  • fodhelper.exe
field:"Process" kind:eq value:"fodhelper.exe"
RegistryKeymatch
  • Software\Classes\ms-settings\shell\open\command transforms: term corpus 2 (kusto 2)
field:"TargetObject" kind:match value:"Software\Classes\ms-settings\shell\open\command"

Output Fields

These fields are emitted when the rule matches.

FieldSource
TimeKeyextend
DomainIndexextend
HostNameextend
HostNameDomainextend
AccountNTDomainextend
AccountNameextend

Preview - TI map IP entity to Cloud App Events

#
Severity
medium
Time window
14d
Group by
$left.TI_ipEntity, $right.IPAddress, IPAddress, IndicatorId
Source
github.com/Azure/Azure-Sentinel

Identifies compromises and attacks and detect malicious activities in one's IP entity from TI

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

Rule Body

id: 4e0a6fc8-697e-4455-be47-831b41ea91ac
name: Preview - TI map IP entity to Cloud App Events
description: |
   'Identifies compromises and attacks and detect malicious activities in one's IP entity from TI'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - CloudAppEvents
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1d;
  let ioc_lookBack = 14d; 
  let IP_Indicators = ThreatIntelligenceIndicator
    | where TimeGenerated >= ago(ioc_lookBack)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where Active == true and ExpirationDateTime > now()
    | where isnotempty(NetworkIP)
  or isnotempty(EmailSourceIpAddress)
  or isnotempty(NetworkDestinationIP)
  or isnotempty(NetworkSourceIP)
    | extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
    | extend TIipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
    | extend TIipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity);
  IP_Indicators
    | join kind=innerunique (
    CloudAppEvents
      | where isnotempty(IPAddress)
      | where TimeGenerated >= ago(dt_lookBack)
      | extend CloudAppEvents_TimeGenerated = TimeGenerated) on $left.TI_ipEntity == $right.IPAddress
      | where CloudAppEvents_TimeGenerated < ExpirationDateTime
      | summarize CloudAppEventsTimeGenerated = argmax(CloudAppEvents_TimeGenerated, *) by IndicatorId, IPAddress
      | extend
          Description = max_CloudAppEvents_TimeGenerated_Description,
          ActivityGroupNames = max_CloudAppEvents_TimeGenerated_ActivityGroupNames,
          ThreatType = max_CloudAppEvents_TimeGenerated_ThreatType,
          ExpirationDateTime = max_CloudAppEvents_TimeGenerated_ExpirationDateTime,
          ConfidenceScore = max_CloudAppEvents_TimeGenerated_ConfidenceScore,
          TI_ipEntity = max_CloudAppEvents_TimeGenerated_TI_ipEntity,
          NetworkDestinationIP = max_CloudAppEvents_TimeGenerated_NetworkDestinationIP,
          NetworkSourceIP = max_CloudAppEvents_TimeGenerated_NetworkSourceIP,
          EmailSourceIPAddress = max_CloudAppEvents_TimeGenerated_EmailSourceIpAddress
      | project CloudAppEventsTimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, TI_ipEntity, NetworkDestinationIP, NetworkSourceIP, EmailSourceIPAddress
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: TI_ipEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: NetworkDestinationIP
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: NetworkSourceIP
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: EmailSourceIPAddress
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

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

let IP_Indicators is inlined into the numbered stages below.

Stages 1 to 6 define let IP_Indicators as the rule's main pipeline source. Stages 7 to 11 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 4: where

| where Active == true and ExpirationDateTime > now()

Stage 5: where

| where isnotempty(NetworkIP)
or isnotempty(EmailSourceIpAddress)
or isnotempty(NetworkDestinationIP)
or isnotempty(NetworkSourceIP)

Stage 6: extend (3 consecutive steps)

| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
| extend TIipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend TIipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
TI_ipEntity =
ifisnotempty(NetworkIP)NetworkIP
elseNetworkDestinationIP

Stage 7: join

IP_Indicators
| join kind=innerunique (
  CloudAppEvents
    | where isnotempty(IPAddress)
    | where TimeGenerated >= ago(dt_lookBack)
    | extend CloudAppEvents_TimeGenerated = TimeGenerated) on $left.TI_ipEntity == $right.IPAddress

Stage 8: where

where CloudAppEvents_TimeGenerated < ExpirationDateTime

Stage 9: summarize

summarize CloudAppEventsTimeGenerated by IndicatorId, IPAddress

Stage 10: extend

extend ActivityGroupNames, ConfidenceScore, Description, EmailSourceIPAddress, ExpirationDateTime, NetworkDestinationIP, NetworkSourceIP, TI_ipEntity, ThreatType

Stage 11: project

project ActivityGroupNames, CloudAppEventsTimeGenerated, ConfidenceScore, Description, EmailSourceIPAddress, ExpirationDateTime, IndicatorId, NetworkDestinationIP, NetworkSourceIP, TI_ipEntity, ThreatType

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Activeeq
  • true corpus 60 (kusto 60)
field:"Active" kind:eq value:"true"
EmailSourceIpAddressis_not_null
  • (no value, null check)
field:"EmailSourceIpAddress" kind:is_not_null
IPAddressis_not_null
  • (no value, null check)
field:"IPAddress" kind:is_not_null
NetworkDestinationIPis_not_null
  • (no value, null check)
field:"NetworkDestinationIP" kind:is_not_null
NetworkIPis_not_null
  • (no value, null check)
field:"NetworkIP" kind:is_not_null
NetworkSourceIPis_not_null
  • (no value, null check)
field:"NetworkSourceIP" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
CloudAppEventsTimeGeneratedproject
ConfidenceScoreproject
Descriptionproject
EmailSourceIPAddressproject
ExpirationDateTimeproject
IndicatorIdproject
NetworkDestinationIPproject
NetworkSourceIPproject
TI_ipEntityproject
ThreatTypeproject

ProofpointPOD - Email sender in TI list

#
Severity
medium
Time window
14d
Group by
$left.TI_emailEntity, $right.ClientEmail, ClientEmail, IndicatorId
Source
github.com/Azure/Azure-Sentinel

'Email sender in TI list.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access
Exfiltration

Rule Body

id: 35a0792a-1269-431e-ac93-7ae2980d4dde
name: ProofpointPOD - Email sender in TI list
description: |
  'Email sender in TI list.'
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ProofpointPOD
    dataTypes:
      - ProofpointPOD_maillog_CL
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - InitialAccess
relevantTechniques:
  - T1078
  - T1567
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  ThreatIntelligenceIndicator
  | where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()
  | where Active == true
  | where isnotempty(EmailSenderAddress)
  | extend TI_emailEntity = EmailSenderAddress
  // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  | join kind=innerunique (
         ProofpointPOD
         | where TimeGenerated >= ago(dt_lookBack)
         | where isnotempty(SrcUserUpn)
         | extend ProofpointPOD_TimeGenerated = TimeGenerated, ClientEmail = SrcUserUpn
  )
  on $left.TI_emailEntity == $right.ClientEmail
  | where ProofpointPOD_TimeGenerated < ExpirationDateTime
  | summarize ProofpointPOD_TimeGenerated = arg_max(ProofpointPOD_TimeGenerated, *) by IndicatorId, ClientEmail
  | project ProofpointPOD_TimeGenerated, Description, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, ClientEmail
  | extend timestamp = ProofpointPOD_TimeGenerated
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ClientEmail
version: 1.1.2
kind: Scheduled

Stages and Predicates

Parameters

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

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()

Stage 3: where

| where Active == true

Stage 4: where

| where isnotempty(EmailSenderAddress)

Stage 5: extend

| extend TI_emailEntity = EmailSenderAddress

Stage 6: join

| join kind=innerunique (
       ProofpointPOD
       | where TimeGenerated >= ago(dt_lookBack)
       | where isnotempty(SrcUserUpn)
       | extend ProofpointPOD_TimeGenerated = TimeGenerated, ClientEmail = SrcUserUpn
)
on $left.TI_emailEntity == $right.ClientEmail

Stage 7: where

where ProofpointPOD_TimeGenerated < ExpirationDateTime

Stage 8: summarize

summarize ProofpointPOD_TimeGenerated by IndicatorId, ClientEmail

Stage 9: project

project ClientEmail, ConfidenceScore, Description, ExpirationDateTime, IndicatorId, ProofpointPOD_TimeGenerated, ThreatType

Stage 10: extend

extend timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientEmailproject
ConfidenceScoreproject
Descriptionproject
ExpirationDateTimeproject
IndicatorIdproject
ProofpointPOD_TimeGeneratedproject
ThreatTypeproject
timestampextend

Script Interpreter Loading DotNet Assembly From Memory

#
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

The query searches for script interpreters (mmc.exe, mshta.exe, wscript.exe, and cscript.exe) loading .NET assemblies from memory. In the case of the MMC executable, the query also checks for the MSC file that was loaded, as some legitimate MSC files are known to load .NET assemblies via MMC.

Known False Positives

  • Some custom MMC snap-ins may load .NET assemblies, which can trigger this alert.

MITRE ATT&CK Coverage

References

Telemetry Coverage

Rule Body

let timeframe = 2*1h;
DeviceEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType == "ClrUnbackedModuleLoaded"
| where InitiatingProcessFileName in~ ("mmc.exe","mshta.exe","wscript.exe","cscript.exe")
| extend DotNetAssemblyName = tostring(parse_json(AdditionalFields).ModuleILPathOrName)
| extend ParsedCommandLine=parse_command_line(InitiatingProcessCommandLine, "windows")
// When a .msc file is opened in MMC, the file path is passed as an argument to MMC.
// Based on testing this is the first argument in the command line. In some cases a command-line switch /32 is passed as the first argument
// and the file path is the second argument. This is handled by the iif statement below.
| extend MscFile=ParsedCommandLine[1]
| extend MscFile=iif(MscFile startswith "/", ParsedCommandLine[2], MscFile)
// Some Microsoft MSC files are known to load .NET assemblies via MMC.
| where not(InitiatingProcessFileName =~ "mmc.exe" and (MscFile startswith @"c:\windows\system32\" or MscFile startswith @"C:\Program Files\Update Services\" or MscFile startswith @"C:\ProgramData\Microsoft\Windows\Start Menu\"))
// Do not alert when MMC is started without any arguments, since in that case it is unknown which MSC file was loaded.
| where not(InitiatingProcessFileName =~ "mmc.exe" and (array_length(ParsedCommandLine) == 1))
// Begin environment-specific filter.
// End environment-specific filter.

Stages and Predicates

Parameters

let timeframe = 2*1h;

Stage 1: source

DeviceEvents

Stage 2: where

| where ingestion_time() >= ago(timeframe)

Stage 3: where

| where ActionType == "ClrUnbackedModuleLoaded"

Stage 4: where

| where InitiatingProcessFileName in~ ("mmc.exe","mshta.exe","wscript.exe","cscript.exe")

Stage 5: extend (4 consecutive steps)

| extend DotNetAssemblyName = tostring(parse_json(AdditionalFields).ModuleILPathOrName)
| extend ParsedCommandLine=parse_command_line(InitiatingProcessCommandLine, "windows")
| extend MscFile=ParsedCommandLine[1]
| extend MscFile=iif(MscFile startswith "/", ParsedCommandLine[2], MscFile)

Stage 6: where

| where not(InitiatingProcessFileName =~ "mmc.exe" and (MscFile startswith @"c:\windows\system32\" or MscFile startswith @"C:\Program Files\Update Services\" or MscFile startswith @"C:\ProgramData\Microsoft\Windows\Start Menu\"))

Stage 7: where

| where not(InitiatingProcessFileName =~ "mmc.exe" and (array_length(ParsedCommandLine) == 1))

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DotNetAssemblyNameextend
ParsedCommandLineextend
MscFileextend

Tailscale: New API access token or OAuth client created

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

Identifies when a new API access token or OAuth client is created in the tailnet. These grant programmatic access - verify the actor and intent.

MITRE ATT&CK Coverage

Rule Body

id: 668b43fd-cf28-961a-85af-957850df5027
name: "Tailscale: New API access token or OAuth client created"
description: |
  Identifies when a new API access token or OAuth client is created in the tailnet. These grant programmatic access - verify the actor and intent.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: TailscaleCCF
    dataTypes:
      - Tailscale_Audit_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - CredentialAccess
relevantTechniques:
  - T1098
  - T1136
query: |
  Tailscale_Audit_CL
        | where Action == "CREATE"
        | where tostring(Target.type) in ("API_KEY", "OAUTH_CLIENT")
        | extend ActorLogin = tostring(Actor.loginName)
        | extend TargetName = tostring(Target.name)
        | extend TargetId = tostring(Target.id)
        | project TimeGenerated, ActorLogin, Action, TargetName, TargetId, Origin, New
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ActorLogin
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0

Stages and Predicates

Stage 1: source

Tailscale_Audit_CL

Stage 2: where

| where Action == "CREATE"

Stage 3: where

| where tostring(Target.type) in ("API_KEY", "OAUTH_CLIENT")

Stage 4: extend (3 consecutive steps)

| extend ActorLogin = tostring(Actor.loginName)
| extend TargetName = tostring(Target.name)
| extend TargetId = tostring(Target.id)

Stage 5: project

| project TimeGenerated, ActorLogin, Action, TargetName, TargetId, Origin, New

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • CREATE
field:"Action" kind:eq value:"CREATE"
Target.typein
  • API_KEY transforms: tostring, kusto_dynamic_path
  • OAUTH_CLIENT transforms: tostring, kusto_dynamic_path
field:"Target.type" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionproject
ActorLoginproject
Newproject
Originproject
TargetIdproject
TargetNameproject
TimeGeneratedproject

Vaikora - High severity AI agent action 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
Group by
AgentId, Severity
Source
github.com/Azure/Azure-Sentinel

Identifies AI agent actions from Vaikora classified as high or critical severity. These events may indicate an agent operating outside safe parameters or triggering policy thresholds.

MITRE ATT&CK Coverage

Rule Body

id: 15c49777-7cb7-4746-8064-6fa4c7a73df8
name: Vaikora - High severity AI agent action detected
description: |
  Identifies AI agent actions from Vaikora classified as high or critical severity. These events may indicate an agent operating outside safe parameters or triggering policy thresholds.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: VaikoraSentinel
    dataTypes:
      - Vaikora_AgentSignals_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
  - Impact
  - Execution
  - PrivilegeEscalation
relevantTechniques:
  - T1059
  - T1078
  - T1548
query: |
  Vaikora_AgentSignals_CL
  | where TimeGenerated > ago(1h)
  | where severity_s in ("high", "critical")
  | summarize
      ActionCount = count(),
      MaxAnomalyScore = max(anomaly_score_d),
      Actions = make_set(action_type_s),
      PolicyDecisions = make_set(policy_decision_s),
      ResourceTypes = make_set(resource_type_s),
      LogHashes = make_set(log_hash_s)
    by AgentId = agent_id_s, Severity = severity_s
  | extend
      ActionList = strcat_array(Actions, ", "),
      PolicyList = strcat_array(PolicyDecisions, ", "),
      ResourceList = strcat_array(ResourceTypes, ", ")
suppressionDuration: 1h
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: Selected
    groupByEntities:
      - Account
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  MaxAnomalyScore: MaxAnomalyScore
  ActionCount: ActionCount
  Actions: ActionList
  PolicyDecisions: PolicyList
  ResourceTypes: ResourceList
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AgentId
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Vaikora_AgentSignals_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where severity_s in ("high", "critical")

Stage 4: summarize

| summarize
    ActionCount = count(),
    MaxAnomalyScore = max(anomaly_score_d),
    Actions = make_set(action_type_s),
    PolicyDecisions = make_set(policy_decision_s),
    ResourceTypes = make_set(resource_type_s),
    LogHashes = make_set(log_hash_s)
  by AgentId = agent_id_s, Severity = severity_s

Stage 5: extend

| extend
    ActionList = strcat_array(Actions, ", "),
    PolicyList = strcat_array(PolicyDecisions, ", "),
    ResourceList = strcat_array(ResourceTypes, ", ")

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
severity_sin
  • critical
  • high
field:"okta::severity" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActionCountsummarize
Actionssummarize
AgentIdsummarize
LogHashessummarize
MaxAnomalyScoresummarize
PolicyDecisionssummarize
ResourceTypessummarize
Severitysummarize
ActionListextend
PolicyListextend
ResourceListextend

VMware SD-WAN Edge - IDS/IPS Signature Update Failed

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

The VMware SD-WAN Edge Management Plane reported a failed IDS/IPS signature update. This can indicate a potential management plane issue, an Edge OS version mismatch (IDS/IPS has been introduced in release 5.2.0.0), or a software issue. If the Edge was able to download signature files before, this error means that the IPS/IDS engine can still provide a level of protection, however, signatures might be missing or inaccurate. If the Edge has no valid signature file, this error could indicate that the Edge Firewall cannot protect from network threats.

Rule Body

id: 27553108-4aaf-4a3e-8ecd-5439d820d474
name: VMware SD-WAN Edge - IDS/IPS Signature Update Failed
version: 1.0.0
kind: Scheduled
description: |-
  The VMware SD-WAN Edge Management Plane reported a failed IDS/IPS signature update. This can indicate a potential management plane issue, an Edge OS version mismatch (IDS/IPS has been introduced in release 5.2.0.0), or a software issue.

  If the Edge was able to download signature files before, this error means that the IPS/IDS engine can still provide a level of protection, however, signatures might be missing or inaccurate. If the Edge has no valid signature file, this error could indicate that the Edge Firewall cannot protect from network threats.
severity: High
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
requiredDataConnectors:
  - connectorId: VMwareSDWAN
    dataTypes:
      - SDWAN
query: |+
  VMware_VECO_EventLogs_CL
  | where event == "MGD_ATPUP_APPLY_IDPS_SIGNATURE_FAILED"
  | extend idpsSignatureVersion = extract("\"version\":\"([0-9]+)\"", 1, tostring(todynamic(detail).data))
  | extend todynamic(detail).edgeSerialNumber
  | extend todynamic(detail).data
  | project-rename idpsSignatureData = detail_data
  | project-rename edgeSerialNumber = detail_edgeSerialNumber
  | project-away detail

suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDescriptionFormat: '{{message}} '
  alertDynamicProperties: []
customDetails:
  idpsSignatureVersion: idpsSignatureVersion
  edgeSerialNumber: edgeSerialNumber
suppressionDuration: 5h

Stages and Predicates

Stage 1: source

VMware_VECO_EventLogs_CL

Stage 2: where

| where event == "MGD_ATPUP_APPLY_IDPS_SIGNATURE_FAILED"

Stage 3: extend (3 consecutive steps)

| extend idpsSignatureVersion = extract("\"version\":\"([0-9]+)\"", 1, tostring(todynamic(detail).data))
| extend todynamic(detail).edgeSerialNumber
| extend todynamic(detail).data

Stage 4: project-rename

| project-rename idpsSignatureData = detail_data

Stage 5: project-rename

| project-rename edgeSerialNumber = detail_edgeSerialNumber

Stage 6: project-away

| project-away detail

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
eventeq
  • MGD_ATPUP_APPLY_IDPS_SIGNATURE_FAILED
field:"event" kind:eq value:"MGD_ATPUP_APPLY_IDPS_SIGNATURE_FAILED"

Output Fields

These fields are emitted when the rule matches.

FieldSource
idpsSignatureVersionextend
idpsSignatureDataproject-rename
edgeSerialNumberproject-rename

Back to all Kusto rules