Detection rules › Kusto
Kusto rules: 0w
1Password - Service account integration token adjustment
#This will alert when a service account integration token has been created, renamed, verified, or revoked. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same. Ref: https://1password.com/ Ref: https://github.com/securehats/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| 1Password | any: Service account token (catch-all) |
Rule Body
id: d54a3cf9-6169-449c-83f1-e7def3359702
name: 1Password - Service account integration token adjustment
version: 1.0.0
kind: Scheduled
description: |-
This will alert when a service account integration token has been created, renamed, verified, or revoked. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same.
Ref: https://1password.com/
Ref: https://github.com/securehats/
severity: Medium
requiredDataConnectors:
- connectorId: 1Password
dataTypes:
- OnePasswordEventLogs_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1134
query: |-
OnePasswordEventLogs_CL
| where log_source == "auditevents"
| where action has_any("create", "trename", "tverify", "trevoke")
| where object_type == "satoken"
| extend
ActorUsername = actor_details.email
, SrcIpAddr = session.ip
subTechniques:
- T1134.003
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUsername
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
Stages and Predicates
Stage 1: source
OnePasswordEventLogs_CL
Stage 2: where
| where log_source == "auditevents"
Stage 3: where
| where action has_any("create", "trename", "tverify", "trevoke")
Stage 4: where
| where object_type == "satoken"
Stage 5: extend
| extend
ActorUsername = actor_details.email
, SrcIpAddr = session.ip
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | match |
| field:"action" kind:match |
log_source | eq |
| field:"log_source" kind:eq value:"auditevents" |
object_type | eq |
| field:"object_type" kind:eq value:"satoken" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorUsername | extend |
SrcIpAddr | extend |
Alsid Password issues
#'Searches for triggered Indicators of Exposures related to password issues'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 472b7cf4-bf1a-4061-b9ab-9fe4894e3c17
name: Alsid Password issues
description: |
'Searches for triggered Indicators of Exposures related to password issues'
severity: Low
requiredDataConnectors:
- connectorId: AlsidForAD
dataTypes:
- AlsidForADLog_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let SeverityTable=datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
let codeNameList = datatable(Codename:string)["C-CLEARTEXT-PASSWORD", "C-PASSWORD-DONT-EXPIRE", "C-USER-REVER-PWDS", "C-PASSWORD-POLICY", "C-USER-PASSWORD", "C-KRBTGT-PASSWORD", "C-AAD-SSO-PASSWORD", "C-REVER-PWD-GPO"];
afad_parser
| where MessageType == 0 and Codename in~ (codeNameList)
| lookup kind=leftouter SeverityTable on Severity
| order by Level
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Let Binding: SeverityTable
let SeverityTable = datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
Let Binding: codeNameList
let codeNameList = datatable(Codename:string)["C-CLEARTEXT-PASSWORD", "C-PASSWORD-DONT-EXPIRE", "C-USER-REVER-PWDS", "C-PASSWORD-POLICY", "C-USER-PASSWORD", "C-KRBTGT-PASSWORD", "C-AAD-SSO-PASSWORD", "C-REVER-PWD-GPO"];
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 0 and Codename in~ (codeNameList)
Stage 3: kusto:lookup
lookup kind=leftouter (SeverityTable) on Severity
Stage 4: sort
sort by Level
Stage 5: extend
extend DnsDomain, HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Codename | in |
| field:"Codename" kind:in value:"codeNameList" |
MessageType | eq |
| field:"MessageType" kind:eq value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
BloodHound Attack Path Finding - WriteOwner Privileges on Tier Zero Objects
#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.
BloodHound Attack Path Finding - WriteOwner Privileges on Tier Zero Objects
Rule Body
id: 924e60be-38af-4a33-98d4-45f2b1643094
name: BloodHound Attack Path Finding - WriteOwner Privileges on Tier Zero Objects
description: BloodHound Attack Path Finding - WriteOwner Privileges on Tier Zero Objects
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "T0WriteOwner"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "T0WriteOwner"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0WriteOwner" |
Certified Pre-Owned - backup of CA private key - rule 2
#This query identifies someone that performs a backup of they CA key.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5059: Key migration operation. |
Rule Body
id: 88f8fbc0-345d-458e-85f6-f73921d5ef50
name: Certified Pre-Owned - backup of CA private key - rule 2
description: |
This query identifies someone that performs a backup of they CA key.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1036
query: |
SecurityEvent
// Fill in the machine name of your CA.
| where EventID == 5059 and Computer contains "<YOUR CA MACHINE NAME>"
| where EventData contains "%%2499" and EventData contains "%%2464"
| extend EventData=parse_xml(EventData)
| mv-apply d=EventData.EventData.Data on
(
where d["@Name"]=="KeyName"
| project KeyName=tostring(d["#text"])
)
| mv-apply d=EventData.EventData.Data on
(
where d["@Name"]=="SubjectUserName"
| project SubjectUserName=tostring(d["#text"])
)
| parse Account with "<YOUR DOMAIN NAME>\\" CleanAccount "$"
| where not(Computer startswith CleanAccount)
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventID == 5059 and Computer contains "<YOUR CA MACHINE NAME>"
Stage 3: where
| where EventData contains "%%2499" and EventData contains "%%2464"
Stage 4: extend
| extend EventData=parse_xml(EventData)
Stage 5: kusto:mv-apply
| mv-apply d=EventData.EventData.Data on
(
where d["@Name"]=="KeyName"
| project KeyName=tostring(d["#text"])
)
Stage 6: kusto:mv-apply
| mv-apply d=EventData.EventData.Data on
(
where d["@Name"]=="SubjectUserName"
| project SubjectUserName=tostring(d["#text"])
)
Stage 7: parse
| parse Account with "<YOUR DOMAIN NAME>\\" CleanAccount "$"
Stage 8: where
| where not(Computer startswith CleanAccount)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Computer | contains |
| field:"Computer" kind:contains value:"<YOUR CA MACHINE NAME>" |
EventData | contains |
| field:"EventData" kind:contains |
EventID | eq |
| field:"EventID" kind:eq value:"5059" |
d["@Name"] | eq |
|
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Computer | starts_with | CleanAccount | excludes:Computer field:"Computer" value:"CleanAccount" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventData | extend |
Detect non-admin requesting token for admin applications
#This rule detects sign-in attempts from non-admin users to admin applications in Entra ID.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
References
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Cisco - firewall block but success logon to Microsoft Entra ID (Kusto)
Rule Body
let ITAccounts=(_GetWatchlist('ITAccounts') | summarize make_set(ITAccounts));
// Materialize Dataset
let DataSetMat= materialize (SigninLogs
| where TimeGenerated > ago(1h)
| where AppDisplayName has_any ("PowerShell", "CLI", "Command Line", "Management Shell")
// Get successful and failed due to no assignment logins
| where ResultType in ("0", "50105")
| summarize max(TimeGenerated) by UserPrincipalName, AppDisplayName, IPAddress, UserId, ResultType
// join IdentityInfo to get more information
| join kind=leftouter (IdentityInfo | where TimeGenerated > ago(14d) | summarize arg_max(TimeGenerated, *) by AccountObjectId ) on $left.UserId == $right.AccountObjectId
// exclude Accounts with Assigned Roles
| where array_length(AssignedRoles) == 0
// exclude known IT personnel Departments
| where Department !has "it" and Department !has "ict" and Department !has "operations"
// exclude service accounts
| where JobTitle != "Service Account");
// exclude IT accounts
let FIL= (DataSetMat
| extend ITAccounts= toscalar(ITAccounts)
| mv-expand ITAccounts
| where AccountUPN contains ITAccounts or AccountDisplayName contains ITAccounts);
DataSetMat
// exclude service accounts
| join kind=leftanti FIL on AccountUPN
| distinct max_TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, JobTitle, Department, UserId, ResultType
Stages and Predicates
let DataSetMat is inlined into the numbered stages below.
Let Binding: ITAccounts
let ITAccounts = (_GetWatchlist('ITAccounts') | summarize make_set(ITAccounts));
Let Binding: FIL
let FIL = (DataSetMat
| extend ITAccounts= toscalar(ITAccounts)
| mv-expand ITAccounts
| where AccountUPN contains ITAccounts or AccountDisplayName contains ITAccounts);
Stage 1: source
let DataSetMat
Stage 2: source
SigninLogs
Stage 3: where
where TimeGenerated > ago(3600s)
Stage 4: where
where (AppDisplayName has "PowerShell" or AppDisplayName has "CLI" or AppDisplayName has "Command Line" or AppDisplayName has "Management Shell")
Stage 5: where
where ResultType in (0, 50105)
Stage 6: summarize
summarize by UserPrincipalName, AppDisplayName, IPAddress, UserId, ResultType
Stage 7: join
join kind=leftouter (IdentityInfo) on $left.UserId, $right.AccountObjectId
Stage 8: where
where AssignedRoles == 0
Stage 9: where
where not (Department has "ict") and not (Department has "it") and not (Department has "operations")
Stage 10: where
where JobTitle != "Service Account"
Stage 11: join (negated)
join kind=leftanti (FIL) on AccountUPN
Stage 12: distinct
distinct AppDisplayName, Department, IPAddress, JobTitle, ResultType, UserId, UserPrincipalName, max_TimeGenerated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AppDisplayName | match |
| field:"AppDisplayName" kind:match |
Department | match |
| field:"Department" kind:match |
JobTitle | ne |
| field:"JobTitle" kind:ne value:"Service Account" |
ResultType | in |
| field:"ResultType" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Department | match | ict | excludes:Department field:"Department" value:"ict" |
Department | match | it | excludes:Department field:"Department" value:"it" |
Department | match | operations | excludes:Department field:"Department" value:"operations" |
AccountDisplayName | cross_field_compare | ITAccounts | excludes:AccountDisplayName field:"AccountDisplayName" value:"ITAccounts" |
AccountUPN | cross_field_compare | ITAccounts | excludes:AccountUPN field:"AccountUPN" value:"ITAccounts" |
AppDisplayName | match | PowerShell, CLI, Command Line, Management Shell | excludes:AppDisplayName |
AssignedRoles | eq | 0 | excludes:AssignedRoles field:"AssignedRoles" value:"0" |
JobTitle | ne | Service Account | excludes:JobTitle field:"JobTitle" value:"Service Account" |
ResultType | in | 0, 50105 | excludes:ResultType field:"ResultType" value:"0" field:"ResultType" value:"50105" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppDisplayName | summarize |
IPAddress | summarize |
ResultType | summarize |
UserId | summarize |
UserPrincipalName | summarize |
GitLab - Brute-force Attempts
#'This query relies on GitLab Application Logs to get failed logins to highlight brute-force attempts from different IP addresses in a short space of time.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 2238d13a-cf05-4973-a83f-d12a25dbb153
name: GitLab - Brute-force Attempts
description: |
'This query relies on GitLab Application Logs to get failed logins to highlight brute-force attempts from different IP addresses in a short space of time.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let LearningPeriod = 7d;
let EndLearningTime = now();
let BinTime = 1h;
let RunTime = 1h;
let MinThreshold = 3.0;
let GitLabFailedLogins = (GitLabApp
| where FailedLogin == 1
| parse kind=regex Message with "Failed Login: username=" User "ip=" IpAddress
| project TimeGenerated, EventTime, Computer, User, HostName, HostIP, IpAddress);
GitLabFailedLogins
| where EventTime between (ago(LearningPeriod) .. EndLearningTime)
| summarize FailedLoginsCountInBinTime = count() by User, bin(EventTime, BinTime)
| summarize AvgOfFailedLoginsInLearning = avg(FailedLoginsCountInBinTime), StdOfFailedLoginsInLearning = stdev(FailedLoginsCountInBinTime) by User
| extend LearningThreshold = max_of(AvgOfFailedLoginsInLearning, MinThreshold)
| join kind=innerunique ( GitLabFailedLogins
| summarize FailedLoginsCountInRunTime = count() by User, IpAddress, EventTime = bin(EventTime, BinTime) ) on User
| where FailedLoginsCountInRunTime >= LearningThreshold
| project User, IpAddress, EventTime, FailedLoginsCountInRunTime, LearningThreshold
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let LearningPeriod = 7d;
let EndLearningTime = now();
let BinTime = 1h;
let RunTime = 1h;
let MinThreshold = 3.0;
let GitLabFailedLogins is inlined into the numbered stages below.
Stages 1 to 4 define let GitLabFailedLogins as the rule's main pipeline source. Stages 5 to 11 use it.
Stage 1: source
GitLabApp
Stage 2: where
| where FailedLogin == 1
Stage 3: parse
| parse kind=regex Message with "Failed Login: username=" User "ip=" IpAddress
Stage 4: project
| project TimeGenerated, EventTime, Computer, User, HostName, HostIP, IpAddress
Stage 5: where
GitLabFailedLogins
| where EventTime between (ago(LearningPeriod) .. EndLearningTime)
Stage 6: summarize
| summarize FailedLoginsCountInBinTime = count() by User, bin(EventTime, BinTime)
Stage 7: summarize
| summarize AvgOfFailedLoginsInLearning = avg(FailedLoginsCountInBinTime), StdOfFailedLoginsInLearning = stdev(FailedLoginsCountInBinTime) by User
Stage 8: extend
| extend LearningThreshold = max_of(AvgOfFailedLoginsInLearning, MinThreshold)
Stage 9: join
| join kind=innerunique ( GitLabFailedLogins
| summarize FailedLoginsCountInRunTime = count() by User, IpAddress, EventTime = bin(EventTime, BinTime) ) on User
Stage 10: where
where FailedLoginsCountInRunTime >= LearningThreshold
Stage 11: project
project EventTime, FailedLoginsCountInRunTime, IpAddress, LearningThreshold, User
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
FailedLogin | eq |
| field:"FailedLogin" kind:eq value:"1" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventTime | project |
FailedLoginsCountInRunTime | project |
IpAddress | project |
LearningThreshold | project |
User | project |
Hunt for accounts with leaked credentials
#This query searches for accounts where Exposure Management detected leaked credentials. This query is correlated with the IdentityInfo table, mainly because you can easily create a detection of this rule if you would like to.
Telemetry Coverage
Rule Body
IdentityInfo
| summarize arg_max(TimeGenerated, AccountUpn, AccountDisplayName, AccountDomain, CriticalityLevel, DistinguishedName) by AccountObjectId
| join kind=inner (
ExposureGraphNodes
// Get accounts with Leaked Credentials
| where NodeProperties.rawData.hasAdLeakedCredentials == "true" or NodeProperties.rawData.hasLeakedCredentials == "true"
// Get the AAD Object ID
| mv-expand EntityIds
| where EntityIds.type == "AadObjectId"
| extend AccountObjectId = extract('objectid=(.*)', 1, tostring(EntityIds.id))
| extend HasAdLeakedCredentials = tostring(NodeProperties.rawData.hasAdLeakedCredentials),
HasLeakedCredentials = tostring(NodeProperties.rawData.hasLeakedCredentials)
| distinct NodeLabel, AccountObjectId, HasAdLeakedCredentials, HasLeakedCredentials
) on AccountObjectId
Stages and Predicates
Stage 1: source
IdentityInfo
Stage 2: summarize
| summarize arg_max(TimeGenerated, AccountUpn, AccountDisplayName, AccountDomain, CriticalityLevel, DistinguishedName) by AccountObjectId
Stage 3: join
| join kind=inner (
ExposureGraphNodes
| where NodeProperties.rawData.hasAdLeakedCredentials == "true" or NodeProperties.rawData.hasLeakedCredentials == "true"
| mv-expand EntityIds
| where EntityIds.type == "AadObjectId"
| extend AccountObjectId = extract('objectid=(.*)', 1, tostring(EntityIds.id))
| extend HasAdLeakedCredentials = tostring(NodeProperties.rawData.hasAdLeakedCredentials),
HasLeakedCredentials = tostring(NodeProperties.rawData.hasLeakedCredentials)
| distinct NodeLabel, AccountObjectId, HasAdLeakedCredentials, HasLeakedCredentials
) on AccountObjectId
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountObjectId | summarize |
Hunt for local AI Agent activity with Agent Info
#This query allows you to find the activity a local AI Agent performed via the CloudAppEvents table using the AgentsInfo and ExposureGraphNodes table.
References
Telemetry Coverage
Rule Body
// Fill in agent name or one of the Agent IDs you have found
let agent_name = "";
let some_agent_id = "";
AgentsInfo
| where TimeGenerated > ago(1d)
| summarize arg_max(TimeGenerated, *) by AgentId
| where (isempty(some_agent_id) and Name =~ agent_name) or (isempty(agent_name) and * has some_agent_id)
// Take local AI Agents
| where Platform == "LocalAgents"
| distinct Name, Platform, SourceAgentId
// Join with Graph Nodes to get the ObservabilityID
| join kind=inner (
ExposureGraphNodes
| where NodeLabel == "ai-agent"
| where parse_json(NodeProperties).rawData.aiAgentMetadata.platform == "LocalAgents"
| extend SourceAIAgentId = extract("{\"type\":\"SourceAIAgentId\",\"id\":\"([^\"]+)\"}", 1, tostring(EntityIds))
| extend A365RegistryAIAgentId = extract("{\"type\":\"A365RegistryAIAgentId\",\"id\":\"tenantid=([^;]+);titleid=([^\"]+)\"}", 2, tostring(EntityIds))
| project SourceAIAgentId, A365RegistryAIAgentId
) on $left.SourceAgentId == $right.SourceAIAgentId
| extend ExtractedObservabilityID = iff(
A365RegistryAIAgentId matches regex @"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})",
extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, A365RegistryAIAgentId),
A365RegistryAIAgentId
)
| join kind=inner (
CloudAppEvents
| where ActionType in ("InvokeAgent","InferenceCall","ExecuteToolBySDK","ExecuteToolByGateway","ExecuteToolByMCPServer")
// Extract the platformIDs and ObservabilityID
| extend PlatformAgentId = tostring(parse_json(RawEventData)["PlatformAgentId"]),
PlatformTargetAgentId = tostring(parse_json(RawEventData)["PlatformTargetAgentId"])
| extend PlatformId = iff(isempty(PlatformAgentId) and isnotempty(PlatformTargetAgentId), PlatformTargetAgentId, PlatformAgentId)
| extend ObservabilityID = iff(
PlatformId matches regex @"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})",
extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, PlatformId),
PlatformId
)
) on $left.ExtractedObservabilityID == $right.ObservabilityID
Stages and Predicates
Parameters
let agent_name = "";
let some_agent_id = "";
Stage 1: source
AgentsInfo
Stage 2: where
| where TimeGenerated > ago(1d)
Stage 3: summarize
| summarize arg_max(TimeGenerated, *) by AgentId
Stage 4: where
| where (isempty(some_agent_id) and Name =~ agent_name) or (isempty(agent_name) and * has some_agent_id)
Stage 5: where
| where Platform == "LocalAgents"
Stage 6: distinct
| distinct Name, Platform, SourceAgentId
Stage 7: join
| join kind=inner (
ExposureGraphNodes
| where NodeLabel == "ai-agent"
| where parse_json(NodeProperties).rawData.aiAgentMetadata.platform == "LocalAgents"
| extend SourceAIAgentId = extract("{\"type\":\"SourceAIAgentId\",\"id\":\"([^\"]+)\"}", 1, tostring(EntityIds))
| extend A365RegistryAIAgentId = extract("{\"type\":\"A365RegistryAIAgentId\",\"id\":\"tenantid=([^;]+);titleid=([^\"]+)\"}", 2, tostring(EntityIds))
| project SourceAIAgentId, A365RegistryAIAgentId
) on $left.SourceAgentId == $right.SourceAIAgentId
Stage 8: extend
extend ExtractedObservabilityID
ExtractedObservabilityID =A365RegistryAIAgentId matches regex @"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})"extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, A365RegistryAIAgentId)A365RegistryAIAgentIdStage 9: join
| join kind=inner (
CloudAppEvents
| where ActionType in ("InvokeAgent","InferenceCall","ExecuteToolBySDK","ExecuteToolByGateway","ExecuteToolByMCPServer")
| extend PlatformAgentId = tostring(parse_json(RawEventData)["PlatformAgentId"]),
PlatformTargetAgentId = tostring(parse_json(RawEventData)["PlatformTargetAgentId"])
| extend PlatformId = iff(isempty(PlatformAgentId) and isnotempty(PlatformTargetAgentId), PlatformTargetAgentId, PlatformAgentId)
| extend ObservabilityID = iff(
PlatformId matches regex @"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})",
extract(@"(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})", 1, PlatformId),
PlatformId
)
) on $left.ExtractedObservabilityID == $right.ObservabilityID
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | in |
| field:"ActionType" kind:in |
NodeLabel | eq |
| field:"NodeLabel" kind:eq value:"ai-agent" |
NodeProperties.rawData.aiAgentMetadata.platform | eq |
| field:"NodeProperties.rawData.aiAgentMetadata.platform" kind:eq value:"LocalAgents" |
Platform | eq |
| field:"Platform" kind:eq value:"LocalAgents" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AgentId | summarize |
ExtractedObservabilityID | extend |
Imperva - Critical severity event not blocked
#'Detects when critical severity event was not blocked.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 4d365217-f96a-437c-9c57-53594fa261c3
name: Imperva - Critical severity event not blocked
description: |
'Detects when critical severity event was not blocked.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: ImpervaWAFCloudAPI
dataTypes:
- ImpervaWAFCloud
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
ImpervaWAFCloud
| where EventSeverity =~ 'CRITICAL'
| where DvcAction !startswith 'REQ_BLOCKED' or DvcAction !startswith 'REQ_BAD_'
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
ImpervaWAFCloud
Stage 2: where
| where EventSeverity =~ 'CRITICAL'
Stage 3: where
| where DvcAction !startswith 'REQ_BLOCKED' or DvcAction !startswith 'REQ_BAD_'
Stage 4: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventSeverity | eq |
| field:"EventSeverity" kind:eq value:"CRITICAL" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
Oracle - Multiple server errors from single IP
#'Detects multiple server errors from one source in short timeframe'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Impact |
Rule Body
id: 268f4fde-5740-11ec-bf63-0242ac130002
name: Oracle - Multiple server errors from single IP
description: |
'Detects multiple server errors from one source in short timeframe'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- OracleWebLogicServer_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
- InitialAccess
relevantTechniques:
- T1498
- T1190
- T1133
query: |
let threshold = 100;
OracleWebLogicServerEvent
| where HttpStatusCode >= 500 and HttpStatusCode <= 599
| summarize MultipleServerErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
| where MultipleServerErrors > threshold
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 100;
Stage 1: source
OracleWebLogicServerEvent
Stage 2: where
| where HttpStatusCode >= 500 and HttpStatusCode <= 599
Stage 3: summarize
| summarize MultipleServerErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
Stage 4: where
| where MultipleServerErrors > threshold
Stage 5: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MultipleServerErrors | summarize |
SrcIpAddr | summarize |
IPCustomEntity | extend |
Pathlock TDnR - Authorization Check Value Changes (SU24)
#Detects changes to authorization check values in the SU24 table in SAP, forwarded by Pathlock Threat Detection and Response. SU24 defines which authorization objects are checked for each transaction; unauthorized changes can effectively disable authorization checks and allow privilege escalation without modifying roles or profiles.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Defense Impairment |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c62
name: Pathlock TDnR - Authorization Check Value Changes (SU24)
kind: Scheduled
description: >-
Detects changes to authorization check values in the SU24 table in SAP, forwarded by Pathlock
Threat Detection and Response. SU24 defines which authorization objects are checked for each
transaction; unauthorized changes can effectively disable authorization checks and allow
privilege escalation without modifying roles or profiles.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- PrivilegeEscalation
relevantTechniques:
- T1562
- T1548
query: |
Pathlock_TDnR_CL
| where DataSource == "SU24_CHANGELOG"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "SU24_CHANGELOG"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"SU24_CHANGELOG" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
Ping Federate - Abnormal password resets for user
#'Detects multiple password reset for user.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Privilege Escalation |
Rule Body
id: 6145efdc-4724-42a6-9756-5bd1ba33982e
name: Ping Federate - Abnormal password resets for user
description: |
'Detects multiple password reset for user.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1078
- T1098
- T1134
query: |
let threshold = 10;
PingFederateEvent
| where EventType =~ 'PWD_RESET'
| summarize count() by DstUserName
| where count_ > threshold
| extend AccountCustomEntity = DstUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 10;
Stage 1: source
PingFederateEvent
Stage 2: where
| where EventType =~ 'PWD_RESET'
Stage 3: summarize
| summarize count() by DstUserName
Stage 4: where
| where count_ > threshold
Stage 5: extend
| extend AccountCustomEntity = DstUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"PWD_RESET" |
count_ | gt |
| field:"count_" kind:gt value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstUserName | summarize |
AccountCustomEntity | extend |
SailPointIdentityNowEventTypeTechnicalName
#'Created to detect new threat events from the data in SailPointIDN_Events.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 2151e8ea-4838-4c74-be12-4d6a950dde7a
name: SailPointIdentityNowEventTypeTechnicalName
description: |
'Created to detect new threat events from the data in SailPointIDN_Events.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SailPointIdentityNow
dataTypes:
- SailPointIDN_Events
- connectorId: SailPointIdentityNowConnector
dataTypes:
- SailPointIDN_Events
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1133
query: |
declare query_parameters(lbperiod:timespan = 14d, technicalName:string = "ENTITLEMENT_ADD_FAILED", type:string = 'ACCESS_ITEM');
SailPointIDN_Events
| where TimeGenerated > ago(lbperiod)
| where EventType == type
| where TechnicalName == technicalName
| where Status == "FAILED"
| sort by Created
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: TechnicalName
version: 1.1.0
kind: Scheduled
Stages and Predicates
Stage 1: source
declare query_parameters(lbperiod:timespan = 14d, technicalName:string = "ENTITLEMENT_ADD_FAILED", type:string = 'ACCESS_ITEM');
SailPointIDN_Events
Stage 2: where
| where TimeGenerated > ago(lbperiod)
Stage 3: where
| where EventType == type
Stage 4: where
| where TechnicalName == technicalName
Stage 5: where
| where Status == "FAILED"
Stage 6: sort
| sort by Created
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Status | eq |
| field:"Status" kind:eq value:"FAILED" |
Sdelete deployed via GPO and run recursively
#This query looks for the Sdelete process being run recursively after being deployed to a host via GPO. Attackers could use this technique to deploy Sdelete to multiple host and delete data on them.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 4688: A new process has been created. |
Rule Body
id: d9f28fdf-abc8-4f1a-a7e7-1aaec87a2fc5
name: Sdelete deployed via GPO and run recursively
description: |
'This query looks for the Sdelete process being run recursively after being deployed to a host via GPO. Attackers could use this technique to deploy Sdelete to multiple host and delete data on them.'
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Impact
relevantTechniques:
- T1485
tags:
-
query: |
SecurityEvent
| where EventID == 4688
| where Process =~ "svchost.exe"
| where CommandLine has "-k GPSvcGroup" or CommandLine has "-s gpsvc"
| extend timekey = bin(TimeGenerated, 1m)
| project timekey, NewProcessId, Computer
| join kind=inner (SecurityEvent
| where EventID == 4688
| where Process =~ "sdelete.exe" or CommandLine has "sdelete"
| where ParentProcessName endswith "svchost.exe"
| where CommandLine has_all ("-s", "-r")
| extend newProcess = Process
| extend timekey = bin(TimeGenerated, 1m)
) on $left.NewProcessId == $right.ProcessId, 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: Account
fieldMappings:
- identifier: FullName
columnName: TargetAccount
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountNTDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventID == 4688
Stage 3: where
| where Process =~ "svchost.exe"
Stage 4: where
| where CommandLine has "-k GPSvcGroup" or CommandLine has "-s gpsvc"
Stage 5: extend
| extend timekey = bin(TimeGenerated, 1m)
Stage 6: project
| project timekey, NewProcessId, Computer
Stage 7: join
| join kind=inner (SecurityEvent
| where EventID == 4688
| where Process =~ "sdelete.exe" or CommandLine has "sdelete"
| where ParentProcessName endswith "svchost.exe"
| where CommandLine has_all ("-s", "-r")
| extend newProcess = Process
| extend timekey = bin(TimeGenerated, 1m)
) on $left.NewProcessId == $right.ProcessId, 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 |
|---|---|---|---|
CommandLine | match |
| field:"CommandLine" kind:match |
EventID | eq |
| field:"EventID" kind:eq value:"4688" |
ParentProcessName | ends_with |
| field:"ParentProcessName" kind:ends_with value:"svchost.exe" |
Process | eq |
| field:"Process" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | project |
NewProcessId | project |
timekey | project |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
AccountNTDomain | extend |
AccountName | extend |
Service Provider Updated
#Detects when service provider settings are updated in Veeam Backup & Replication.
Rule Body
id: 35fb4771-9b8f-47f7-bd5b-3085d584f7ce
name: Service Provider Updated
description: Detects when service provider settings are updated in Veeam Backup &
Replication.
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 == 27500\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 == 27500
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:"27500" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
Sonrai Ticket Updated
#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.
'Checks if Sonrai tickets have been updated. It uses the action type to check if a ticket has been updated'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Defense Impairment | |
| Credential Access | |
| Discovery | |
| Lateral Movement | |
| Collection | |
| Command & Control | |
| Exfiltration | |
| Impact |
Rule Body
id: af9b8eb1-a8ef-40aa-92a4-1fc73a1479c7
name: Sonrai Ticket Updated
description: |
'Checks if Sonrai tickets have been updated.
It uses the action type to check if a ticket has been updated'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SonraiDataConnector
dataTypes:
- Sonrai_Tickets_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- CommandAndControl
- CredentialAccess
- DefenseEvasion
- Discovery
- Execution
- Exfiltration
- Impact
- InitialAccess
- LateralMovement
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1566
- T1059
- T1547
- T1548
- T1562
- T1003
- T1087
- T1021
- T1119
- T1071
- T1041
- T1499
query: |
Sonrai_Tickets_CL
| where action_d == 5
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: digest_criticalResourceName_s
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
ticketName: digest_title_s
ticketStatus: digest_status_s
ticketSeverity: digest_severityCategory_s
resourceLabel: digest_resourceLabel_s
resourceType: digest_resourceType_s
criticalResource: digest_criticalResourceName_s
ticketOrg: digest_org_s
alertDetailsOverride:
alertSeverityColumnName: digest_severityCategory_s
alertDisplayNameFormat: Updated - {{digest_ticketSrn_s}} - {{digest_ticketKeyName_s}}
alertDescriptionFormat: digest_ticketKeyDescription_s
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
Sonrai_Tickets_CL
Stage 2: where
| where action_d == 5
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action_d | eq |
| field:"action_d" kind:eq value:"5" |
Tenant Replica Started
#Detects when a tenant replica starts.
Rule Body
id: c4d891cd-4b22-419c-8f40-7603eb1cc3a3
name: Tenant Replica Started
description: Detects when a tenant replica starts.
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 == 26800\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 == 26800
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:"26800" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
Tomcat - Multiple client errors from single IP address
#'Detects multiple client errors from one source in short timeframe'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 4fa66058-4870-11ec-81d3-0242ac130003
name: Tomcat - Multiple client errors from single IP address
description: |
'Detects multiple client errors from one source in short timeframe'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- Tomcat_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let threshold = 100;
TomcatEvent
| where HttpStatusCode >= 400 and HttpStatusCode <= 499
| summarize MultipleClientErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
| where MultipleClientErrors > threshold
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 100;
Stage 1: source
TomcatEvent
Stage 2: where
| where HttpStatusCode >= 400 and HttpStatusCode <= 499
Stage 3: summarize
| summarize MultipleClientErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
Stage 4: where
| where MultipleClientErrors > threshold
Stage 5: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MultipleClientErrors | summarize |
SrcIpAddr | summarize |
IPCustomEntity | extend |
Valimail Enforce - DMARC Policy Weakened to None
#This query searches for DMARC policies changed to 'none', which disables enforcement and leaves the domain vulnerable to spoofing and phishing attacks.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Defense Impairment |
Rule Body
id: 44ec1fa4-a502-41ae-879a-3aad3557edce
name: Valimail Enforce - DMARC Policy Weakened to None
description: |
This query searches for DMARC policies changed to 'none', which disables enforcement
and leaves the domain vulnerable to spoofing and phishing attacks.
severity: High
status: Available
requiredDataConnectors:
- connectorId: ValimailEnforce
dataTypes:
- ValimailEnforceEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- InitialAccess
relevantTechniques:
- T1566
- T1562
query: |
ValimailEnforceEvents_CL
| where EventSeverity == "High"
| where EventType == "dmarc_policy_set_to_none"
| summarize
EventCount = count(),
FirstSeen = min(PerformedAt),
LastSeen = max(PerformedAt),
Changes = make_set(EventChange)
by Subject, User, EventCategory
| extend
AccountName = tostring(split(User, "@")[0]),
AccountDomain = tostring(split(User, "@")[1]),
DomainName = Subject
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountDomain
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DomainName
alertDetailsOverride:
alertDisplayNameFormat: "DMARC policy set to NONE on domain {{Subject}} by {{User}}"
alertDescriptionFormat: |
The DMARC policy for domain '{{Subject}}' was set to 'none' by '{{User}}',
disabling email authentication enforcement. This may expose the domain to spoofing.
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1d
matchingMethod: Selected
groupByEntities:
- DNS
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
ValimailEnforceEvents_CL
Stage 2: where
| where EventSeverity == "High"
Stage 3: where
| where EventType == "dmarc_policy_set_to_none"
Stage 4: summarize
| summarize
EventCount = count(),
FirstSeen = min(PerformedAt),
LastSeen = max(PerformedAt),
Changes = make_set(EventChange)
by Subject, User, EventCategory
Stage 5: extend
| extend
AccountName = tostring(split(User, "@")[0]),
AccountDomain = tostring(split(User, "@")[1]),
DomainName = Subject
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventSeverity | eq |
| field:"EventSeverity" kind:eq value:"High" |
EventType | eq |
| field:"EventType" kind:eq value:"dmarc_policy_set_to_none" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Changes | summarize |
EventCategory | summarize |
EventCount | summarize |
FirstSeen | summarize |
LastSeen | summarize |
Subject | summarize |
User | summarize |
AccountDomain | extend |
AccountName | extend |
DomainName | extend |
ZeroFox Alerts - Medium Severity Alerts
#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 medium severity alerts from ZeroFox'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Resource Development | |
| Initial Access |
Rule Body
id: a6496de5-911b-4199-b7db-d34ac9d70df3
name: ZeroFox Alerts - Medium Severity Alerts
description: |
'Detects medium severity alerts from ZeroFox'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: ZeroFox_Alert_Polling
dataTypes:
- ZeroFoxAlertPoller_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- ResourceDevelopment
- InitialAccess
relevantTechniques:
- T1583
- T1586
- T1566
query: |
ZeroFoxAlertPoller_CL
| where Severity in (4)
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: entity_name_s
eventGroupingSettings:
aggregationKind: AlertPerResult
kind: Scheduled
version: 1.0.0
Stages and Predicates
Stage 1: source
ZeroFoxAlertPoller_CL
Stage 2: where
| where Severity in (4)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Severity | in |
| field:"Severity" kind:in value:"4" |