Detection rules › Kusto
Kusto rules: 21
ApexOne - Attack Discovery Detection
#'Detects Attack Discovery Detection events.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | match |
| field:"EventMessage" kind:match value:"Attack Discovery Detection" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
IPCustomEntity | extend |
Azure VM Run Command operation executed during suspicious login window
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityInsights.ActionUncommonlyPerformedByUser | eq |
| field:"ActivityInsights.ActionUncommonlyPerformedByUser" kind:eq value:"True" |
Authorization | match |
| field:"Authorization" kind:match value:"virtualMachines" |
EventSource | eq |
| field:"EventSource" kind:eq value:"Azure AD" |
OperationNameValue | eq |
| field:"azure_ad::operation_name_value" kind:eq value:"MICROSOFT.COMPUTE/VIRTUALMACHINES/RUNCOMMAND/ACTION" |
StartTime | ge |
| field:"StartTime" kind:ge value:"UEBAWindowStart" |
StartTime | le |
| field:"StartTime" kind:le value:"UEBAWindowEnd" |
UEBASourceIPLocation | is_not_null | field:"UEBASourceIPLocation" kind:is_not_null | |
UserPrincipalName | is_not_null | field:"UserPrincipalName" kind:is_not_null | |
list_ActivityStatusValue | match |
| field:"list_ActivityStatusValue" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Caller | project |
CallerIpAddress | project |
EndTime | project |
StartTime | project |
Subscription | project |
UEBAActionType | project |
UEBAActivityInsights | project |
UEBAEventTime | project |
UEBASourceIPLocation | project |
UEBAUsersInsights | project |
VirtualMachineName | project |
AccountName | extend |
AccountUPNSuffix | extend |
Cayosoft Guardian - Cloud Application Security Threats
#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 =isnotempty(CleanCategories["Attack tactics"])split(replace_string(tostring(CleanCategories["Attack tactics"]), ' ', ''), ',')dynamic([])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.
| Field | Kind | Values | Search |
|---|---|---|---|
IsEntra | eq |
| field:"IsEntra" kind:eq value:"true" |
TargetObjectType | match |
| field:"TargetObjectType" kind:match |
Cortex XDR Incident - High
#'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.
| Field | Kind | Values | Search |
|---|---|---|---|
HostCount | gt |
| field:"HostCount" kind:gt value:"0" |
Severity | contains |
| field:"Severity" kind:contains value:"high" |
UserCount | gt |
| field:"UserCount" kind:gt value:"0" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Status | contains | resolved_auto | excludes:Status field:"Status" value:"resolved_auto" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertCategories | project |
AlertCount | project |
CreationTime | project |
Description | project |
HostCount | project |
Hosts_s | project |
IncidentId | project |
IncidentSources | project |
ModificationTime | project |
Severity | project |
Status | project |
TimeGenerated | project |
UserCount | project |
Users_s | project |
XdrUrl | project |
Credential Dumping Tools - Service Installation
#This query detects the installation of a Windows service that contains artifacts from credential dumping tools such as Mimikatz.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Service-Control-Manager | Event ID 7045: A service was installed in the system. |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | eq |
| field:"EventID" kind:eq value:"7045" |
ImagePath | match |
| field:"ImagePath" kind:match |
ServiceName | match |
| field:"ServiceName" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | summarize |
Computer | summarize |
EndTime | summarize |
EventID | summarize |
ImagePath | summarize |
ServiceName | summarize |
StartTime | summarize |
DnsDomain | extend |
HostName | extend |
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.
'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
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Credential Access | |
| Collection | |
| Exfiltration |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"stealer_logs" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
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.
"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
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Stealth | |
| Command & Control | |
| Exfiltration | |
| Impact |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"High" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertUID | project |
Categories | project |
Country | project |
Domain | project |
FirstSeen | project |
IPversion | project |
ISP | project |
LastSeen | project |
LastUsersReported | project |
NetworkIP | project |
ProductName | project |
ProviderName | project |
RiskScore | project |
ThreatActors | project |
TimeGenerated | project |
TopDomain | project |
UID | project |
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.
"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
| Tactic | Techniques |
|---|---|
| Credential Access | |
| Collection | |
| Exfiltration | |
| Impact |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"Critical" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertTitle | project |
AlertUID | project |
AssetType | project |
AssetValue | project |
Description | project |
FirstSeen | project |
Impact | project |
LastSeen | project |
ProductName | project |
ProviderName | project |
RiskScore | project |
TimeGenerated | project |
UID | project |
Failed AzureAD logons but success logon to AWS Console
#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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- AWS Console Login (Panther)
- AWS Console Login Failed During MFA Challenge (Splunk)
- AWS ConsoleLogin Failed Authentication (Sigma)
- AWS CreateLoginProfile (Splunk)
- AWS Credential Access Failed Login (Splunk)
- AWS High Number Of Failed Authentications For User (Splunk)
- AWS High Number Of Failed Authentications From Ip (Splunk)
- AWS Multiple Failed MFA Requests For User (Splunk)
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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventName | eq |
| field:"aws::eventName" kind:eq value:"ConsoleLogin" |
LoginResult | eq |
| field:"LoginResult" kind:eq value:"Success" |
SourceIpAddress | in |
| field:"aws::sourceIPAddress" kind:in value:"Suspicious_signins" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AWSRegion | summarize |
AccountName | summarize |
AccountUPNSuffix | summarize |
EndTime | summarize |
EventTypeName | summarize |
LoginResult | summarize |
MFAUsed | summarize |
Reason | summarize |
RecipientAccountId | summarize |
SourceIpAddress | summarize |
StartTime | summarize |
UserAgent | summarize |
UserIdentityType | summarize |
timestamp | extend |
File Server Settings Updated
#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.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"28940" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
GCP IAM - Disable Data Access Logging
#'Detects when Data Access Logging is disabled.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
PayloadMethodname | eq |
| field:"gcp::method_name" kind:eq value:"SetIamPolicy" |
s | is_null | field:"s" kind:is_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
s | extend |
AccountName | extend |
AccountUPNSuffix | extend |
GCP IAM - Empty user agent
#'Detects requests where user agent is empty.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
HttpUserAgentOriginal | is_null | field:"HttpUserAgentOriginal" kind:is_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | extend |
AccountUPNSuffix | extend |
GreyNoise TI Map IP Entity to CommonSecurityLog
#This query maps GreyNoise IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in CommonSecurityLog.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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 =isnotempty(NetworkIP)NetworkIPNetworkDestinationIPStage 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSourceIpAddress | is_not_null | field:"EmailSourceIpAddress" kind:is_not_null | |
NetworkDestinationIP | is_not_null | field:"NetworkDestinationIP" kind:is_not_null | |
NetworkIP | is_not_null | field:"NetworkIP" kind:is_not_null | |
NetworkSourceIP | is_not_null | field:"NetworkSourceIP" kind:is_not_null | |
SourceSystem | eq |
| field:"SourceSystem" kind:eq value:"GreyNoise" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TI_ipEntity | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 | excludes:TI_ipEntity |
TI_ipEntity | starts_with | 127. | excludes:TI_ipEntity field:"TI_ipEntity" value:"127." |
TI_ipEntity | starts_with | :: | excludes:TI_ipEntity field:"TI_ipEntity" value:"::" |
TI_ipEntity | starts_with | fe80 | excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CS_ipEntity | project |
ConfidenceScore | project |
DestinationIP | project |
DeviceAction | project |
DeviceProduct | project |
DeviceVendor | project |
ExpirationDateTime | project |
IndicatorId | project |
LogSeverity | project |
Message | project |
MessageIP | project |
SourceIP | project |
TI_ipEntity | project |
ThreatType | project |
Type | project |
timestamp | project |
High count of failed attempts from same client IP
#'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
| Tactic | Techniques |
|---|---|
| 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 =DomainIndex != -1substring(Computer, (DomainIndex + 1))ComputerIndicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | project |
FailedConnectionsCount | project |
TimeGenerated | project |
cIP | project |
sIP | project |
set_csMethod | project |
set_csUserAgent | project |
set_csUserName | project |
set_decodedUriQuery | project |
set_sPort | project |
set_sSiteName | project |
set_scStatusFull | project |
set_scStatusFull_Friendly | project |
set_scWin32Status_Friendly | project |
set_scWin32Status_Hex | project |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
Hunt for Defender for Identity not installed but eligible
#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
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 =isempty(MdiDeviceName)"This server is eligible for MDI but does not have MDI installed""None"Stage 8: where
where Issue != "None"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CriticalityRuleNames | match |
| field:"CriticalityRuleNames" kind:match |
DeviceRoles | match |
| field:"DeviceRoles" kind:match |
Issue | ne |
| field:"Issue" kind:ne value:"None" |
SoftwareName | eq |
| field:"SoftwareName" kind:eq value:"azure_advanced_threat_protection_sensor" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Issue | extend |
Infoblox - TI - Syslog Match Found - URL
#'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
| Tactic | Techniques |
|---|---|
| Impact |
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
Description | match |
| field:"Description" kind:match value:"Infoblox - URL" |
DomainName | is_not_null | field:"DomainName" kind:is_not_null | |
Url | is_not_null | field:"Url" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
AdditionalInformation | project |
Computer | project |
ConfidenceScore | project |
Description | project |
ExpirationDateTime | project |
ExternalIndicatorId | project |
HitTime | project |
HostIP | project |
IndicatorId | project |
LatestIndicatorTime | project |
ProcessName | project |
SourceSystem | project |
SyslogMessage | project |
Tags | project |
ThreatType | project |
TrafficLightProtocolLevel | project |
Type | project |
Url | project |
McAfee ePO - Spam Email detected
#Detects when email was marked as spam.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| McAfee-ePolicy-Orchestrator | Event ID 4650: Spam email detected |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventId | eq |
| field:"EventId" kind:eq value:"4650" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostCustomEntity | extend |
IPCustomEntity | extend |
OracleDBAudit - Shutdown Server
#'Detects when "SHUTDOWN" command was sent to server.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
DbAction | eq |
| field:"DbAction" kind:eq value:"SHUTDOWN" |
SrcIpAddr | is_not_null | field:"src_ip" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstUserName | project |
SrcIpAddr | project |
AccountCustomEntity | extend |
IPCustomEntity | extend |
Possible contact with a domain generated by a DGA
#'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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
DGADomain | gt |
| field:"DGADomain" kind:gt value:"8" |
DGADomain | regex_match |
| field:"DGADomain" kind:regex_match value:"^[A-Za-z]{0,}$" |
DestinationHostName | is_not_null | field:"DestinationHostName" kind:is_not_null | |
Name | ends_with |
| field:"Name" kind:ends_with |
Name | match |
| field:"Name" kind:match value:"." |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Name | ends_with | .home | excludes:Name field:"Name" value:".home" |
Name | ends_with | .lan | excludes:Name field:"Name" value:".lan" |
DGADomain | gt | 8 | excludes:DGADomain field:"DGADomain" value:"8" |
DGADomain | regex_match | ^[A-Za-z]{0,}$ | excludes:DGADomain field:"DGADomain" value:"^[A-Za-z]{0,}$" |
DestinationHostName | is_not_null | excludes:DestinationHostName | |
Name | match | . | excludes:Name field:"Name" value:"." |
count_ | gt | 1 | excludes:count_ field:"count_" value:"1" |
triCount | gt | 500 | excludes:triCount field:"triCount" value:"500" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | summarize |
DestinationIP | summarize |
Name | summarize |
SourceIP | summarize |
Potential Fodhelper UAC Bypass
#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
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | eq |
| field:"EventID" kind:eq |
ParentProcessName | ends_with |
| field:"ParentProcessName" kind:ends_with |
Process | eq |
| field:"Process" kind:eq value:"fodhelper.exe" |
RegistryKey | match |
| field:"TargetObject" kind:match value:"Software\Classes\ms-settings\shell\open\command" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TimeKey | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
AccountNTDomain | extend |
AccountName | extend |
Preview - TI map IP entity to Cloud App Events
#Identifies compromises and attacks and detect malicious activities in one's IP entity from TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | CloudAppEvents action any: Cloud app activity |
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 =isnotempty(NetworkIP)NetworkIPNetworkDestinationIPStage 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSourceIpAddress | is_not_null | field:"EmailSourceIpAddress" kind:is_not_null | |
IPAddress | is_not_null | field:"IPAddress" kind:is_not_null | |
NetworkDestinationIP | is_not_null | field:"NetworkDestinationIP" kind:is_not_null | |
NetworkIP | is_not_null | field:"NetworkIP" kind:is_not_null | |
NetworkSourceIP | is_not_null | field:"NetworkSourceIP" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
CloudAppEventsTimeGenerated | project |
ConfidenceScore | project |
Description | project |
EmailSourceIPAddress | project |
ExpirationDateTime | project |
IndicatorId | project |
NetworkDestinationIP | project |
NetworkSourceIP | project |
TI_ipEntity | project |
ThreatType | project |
ProofpointPOD - Email sender in TI list
#'Email sender in TI list.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSenderAddress | is_not_null | field:"EmailSenderAddress" kind:is_not_null | |
SrcUserUpn | is_not_null | field:"SrcUserUpn" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ClientEmail | project |
ConfidenceScore | project |
Description | project |
ExpirationDateTime | project |
IndicatorId | project |
ProofpointPOD_TimeGenerated | project |
ThreatType | project |
timestamp | extend |
Script Interpreter Loading DotNet Assembly From Memory
#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
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceEvents action ClrUnbackedModuleLoaded: CLR unbacked module loaded |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ClrUnbackedModuleLoaded" |
InitiatingProcessFileName | in |
| field:"InitiatingProcessFileName" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
MscFile | starts_with | C:\Program Files\Update Services\ | excludes:MscFile field:"MscFile" value:"C:\Program Files\Update Services\" |
MscFile | starts_with | C:\ProgramData\Microsoft\Windows\Start Menu\ | excludes:MscFile field:"MscFile" value:"C:\ProgramData\Microsoft\Windows\Start Menu\" |
MscFile | starts_with | c:\windows\system32\ | excludes:MscFile field:"MscFile" value:"c:\windows\system32\" |
InitiatingProcessFileName | eq | mmc.exe | excludes:InitiatingProcessFileName field:"InitiatingProcessFileName" value:"mmc.exe" |
ParsedCommandLine | eq | 1 | excludes:ParsedCommandLine field:"ParsedCommandLine" value:"1" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DotNetAssemblyName | extend |
ParsedCommandLine | extend |
MscFile | extend |
Tailscale: New API access token or OAuth client created
#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
| Tactic | Techniques |
|---|---|
| Persistence |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"CREATE" |
Target.type | in |
| field:"Target.type" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
ActorLogin | project |
New | project |
Origin | project |
TargetId | project |
TargetName | project |
TimeGenerated | project |
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.
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
| Tactic | Techniques |
|---|---|
| Execution | |
| Privilege Escalation |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
severity_s | in |
| field:"okta::severity" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActionCount | summarize |
Actions | summarize |
AgentId | summarize |
LogHashes | summarize |
MaxAnomalyScore | summarize |
PolicyDecisions | summarize |
ResourceTypes | summarize |
Severity | summarize |
ActionList | extend |
PolicyList | extend |
ResourceList | extend |
VMware SD-WAN Edge - IDS/IPS Signature Update Failed
#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.
| Field | Kind | Values | Search |
|---|---|---|---|
event | eq |
| field:"event" kind:eq value:"MGD_ATPUP_APPLY_IDPS_SIGNATURE_FAILED" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
idpsSignatureVersion | extend |
idpsSignatureData | project-rename |
edgeSerialNumber | project-rename |