Detection rules › Kusto
Semperis DSP Failed Logons
'Alerts when there are failed logons in the DSP system.'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1078 Valid Accounts |
| Credential Access | T1110 Brute Force |
Rule body kusto
id: "0e105444-fe13-4ce6-9239-21880076a3f9"
name: Semperis DSP Failed Logons
description: |
'Alerts when there are failed logons in the DSP system.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SemperisDSP
dataTypes:
- dsp_parser
queryFrequency: 30m
queryPeriod: 30m
tactics:
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1078
- T1110
triggerOperator: gt
triggerThreshold: 0
query: |
SecurityEvent
| where EventSourceName == 'Semperis-Operation-Log' and EventID == 20002
| sort by TimeGenerated desc
| extend p1Xml = parse_xml(EventData).EventData.Data
| mv-expand bagexpansion=array p1Xml
| evaluate bag_unpack(p1Xml)
| extend Name=column_ifexists('@Name', ''), Value=column_ifexists('#text', '')
| evaluate pivot(Name, any(Value), TimeGenerated, EventSourceName, Channel, Computer, Level, EventLevelName, EventID, Task, Type, _ResourceId)
| extend det = column_ifexists('details', '')
| parse det with * "Trustee Name: " TrusteeName " Correlation ID: " * " Source: " HostIP "WebSite Target" *
| extend host = tostring(HostIP)
| extend HostIP = trim_end(":", HostIP)
| project TimeGenerated, TrusteeName, HostIP, _ResourceId
| extend NTDomain = tostring(split(TrusteeName, '\\', 0)[0]), Name = tostring(split(TrusteeName, '\\', 1)[0])
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: HostIP
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: NTDomain
columnName: NTDomain
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Failed Logon -- Alert from Semperis Directory Services Protector
alertDescriptionFormat: A failed logon was detected to the DSP system.
version: 2.0.7
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventSourceName == 'Semperis-Operation-Log' and EventID == 20002
Stage 3: sort
| sort by TimeGenerated desc
Stage 4: extend
| extend p1Xml = parse_xml(EventData).EventData.Data
Stage 5: mv-expand
| mv-expand bagexpansion=array p1Xml
Stage 6: evaluate
| evaluate bag_unpack(p1Xml)
Stage 7: extend
| extend Name=column_ifexists('@Name', ''), Value=column_ifexists('#text', '')
Stage 8: evaluate
| evaluate pivot(Name, any(Value), TimeGenerated, EventSourceName, Channel, Computer, Level, EventLevelName, EventID, Task, Type, _ResourceId)
Stage 9: extend
| extend det = column_ifexists('details', '')
Stage 10: parse
| parse det with * "Trustee Name: " TrusteeName " Correlation ID: " * " Source: " HostIP "WebSite Target" *
Stage 11: extend
| extend host = tostring(HostIP)
Stage 12: extend
| extend HostIP = trim_end(":", HostIP)
Stage 13: project
| project TimeGenerated, TrusteeName, HostIP, _ResourceId
Stage 14: extend
| extend NTDomain = tostring(split(TrusteeName, '\\', 0)[0]), Name = tostring(split(TrusteeName, '\\', 1)[0])
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
EventID | eq |
|
EventSourceName | eq |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
HostIP | project |
TimeGenerated | project |
TrusteeName | project |
_ResourceId | project |
NTDomain | extend |
Name | extend |