Detection rules › Kusto
Kusto rules: 1f
Application Group Deleted
#Detects when an application group is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: fa8d692d-5b00-4a6c-99b3-30b4710efa59
name: Application Group Deleted
description: Detects when an application group is deleted from Veeam Backup & Replication.
This might indicate unauthorized removal of critical components.
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 == 30500\n| extend Name = extract(\"\
Name=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ UserName = user,\n [\"Application Group Name\"] = Name,\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 == 30500
Stage 3: extend
| extend Name = extract("Name=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
["Application Group Name"] = Name,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"30500" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Application Group Name | project |
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
Cisco Duo - Multiple user login failures
#'Detects when multiple user login failures occurs.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 034f62b6-df51-49f3-831f-1e4cfd3c40d2
name: Cisco Duo - Multiple user login failures
description: |
'Detects when multiple user login failures occurs.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CiscoDuoAuthConnectorDefinition
dataTypes:
- DuoAuthentication_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
let threshold = 10;
DuoAuthentication_CL
| where Result in~ ('denied', 'failure')
| summarize count() by UserName, bin(TimeGenerated, 10m)
| where count_ > threshold
| extend AccountCustomEntity = UserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 10;
Stage 1: source
DuoAuthentication_CL
Stage 2: where
| where Result in~ ('denied', 'failure')
Stage 3: summarize
| summarize count() by UserName, bin(TimeGenerated, 10m)
Stage 4: where
| where count_ > threshold
Stage 5: extend
| extend AccountCustomEntity = UserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Result | in |
| field:"Result" kind:in |
count_ | gt |
| field:"count_" kind:gt value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserName | summarize |
AccountCustomEntity | extend |
Claroty - Suspicious file transfer
#Detects Claroty events where EventOriginalType or EventType contains Suspicious File Transfer and maps the destination IP as the primary entity for triage.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery | |
| Exfiltration |
Rule Body
id: 5cf35bad-677f-4c23-8927-1611e7ff6f28
name: Claroty - Suspicious file transfer
description: 'Detects Claroty events where EventOriginalType or EventType contains Suspicious File Transfer and maps the destination
IP as the primary entity for triage.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
- Exfiltration
relevantTechniques:
- T1018
- T1020
query: |
ClarotyEvent
| where EventOriginalType has 'Suspicious File Transfer' or EventType has 'Suspicious File Transfer'
| project TimeGenerated, DstIpAddr, EventOriginalType, EventType
| extend IPCustomEntity = DstIpAddr
| project TimeGenerated, DstIpAddr, EventOriginalType, EventType, IPCustomEntity
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
alertDetailsOverride:
alertDisplayNameFormat: Claroty suspicious file transfer to {{DstIpAddr}}
alertDescriptionFormat: Claroty flagged suspicious file transfer activity to {{DstIpAddr}} using event type {{EventType}}
customDetails:
EventOriginalType: EventOriginalType
EventType: EventType
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
ClarotyEvent
Stage 2: where
| where EventOriginalType has 'Suspicious File Transfer' or EventType has 'Suspicious File Transfer'
Stage 3: project
| project TimeGenerated, DstIpAddr, EventOriginalType, EventType
Stage 4: extend
| extend IPCustomEntity = DstIpAddr
Stage 5: project
| project TimeGenerated, DstIpAddr, EventOriginalType, EventType, IPCustomEntity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventOriginalType | match |
| field:"EventOriginalType" kind:match value:"Suspicious File Transfer" |
EventType | match |
| field:"EventType" kind:match value:"Suspicious File Transfer" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstIpAddr | project |
EventOriginalType | project |
EventType | project |
IPCustomEntity | project |
TimeGenerated | project |
Cloudflare - Unexpected POST requests
#'Detects post requests to unusual extensions.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Command & Control |
Rule Body
id: 7313352a-09f6-4a84-88bd-6f17f1cbeb88
name: Cloudflare - Unexpected POST requests
description: |
'Detects post requests to unusual extensions.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CloudflareDataConnector
dataTypes:
- Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- CommandAndControl
relevantTechniques:
- T1505
- T1071
query: |
Cloudflare
| where HttpRequestMethod in~ ('POST', 'PUT')
| where tostring(HttpStatusCode) startswith '2'
| where DstBytes != 0 or SrcBytes != 0
| extend fe = extract(@'.*(\.\w+)$', 1, ClientRequestURI)
| where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
Cloudflare
Stage 2: where
| where HttpRequestMethod in~ ('POST', 'PUT')
Stage 3: where
| where tostring(HttpStatusCode) startswith '2'
Stage 4: where
| where DstBytes != 0 or SrcBytes != 0
Stage 5: extend
| extend fe = extract(@'.*(\.\w+)$', 1, ClientRequestURI)
Stage 6: where
| where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DstBytes | ne |
| field:"DstBytes" kind:ne value:"0" |
HttpRequestMethod | in |
| field:"HttpRequestMethod" kind:in |
HttpStatusCode | starts_with |
| field:"HttpStatusCode" kind:starts_with value:"2" |
SrcBytes | ne |
| field:"SrcBytes" kind:ne value:"0" |
fe | in |
| field:"fe" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
fe | extend |
CloudNGFW By Palo Alto Networks - Threat signatures from Unusual IP addresses
#'Identifies Palo Alto Threat signatures from unusual IP addresses which are not historically seen. This detection is also leveraged and required for MDE and PAN Fusion scenario https://docs.microsoft.com/Azure/sentinel/fusion-scenario-reference#network-request-to-tor-anonymization-service-followed-by-anomalous-traffic-flagged-by-palo-alto-networks-firewall'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery | |
| Command & Control | |
| Exfiltration |
Rule Body
id: 89a86f70-615f-4a79-9621-6f68c50f365f
name: CloudNGFW By Palo Alto Networks - Threat signatures from Unusual IP addresses
description: |
'Identifies Palo Alto Threat signatures from unusual IP addresses which are not historically seen.
This detection is also leveraged and required for MDE and PAN Fusion scenario
https://docs.microsoft.com/Azure/sentinel/fusion-scenario-reference#network-request-to-tor-anonymization-service-followed-by-anomalous-traffic-flagged-by-palo-alto-networks-firewall'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureCloudNGFWByPaloAltoNetworks
dataTypes:
- fluentbit_CL
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
- Exfiltration
- CommandAndControl
relevantTechniques:
- T1046
- T1030
- T1071.001
tags:
- Fusion
query: |
let starttime = 7d;
let endtime = 1d;
let timeframe = 1h;
let HistThreshold = 25;
let CurrThreshold = 10;
let HistoricalThreats = fluentbit_CL
| where ident_s == "THREAT"
| extend message = parse_json(Message)
| where isnotempty(message.src_ip)
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
| where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
| extend src_ip = tostring(message.src_ip)
| summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s;
let CurrentHourThreats = fluentbit_CL
| where ident_s == "THREAT"
| extend message = parse_json(Message)
| where isnotempty(message.src_ip)
| where TimeGenerated > ago(timeframe)
| where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
| extend src_ip = tostring(message.src_ip)
| summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s;
CurrentHourThreats
| where TotalEvents < CurrThreshold
| join kind = leftanti (HistoricalThreats
| where TotalEvents > HistThreshold) on src_ip
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: src_ip
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let starttime = 7d;
let endtime = 1d;
let timeframe = 1h;
let HistThreshold = 25;
let CurrThreshold = 10;
let CurrentHourThreats is inlined into the numbered stages below.
Let Binding: HistoricalThreats
let HistoricalThreats = fluentbit_CL
| where ident_s == "THREAT"
| extend message = parse_json(Message)
| where isnotempty(message.src_ip)
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
| where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
| extend src_ip = tostring(message.src_ip)
| summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s;
Stages 1 to 8 define let CurrentHourThreats as the rule's main pipeline source. Stages 9 to 10 use it.
Stage 1: source
fluentbit_CL
Stage 2: where
| where ident_s == "THREAT"
Stage 3: extend
| extend message = parse_json(Message)
Stage 4: where
| where isnotempty(message.src_ip)
Stage 5: where
| where TimeGenerated > ago(timeframe)
Stage 6: where
| where message.sub_type in ('spyware', 'scan', 'file', 'vulnerability', 'flood', 'packet', 'virus','wildfire', 'wildfire-virus')
Stage 7: extend
| extend src_ip = tostring(message.src_ip)
Stage 8: summarize
| summarize TotalEvents = count(), ThreatTypes = make_set(message.sub_type), DestinationIpList = make_set(message.dst), FirstSeen = min(TimeGenerated) , LastSeen = max(TimeGenerated) by src_ip, tostring(message.action), FirewallName_s
Stage 9: where
CurrentHourThreats
| where TotalEvents < CurrThreshold
Stage 10: join (negated)
join kind=leftanti (HistoricalThreats) on src_ip
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
TotalEvents | lt |
| field:"TotalEvents" kind:lt value:"10" |
ident_s | eq |
| field:"ident_s" kind:eq value:"THREAT" |
message.src_ip | is_not_null | field:"message.src_ip" kind:is_not_null | |
message.sub_type | in |
| field:"message.sub_type" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TotalEvents | gt | 25 | excludes:TotalEvents field:"TotalEvents" value:"25" |
ident_s | eq | THREAT | excludes:ident_s field:"ident_s" value:"THREAT" |
message.src_ip | is_not_null | excludes:message.src_ip | |
message.sub_type | in | file, flood, packet, scan, spyware, virus, vulnerability, wildfire, wildfire-virus | excludes:message.sub_type |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DestinationIpList | summarize |
FirewallName_s | summarize |
FirstSeen | summarize |
LastSeen | summarize |
ThreatTypes | summarize |
TotalEvents | summarize |
src_ip | summarize |
Corelight - Multiple files sent over HTTP with abnormal requests
#'Detects sources sending multiple compressed files greater than 10MBs sent over HTTP in a short amount of time.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: 7226d37b-50ee-4e3b-9f80-5b74080d8f2c
name: Corelight - Multiple files sent over HTTP with abnormal requests
description: |
'Detects sources sending multiple compressed files greater than 10MBs sent over HTTP in a short amount of time.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Corelight
dataTypes:
- corelight_http
- corelight_http_red
- corelight_http2
- corelight_http_agg
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1030
query: |
let threshold = 25;
let fl_size_threshold = 10000000;
union isfuzzy=true corelight_http, corelight_http_agg
| where method in~ ('POST', 'PUT')
| where isempty(referrer)
| where tolong(request_body_len) > fl_size_threshold
| summarize count() by uri, id_orig_h, bin(TimeGenerated, 15m)
| where count_ > threshold
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: id_orig_h
version: 2.2.0
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 25;
let fl_size_threshold = 10000000;
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: corelight_http, corelight_http_agg
Leg 1: corelight_http
Leg 2: corelight_http_agg
Applied to the Combined Result
| where method in~ ('POST', 'PUT') | where isempty(referrer) | where tolong(request_body_len) > fl_size_threshold | summarize count() by uri, id_orig_h, bin(TimeGenerated, 15m) | where count_ > threshold
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
count_ | gt |
| field:"count_" kind:gt value:"25" |
method | in |
| field:"method" kind:in |
referrer | is_null | field:"referrer" kind:is_null | |
request_body_len | gt |
| field:"request_body_len" kind:gt value:"10000000" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
id_orig_h | summarize |
uri | summarize |
Cyble Vision Alerts Discovered Subdomain
#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.
'Triggers when a new subdomain is detected for a monitored keyword/domain.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: 7a0f79cc-8d28-44b5-ac1e-6176565bb7b8
name: Cyble Vision Alerts Discovered Subdomain
description: |
'Triggers when a new subdomain is detected for a monitored keyword/domain.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- Reconnaissance
relevantTechniques:
- T1595
query: |
Alerts_subdomains
| where Service == "subdomains"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: New Subdomain Detected {{SD_Subdomain}}
alertDescriptionFormat: |
A new subdomain was discovered for monitored domain/keyword. Keyword {{SD_KeywordName}} Bucket {{SD_BucketName}} Severity {{MappedSeverity}} This may represent new attack surface exposure or infrastructure expansion.
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
SD_Subdomain: SD_Subdomain
SD_KeywordName: KeywordName
SD_EntityID: EntityID
SD_BucketName: BucketName
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: SD_Subdomain
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_subdomains
Stage 2: where
| where Service == "subdomains"
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:"subdomains" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
Dev-0530 File Extension Rename
#Dev-0530 actors are known to encrypt the contents of the victims device as well as renaming the file extensions. This query looks for the creation of files with .h0lyenc extension or presence of ransom note.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
Rule Body
id: d82eb796-d1eb-43c8-a813-325ce3417cef
name: Dev-0530 File Extension Rename
description: |
'Dev-0530 actors are known to encrypt the contents of the victims device as well as renaming the file extensions. This query looks for the creation of files with .h0lyenc extension or presence of ransom note.'
severity: High
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceFileEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1486
tags:
- Dev-0530
- Schema: ASIMFileEvent
SchemaVersion: 0.1.0
query: |
union isfuzzy=true
(DeviceFileEvents
| where ActionType == "FileCreated"
| where FileName endswith ".h0lyenc" or FolderPath == "C:\\FOR_DECRYPT.html"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated)
by
AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain,
DeviceName,
Type,
InitiatingProcessId,
FileName,
FolderPath,
EventType = ActionType,
Commandline = InitiatingProcessCommandLine,
InitiatingProcessFileName,
InitiatingProcessSHA256,
FileHashCustomEntity = SHA256,
AlgorithmCustomEntity = "SHA256"
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
),
(imFileEvent
| where EventType == "FileCreated"
| where TargetFilePath endswith ".h0lyenc" or TargetFilePath == "C:\\FOR_DECRYPT.html"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated)
by
ActorUsername,
DvcHostname,
DvcDomain,
DvcId,
Type,
EventType,
FileHashCustomEntity = TargetFileSHA256,
Hash,
TargetFilePath,
Commandline = ActingProcessCommandLine,
AlgorithmCustomEntity = "SHA256"
| extend AccountName = tostring(split(ActorUsername, @'\')[1]), AccountDomain = tostring(split(ActorUsername, @'\')[0])
| extend HostName = DvcHostname, HostNameDomain = DvcDomain
| extend DeviceName = strcat(DvcHostname, ".", DvcDomain )
)
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUserName
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: AlgorithmCustomEntity
- identifier: Value
columnName: FileHashCustomEntity
version: 1.1.1
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: DeviceFileEvents, imFileEvent
Leg 1: DeviceFileEvents
DeviceFileEvents
| where ActionType == "FileCreated"
| where FileName endswith ".h0lyenc" or FolderPath == "C:\\FOR_DECRYPT.html"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated)
by
AccountName = InitiatingProcessAccountName, AccountDomain = InitiatingProcessAccountDomain,
DeviceName,
Type,
InitiatingProcessId,
FileName,
FolderPath,
EventType = ActionType,
Commandline = InitiatingProcessCommandLine,
InitiatingProcessFileName,
InitiatingProcessSHA256,
FileHashCustomEntity = SHA256,
AlgorithmCustomEntity = "SHA256"
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
Leg 2: imFileEvent
imFileEvent
| where EventType == "FileCreated"
| where TargetFilePath endswith ".h0lyenc" or TargetFilePath == "C:\\FOR_DECRYPT.html"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated)
by
ActorUsername,
DvcHostname,
DvcDomain,
DvcId,
Type,
EventType,
FileHashCustomEntity = TargetFileSHA256,
Hash,
TargetFilePath,
Commandline = ActingProcessCommandLine,
AlgorithmCustomEntity = "SHA256"
| extend AccountName = tostring(split(ActorUsername, @'\')[1]), AccountDomain = tostring(split(ActorUsername, @'\')[0])
| extend HostName = DvcHostname, HostNameDomain = DvcDomain
| extend DeviceName = strcat(DvcHostname, ".", DvcDomain )
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"FileCreated" |
EventType | eq |
| field:"OperationName" kind:eq value:"FileCreated" |
FileName | ends_with |
| field:"file_name" kind:ends_with value:".h0lyenc" |
FolderPath | eq |
| field:"TargetFilename" kind:eq value:"C:\FOR_DECRYPT.html" |
TargetFilePath | ends_with |
| field:"TargetFilename" kind:ends_with value:".h0lyenc" |
TargetFilePath | eq |
| field:"TargetFilename" kind:eq value:"C:\FOR_DECRYPT.html" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorUsername | summarize |
AlgorithmCustomEntity | summarize |
Commandline | summarize |
DvcDomain | summarize |
DvcHostname | summarize |
DvcId | summarize |
EndTime | summarize |
EventType | summarize |
FileHashCustomEntity | summarize |
Hash | summarize |
StartTime | summarize |
TargetFilePath | summarize |
Type | summarize |
AccountDomain | extend |
AccountName | extend |
HostName | extend |
HostNameDomain | extend |
DeviceName | extend |
DLL Hijacking - HijackLibs
#Below query detects DLL Hijacking that involves the DLLs and Processes shared by Wietze in the HijackLibs repo. False Positives(especially zoom.exe) and some false negatives might happen since the query is prepared in a semi-automated way. NOTE: The query uses the HijakLibs data as of 2022/09/06 19:20. It will be updated on a regular basis.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth | No specific technique |
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceImageLoadEvents action any: Image load |
Rule Body
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Query parameters:
let HijackLibs = externaldata(DLLName:string, ExpectedLocation:string, Process: string )
[@"https://github.com/Cyb3r-Monk/Threat-Hunting-and-Detection/raw/main/Defense%20Evasion/HijackLibs.csv"]
with (format="csv", ignoreFirstRecord=True);
//
HijackLibs
| join hint.strategy=shuffle kind=rightsemi(
DeviceImageLoadEvents
| extend DLLDir = case(FolderPath startswith "C:\\windows\\system32\\driverstore\\filerepository\\prnms002.inf", replace_regex(tolower(FolderPath), @'c:\\windows\\system32\\driverstore\\filerepository\\prnms002\.inf_.*\\amd64', @'%SYSTEM32%\\driverstore\\filerepository\\prnms002.inf_%VERSION%\\amd64'),
FolderPath has_all ('Windows Kits', 'bin', 'x86'), '%PROGRAMFILES%\\windows kits\\10\\bin\\%VERSION%\\x86',
FolderPath has_all ('Windows Kits', 'bin', 'x64'), '%PROGRAMFILES%\\windows kits\\10\\bin\\%VERSION%\\x64',
FolderPath has_all ('Windows Kits', 'bin', 'arm64'), '%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\arm64',
FolderPath has_all ('Program Files', 'Edge', 'Application'), tolower('%PROGRAMFILES%\\microsoft\\edge\\application\\%VERSION%'),
FolderPath has_all ('Program Files', 'Microsoft Office', 'root'), tolower('%PROGRAMFILES%\\microsoft office\\root\\office%VERSION%'),
FolderPath contains '\\Microsoft Office\\Office', tolower('%PROGRAMFILES%\\Microsoft Office\\OFFICE%VERSION%'),
FolderPath has_all ('ProgramData', 'Microsoft', 'Windows Defender', 'Platform'), tolower('%PROGRAMDATA%\\Microsoft\\Windows Defender\\Platform\\%VERSION%'),
FolderPath startswith "C:\\Program Files (x86)", tolower(replace_string(tolower(FolderPath), 'c:\\program files (x86)', '%programfiles%')),
FolderPath startswith "C:\\Program Files", tolower(replace_string(tolower(FolderPath), 'c:\\program files', '%programfiles%')),
FolderPath startswith "C:\\WINDOWS\\SysWow64", tolower(replace_string(tolower(FolderPath), 'c:\\windows\\syswow64', '%syswow64%')),
FolderPath startswith "C:\\WINDOWS\\System32", tolower(replace_string(tolower(FolderPath), 'c:\\windows\\system32', '%system32%')),
FolderPath startswith "C:\\ProgramData", tolower(replace_string(tolower(FolderPath), 'c:\\programdata', '%programdata%')),
FolderPath has "AppData\\Local", tolower(replace_regex(tolower(FolderPath), @'c:\\users\\.*\\appdata\\local', '%localappdata%')),
FolderPath has "AppData\\Roaming", tolower(replace_regex(tolower(FolderPath), @'c:\\users\\.*\\appdata\\roaming', '%appdata%')),
tolower(FolderPath)),
Process = case( InitiatingProcessFolderPath has_all ('WindowsApps', 'msteams.exe'), tolower('%PROGRAMFILES%\\WindowsApps\\MicrosoftTeams%VERSION%\\msteams.exe'),
InitiatingProcessFolderPath has_all ('Windows Kits', 'x86', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\x86\\oleview.exe'),
InitiatingProcessFolderPath has_all ('Windows Kits', 'x64', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\x64\\oleview.exe'),
InitiatingProcessFolderPath has_all ('Windows Kits', 'arm64', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\arm64\\oleview.exe'),
InitiatingProcessFolderPath has_all ('Program Files', 'trend micro', 'pwmsvc.exe'), tolower('%PROGRAMFILES%\\trend micro\\passwordmanager\\pwmsvc.exe'),
InitiatingProcessFolderPath has_all ('Program Files', 'trend micro', 'coreserviceshell.exe'), tolower('%PROGRAMFILES%\\trend micro\\amsp\\coreserviceshell.exe'),
InitiatingProcessFolderPath has_all ('Program Files', 'EdgeWebView', 'Application', 'msedgewebview2.exe'), tolower('%PROGRAMFILES%\\Microsoft\\EdgeWebView\\Application\\%VERSION%\\msedgewebview2.exe'),
InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft Office', 'root'), tolower(replace_regex(InitiatingProcessFolderPath, @'.*?\\Microsoft Office\\root\\Office.*\\(.*)', @'%PROGRAMFILES%\\Microsoft Office\\root\\Office%VERSION%\\\1')),
InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft Office', 'outlook.exe'), tolower('%PROGRAMFILES%\\Microsoft Office\\OFFICE%VERSION%\\outlook.exe'),
InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft', 'Windows Defender', 'MsMpEng'), tolower('%PROGRAMDATA%\\Microsoft\\Windows Defender\\Platform\\%VERSION%\\MsMpEng.exe'),
InitiatingProcessFolderPath startswith "C:\\Program Files (x86)", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\program files (x86)', '%programfiles%')),
InitiatingProcessFolderPath startswith "C:\\Program Files", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\program files', '%programfiles%')),
InitiatingProcessFolderPath startswith "C:\\WINDOWS", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\windows', '%windir%')),
InitiatingProcessFolderPath startswith "C:\\WINDOWS\\System32", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\windows\\system32', '%system32%')),
InitiatingProcessFolderPath startswith "C:\\ProgramData", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\programdata', '%programdata%')),
InitiatingProcessFolderPath has "AppData\\Local", tolower(replace_regex(tolower(InitiatingProcessFolderPath), @'c:\\users\\.*\\appdata\\local', '%localappdata%')),
InitiatingProcessFolderPath has "AppData\\Roaming", tolower(replace_regex(tolower(InitiatingProcessFolderPath), @'c:\\users\\.*\\appdata\\roaming', '%appdata%')),
tolower(InitiatingProcessFolderPath)),
DLLName = tolower(FileName)
)
on Process, DLLName
| extend DLLDir = tostring(parse_path(DLLDir).DirectoryPath)
| join kind=leftanti HijackLibs on Process, DLLName, $left.DLLDir==$right.ExpectedLocation
Stages and Predicates
Let Binding: HijackLibs
let HijackLibs = externaldata(DLLName:string, ExpectedLocation:string, Process: string )
[@"https://github.com/Cyb3r-Monk/Threat-Hunting-and-Detection/raw/main/Defense%20Evasion/HijackLibs.csv"]
with (format="csv", ignoreFirstRecord=True);
Stage 1: source
externaldata(...)
Stage 2: join
| join hint.strategy=shuffle kind=rightsemi(
DeviceImageLoadEvents
| extend DLLDir = case(FolderPath startswith "C:\\windows\\system32\\driverstore\\filerepository\\prnms002.inf", replace_regex(tolower(FolderPath), @'c:\\windows\\system32\\driverstore\\filerepository\\prnms002\.inf_.*\\amd64', @'%SYSTEM32%\\driverstore\\filerepository\\prnms002.inf_%VERSION%\\amd64'),
FolderPath has_all ('Windows Kits', 'bin', 'x86'), '%PROGRAMFILES%\\windows kits\\10\\bin\\%VERSION%\\x86',
FolderPath has_all ('Windows Kits', 'bin', 'x64'), '%PROGRAMFILES%\\windows kits\\10\\bin\\%VERSION%\\x64',
FolderPath has_all ('Windows Kits', 'bin', 'arm64'), '%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\arm64',
FolderPath has_all ('Program Files', 'Edge', 'Application'), tolower('%PROGRAMFILES%\\microsoft\\edge\\application\\%VERSION%'),
FolderPath has_all ('Program Files', 'Microsoft Office', 'root'), tolower('%PROGRAMFILES%\\microsoft office\\root\\office%VERSION%'),
FolderPath contains '\\Microsoft Office\\Office', tolower('%PROGRAMFILES%\\Microsoft Office\\OFFICE%VERSION%'),
FolderPath has_all ('ProgramData', 'Microsoft', 'Windows Defender', 'Platform'), tolower('%PROGRAMDATA%\\Microsoft\\Windows Defender\\Platform\\%VERSION%'),
FolderPath startswith "C:\\Program Files (x86)", tolower(replace_string(tolower(FolderPath), 'c:\\program files (x86)', '%programfiles%')),
FolderPath startswith "C:\\Program Files", tolower(replace_string(tolower(FolderPath), 'c:\\program files', '%programfiles%')),
FolderPath startswith "C:\\WINDOWS\\SysWow64", tolower(replace_string(tolower(FolderPath), 'c:\\windows\\syswow64', '%syswow64%')),
FolderPath startswith "C:\\WINDOWS\\System32", tolower(replace_string(tolower(FolderPath), 'c:\\windows\\system32', '%system32%')),
FolderPath startswith "C:\\ProgramData", tolower(replace_string(tolower(FolderPath), 'c:\\programdata', '%programdata%')),
FolderPath has "AppData\\Local", tolower(replace_regex(tolower(FolderPath), @'c:\\users\\.*\\appdata\\local', '%localappdata%')),
FolderPath has "AppData\\Roaming", tolower(replace_regex(tolower(FolderPath), @'c:\\users\\.*\\appdata\\roaming', '%appdata%')),
tolower(FolderPath)),
Process = case( InitiatingProcessFolderPath has_all ('WindowsApps', 'msteams.exe'), tolower('%PROGRAMFILES%\\WindowsApps\\MicrosoftTeams%VERSION%\\msteams.exe'),
InitiatingProcessFolderPath has_all ('Windows Kits', 'x86', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\x86\\oleview.exe'),
InitiatingProcessFolderPath has_all ('Windows Kits', 'x64', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\x64\\oleview.exe'),
InitiatingProcessFolderPath has_all ('Windows Kits', 'arm64', 'oleview.exe'), tolower('%PROGRAMFILES%\\Windows Kits\\10\\bin\\%VERSION%\\arm64\\oleview.exe'),
InitiatingProcessFolderPath has_all ('Program Files', 'trend micro', 'pwmsvc.exe'), tolower('%PROGRAMFILES%\\trend micro\\passwordmanager\\pwmsvc.exe'),
InitiatingProcessFolderPath has_all ('Program Files', 'trend micro', 'coreserviceshell.exe'), tolower('%PROGRAMFILES%\\trend micro\\amsp\\coreserviceshell.exe'),
InitiatingProcessFolderPath has_all ('Program Files', 'EdgeWebView', 'Application', 'msedgewebview2.exe'), tolower('%PROGRAMFILES%\\Microsoft\\EdgeWebView\\Application\\%VERSION%\\msedgewebview2.exe'),
InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft Office', 'root'), tolower(replace_regex(InitiatingProcessFolderPath, @'.*?\\Microsoft Office\\root\\Office.*\\(.*)', @'%PROGRAMFILES%\\Microsoft Office\\root\\Office%VERSION%\\\1')),
InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft Office', 'outlook.exe'), tolower('%PROGRAMFILES%\\Microsoft Office\\OFFICE%VERSION%\\outlook.exe'),
InitiatingProcessFolderPath has_all ('Program Files', 'Microsoft', 'Windows Defender', 'MsMpEng'), tolower('%PROGRAMDATA%\\Microsoft\\Windows Defender\\Platform\\%VERSION%\\MsMpEng.exe'),
InitiatingProcessFolderPath startswith "C:\\Program Files (x86)", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\program files (x86)', '%programfiles%')),
InitiatingProcessFolderPath startswith "C:\\Program Files", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\program files', '%programfiles%')),
InitiatingProcessFolderPath startswith "C:\\WINDOWS", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\windows', '%windir%')),
InitiatingProcessFolderPath startswith "C:\\WINDOWS\\System32", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\windows\\system32', '%system32%')),
InitiatingProcessFolderPath startswith "C:\\ProgramData", tolower(replace_string(tolower(InitiatingProcessFolderPath), 'c:\\programdata', '%programdata%')),
InitiatingProcessFolderPath has "AppData\\Local", tolower(replace_regex(tolower(InitiatingProcessFolderPath), @'c:\\users\\.*\\appdata\\local', '%localappdata%')),
InitiatingProcessFolderPath has "AppData\\Roaming", tolower(replace_regex(tolower(InitiatingProcessFolderPath), @'c:\\users\\.*\\appdata\\roaming', '%appdata%')),
tolower(InitiatingProcessFolderPath)),
DLLName = tolower(FileName)
)
on Process, DLLName
Stage 3: extend
extend DLLDir
Stage 4: join (negated)
join kind=leftanti (HijackLibs) on Process, DLLName, $left.DLLDir, $right.ExpectedLocation
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DLLDir | extend |
External Fabric Module XFM1 is unhealthy
#External Fabric Module XFM1 is unhealthy
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: a8130dcc-3617-41c0-a7ac-5f352bcfffaf
name: External Fabric Module XFM1 is unhealthy
version: 1.0.0
kind: NRT
description: External Fabric Module XFM1 is unhealthy
severity: High
tactics:
- Execution
relevantTechniques:
- T0871
query: |2-
Syslog
| where SyslogMessage has "purity.alert"
| extend Message = replace_string(SyslogMessage, "#012", "\n")
| extend UTCTime = extract(@"UTC Time:\s*(\d{4}\s\w{3}\s\d{1,2}\s\d{2}:\d{2}:\d{2})\sUTC", 1, SyslogMessage)
| extend PureAlertID = extract(@"Alert ID: ([\w-]+)", 1, SyslogMessage)
| extend PureMessage = extract(@"\(Alert ID: [\w-]+\)\s(.*?)\s\[\d+\]", 1, SyslogMessage)
| extend PureSeverity = extract(@"\s(\w+)\s", 1, SyslogMessage)
| extend PureAlertState = extract(@"purity\.alert:\s\w+\s(\w+)", 1, SyslogMessage)
| extend PureObjectName = extract(@"\s(\S+):", 1, SyslogMessage)
| extend PureProcessID = extract(@"\[(\d+)\]", 1, SyslogMessage)
| extend PureAction = extract(@"Suggested Action:\s*(.*?)(?:\s*Knowledge Base Article:|$)", 1, SyslogMessage)
| extend PureUrl = extract(@"Knowledge Base Article:\s*(.*)", 1, SyslogMessage)
| project PureMessage, TimeGenerated, PureProcessID, HostIP, Computer, PureObjectName, PureSeverity, PureAlertID, PureAlertState, PureAction, PureUrl
| where PureMessage matches regex @"(External Fabric Module XFM1 is unhealthy)"
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: HostIP
suppressionEnabled: false
suppressionDuration: 5h
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AllEntities
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: SingleAlert
Stages and Predicates
Stage 1: source
Syslog
Stage 2: where
| where SyslogMessage has "purity.alert"
Stage 3: extend (10 consecutive steps)
| extend Message = replace_string(SyslogMessage, "#012", "\n")
| extend UTCTime = extract(@"UTC Time:\s*(\d{4}\s\w{3}\s\d{1,2}\s\d{2}:\d{2}:\d{2})\sUTC", 1, SyslogMessage)
| extend PureAlertID = extract(@"Alert ID: ([\w-]+)", 1, SyslogMessage)
| extend PureMessage = extract(@"\(Alert ID: [\w-]+\)\s(.*?)\s\[\d+\]", 1, SyslogMessage)
| extend PureSeverity = extract(@"\s(\w+)\s", 1, SyslogMessage)
| extend PureAlertState = extract(@"purity\.alert:\s\w+\s(\w+)", 1, SyslogMessage)
| extend PureObjectName = extract(@"\s(\S+):", 1, SyslogMessage)
| extend PureProcessID = extract(@"\[(\d+)\]", 1, SyslogMessage)
| extend PureAction = extract(@"Suggested Action:\s*(.*?)(?:\s*Knowledge Base Article:|$)", 1, SyslogMessage)
| extend PureUrl = extract(@"Knowledge Base Article:\s*(.*)", 1, SyslogMessage)
Stage 4: project
| project PureMessage, TimeGenerated, PureProcessID, HostIP, Computer, PureObjectName, PureSeverity, PureAlertID, PureAlertState, PureAction, PureUrl
Stage 5: where
| where PureMessage matches regex @"(External Fabric Module XFM1 is unhealthy)"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PureMessage | regex_match |
| field:"PureMessage" kind:regex_match value:"(External Fabric Module XFM1 is unhealthy)" |
SyslogMessage | match |
| field:"SyslogMessage" kind:match value:"purity.alert" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | project |
HostIP | project |
PureAction | project |
PureAlertID | project |
PureAlertState | project |
PureMessage | project |
PureObjectName | project |
PureProcessID | project |
PureSeverity | project |
PureUrl | project |
TimeGenerated | project |
GCP IAM - Service Account Enumeration
#Detects possible service account enumeration.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GCP | google.iam.admin.ListServiceAccounts: ListServiceAccounts |
Rule Body
id: 50e0437e-912d-4cd0-ac19-fef0aebdd3d7
name: GCP IAM - Service Account Enumeration
description: |
'Detects possible service account enumeration.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: GCPIAMDataConnector
dataTypes:
- GCP_IAM
queryFrequency: 12h
queryPeriod: 12h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1087
query: |
let threshold = 5;
GCP_IAM
| where PayloadMethodname =~ 'google.iam.admin.v1.ListServiceAccounts'
| summarize count() by PayloadAuthenticationinfoPrincipalemail, bin(TimeGenerated, 1h)
| where count_ > threshold
| extend AccountCustomEntity = PayloadAuthenticationinfoPrincipalemail
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 5;
Stage 1: source
GCP_IAM
Stage 2: where
| where PayloadMethodname =~ 'google.iam.admin.v1.ListServiceAccounts'
Stage 3: summarize
| summarize count() by PayloadAuthenticationinfoPrincipalemail, bin(TimeGenerated, 1h)
Stage 4: where
| where count_ > threshold
Stage 5: extend
| extend AccountCustomEntity = PayloadAuthenticationinfoPrincipalemail
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PayloadMethodname | eq |
| field:"gcp::method_name" kind:eq value:"google.iam.admin.v1.ListServiceAccounts" |
count_ | gt |
| field:"count_" kind:gt value:"5" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
PayloadAuthenticationinfoPrincipalemail | summarize |
AccountCustomEntity | extend |
Global Network Traffic Rules Deleted
#Detects when a global network traffic rule is deleted in Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: 0995ccd8-16d4-4764-83b2-511235ca4e4b
name: Global Network Traffic Rules Deleted
description: Detects when a global network traffic rule is deleted in Veeam Backup
& Replication. This might indicate unauthorized removal of critical components.
severity: Low
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 == 32400\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n [\"User Name\"] = 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 == 32400
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["User Name"] = 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:"32400" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
User Name | project |
Hunt for AI Agent activity with Agent Info
#This query allows you to find the activity an AI Agent performed via the CloudAppEvents table using the AgentsInfo table.
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | CloudAppEvents action any: Cloud app activity |
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)
// Skip local AI Agents
| where Platform != "LocalAgents"
| extend ExtractedObservabilityID = iff(
ObservabilityID 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, ObservabilityID),
ObservabilityID
)
// Add fallback on titleId if ObservabilityID is empty
| extend ExtractedObservabilityID = iff(ExtractedObservabilityID == "", 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, tostring(parse_json(RawAgentInfo).titleId)), ExtractedObservabilityID)
| project Name, Platform, ExtractedObservabilityID
| join kind=inner (
CloudAppEvents
| where TimeGenerated > ago(7d)
| 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: extend
| extend ExtractedObservabilityID = iff(
ObservabilityID 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, ObservabilityID),
ObservabilityID
)
ExtractedObservabilityID =ObservabilityID 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, ObservabilityID)ObservabilityIDStage 7: extend
| extend ExtractedObservabilityID = iff(ExtractedObservabilityID == "", 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, tostring(parse_json(RawAgentInfo).titleId)), ExtractedObservabilityID)
ExtractedObservabilityID =ExtractedObservabilityID == ""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, tostring(parse_json(RawAgentInfo).titleId))ExtractedObservabilityIDStage 8: project
| project Name, Platform, ExtractedObservabilityID
Stage 9: join
| join kind=inner (
CloudAppEvents
| where TimeGenerated > ago(7d)
| 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 |
Platform | ne |
| field:"Platform" kind:ne value:"LocalAgents" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ExtractedObservabilityID | project |
Name | project |
Platform | project |
McAfee ePO - Multiple threats on same host
#'Rule fires when multiple threat events were detected on the same host.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Privilege Escalation | |
| Stealth | |
| Defense Impairment |
Rule Body
id: f53e5168-afdb-4fad-b29a-bb9cb71ec460
name: McAfee ePO - Multiple threats on same host
description: |
'Rule fires when multiple threat events were detected on the same host.'
severity: Medium
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- Persistence
- DefenseEvasion
- PrivilegeEscalation
relevantTechniques:
- T1562
- T1070
- T1189
- T1195
- T1543
- T1055
query: |
McAfeeEPOEvent
| where isnotempty(ThreatName)
| where ThreatName != '_'
| summarize th_cnt = dcount(ThreatName), th_list = makeset(ThreatName) by DvcIpAddr
| where th_cnt > 1
| extend IPCustomEntity = DvcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
McAfeeEPOEvent
Stage 2: where
| where isnotempty(ThreatName)
Stage 3: where
| where ThreatName != '_'
Stage 4: summarize
| summarize th_cnt = dcount(ThreatName), th_list = makeset(ThreatName) by DvcIpAddr
Stage 5: where
| where th_cnt > 1
Stage 6: extend
| extend IPCustomEntity = DvcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ThreatName | is_not_null | field:"ThreatName" kind:is_not_null | |
ThreatName | ne |
| field:"ThreatName" kind:ne value:"_" |
th_cnt | gt |
| field:"th_cnt" kind:gt value:"1" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DvcIpAddr | summarize |
th_cnt | summarize |
th_list | summarize |
IPCustomEntity | extend |
NetClean ProActive Incidents
#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.
'NetClean Incident'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 77548170-5c60-42e5-bdac-b0360d0779bb
name: NetClean ProActive Incidents
description: |
'NetClean Incident'
severity: High
requiredDataConnectors:
- connectorId: Netclean_ProActive_Incidents
dataTypes:
- Netclean_ProActive_Incidents
status: Available
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
tactics:
- Discovery
relevantTechniques:
- T1083
query: |
Netclean_Incidents_CL | where value_incidentVersion_d == 1
entityMappings:
- entityType: FileHash
fieldMappings:
- identifier: Value
columnName: value_file_calculatedHashes_sha1_s
- identifier: Algorithm
columnName: value_detectionHashType_s
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: value_file_owner_computerUser_domain_s
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: value_device_machineName_s
- entityType: IP
fieldMappings:
- identifier: Address
columnName: value_device_networkInterfaces_s
alertDetailsOverride:
alertDisplayNameFormat: NetClean {{value_agent_type_s}} {{type_s}}
alertDescriptionFormat: A new NetClean {{value_agent_type_s}} {{key_type_s}} has been Created {{TimeGenerated}}
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
Netclean_Incidents_CL
Stage 2: where
| where value_incidentVersion_d == 1
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
value_incidentVersion_d | eq |
| field:"value_incidentVersion_d" kind:eq value:"1" |
Palo Alto Prisma Cloud - IAM Group with Administrator Access Permissions
#'Detects IAM Groups with Administrator Access Permissions.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: ac76d9c0-17a3-4aaa-a341-48f4c0b1c882
name: Palo Alto Prisma Cloud - IAM Group with Administrator Access Permissions
description: |
'Detects IAM Groups with Administrator Access Permissions.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: PaloAltoPrismaCloud
dataTypes:
- PaloAltoPrismaCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
PaloAltoPrismaCloud
| where Reason =~ 'NEW_ALERT'
| where Status =~ 'open'
| where AlertMessage has 'IAM Groups with Administrator Access Permissions'
| extend AccountCustomEntity = UserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
PaloAltoPrismaCloud
Stage 2: where
| where Reason =~ 'NEW_ALERT'
Stage 3: where
| where Status =~ 'open'
Stage 4: where
| where AlertMessage has 'IAM Groups with Administrator Access Permissions'
Stage 5: extend
| extend AccountCustomEntity = UserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertMessage | match |
| field:"AlertMessage" kind:match value:"IAM Groups with Administrator Access Permissions" |
Reason | eq |
| field:"Reason" kind:eq value:"NEW_ALERT" |
Status | eq |
| field:"Status" kind:eq value:"open" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
Pathlock TDnR - STRUST PSE Certificate Changes
#Detects changes to STRUST PSE (Personal Security Environment) certificate files in SAP, forwarded by Pathlock Threat Detection and Response. PSE modifications may indicate certificate tampering, introduction of rogue trusted certificates, or preparation for man-in-the-middle attacks against SAP communication channels.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment | |
| Credential Access |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c42
name: Pathlock TDnR - STRUST PSE Certificate Changes
kind: Scheduled
description: >-
Detects changes to STRUST PSE (Personal Security Environment) certificate files in SAP,
forwarded by Pathlock Threat Detection and Response. PSE modifications may indicate certificate
tampering, introduction of rogue trusted certificates, or preparation for man-in-the-middle attacks
against SAP communication channels.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- DefenseEvasion
relevantTechniques:
- T1552
- T1553
query: |
Pathlock_TDnR_CL
| where DataSource == "PSE_CHANGES"
| 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 == "PSE_CHANGES"
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:"PSE_CHANGES" |
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 |
RecordedFuture Threat Hunting Url All Actors
#'Recorded Future Threat Hunting Url correlation for all actors.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation | |
| Stealth |
Rule Body
id: 3f6f0d1a-f2f9-4e01-881a-c55a4a71905b
name: RecordedFuture Threat Hunting Url All Actors
description: |
'Recorded Future Threat Hunting Url correlation for all actors.'
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligenceUploadIndicatorsAPI
dataTypes:
- ThreatIntelIndicators
queryFrequency: 15m
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
- DefenseEvasion
relevantTechniques:
- T1098
- T1078
customDetails:
ActorInformation: RecordedFuturePortalLink
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: '{{Description}}'
alertDescriptionFormat: '*{{Description}}**\n\nCorrelation found on {{Url}} from the {{Type}} table.\n'
alertDynamicProperties:
- alertProperty: AlertLink
value: RecordedFuturePortalLink
query: |
let ioc_lookBack = 1d;
// The source table (_Im_WebSession) is a ASIM parser table, but can be replaced by any infrastructure table containing Url data.
// The following workbook: Recorded Future - Url Correlation will help researching available data and selecting tables and columns
_Im_WebSession
| where isnotempty(Url)
| extend lowerUrl=tolower(Url)
| join kind=inner (
ThreatIntelIndicators
// Only look for IOCs
| where ObservableKey == 'url:value'
| where isnotempty(ObservableValue)
// Only look at Recorded Future Threat Hunt Indicators.
| where Data.description startswith "Recorded Future - Threat Hunt"
// Only work with the latest indicators
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where IsActive == true and ValidUntil > now()
| extend lowerUrl=tolower(ObservableValue)
) on lowerUrl
// select column from the source table to match with Recorded Future ThreatIntelIndicators $left.Url
| mv-expand Label=Data.labels
| extend RecordedFuturePortalLink = parse_json(tostring(Label)).RecordedFuturePortalLink
| where isnotempty(RecordedFuturePortalLink)
| summarize arg_max(TimeGenerated, *) by ObservableValue
| project Url=ObservableValue, Description=Data.description, Type, TimeGenerated, RecordedFuturePortalLink
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.1.1
kind: Scheduled
Stages and Predicates
Parameters
let ioc_lookBack = 1d;
Stage 1: source
_Im_WebSession
Stage 2: where
| where isnotempty(Url)
Stage 3: extend
| extend lowerUrl=tolower(Url)
Stage 4: join
| join kind=inner (
ThreatIntelIndicators
| where ObservableKey == 'url:value'
| where isnotempty(ObservableValue)
| where Data.description startswith "Recorded Future - Threat Hunt"
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where IsActive == true and ValidUntil > now()
| extend lowerUrl=tolower(ObservableValue)
) on lowerUrl
Stage 5: mv-expand
mv-expand Label
Stage 6: extend
extend RecordedFuturePortalLink
Stage 7: where
where isnotempty(RecordedFuturePortalLink)
Stage 8: summarize
summarize by ObservableValue
Stage 9: project
project Description, RecordedFuturePortalLink, TimeGenerated, Type, Url
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Data.description | starts_with |
| field:"Data.description" kind:starts_with value:"Recorded Future - Threat Hunt" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
ObservableKey | eq |
| field:"ObservableKey" kind:eq value:"url:value" |
ObservableValue | is_not_null | field:"ObservableValue" kind:is_not_null | |
RecordedFuturePortalLink | is_not_null | field:"RecordedFuturePortalLink" 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 |
|---|---|
Description | project |
RecordedFuturePortalLink | project |
TimeGenerated | project |
Type | project |
Url | project |
Sdelete deployed via GPO and run recursively (ASIM Version)
#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. This query uses the Advanced Security Information Model. Parsers will need to be deployed before use: https://docs.microsoft.com/azure/sentinel/normalization
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
Rule Body
id: 30c8b802-ace1-4408-bc29-4c5c5afb49e1
name: Sdelete deployed via GPO and run recursively (ASIM Version)
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.
This query uses the Advanced Security Information Model. Parsers will need to be deployed before use: https://docs.microsoft.com/azure/sentinel/normalization'
severity: Medium
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1485
tags:
-
query: |
_Im_ProcessEvent
| where EventType =~ "ProcessCreated"
| where Process endswith "svchost.exe"
| where CommandLine has "-k GPSvcGroup" or CommandLine has "-s gpsvc"
| extend timekey = bin(TimeGenerated, 1m)
| project timekey, ActingProcessId, Dvc
| join kind=inner (
_Im_ProcessEvent
| where EventType =~ "ProcessCreated"
| where Process =~ "sdelete.exe" or CommandLine has "sdelete"
| where ActingProcessName endswith "svchost.exe"
| where CommandLine has_all ("-s", "-r")
| extend timekey = bin(TimeGenerated, 1m)
)
on $left.ActingProcessId == $right.ParentProcessId, timekey, Dvc
| extend AccountName = tostring(split(ActorUsername, @'\')[1]), AccountNTDomain = tostring(split(ActorUsername, @'\')[0])
| extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)
| project-away DomainIndex
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUsername
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountNTDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Dvc
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DvcIpAddr
version: 1.0.6
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Threat Protection" ]
Stages and Predicates
Stage 1: source
_Im_ProcessEvent
Stage 2: where
| where EventType =~ "ProcessCreated"
Stage 3: where
| where Process endswith "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, ActingProcessId, Dvc
Stage 7: join
| join kind=inner (
_Im_ProcessEvent
| where EventType =~ "ProcessCreated"
| where Process =~ "sdelete.exe" or CommandLine has "sdelete"
| where ActingProcessName endswith "svchost.exe"
| where CommandLine has_all ("-s", "-r")
| extend timekey = bin(TimeGenerated, 1m)
)
on $left.ActingProcessId == $right.ParentProcessId, timekey, Dvc
Stage 8: extend (3 consecutive steps)
extend AccountNTDomain, AccountName, DomainIndex, HostName, HostNameDomain
Stage 9: project-away
project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActingProcessName | ends_with |
| field:"ActingProcessName" kind:ends_with value:"svchost.exe" |
CommandLine | match |
| field:"CommandLine" kind:match |
EventType | eq |
| field:"EventType" kind:eq value:"ProcessCreated" |
Process | ends_with |
| field:"Process" kind:ends_with value:"svchost.exe" |
Process | eq |
| field:"Process" kind:eq value:"sdelete.exe" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActingProcessId | project |
Dvc | project |
timekey | project |
AccountNTDomain | extend |
AccountName | extend |
HostName | extend |
HostNameDomain | extend |
StealthTalk - Password brute force
#Identifies a brute-force authentication attempt against a single StealthTalk user account. The StealthTalk anti-brute-force subsystem emits a MultiFailLogin event when consecutive failed attempts trigger an automatic account lockout. This rule fires when the reported number of consecutive failures is 9 or more, distinguishing a credential-guessing attack from an isolated user error. The lockout duration (LoginBlockingSeconds) is surfaced as a custom detail so the SOC analyst can prioritise by attack persistence.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: b8e5f3a2-9c4d-4d1f-8a7b-3c2d1e0f9a8b
name: StealthTalk - Password brute force
description: |
Identifies a brute-force authentication attempt against a single StealthTalk user account. The StealthTalk anti-brute-force subsystem emits a `MultiFailLogin` event when consecutive failed attempts trigger an automatic account lockout. This rule fires when the reported number of consecutive failures is 9 or more, distinguishing a credential-guessing attack from an isolated user error. The lockout duration (`LoginBlockingSeconds`) is surfaced as a custom detail so the SOC analyst can prioritise by attack persistence.
severity: High
requiredDataConnectors:
- connectorId: StealthTalkAnomalousAuth
dataTypes:
- StealthTalkAnomalousAuth_CL
queryFrequency: 15m
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CredentialAccess
- InitialAccess
relevantTechniques:
- T1110
query: |
let LookbackPeriod = 5h;
let MinFailedAttempts = 9;
StealthTalkAnomalousAuth_CL
| where TimeGenerated >= ago(LookbackPeriod)
| where EventType == "MultiFailLogin"
| where PassedAttempts >= MinFailedAttempts
| extend
BlockingMinutes = LoginBlockingSeconds / 60,
AlertName = "PasswordBruteForce",
AlertDetails = strcat(
"User ", UserId,
" had ", PassedAttempts, " consecutive failed login attempts",
" on device ", DeviceId, ".",
" Account blocked for ", LoginBlockingSeconds, " seconds",
" (", LoginBlockingSeconds / 60, " min)."
)
| project
TimeGenerated, UserId, DeviceId,
PassedAttempts, LoginBlockingSeconds, BlockingMinutes,
AppVersion, RawEventId, AlertName, AlertDetails
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: UserId
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DeviceId
customDetails:
PassedAttempts: PassedAttempts
BlockingMinutes: BlockingMinutes
LoginBlockingSeconds: LoginBlockingSeconds
AppVersion: AppVersion
EventReference: RawEventId
alertDetailsOverride:
alertDisplayNameFormat: 'StealthTalk: Password Brute Force - {{UserId}} ({{PassedAttempts}} failed attempts)'
alertDescriptionFormat: '{{AlertDetails}}'
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: Selected
groupByEntities:
- Account
suppressionEnabled: false
suppressionDuration: 5h
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let LookbackPeriod = 5h;
let MinFailedAttempts = 9;
Stage 1: source
StealthTalkAnomalousAuth_CL
Stage 2: where
| where TimeGenerated >= ago(LookbackPeriod)
Stage 3: where
| where EventType == "MultiFailLogin"
Stage 4: where
| where PassedAttempts >= MinFailedAttempts
Stage 5: extend
| extend
BlockingMinutes = LoginBlockingSeconds / 60,
AlertName = "PasswordBruteForce",
AlertDetails = strcat(
"User ", UserId,
" had ", PassedAttempts, " consecutive failed login attempts",
" on device ", DeviceId, ".",
" Account blocked for ", LoginBlockingSeconds, " seconds",
" (", LoginBlockingSeconds / 60, " min)."
)
Stage 6: project
| project
TimeGenerated, UserId, DeviceId,
PassedAttempts, LoginBlockingSeconds, BlockingMinutes,
AppVersion, RawEventId, AlertName, AlertDetails
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"MultiFailLogin" |
PassedAttempts | ge |
| field:"PassedAttempts" kind:ge value:"9" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertDetails | project |
AlertName | project |
AppVersion | project |
BlockingMinutes | project |
DeviceId | project |
LoginBlockingSeconds | project |
PassedAttempts | project |
RawEventId | project |
TimeGenerated | project |
UserId | project |
Suspicious linking of existing user to external User
#This query will detect when an attempt is made to update an existing user and link it to an guest or external identity. These activities are unusual and such linking of external identities should be investigated. In some cases you may see internal Entra ID sync accounts (Sync_) do this which may be benign
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Update user |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created and Deleted in Short Timeframe (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Account created or deleted by non-approved user (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- Authentication Method Changed for Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
Rule Body
id: 22a320c2-e1e5-4c74-a35b-39fc9cdcf859
name: Suspicious linking of existing user to external User
description: |
' This query will detect when an attempt is made to update an existing user and link it to an guest or external identity. These activities are unusual and such linking of external
identities should be investigated. In some cases you may see internal Entra ID sync accounts (Sync_) do this which may be benign'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1078.004
tags:
- GuestorExternalIdentities
query: |
AuditLogs
| where OperationName=~ "Update user"
| where Result =~ "success"
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties
| extend displayName = tostring(TargetResources_modifiedProperties.displayName),
TargetUPN_oldValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.oldValue))[0]),
TargetUPN_newValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue))[0])
| where displayName == "UserPrincipalName" and TargetUPN_oldValue !has "#EXT" and TargetUPN_newValue has "#EXT"
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName))
| summarize arg_max(TimeGenerated, *) by CorrelationId
| project-reorder TimeGenerated, InitiatedBy, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, TargetUPN_oldValue, TargetUPN_newValue
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend TargetAccountName = tostring(split(TargetUPN_oldValue, "@")[0]), TargetUPNSuffix = tostring(split(TargetUPN_oldValue, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatingAppName
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUPN_oldValue
- identifier: Name
columnName: TargetAccountName
- identifier: UPNSuffix
columnName: TargetUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIPAddress
version: 1.1.0
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where OperationName=~ "Update user"
Stage 3: where
| where Result =~ "success"
Stage 4: mv-expand
| mv-expand TargetResources
Stage 5: mv-expand
| mv-expand TargetResources.modifiedProperties
Stage 6: extend
| extend displayName = tostring(TargetResources_modifiedProperties.displayName),
TargetUPN_oldValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.oldValue))[0]),
TargetUPN_newValue = tostring(parse_json(tostring(TargetResources_modifiedProperties.newValue))[0])
Stage 7: where
| where displayName == "UserPrincipalName" and TargetUPN_oldValue !has "#EXT" and TargetUPN_newValue has "#EXT"
Stage 8: extend (6 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend InitiatedBy = tostring(iff(isnotempty(InitiatingUserPrincipalName),InitiatingUserPrincipalName, InitiatingAppName))
Stage 9: summarize
| summarize arg_max(TimeGenerated, *) by CorrelationId
Stage 10: project-reorder
| project-reorder TimeGenerated, InitiatedBy, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, TargetUPN_oldValue, TargetUPN_newValue
Stage 11: extend
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
Stage 12: extend
| extend TargetAccountName = tostring(split(TargetUPN_oldValue, "@")[0]), TargetUPNSuffix = tostring(split(TargetUPN_oldValue, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | eq |
| field:"OperationName" kind:eq value:"Update user" |
Result | eq |
| field:"Result" kind:eq value:"success" |
TargetUPN_newValue | match |
| field:"TargetUPN_newValue" kind:match value:"#EXT" |
displayName | eq |
| field:"displayName" kind:eq value:"UserPrincipalName" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TargetUPN_oldValue | match | #EXT | excludes:TargetUPN_oldValue field:"TargetUPN_oldValue" value:"#EXT" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CorrelationId | summarize |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
TargetAccountName | extend |
TargetUPNSuffix | extend |
Suspicious use of CPL file
#This query identifies .cpl files being loaded and verifies if the corresponding file is suspicious by looking at the signature and global prevalence.
Known False Positives
- Legitimate custom software might create a control panel item that is unsigned or has a low global prevalence.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
References
Telemetry Coverage
Rule Body
let timeframe = 2*1h;
let default_global_prevalence = 0;
let suspiciousCPLs = DeviceImageLoadEvents
| where ingestion_time() >= ago(timeframe)
// Begin environment-specific filter.
// End environment-specific filter.
| where FileName endswith ".cpl"
| summarize by SHA1
// FileProfile is case-sensitive and works on lower-case hashes.
| extend SHA1=tolower(SHA1)
| invoke FileProfile(SHA1, 1000)
| where not(ProfileAvailability =~ "Error")
// Begin environment-specific filter.
// End environment-specific filter.
| where ((isempty(Signer) or not(IsCertificateValid==1)) and coalesce(GlobalPrevalence,default_global_prevalence) < 100) or coalesce(GlobalPrevalence,default_global_prevalence) < 50;
let loadedDlls=DeviceImageLoadEvents
| where ingestion_time() >= ago(timeframe)
// FileProfile is case-sensitive and works on lower-case hashes.
| extend SHA1=tolower(SHA1)
| where SHA1 in~ ((suspiciousCPLs|project SHA1)) and ActionType =~ "ImageLoaded"
// Begin environment-specific filter.
// End environment-specific filter.
;
loadedDlls
| join kind=leftouter suspiciousCPLs on SHA1
// Begin environment-specific filter.
// End environment-specific filter.
Stages and Predicates
Parameters
let timeframe = 2*1h;
let default_global_prevalence = 0;
let loadedDlls is inlined into the numbered stages below.
Let Binding: suspiciousCPLs
let suspiciousCPLs = DeviceImageLoadEvents
| where ingestion_time() >= ago(timeframe)
| where FileName endswith ".cpl"
| summarize by SHA1
| extend SHA1=tolower(SHA1)
| invoke FileProfile(SHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where ((isempty(Signer) or not(IsCertificateValid==1)) and coalesce(GlobalPrevalence,default_global_prevalence) < 100) or coalesce(GlobalPrevalence,default_global_prevalence) < 50;
Stage 1: source
let suspiciousCPLs
Stage 2: source
let loadedDlls
Stage 3: source
DeviceImageLoadEvents
Stage 4: where
where ...
Stage 5: extend
extend SHA1
Stage 6: where
where ActionType =~ "ImageLoaded"
Stage 7: join
join kind=leftouter (suspiciousCPLs) on SHA1
Stage 8: summarize aggregation inside the join branch
summarize by SHA1
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ImageLoaded" |
FileName | ends_with |
| field:"FileName" kind:ends_with value:".cpl" |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt |
Signer | is_null | field:"Signer" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
ProfileAvailability | eq | Error | excludes:ProfileAvailability field:"ProfileAvailability" value:"Error" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SHA1 | summarize |
Tape Library Deleted
#Detects when a tape library is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: ecf98b4a-3986-4739-879d-dd446e839153
name: Tape Library Deleted
description: Detects when a tape library is deleted from Veeam Backup & Replication.
This might indicate unauthorized removal of critical components.
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 == 23633\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n [\"User Name\"] = 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 == 23633
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["User Name"] = 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:"23633" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
User Name | project |
User account added to built in domain local or global group
#Identifies when a user account has been added to a privileged built in domain local group or global group such as the Enterprise Admins, Cert Publishers or DnsAdmins. Be sure to verify this is an expected addition.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rule Body
id: a35f2c18-1b97-458f-ad26-e033af18eb99
name: User account added to built in domain local or global group
description: |
'Identifies when a user account has been added to a privileged built in domain local group or global group such as the Enterprise Admins, Cert Publishers or DnsAdmins. Be sure to verify this is an expected addition.'
severity: Low
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1098
- T1078
query: |
// For AD SID mappings - https://docs.microsoft.com/windows/security/identity-protection/access-control/active-directory-security-groups
let WellKnownLocalSID = "S-1-5-32-5[0-9][0-9]$";
let WellKnownGroupSID = "S-1-5-21-[0-9]*-[0-9]*-[0-9]*-5[0-9][0-9]$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1102$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1103$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-498$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1000$";
union isfuzzy=true
(
SecurityEvent
// 4728 - A member was added to a security-enabled global group
// 4732 - A member was added to a security-enabled local group
// 4756 - A member was added to a security-enabled universal group
| where EventID in (4728, 4732, 4756)
| where AccountType == "User"
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
// Exclude Remote Desktop Users group: S-1-5-32-555
| where TargetSid !in ("S-1-5-32-555")
| extend SimpleMemberName = iff(MemberName == "-", MemberName, substring(MemberName, 3, indexof_regex(MemberName, @",OU|,CN") - 3))
| project TimeGenerated, EventID, Activity, Computer, SimpleMemberName, MemberName, MemberSid, TargetAccount, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName,
SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid
),
(
WindowsEvent
// 4728 - A member was added to a security-enabled global group
// 4732 - A member was added to a security-enabled local group
// 4756 - A member was added to a security-enabled universal group
| where EventID in (4728, 4732, 4756) and not(EventData has "S-1-5-32-555")
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend AccountType=case(Account endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| extend MemberName = tostring(EventData.MemberName)
| where AccountType == "User"
| extend TargetSid = tostring(EventData.TargetSid)
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
// Exclude Remote Desktop Users group: S-1-5-32-555
| where TargetSid !in ("S-1-5-32-555")
| extend SimpleMemberName = iff(MemberName == "-", MemberName, substring(MemberName, 3, indexof_regex(MemberName, @",OU|,CN") - 3))
| extend MemberSid = tostring(EventData.MemberSid)
| extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName),
TargetAccount = strcat(tostring(EventData.TargetDomainName),"\\", tostring(EventData.TargetUserName))
| extend UserPrincipalName = tostring(EventData.UserPrincipalName)
| extend SubjectUserName = tostring(EventData.SubjectUserName), SubjectDomainName = tostring(EventData.SubjectDomainName),
SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| project TimeGenerated, EventID, Computer, SimpleMemberName, MemberName, MemberSid, TargetAccount, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName,
SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid
)
| extend GroupAddedMemberTo = TargetAccount, AddedByAccount = SubjectAccount, AddedByAccountName = SubjectUserName, AddedByAccountDomainName = SubjectDomainName,
AddedByAccountSid = SubjectUserSid, AddedMemberName = SimpleMemberName, AddedMemberSid = MemberSid
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SubjectAccount
- identifier: Name
columnName: SubjectUserName
- identifier: NTDomain
columnName: SubjectDomainName
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: SubjectUserSid
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AddedMemberName
- identifier: Sid
columnName: AddedMemberSid
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: HostNameDomain
version: 1.3.8
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Parameters
let WellKnownLocalSID = "S-1-5-32-5[0-9][0-9]$";
Let Binding: WellKnownGroupSID
let WellKnownGroupSID = "S-1-5-21-[0-9]*-[0-9]*-[0-9]*-5[0-9][0-9]$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1102$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1103$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-498$|S-1-5-21-[0-9]*-[0-9]*-[0-9]*-1000$";
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, WindowsEvent
Leg 1: SecurityEvent
SecurityEvent
| where EventID in (4728, 4732, 4756)
| where AccountType == "User"
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| where TargetSid !in ("S-1-5-32-555")
| extend SimpleMemberName = iff(MemberName == "-", MemberName, substring(MemberName, 3, indexof_regex(MemberName, @",OU|,CN") - 3))
| project TimeGenerated, EventID, Activity, Computer, SimpleMemberName, MemberName, MemberSid, TargetAccount, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName,
SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid
Leg 2: WindowsEvent
WindowsEvent
| where EventID in (4728, 4732, 4756) and not(EventData has "S-1-5-32-555")
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend AccountType=case(Account endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| extend MemberName = tostring(EventData.MemberName)
| where AccountType == "User"
| extend TargetSid = tostring(EventData.TargetSid)
| where TargetSid matches regex WellKnownLocalSID or TargetSid matches regex WellKnownGroupSID
| where TargetSid !in ("S-1-5-32-555")
| extend SimpleMemberName = iff(MemberName == "-", MemberName, substring(MemberName, 3, indexof_regex(MemberName, @",OU|,CN") - 3))
| extend MemberSid = tostring(EventData.MemberSid)
| extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName),
TargetAccount = strcat(tostring(EventData.TargetDomainName),"\\", tostring(EventData.TargetUserName))
| extend UserPrincipalName = tostring(EventData.UserPrincipalName)
| extend SubjectUserName = tostring(EventData.SubjectUserName), SubjectDomainName = tostring(EventData.SubjectDomainName),
SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| project TimeGenerated, EventID, Computer, SimpleMemberName, MemberName, MemberSid, TargetAccount, TargetUserName, TargetDomainName, TargetSid, UserPrincipalName,
SubjectAccount, SubjectUserName, SubjectDomainName, SubjectUserSid
Applied to the Combined Result
| extend GroupAddedMemberTo = TargetAccount, AddedByAccount = SubjectAccount, AddedByAccountName = SubjectUserName, AddedByAccountDomainName = SubjectDomainName,
AddedByAccountSid = SubjectUserSid, AddedMemberName = SimpleMemberName, AddedMemberSid = MemberSid
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccountType | eq |
| field:"AccountType" kind:eq value:"User" |
EventID | in |
| field:"EventID" kind:in |
TargetSid | regex_match |
| field:"TargetSid" kind:regex_match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TargetSid | eq | S-1-5-32-555 | excludes:TargetSid field:"TargetSid" value:"S-1-5-32-555" |
EventData | match | S-1-5-32-555 | excludes:EventData field:"EventData" value:"S-1-5-32-555" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | project |
EventID | project |
MemberName | project |
MemberSid | project |
SimpleMemberName | project |
SubjectAccount | project |
SubjectDomainName | project |
SubjectUserName | project |
SubjectUserSid | project |
TargetAccount | project |
TargetDomainName | project |
TargetSid | project |
TargetUserName | project |
TimeGenerated | project |
UserPrincipalName | project |
AddedByAccount | extend |
AddedByAccountDomainName | extend |
AddedByAccountName | extend |
AddedByAccountSid | extend |
AddedMemberName | extend |
AddedMemberSid | extend |
GroupAddedMemberTo | extend |
HostName | extend |
HostNameDomain | extend |
Vectra Create Incident Based on Tag for Accounts
#Create an incident when the account entity presents a specific tag. If the tag is present, an incident should be created and marked with highest priority.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: 87325835-dd8c-41e7-b686-fd5adbbd0aee
name: Vectra Create Incident Based on Tag for Accounts
version: 1.1.0
kind: Scheduled
description: Create an incident when the account entity presents a specific tag. If the tag is present, an incident should be created and marked with highest priority.
status: Available
requiredDataConnectors:
- connectorId: VectraXDR
dataTypes:
- Entities_Data_CL
severity: High
queryFrequency: 10m
queryPeriod: 10m
suppressionDuration: PT1H
suppressionEnabled: false
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1546
query: |
Entities_Data_CL
| where entity_type == "account"
| extend Tags = todynamic(tags)
| where set_has_element(Tags, "MDR - Customer Escalation")
| summarize arg_max(['last_modified_timestamp'], *) by ['name']
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: name
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P7D
matchingMethod: AllEntities
groupByEntities: [Account]
groupByAlertDetails: [DisplayName]
customDetails:
entity_id: id
entity_type: entity_type
tags: tags
attack_profile: attack_profile
ip_address: ip
alertDetailsOverride:
alertDisplayNameFormat: Vectra AI Incident- {{name}}
alertDescriptionFormat: An incident has been escalated for Vectra AI entity {{name}} that is presenting an urgency score of {{urgency_score}}
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Stage 1: source
Entities_Data_CL
Stage 2: where
| where entity_type == "account"
Stage 3: extend
| extend Tags = todynamic(tags)
Stage 4: where
| where set_has_element(Tags, "MDR - Customer Escalation")
Stage 5: summarize
| summarize arg_max(['last_modified_timestamp'], *) by ['name']
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Tags | in |
| field:"Tags" kind:in value:"MDR - Customer Escalation" |
entity_type | eq |
| field:"entity_type" kind:eq value:"account" |
Veeam ONE Unusual Job Duration
#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 Veeam ONE unusual job duration alerts.
Rule Body
id: 04530ba4-b26e-4a83-8057-a4fc286de848
name: Veeam ONE Unusual Job Duration
description: Detects Veeam ONE unusual job duration alerts.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: VeeamCustomTablesDataConnector
dataTypes:
- VeeamOneTriggeredAlarms_CL
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: VeeamOneTriggeredAlarms_CL | where PredefinedAlarmId == 369
version: 1.0.1
kind: Scheduled
customDetails:
PredefinedAlarmId: PredefinedAlarmId
TriggeredAlarmId: TriggeredAlarmId
Description: Description
Name: Name
VoneHostName: VoneHostName
TriggeredTime: TriggeredTime
Status: Status
Comment: Comment
ObjectId: ObjectId
ObjectName: ObjectName
ObjectType: ObjectType
Stages and Predicates
Stage 1: source
VeeamOneTriggeredAlarms_CL
Stage 2: where
| where PredefinedAlarmId == 369
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PredefinedAlarmId | eq |
| field:"PredefinedAlarmId" kind:eq value:"369" |