Detection rules › Kusto
Kusto rules: 1w
Abnormal Security - User-reported email judged malicious
#'Creates an incident when an email a user reported to the Abnormal Security Abuse Mailbox is judged malicious. Surfaces the reporter, the original recipient, and the campaign so analysts can scope and remediate look-alike messages. Spam- and safe-judged reports are intentionally excluded.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 51c6ba55-fecd-4be0-9064-1aafc4d3e8d6
name: Abnormal Security - User-reported email judged malicious
description: |
'Creates an incident when an email a user reported to the Abnormal Security Abuse
Mailbox is judged malicious. Surfaces the reporter, the original recipient, and the
campaign so analysts can scope and remediate look-alike messages. Spam- and safe-judged
reports are intentionally excluded.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AbnormalSecurityPush
dataTypes:
- ABNORMAL_SECURITY_ABUSE_MAILBOX_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
ABNORMAL_SECURITY_ABUSE_MAILBOX_CL
| where abx_body_abx_body_reported_b == true
| where tolower(abx_body_abx_body_judgement_s) == "malicious"
| extend
ReporterEmail = abx_body_abx_body_reporter_address_s,
RecipientEmail = abx_body_abx_body_recipient_address_s,
Subject = abx_body_abx_body_subject_s,
Judgement = abx_body_abx_body_judgement_s,
MessageId = abx_body_abx_body_internet_message_id_s,
CampaignId = abx_body_abx_body_campaign_id_g
| project TimeGenerated, ReporterEmail, RecipientEmail, Subject, Judgement, MessageId, CampaignId
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ReporterEmail
- entityType: MailMessage
fieldMappings:
- identifier: Recipient
columnName: RecipientEmail
- identifier: Subject
columnName: Subject
- identifier: NetworkMessageId
columnName: MessageId
customDetails:
Judgement: Judgement
CampaignId: CampaignId
alertDetailsOverride:
alertDisplayNameFormat: "Abnormal Security: user-reported {{Judgement}} email ({{Subject}})"
alertDescriptionFormat: "An email reported by {{ReporterEmail}} was judged {{Judgement}} by Abnormal Security. Original recipient: {{RecipientEmail}}."
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
suppressionDuration: PT5H
suppressionEnabled: false
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
ABNORMAL_SECURITY_ABUSE_MAILBOX_CL
Stage 2: where
| where abx_body_abx_body_reported_b == true
Stage 3: where
| where tolower(abx_body_abx_body_judgement_s) == "malicious"
Stage 4: extend
| extend
ReporterEmail = abx_body_abx_body_reporter_address_s,
RecipientEmail = abx_body_abx_body_recipient_address_s,
Subject = abx_body_abx_body_subject_s,
Judgement = abx_body_abx_body_judgement_s,
MessageId = abx_body_abx_body_internet_message_id_s,
CampaignId = abx_body_abx_body_campaign_id_g
Stage 5: project
| project TimeGenerated, ReporterEmail, RecipientEmail, Subject, Judgement, MessageId, CampaignId
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CampaignId | project |
Judgement | project |
MessageId | project |
RecipientEmail | project |
ReporterEmail | project |
Subject | project |
TimeGenerated | project |
ApexOne - Suspicious commandline arguments
#'Detects suspicious commandline arguments.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: 4d7199b2-67b8-11ec-90d6-0242ac120003
name: ApexOne - Suspicious commandline arguments
description: |
'Detects suspicious commandline arguments.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1059
query: |
TMApexOneEvent
| where EventMessage has "Endpoint Application"
| where Command has_any ("whoami", "dpkg", "useradd", "sudo")
| extend IPCustomEntity = SrcIpAddr, AccountCustomEntity = DstUserName
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
TMApexOneEvent
Stage 2: where
| where EventMessage has "Endpoint Application"
Stage 3: where
| where Command has_any ("whoami", "dpkg", "useradd", "sudo")
Stage 4: extend
| extend IPCustomEntity = SrcIpAddr, AccountCustomEntity = DstUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Command | match |
| field:"Command" kind:match |
EventMessage | match |
| field:"EventMessage" kind:match value:"Endpoint Application" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
IPCustomEntity | extend |
Azure secure score MFA registration V2
#'This query searches for multi-factor authentication (MFA) helps protect devices and data that are accessible to these users. Adding more authentication methods, such as the Microsoft Authenticator app or a phone number, increases the level of protection if one factor is compromised.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 8EB2B20A-BF64-4DCC-9D98-1AD559502C00
name: Azure secure score MFA registration V2
description: |
'This query searches for multi-factor authentication (MFA) helps protect devices and data that are accessible to these users. Adding more authentication methods, such as the Microsoft Authenticator app or a phone number, increases the level of protection if one factor is compromised.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SenservaPro
dataTypes:
- SenservaPro_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1056
query: |
SenservaPro_CL
| where ControlName_s == 'AzureSecureScoreMFARegistrationV2'
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: ControlName_s
- identifier: AadTenantId
columnName: TenantId
- identifier: DisplayName
columnName: TenantDisplayName_s
- entityType: SecurityGroup
fieldMappings:
- identifier: DistinguishedName
columnName: Group_s
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: SourceSystem
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
SenservaPro_CL
Stage 2: where
| where ControlName_s == 'AzureSecureScoreMFARegistrationV2'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ControlName_s | eq |
| field:"ControlName_s" kind:eq value:"AzureSecureScoreMFARegistrationV2" |
Cisco SE - Possible webshell
#'Detects possible webshell on host.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: d2c97cc9-1ccc-494d-bad4-564700451a2b
name: Cisco SE - Possible webshell
description: |
'Detects possible webshell on host.'
severity: High
requiredDataConnectors:
- connectorId: CiscoSecureEndpoint
dataTypes:
- CiscoSecureEndpoint
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CommandAndControl
relevantTechniques:
- T1102
query: |
CiscoSecureEndpoint
| where EventMessage has 'Possible webshell'
| extend HostCustomEntity = DstHostname, MalwareCustomEntity = ThreatName
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostCustomEntity
- entityType: Malware
fieldMappings:
- identifier: Name
columnName: MalwareCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CiscoSecureEndpoint
Stage 2: where
| where EventMessage has 'Possible webshell'
Stage 3: extend
| extend HostCustomEntity = DstHostname, MalwareCustomEntity = ThreatName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | match |
| field:"EventMessage" kind:match value:"Possible webshell" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostCustomEntity | extend |
MalwareCustomEntity | extend |
CTERA Mass Access Denied Detection Analytic
#This analytic rule detects and alerts when access denied operations generated by the CTERA Edge Filer goes over a predefined threshold
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: 88341fc3-38e1-46db-8bb1-6c052e749991
name: CTERA Mass Access Denied Detection Analytic
description: 'This analytic rule detects and alerts when access denied operations generated by the CTERA Edge Filer goes over a predefined threshold'
kind: Scheduled
severity: High
status: Available
requiredDataConnectors:
- connectorId: CTERA
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 1000
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
Syslog
| where ProcessName == 'gw-audit'
| extend
TenantName = extract("\"vportal\":\"([^\"]*)\"", 1, SyslogMessage),
UserName = extract("user=([^|]*)", 1, SyslogMessage),
Operation = extract("op=([^|]*)", 1, SyslogMessage),
EdgeFiler = extract("\"client\":\"([^\"]*)\"", 1, SyslogMessage),
RootPath = extract("rootPath=([^|]*)", 1, SyslogMessage),
Share = extract("share=([^|]*)", 1, SyslogMessage),
LocalPath = extract("path=([^|]*)", 1, SyslogMessage),
Timestamp = todatetime(extract("\"@timestamp\":\"([^\"]*)\"", 1, SyslogMessage))
| where Operation in ('OpenDenied', 'createDenied', 'AclDenied', 'deleteDenied')
| summarize Count = count() by bin(Timestamp, 5m), UserName, EdgeFiler
| where Count > 1000
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertnameFormat: 'CTERA Batch Access Denied Detection'
alertDescriptionFormat: >
Detected {{Count}} denied access attempts by user {{UserName}} on Edge Filer
{{EdgeFiler}} within 5 minutes. Please investigate unauthorized access attempts or misconfigurations.
customDetails:
TenantName: TenantName
UserName: UserName
EdgeFiler: EdgeFiler
RootPath: RootPath
Share: Share
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: UserName
- entityType: File
fieldMappings:
- identifier: Name
columnName: EdgeFiler
suppressionDuration: PT5H
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Syslog
Stage 2: where
| where ProcessName == 'gw-audit'
Stage 3: extend
| extend
TenantName = extract("\"vportal\":\"([^\"]*)\"", 1, SyslogMessage),
UserName = extract("user=([^|]*)", 1, SyslogMessage),
Operation = extract("op=([^|]*)", 1, SyslogMessage),
EdgeFiler = extract("\"client\":\"([^\"]*)\"", 1, SyslogMessage),
RootPath = extract("rootPath=([^|]*)", 1, SyslogMessage),
Share = extract("share=([^|]*)", 1, SyslogMessage),
LocalPath = extract("path=([^|]*)", 1, SyslogMessage),
Timestamp = todatetime(extract("\"@timestamp\":\"([^\"]*)\"", 1, SyslogMessage))
Stage 4: where
| where Operation in ('OpenDenied', 'createDenied', 'AclDenied', 'deleteDenied')
Stage 5: summarize
| summarize Count = count() by bin(Timestamp, 5m), UserName, EdgeFiler
Stage 6: where
| where Count > 1000
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Count | gt |
| field:"Count" kind:gt value:"1000" |
Operation | in |
| field:"Operation" kind:in |
ProcessName | eq |
| field:"process_name" kind:eq value:"gw-audit" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Count | summarize |
EdgeFiler | summarize |
UserName | summarize |
CYFIRMA - High severity File Hash Indicators with Block Action and Malware
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This KQL query retrieves file hash indicators (MD5, SHA1, SHA256) from the CyfirmaIndicators_CL table within the last 5 minutes. It filters records with a confidence score of 80 or higher, containing file hash patterns, a recommended action of 'Block', and roles marked as 'Malware'. Extracted hashes and key threat intelligence details are projected for Blocking and investigation."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Stealth | |
| Defense Impairment | |
| Credential Access | |
| Discovery | |
| Lateral Movement | |
| Collection | |
| Impact |
Rule Body
id: 990fc0dc-e7a5-4f6d-bc24-8569652cd773
name: CYFIRMA - High severity File Hash Indicators with Block Action and Malware
description: |
"This KQL query retrieves file hash indicators (MD5, SHA1, SHA256) from the CyfirmaIndicators_CL table within the last 5 minutes.
It filters records with a confidence score of 80 or higher, containing file hash patterns, a recommended action of 'Block', and roles marked as 'Malware'.
Extracted hashes and key threat intelligence details are projected for Blocking and investigation."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
// File Hash Indicators with Block Action and Malware
let timeFrame = 5m;
CyfirmaIndicators_CL
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (Roles contains "Malware")
| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
Algo_MD5='md5',
Algo_SHA1= 'SHA1',
Algo_SHA256='SHA256',
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
MD5,
Algo_MD5,
SHA1,
Algo_SHA1,
SHA256,
Algo_SHA256,
ThreatActors,
Sources,
RecommendedActions,
Roles,
Country,
name,
Description,
ConfidenceScore,
SecurityVendors,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- InitialAccess
- Execution
- Persistence
- PrivilegeEscalation
- DefenseEvasion
- CredentialAccess
- Discovery
- LateralMovement
- Collection
- Impact
relevantTechniques:
- T1566
- T1203
- T1059
- T1204
- T1547
- T1053
- T1055
- T1027
- T1562
- T1036
- T1003
- T1555
- T1082
- T1057
- T1021
- T1113
- T1486
- T1566.001
- T1059.001
- T1059.003
- T1547.001
- T1053.005
- T1562.001
- T1003.001
- T1555.003
- T1021.002
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence File Hash Indicators with Block Action and Malware - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProviderName
value: ProviderName
- alertProperty: ProductName
value: ProductName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
created: created
modified: modified
valid_from: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
entityMappings:
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_MD5
- identifier: Value
columnName: MD5
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_SHA1
- identifier: Value
columnName: SHA1
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_SHA256
- identifier: Value
columnName: SHA256
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (Roles contains "Malware")
Stage 3: extend (4 consecutive steps)
| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
Algo_MD5='md5',
Algo_SHA1= 'SHA1',
Algo_SHA256='SHA256',
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 4: project
| project
MD5,
Algo_MD5,
SHA1,
Algo_SHA1,
SHA256,
Algo_SHA256,
ThreatActors,
Sources,
RecommendedActions,
Roles,
Country,
name,
Description,
ConfidenceScore,
SecurityVendors,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Block" |
Roles | contains |
| field:"Roles" kind:contains value:"Malware" |
pattern | contains |
| field:"pattern" kind:contains value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Algo_MD5 | project |
Algo_SHA1 | project |
Algo_SHA256 | project |
ConfidenceScore | project |
Country | project |
Description | project |
IndicatorID | project |
MD5 | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SHA1 | project |
SHA256 | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
created | project |
modified | project |
name | project |
valid_from | project |
CYFIRMA - High severity Trojan File Hash Indicators with Monitor Action Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This KQL query extracts file hash indicators associated with Trojan activity from the CyfirmaIndicators_CL table. It specifically targets indicators containing file hashes linked to Trojan behavior and retrieves MD5, SHA1, and SHA256 values. The query also includes contextual threat intelligence such as threat actors, tags, sources, and geolocation information."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Stealth | |
| Credential Access | |
| Command & Control |
Rule Body
id: 4afd8960-8bee-4cac-bb5e-a4f200b1f9f3
name: CYFIRMA - High severity Trojan File Hash Indicators with Monitor Action Rule
description: |
"This KQL query extracts file hash indicators associated with Trojan activity from the CyfirmaIndicators_CL table.
It specifically targets indicators containing file hashes linked to Trojan behavior and retrieves MD5, SHA1, and SHA256 values.
The query also includes contextual threat intelligence such as threat actors, tags, sources, and geolocation information."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//Trojan File Hash Indicators with Monitor Action
let timeFrame = 5m;
CyfirmaIndicators_CL
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern contains 'file:hashes' and RecommendedActions has 'Monitor' and (Roles contains 'Trojan')
| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
Algo_MD5='md5',
Algo_SHA1= 'SHA1',
Algo_SHA256='SHA256',
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
MD5,
Algo_MD5,
SHA1,
Algo_SHA1,
SHA256,
Algo_SHA256,
ThreatActors,
Sources,
RecommendedActions,
Roles,
Country,
name,
Description,
ConfidenceScore,
SecurityVendors,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- InitialAccess
- Execution
- Persistence
- DefenseEvasion
- CommandAndControl
- CredentialAccess
relevantTechniques:
- T1566
- T1204
- T1547
- T1027
- T1071
- T1003
- T1566.001
- T1547.001
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence Trojan File Hash Indicators with Monitor Action - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
created: created
modified: modified
ValidFrom: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
entityMappings:
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_MD5
- identifier: Value
columnName: MD5
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_SHA1
- identifier: Value
columnName: SHA1
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_SHA256
- identifier: Value
columnName: SHA256
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where ConfidenceScore >= 80
and TimeGenerated between (ago(timeFrame) .. now())
and pattern contains 'file:hashes' and RecommendedActions has 'Monitor' and (Roles contains 'Trojan')
Stage 3: extend (4 consecutive steps)
| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
Algo_MD5='md5',
Algo_SHA1= 'SHA1',
Algo_SHA256='SHA256',
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 4: project
| project
MD5,
Algo_MD5,
SHA1,
Algo_SHA1,
SHA256,
Algo_SHA256,
ThreatActors,
Sources,
RecommendedActions,
Roles,
Country,
name,
Description,
ConfidenceScore,
SecurityVendors,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Monitor" |
Roles | contains |
| field:"Roles" kind:contains value:"Trojan" |
pattern | contains |
| field:"pattern" kind:contains value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Algo_MD5 | project |
Algo_SHA1 | project |
Algo_SHA256 | project |
ConfidenceScore | project |
Country | project |
Description | project |
IndicatorID | project |
MD5 | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SHA1 | project |
SHA256 | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
created | project |
modified | project |
name | project |
valid_from | project |
CYFIRMA - Medium Severity Attack Surface based Vulnerabilities Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This rule detects medium severity attack surface-based vulnerabilities from CYFIRMA's vulnerability intelligence data. It identifies vulnerabilities with a confidence score of 50 or higher, excluding those categorized as 'ASSET_VULNERABILITY', and generates alerts for assets that may be at risk."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Privilege Escalation | |
| Defense Impairment | |
| Credential Access | |
| Lateral Movement |
Rule Body
id: 4c1b282b-62f1-4783-bf40-94c44f0ae630
name: CYFIRMA - Medium Severity Attack Surface based Vulnerabilities Rule
description: |
"This rule detects medium severity attack surface-based vulnerabilities from CYFIRMA's vulnerability intelligence data. It identifies vulnerabilities with a confidence score of 50 or higher, excluding those categorized as 'ASSET_VULNERABILITY', and generates alerts for assets that may be at risk."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaVulnerabilitiesIntelDC
dataTypes:
- CyfirmaVulnerabilities_CL
query: |
// Medium severity - Attack Surface based Vulnerabilities
let timeFrame= 5m;
CyfirmaVulnerabilities_CL
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
| mv-expand extensionKeys
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
attack_complexity = tostring(props.attack_complexity),
cvss_score = toreal(props.cvss_score),
integrity_impact = tostring(props.integrity_impact),
impact_score = tostring(props.impact_score),
attack_vector = tostring(props.attack_vector),
privileges_required = tostring(props.privileges_required),
cvss_version = tostring(props.cvss_version),
user_interaction = tostring(props.user_interaction),
cvss_vector = tostring(props.cvss_vector),
scope = tostring(props.scope),
confidentiality_impact = tostring(props.confidentiality_impact),
exploitability_score = toreal(props.exploitability_score),
products = tostring(props.products),
technologies = tostring(props.technologies),
vendors = tostring(props.vendors),
confidence_score = toint(confidence),
servers = tostring(props.servers),
vulnerability_type = tostring(props.vulnerability_type),
vulnerability_category = tostring(props.vulnerability_category),
NetworkIPs = tostring(props.ips),
ProviderName ='CYFIRMA',
ProductName ='DeCYFIR/DeTCT'
| summarize arg_max(
integrity_impact,
TimeGenerated,
id,
description,
confidence_score,
created,
modified,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
)
by name
| where confidence_score >= 60 and vulnerability_category == 'ATTACK_SURFACE_VULNERABILITY' and TimeGenerated between (ago(timeFrame) .. now())
| project
TimeGenerated,
name,
confidence_score,
integrity_impact,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
UID = id,
description,
created,
modified,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: false
tactics:
- Execution
- LateralMovement
- PrivilegeEscalation
- InitialAccess
- CredentialAccess
- DefenseEvasion
relevantTechniques:
- T1059
- T1203
- T1210
- T1068
- T1190
- T1133
- T1003
- T1553
- T1548.002
- T1021.002
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - Medium Severity Attack Surface based Vulnerability Identified - {{name}} "
alertDescriptionFormat: "{{description}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
TimeGenerated: TimeGenerated
CVE: name
ConfidenceScore: confidence_score
IntegrityImpact: integrity_impact
AttackComplexity: attack_complexity
CVSSScore: cvss_score
ImpactScore: impact_score
AttackVector: attack_vector
ConfidentialImpact: confidentiality_impact
PrivilegesRequired: privileges_required
CVSSVersion: cvss_version
UserInteraction: user_interaction
CVSSVector: scope
scope: exploitability_score
ExploitabilityScore: modified
Modified: products
Products: technologies
Vendors: vendors
Technologies: technologies
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaVulnerabilities_CL
Stage 2: extend
| extend parsed = parse_json(extensions)
Stage 3: extend
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
attack_complexity = tostring(props.attack_complexity),
cvss_score = toreal(props.cvss_score),
integrity_impact = tostring(props.integrity_impact),
impact_score = tostring(props.impact_score),
attack_vector = tostring(props.attack_vector),
privileges_required = tostring(props.privileges_required),
cvss_version = tostring(props.cvss_version),
user_interaction = tostring(props.user_interaction),
cvss_vector = tostring(props.cvss_vector),
scope = tostring(props.scope),
confidentiality_impact = tostring(props.confidentiality_impact),
exploitability_score = toreal(props.exploitability_score),
products = tostring(props.products),
technologies = tostring(props.technologies),
vendors = tostring(props.vendors),
confidence_score = toint(confidence),
servers = tostring(props.servers),
vulnerability_type = tostring(props.vulnerability_type),
vulnerability_category = tostring(props.vulnerability_category),
NetworkIPs = tostring(props.ips),
ProviderName ='CYFIRMA',
ProductName ='DeCYFIR/DeTCT'
Stage 6: summarize
| summarize arg_max(
integrity_impact,
TimeGenerated,
id,
description,
confidence_score,
created,
modified,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
)
by name
Stage 7: where
| where confidence_score >= 60 and vulnerability_category == 'ATTACK_SURFACE_VULNERABILITY' and TimeGenerated between (ago(timeFrame) .. now())
Stage 8: project
| project
TimeGenerated,
name,
confidence_score,
integrity_impact,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
UID = id,
description,
created,
modified,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
confidence_score | ge |
| field:"confidence_score" kind:ge value:"60" |
vulnerability_category | eq |
| field:"vulnerability_category" kind:eq value:"ATTACK_SURFACE_VULNERABILITY" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
NetworkIPs | project |
ProductName | project |
ProviderName | project |
TimeGenerated | project |
UID | project |
attack_complexity | project |
attack_vector | project |
confidence_score | project |
confidentiality_impact | project |
created | project |
cvss_score | project |
cvss_vector | project |
cvss_version | project |
description | project |
exploitability_score | project |
impact_score | project |
integrity_impact | project |
modified | project |
name | project |
privileges_required | project |
products | project |
scope | project |
servers | project |
technologies | project |
user_interaction | project |
vendors | project |
vulnerability_category | project |
vulnerability_type | project |
Detect Potential Kerberoast Activities
#This query aim to detect if someone requests service tickets (where count => maxcount) The query requires trimming to set a baseline level for MaxCount
Mitre Technique: Kerberoasting (T1558.003) @MattiasBorg82
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | IdentityLogonEvents action LogonSuccess: Identity logon succeeded |
Rule Body
id: 12134de5-361b-427c-a1a0-d43f40a593c4
name: Detect Potential Kerberoast Activities
description: |
This query aim to detect if someone requests service tickets (where count => maxcount)
The query requires trimming to set a baseline level for MaxCount
Mitre Technique: Kerberoasting (T1558.003)
@MattiasBorg82
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- IdentityLogonEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1558.003
tag:
- Kerberoast
query: |
let MaxCount = 70; //Number of requests per 2 minute timeframe, depending on org size.
IdentityLogonEvents
| where ActionType == "LogonSuccess"
| where Protocol == "Kerberos"
| extend json = todynamic(parse_json(tostring(AdditionalFields)))
| extend SPN = json.Spns,
AttackTechniques = json.AttackTechniques
| project-away json
| where isnotempty(SPN)
| where AttackTechniques has "T1558.003"
| mv-expand SPN
| extend SPNType = tostring(extract(@"^\w+",0,tostring(SPN)))
| distinct tostring(SPN), DeviceName, AccountUpn, AccountSid, bin(TimeGenerated, 2m), ReportId, tostring(AttackTechniques)
| summarize count(), SPNS=(make_list(SPN, 100000)),ReportId=tostring((make_list(ReportId, 100000))[0]) by AccountUpn,AccountSid,DeviceName, bin(TimeGenerated, 2m), tostring(AttackTechniques)
| extend SPNS = (replace_regex(tostring(SPNS), @'[^\w+-\/]+', ''))
| where count_ >= MaxCount
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
| extend AccountDomain = tostring(split(AccountUpn, '@')[1]), AccountName = tostring(split(AccountUpn, '@')[0])
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountUpn
- identifier: DnsDomain
columnName: AccountDomain
- identifier: Name
columnName: AccountName
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let MaxCount = 70;
Stage 1: source
IdentityLogonEvents
Stage 2: where
| where ActionType == "LogonSuccess"
Stage 3: where
| where Protocol == "Kerberos"
Stage 4: extend
| extend json = todynamic(parse_json(tostring(AdditionalFields)))
Stage 5: extend
| extend SPN = json.Spns,
AttackTechniques = json.AttackTechniques
Stage 6: project-away
| project-away json
Stage 7: where
| where isnotempty(SPN)
Stage 8: where
| where AttackTechniques has "T1558.003"
Stage 9: mv-expand
| mv-expand SPN
Stage 10: extend
| extend SPNType = tostring(extract(@"^\w+",0,tostring(SPN)))
Stage 11: distinct
| distinct tostring(SPN), DeviceName, AccountUpn, AccountSid, bin(TimeGenerated, 2m), ReportId, tostring(AttackTechniques)
Stage 12: summarize
| summarize count(), SPNS=(make_list(SPN, 100000)),ReportId=tostring((make_list(ReportId, 100000))[0]) by AccountUpn,AccountSid,DeviceName, bin(TimeGenerated, 2m), tostring(AttackTechniques)
Stage 13: extend
| extend SPNS = (replace_regex(tostring(SPNS), @'[^\w+-\/]+', ''))
Stage 14: where
| where count_ >= MaxCount
Stage 15: extend (3 consecutive steps)
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
| extend AccountDomain = tostring(split(AccountUpn, '@')[1]), AccountName = tostring(split(AccountUpn, '@')[0])
HostName =DeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))DeviceNameIndicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"LogonSuccess" |
AttackTechniques | match |
| field:"AttackTechniques" kind:match value:"T1558.003" |
Protocol | eq |
| field:"Protocol" kind:eq value:"Kerberos" |
SPN | is_not_null | field:"SPN" kind:is_not_null | |
count_ | ge |
| field:"count_" kind:ge value:"70" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountSid | summarize |
AccountUpn | summarize |
DeviceName | summarize |
ReportId | summarize |
SPNS | extend |
HostName | extend |
DnsDomain | extend |
AccountDomain | extend |
AccountName | extend |
High count of failed logons by a user
#'Identifies when 100 or more failed attempts by a given user in 10 minutes occur on the IIS Server. This could be indicative of attempted brute force based on known account information. This could also simply indicate a misconfigured service or device. References: IIS status code mapping - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0 Win32 Status code mapping - https://msdn.microsoft.com/library/cc231199.aspx'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 884c4957-70ea-4f57-80b9-1bca3890315b
name: High count of failed logons by a user
description: |
'Identifies when 100 or more failed attempts by a given user in 10 minutes occur on the IIS Server.
This could be indicative of attempted brute force based on known account information.
This could also simply indicate a misconfigured service or device.
References:
IIS status code mapping - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0
Win32 Status code mapping - https://msdn.microsoft.com/library/cc231199.aspx'
severity: Medium
requiredDataConnectors:
- connectorId: AzureMonitor(IIS)
dataTypes:
- W3CIISLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let timeBin = 10m;
let failedThreshold = 100;
W3CIISLog
| where scStatus in ("401","403")
| where csUserName != "-"
// Handling Exchange specific items in IIS logs to remove the unique log identifier in the URI
| extend csUriQuery = iff(csUriQuery startswith "MailboxId=", tostring(split(csUriQuery, "&")[0]) , csUriQuery )
| extend csUriQuery = iff(csUriQuery startswith "X-ARR-CACHE-HIT=", strcat(tostring(split(csUriQuery, "&")[0]),tostring(split(csUriQuery, "&")[1])) , csUriQuery )
| extend scStatusFull = strcat(scStatus, ".",scSubStatus)
// Map common IIS codes
| extend scStatusFull_Friendly = case(
scStatusFull == "401.0", "Access denied.",
scStatusFull == "401.1", "Logon failed.",
scStatusFull == "401.2", "Logon failed due to server configuration.",
scStatusFull == "401.3", "Unauthorized due to ACL on resource.",
scStatusFull == "401.4", "Authorization failed by filter.",
scStatusFull == "401.5", "Authorization failed by ISAPI/CGI application.",
scStatusFull == "403.0", "Forbidden.",
scStatusFull == "403.4", "SSL required.",
"See - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0")
// Mapping to Hex so can be mapped using website in comments above
| extend scWin32Status_Hex = tohex(tolong(scWin32Status))
// Map common win32 codes
| extend scWin32Status_Friendly = case(
scWin32Status_Hex =~ "775", "The referenced account is currently locked out and cannot be logged on to.",
scWin32Status_Hex =~ "52e", "Logon failure: Unknown user name or bad password.",
scWin32Status_Hex =~ "532", "Logon failure: The specified account password has expired.",
scWin32Status_Hex =~ "533", "Logon failure: Account currently disabled.",
scWin32Status_Hex =~ "2ee2", "The request has timed out.",
scWin32Status_Hex =~ "0", "The operation completed successfully.",
scWin32Status_Hex =~ "1", "Incorrect function.",
scWin32Status_Hex =~ "2", "The system cannot find the file specified.",
scWin32Status_Hex =~ "3", "The system cannot find the path specified.",
scWin32Status_Hex =~ "4", "The system cannot open the file.",
scWin32Status_Hex =~ "5", "Access is denied.",
scWin32Status_Hex =~ "8009030e", "SEC_E_NO_CREDENTIALS",
scWin32Status_Hex =~ "8009030C", "SEC_E_LOGON_DENIED",
"See - https://msdn.microsoft.com/library/cc231199.aspx")
// decode URI when available
| extend decodedUriQuery = url_decode(csUriQuery)
// Count of failed logons by a user
| summarize makeset(decodedUriQuery), makeset(cIP), makeset(sSiteName), makeset(sPort), makeset(csUserAgent), makeset(csMethod), makeset(csUriQuery), makeset(scStatusFull), makeset(scStatusFull_Friendly), makeset(scWin32Status_Hex), makeset(scWin32Status_Friendly), FailedConnectionsCount = count() by bin(TimeGenerated, timeBin), csUserName, Computer, sIP
| where FailedConnectionsCount >= failedThreshold
| project TimeGenerated, csUserName, set_decodedUriQuery, Computer, set_sSiteName, sIP, set_cIP, set_sPort, set_csUserAgent, set_csMethod, set_scStatusFull, set_scStatusFull_Friendly, set_scWin32Status_Hex, set_scWin32Status_Friendly, FailedConnectionsCount
| order by FailedConnectionsCount
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(csUserName, "@")[0]), AccountUPNSuffix = tostring(split(csUserName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: csUserName
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: HostNameDomain
version: 1.0.3
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let timeBin = 10m;
let failedThreshold = 100;
Stage 1: source
W3CIISLog
Stage 2: where
| where scStatus in ("401","403")
Stage 3: where
| where csUserName != "-"
Stage 4: extend (7 consecutive steps)
| extend csUriQuery = iff(csUriQuery startswith "MailboxId=", tostring(split(csUriQuery, "&")[0]) , csUriQuery )
| extend csUriQuery = iff(csUriQuery startswith "X-ARR-CACHE-HIT=", strcat(tostring(split(csUriQuery, "&")[0]),tostring(split(csUriQuery, "&")[1])) , csUriQuery )
| extend scStatusFull = strcat(scStatus, ".",scSubStatus)
| extend scStatusFull_Friendly = case(
scStatusFull == "401.0", "Access denied.",
scStatusFull == "401.1", "Logon failed.",
scStatusFull == "401.2", "Logon failed due to server configuration.",
scStatusFull == "401.3", "Unauthorized due to ACL on resource.",
scStatusFull == "401.4", "Authorization failed by filter.",
scStatusFull == "401.5", "Authorization failed by ISAPI/CGI application.",
scStatusFull == "403.0", "Forbidden.",
scStatusFull == "403.4", "SSL required.",
"See - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0")
| extend scWin32Status_Hex = tohex(tolong(scWin32Status))
| extend scWin32Status_Friendly = case(
scWin32Status_Hex =~ "775", "The referenced account is currently locked out and cannot be logged on to.",
scWin32Status_Hex =~ "52e", "Logon failure: Unknown user name or bad password.",
scWin32Status_Hex =~ "532", "Logon failure: The specified account password has expired.",
scWin32Status_Hex =~ "533", "Logon failure: Account currently disabled.",
scWin32Status_Hex =~ "2ee2", "The request has timed out.",
scWin32Status_Hex =~ "0", "The operation completed successfully.",
scWin32Status_Hex =~ "1", "Incorrect function.",
scWin32Status_Hex =~ "2", "The system cannot find the file specified.",
scWin32Status_Hex =~ "3", "The system cannot find the path specified.",
scWin32Status_Hex =~ "4", "The system cannot open the file.",
scWin32Status_Hex =~ "5", "Access is denied.",
scWin32Status_Hex =~ "8009030e", "SEC_E_NO_CREDENTIALS",
scWin32Status_Hex =~ "8009030C", "SEC_E_LOGON_DENIED",
"See - https://msdn.microsoft.com/library/cc231199.aspx")
| extend decodedUriQuery = url_decode(csUriQuery)
csUriQuery =csUriQuery startswith "MailboxId="tostring(split(csUriQuery, "&")[0])csUriQueryStage 5: summarize
| summarize makeset(decodedUriQuery), makeset(cIP), makeset(sSiteName), makeset(sPort), makeset(csUserAgent), makeset(csMethod), makeset(csUriQuery), makeset(scStatusFull), makeset(scStatusFull_Friendly), makeset(scWin32Status_Hex), makeset(scWin32Status_Friendly), FailedConnectionsCount = count() by bin(TimeGenerated, timeBin), csUserName, Computer, sIP
Stage 6: where
| where FailedConnectionsCount >= failedThreshold
Stage 7: project
| project TimeGenerated, csUserName, set_decodedUriQuery, Computer, set_sSiteName, sIP, set_cIP, set_sPort, set_csUserAgent, set_csMethod, set_scStatusFull, set_scStatusFull_Friendly, set_scWin32Status_Hex, set_scWin32Status_Friendly, FailedConnectionsCount
Stage 8: sort
| order by FailedConnectionsCount
Stage 9: extend (3 consecutive steps)
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(csUserName, "@")[0]), AccountUPNSuffix = tostring(split(csUserName, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
FailedConnectionsCount | ge |
| field:"FailedConnectionsCount" kind:ge value:"100" |
csUserName | ne |
| field:"csUserName" kind:ne value:"-" |
scStatus | in |
| field:"scStatus" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | project |
FailedConnectionsCount | project |
TimeGenerated | project |
csUserName | project |
sIP | project |
set_cIP | project |
set_csMethod | project |
set_csUserAgent | project |
set_decodedUriQuery | project |
set_sPort | project |
set_sSiteName | project |
set_scStatusFull | project |
set_scStatusFull_Friendly | project |
set_scWin32Status_Friendly | project |
set_scWin32Status_Hex | project |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
AccountName | extend |
AccountUPNSuffix | extend |
Keeper Security - User MFA Changed
#'Creates an informational incident based on Keeper Security User MFA Changed data in Microsoft Sentinel'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: 75ffc8a4-86db-4f48-8506-cb4c049be484
name: Keeper Security - User MFA Changed
description: |
'Creates an informational incident based on Keeper Security User MFA Changed data in Microsoft Sentinel'
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: KeeperSecurityPush2
dataTypes:
- KeeperSecurityEventNewLogs_CL
suppressionDuration: PT5H
eventGroupingSettings:
aggregationKind: AlertPerResult
suppressionEnabled: false
tactics:
- Persistence
relevantTechniques:
- T1556
query: |
KeeperSecurityEventNewLogs_CL
| where AuditEvent in (```set_two_factor_off```, ```set_two_factor_on```)
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: "{{AuditEvent}} on {{RemoteAddress}}"
alertDescriptionFormat: "{{AuditEvent}} has been captured in the Keeper Security Event Logs"
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Username
- entityType: IP
fieldMappings:
- identifier: Address
columnName: RemoteAddress
version: 1.0.3
kind: NRT
Stages and Predicates
Stage 1: source
KeeperSecurityEventNewLogs_CL
Stage 2: where
| where AuditEvent in (```set_two_factor_off```, ```set_two_factor_on```)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AuditEvent | in |
| field:"AuditEvent" kind:in |
Malware Detection Settings Updated
#Detects when malware detection settings are updated.
Rule Body
id: effd8410-3119-41c8-a228-9c0c8ce10d67
name: Malware Detection Settings Updated
description: Detects when malware detection settings are updated.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 42290\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 == 42290
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:"42290" |
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 |
Multiple admin membership removals from newly created admin.
#This query detects when newly created Global admin removes multiple existing global admins which can be an attempt by adversaries to lock down organization and retain sole access. Investigate reasoning and intention of multiple membership removal by new Global admins and take necessary actions accordingly.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | any: Entra ID audit event (any operation) |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- [Entra ID] Mass Privileged Role Change Activity Detected (Kusto)
- [Entra ID] Privilege Elevation Request Denied (Kusto)
- [Entra ID] Privileged Role Assigned to a New User (Kusto)
- [Entra ID] Privileged Role Assigned to User (Kusto)
- Account Elevated to New Role (Kusto)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Assigned To Azure RBAC/Microsoft Entra Role (Sigma)
Rule Body
id: cda5928c-2c1e-4575-9dfa-07568bc27a4f
name: Multiple admin membership removals from newly created admin.
description: |
'This query detects when newly created Global admin removes multiple existing global admins which can be an attempt by adversaries to lock down organization and retain sole access.
Investigate reasoning and intention of multiple membership removal by new Global admins and take necessary actions accordingly.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Impact
relevantTechniques:
- T1531
tags:
- DEV-0537
query: |
let lookback = 7d;
let timeframe = 1h;
let GlobalAdminsRemoved = AuditLogs
| where TimeGenerated > ago(timeframe)
| where Category =~ "RoleManagement"
| where AADOperationType in ("Unassign", "RemoveEligibleRole")
| where ActivityDisplayName has_any ("Remove member from role", "Remove eligible member from role")
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend Target = tostring(TargetResource.userPrincipalName),
props = TargetResource.modifiedProperties
)
| mv-apply Property = props on
(
where Property.displayName =~ "Role.DisplayName"
| extend RoleName = trim('"',tostring(Property.oldValue))
)
| where RoleName =~ "Global Administrator" // Add other Privileged role if applicable
| 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(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
| where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax" // Filtering PIM events
| summarize RemovedGlobalAdminTime = max(TimeGenerated), TargetAdmins = make_set(Target,100) by OperationName, RoleName, Initiator, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, Result;
let GlobalAdminsAdded = AuditLogs
| where TimeGenerated > ago(lookback)
| where Category =~ "RoleManagement"
| where AADOperationType in ("Assign", "AssignEligibleRole")
| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role") and Result == "success"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend Target = tostring(TargetResource.userPrincipalName),
props = TargetResource.modifiedProperties
)
| mv-apply Property = props on
(
where Property.displayName =~ "Role.DisplayName"
| extend RoleName = trim('"',tostring(Property.newValue))
)
| where RoleName =~ "Global Administrator" // Add other Privileged role if applicable
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, tostring(InitiatedBy.user.userPrincipalName))
| where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax" // Filtering PIM events
| summarize AddedGlobalAdminTime = max(TimeGenerated) by OperationName, RoleName, Target, Initiator, Result;
GlobalAdminsAdded
| join kind= inner GlobalAdminsRemoved on $left.Target == $right.Initiator
| where AddedGlobalAdminTime < RemovedGlobalAdminTime
| extend NoofAdminsRemoved = array_length(TargetAdmins)
| where NoofAdminsRemoved > 1
| project AddedGlobalAdminTime, Initiator, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, Target, RemovedGlobalAdminTime, TargetAdmins, NoofAdminsRemoved
| extend TargetName = tostring(split(Target,'@',0)[0]), TargetUPNSuffix = tostring(split(Target,'@',1)[0])
| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Target
- identifier: Name
columnName: TargetName
- identifier: UPNSuffix
columnName: TargetUPNSuffix
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatedByName
- identifier: UPNSuffix
columnName: InitiatedByUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let lookback = 7d;
let timeframe = 1h;
let GlobalAdminsAdded is inlined into the numbered stages below.
Let Binding: GlobalAdminsRemoved
let GlobalAdminsRemoved = AuditLogs
| where TimeGenerated > ago(timeframe)
| where Category =~ "RoleManagement"
| where AADOperationType in ("Unassign", "RemoveEligibleRole")
| where ActivityDisplayName has_any ("Remove member from role", "Remove eligible member from role")
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend Target = tostring(TargetResource.userPrincipalName),
props = TargetResource.modifiedProperties
)
| mv-apply Property = props on
(
where Property.displayName =~ "Role.DisplayName"
| extend RoleName = trim('"',tostring(Property.oldValue))
)
| where RoleName =~ "Global Administrator"
| 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(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
| where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax"
| summarize RemovedGlobalAdminTime = max(TimeGenerated), TargetAdmins = make_set(Target,100) by OperationName, RoleName, Initiator, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, Result;
Stages 1 to 12 define let GlobalAdminsAdded as the rule's main pipeline source. Stages 13 to 19 use it.
Stage 1: source
AuditLogs
Stage 2: where
| where TimeGenerated > ago(lookback)
Stage 3: where
| where Category =~ "RoleManagement"
Stage 4: where
| where AADOperationType in ("Assign", "AssignEligibleRole")
Stage 5: where
| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role") and Result == "success"
Stage 6: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "User"
| extend Target = tostring(TargetResource.userPrincipalName),
props = TargetResource.modifiedProperties
)
Stage 7: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName =~ "Role.DisplayName"
| extend RoleName = trim('"',tostring(Property.newValue))
)
Stage 8: where
| where RoleName =~ "Global Administrator"
Stage 9: extend
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
Stage 10: extend
| extend Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, tostring(InitiatedBy.user.userPrincipalName))
Stage 11: where
| where Initiator != "MS-PIM" and Initiator != "MS-PIM-Fairfax"
Stage 12: summarize
| summarize AddedGlobalAdminTime = max(TimeGenerated) by OperationName, RoleName, Target, Initiator, Result
Stage 13: join
GlobalAdminsAdded
| join kind= inner GlobalAdminsRemoved on $left.Target == $right.Initiator
Stage 14: where
where AddedGlobalAdminTime < RemovedGlobalAdminTime
Stage 15: extend
extend NoofAdminsRemoved
Stage 16: where
where NoofAdminsRemoved > 1
Stage 17: project
project AddedGlobalAdminTime, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserPrincipalName, Initiator, NoofAdminsRemoved, RemovedGlobalAdminTime, Target, TargetAdmins
Stage 18: extend
extend TargetName, TargetUPNSuffix
Stage 19: extend
extend InitiatedByName, InitiatedByUPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AADOperationType | in |
| field:"AADOperationType" kind:in |
ActivityDisplayName | match |
| field:"azure_ad::activity_display_name" kind:match |
Category | eq |
| field:"Category" kind:eq value:"RoleManagement" |
Initiator | ne |
| field:"Initiator" kind:ne |
NoofAdminsRemoved | gt |
| field:"NoofAdminsRemoved" kind:gt value:"1" |
Property.displayName | eq |
| field:"Property.displayName" kind:eq value:"Role.DisplayName" |
Result | eq |
| field:"Result" kind:eq value:"success" |
RoleName | eq |
| field:"RoleName" kind:eq value:"Global Administrator" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"User" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AddedGlobalAdminTime | project |
InitiatingAadUserId | project |
InitiatingAppName | project |
InitiatingAppServicePrincipalId | project |
InitiatingIpAddress | project |
InitiatingUserPrincipalName | project |
Initiator | project |
NoofAdminsRemoved | project |
RemovedGlobalAdminTime | project |
Target | project |
TargetAdmins | project |
TargetName | extend |
TargetUPNSuffix | extend |
InitiatedByName | extend |
InitiatedByUPNSuffix | extend |
NetworkSecurityGroups Alert From Prancer
#'High severity network security groups alerts found by Prancer.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: a8babf91-b844-477c-8abf-d31e3df74933
name: NetworkSecurityGroups Alert From Prancer
description: |
'High severity network security groups alerts found by Prancer.'
severity: High
requiredDataConnectors:
- connectorId: PrancerLogData
dataTypes:
- prancer_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: SingleAlert
status: Available
tactics:
- Reconnaissance
relevantTechniques:
- T1595
query: |
union prancer_CL
| where deviceProduct_s == 'azure'
| where parse_json(data_data_snapshots_s)[0].type == 'Microsoft.Network/networkSecurityGroups' //and parse_json(data_data_snapshots_s)[0].region != ''
| where data_data_severity_s == 'High' and data_data_result_s == 'failed'
| extend snapshot = parse_json(data_data_snapshots_s)
| mv-expand snapshot
| extend
id = tostring(snapshot.id),
structure = tostring(snapshot.structure),
reference = tostring(snapshot.reference),
source = tostring(snapshot.source),
collection = tostring(snapshot.collection),
type = tostring(snapshot.type),
region = tostring(snapshot.region),
resourceTypes = tostring(snapshot.resourceTypes),
path = tostring(snapshot.path)
customDetails:
entityMappings:
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: path
alertDetailsOverride:
alertDisplayNameFormat: "{{data_data_message_s}}"
alertDescriptionFormat: "{{data_data_description_s}}"
alertSeverityColumnName: "{{data_data_severity_s}}"
alertDynamicProperties:
- alertProperty: RemediationSteps
value: data_data_remediation_description_s
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: union
union of 1 branches
Stage 2: source
prancer_CL
Stage 3: where
where deviceProduct_s == "azure"
Stage 4: where
where parse_json(data_data_snapshots_s)[0].type == "Microsoft.Network/networkSecurityGroups"
Stage 5: where
where data_data_result_s == "failed" and data_data_severity_s == "High"
Stage 6: extend
extend snapshot
Stage 7: mv-expand
mv-expand snapshot
Stage 8: extend
extend collection, id, path, reference, region, resourceTypes, source, structure, type
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
data_data_result_s | eq |
| field:"data_data_result_s" kind:eq value:"failed" |
data_data_severity_s | eq |
| field:"data_data_severity_s" kind:eq value:"High" |
data_data_snapshots_s[0].type | eq |
| field:"data_data_snapshots_s[0].type" kind:eq value:"Microsoft.Network/networkSecurityGroups" |
deviceProduct_s | eq |
| field:"deviceProduct_s" kind:eq value:"azure" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
snapshot | extend |
collection | extend |
id | extend |
path | extend |
reference | extend |
region | extend |
resourceTypes | extend |
source | extend |
structure | extend |
type | extend |
New EXE deployed via Default Domain or Default Domain Controller Policies
#This detection highlights executables deployed to hosts via either the Default Domain or Default Domain Controller Policies. These policies apply to all hosts or Domain Controllers and best practice is that these policies should not be used for deployment of files. A threat actor may use these policies to deploy files or scripts to all hosts in a domain.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Lateral Movement |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 4688: A new process has been created. |
Rule Body
id: 05b4bccd-dd12-423d-8de4-5a6fb526bb4f
name: New EXE deployed via Default Domain or Default Domain Controller Policies
description: |
'This detection highlights executables deployed to hosts via either the Default Domain or Default Domain Controller Policies. These policies apply to all hosts or Domain Controllers and best practice is that these policies should not be used for deployment of files.
A threat actor may use these policies to deploy files or scripts to all hosts in a domain.'
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Execution
- LateralMovement
relevantTechniques:
- T1072
- T1570
query: |
let known_processes = (
SecurityEvent
// If adjusting Query Period or Frequency update these
| where TimeGenerated between(ago(14d)..ago(1d))
| where EventID == 4688
| where NewProcessName has_any ("Policies\\{6AC1786C-016F-11D2-945F-00C04fB984F9}", "Policies\\{31B2F340-016D-11D2-945F-00C04FB984F9}")
| summarize by Process);
SecurityEvent
// If adjusting Query Period or Frequency update these
| where TimeGenerated > ago(1d)
| where EventID == 4688
| where NewProcessName has_any ("Policies\\{6AC1786C-016F-11D2-945F-00C04fB984F9}", "Policies\\{31B2F340-016D-11D2-945F-00C04FB984F9}")
| where Process !in (known_processes)
// This will likely apply to multiple hosts so summarize these data
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by Process, NewProcessName, CommandLine, Computer
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
version: 1.0.2
kind: Scheduled
Stages and Predicates
Let Binding: known_processes
let known_processes = (
SecurityEvent
| where TimeGenerated between(ago(14d)..ago(1d))
| where EventID == 4688
| where NewProcessName has_any ("Policies\\{6AC1786C-016F-11D2-945F-00C04fB984F9}", "Policies\\{31B2F340-016D-11D2-945F-00C04FB984F9}")
| summarize by Process);
Stage 1: source
SecurityEvent
Stage 2: where
where TimeGenerated > ago(86400s)
Stage 3: where
where EventID == 4688
Stage 4: where
where (NewProcessName has @"Policies\{6AC1786C-016F-11D2-945F-00C04fB984F9}" or NewProcessName has @"Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}")
Stage 5: where
where not (Process == "known_processes")
Stage 6: summarize
summarize FirstSeen, LastSeen by Process, NewProcessName, CommandLine, Computer
Stage 7: extend
extend DomainIndex, HostName
Stage 8: extend
extend HostNameDomain
HostNameDomain =DomainIndex != -1substring(Computer, (DomainIndex + 1))ComputerIndicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | eq |
| field:"EventID" kind:eq value:"4688" |
NewProcessName | match |
| field:"Image" kind:match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Process | eq | known_processes | excludes:Process field:"Process" value:"known_processes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CommandLine | summarize |
Computer | summarize |
FirstSeen | summarize |
LastSeen | summarize |
NewProcessName | summarize |
Process | summarize |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
New user created and added to the built-in administrators group
#Identifies when a user account was created and then added to the builtin Administrators group in the same day. This should be monitored closely and all additions reviewed.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rule Body
id: aa1eff90-29d4-49dc-a3ea-b65199f516db
name: New user created and added to the built-in administrators group
description: |
'Identifies when a user account was created and then added to the builtin Administrators group in the same day.
This should be monitored closely and all additions reviewed.'
severity: Low
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1098
- T1078
query: |
(union isfuzzy=true
(SecurityEvent
| where EventID == 4720
| where AccountType == "User"
| project CreatedUserTime = TimeGenerated, CreatedUserEventID = EventID, CreatedUserActivity = Activity, Computer = toupper(Computer),
CreatedUser = tolower(TargetAccount), CreatedUserAccountName = TargetUserName, CreatedUserDomainName = TargetDomainName, CreatedUserSid = TargetSid,
AccountUsedToCreateUser = SubjectAccount, CreatedByAccountName = SubjectUserName, CreatedByDomainName = SubjectDomainName, SidofAccountUsedToCreateUser = SubjectUserSid
),
(WindowsEvent
| where EventID == 4720
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| where AccountType == "User"
| extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
| extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
| extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName)
| extend Activity="4720 - A user account was created."
| extend TargetSid = tostring(EventData.TargetSid)
| project CreatedUserTime = TimeGenerated, CreatedUserEventID = EventID, CreatedUserActivity = Activity, Computer = toupper(Computer),
CreatedUser = tolower(TargetAccount), CreatedUserAccountName = TargetUserName, CreatedUserDomainName = TargetDomainName, CreatedUserSid = TargetSid,
AccountUsedToCreateUser = SubjectAccount, CreatedByAccountName = SubjectUserName, CreatedByDomainName = SubjectDomainName, SidofAccountUsedToCreateUser = SubjectUserSid
)
)
| join kind=inner
(
(union isfuzzy=true
(SecurityEvent
| where AccountType == "User"
// 4732 - A member was added to a security-enabled local group
| where EventID == 4732
// TargetSid is the builin Admins group: S-1-5-32-544
| where TargetSid == "S-1-5-32-544"
| project GroupAddTime = TimeGenerated, GroupAddEventID = EventID, GroupAddActivity = Activity, Computer = toupper(Computer), GroupName = tolower(TargetAccount),
GroupSid = TargetSid, AccountThatAddedUser = SubjectAccount, SIDofAccountThatAddedUser = SubjectUserSid, AddedByAccountName = SubjectUserName, AddedByDomainName = SubjectDomainName,
CreatedUserSid = MemberSid
),
( WindowsEvent
// 4732 - A member was added to a security-enabled local group
| where EventID == 4732 and EventData has "S-1-5-32-544"
//TargetSid is the builin Admins group: S-1-5-32-544
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| where AccountType == "User"
| extend TargetSid = tostring(EventData.TargetSid)
| where TargetSid == "S-1-5-32-544"
| extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
| extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
| extend Activity="4732 - A member was added to a security-enabled local group."
| extend MemberSid = tostring(EventData.MemberSid)
| project GroupAddTime = TimeGenerated, GroupAddEventID = EventID, GroupAddActivity = Activity, Computer = toupper(Computer), GroupName = tolower(TargetAccount),
GroupSid = TargetSid, AccountThatAddedUser = SubjectAccount, SIDofAccountThatAddedUser = SubjectUserSid, AddedByAccountName = SubjectUserName, AddedByDomainName = SubjectDomainName,
CreatedUserSid = MemberSid
)
)
)
on CreatedUserSid
//Create User first, then the add to the group.
| project Computer, CreatedUserTime, CreatedUserEventID, CreatedUserActivity, CreatedUser, CreatedUserSid, CreatedUserAccountName, CreatedUserDomainName,
GroupAddTime, GroupAddEventID, GroupAddActivity, GroupName, GroupSid,
AccountUsedToCreateUser, SidofAccountUsedToCreateUser, CreatedByAccountName, CreatedByDomainName,
AccountThatAddedUser, SIDofAccountThatAddedUser, AddedByAccountName, AddedByDomainName
| 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: AccountUsedToCreateUser
- identifier: Name
columnName: CreatedByAccountName
- identifier: NTDomain
columnName: CreatedByDomainName
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountThatAddedUser
- identifier: Name
columnName: AddedByAccountName
- identifier: NTDomain
columnName: AddedByDomainName
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: CreatedUser
- identifier: Name
columnName: CreatedUserAccountName
- identifier: NTDomain
columnName: CreatedUserDomainName
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: CreatedUserSid
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: HostNameDomain
version: 1.2.1
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
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 == 4720
| where AccountType == "User"
| project CreatedUserTime = TimeGenerated, CreatedUserEventID = EventID, CreatedUserActivity = Activity, Computer = toupper(Computer),
CreatedUser = tolower(TargetAccount), CreatedUserAccountName = TargetUserName, CreatedUserDomainName = TargetDomainName, CreatedUserSid = TargetSid,
AccountUsedToCreateUser = SubjectAccount, CreatedByAccountName = SubjectUserName, CreatedByDomainName = SubjectDomainName, SidofAccountUsedToCreateUser = SubjectUserSid
Leg 2: WindowsEvent
WindowsEvent
| where EventID == 4720
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| where AccountType == "User"
| extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
| extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
| extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName)
| extend Activity="4720 - A user account was created."
| extend TargetSid = tostring(EventData.TargetSid)
| project CreatedUserTime = TimeGenerated, CreatedUserEventID = EventID, CreatedUserActivity = Activity, Computer = toupper(Computer),
CreatedUser = tolower(TargetAccount), CreatedUserAccountName = TargetUserName, CreatedUserDomainName = TargetDomainName, CreatedUserSid = TargetSid,
AccountUsedToCreateUser = SubjectAccount, CreatedByAccountName = SubjectUserName, CreatedByDomainName = SubjectDomainName, SidofAccountUsedToCreateUser = SubjectUserSid
Applied to the Combined Result
| join kind=inner
(
(union isfuzzy=true
(SecurityEvent
| where AccountType == "User"
| where EventID == 4732
| where TargetSid == "S-1-5-32-544"
| project GroupAddTime = TimeGenerated, GroupAddEventID = EventID, GroupAddActivity = Activity, Computer = toupper(Computer), GroupName = tolower(TargetAccount),
GroupSid = TargetSid, AccountThatAddedUser = SubjectAccount, SIDofAccountThatAddedUser = SubjectUserSid, AddedByAccountName = SubjectUserName, AddedByDomainName = SubjectDomainName,
CreatedUserSid = MemberSid
),
( WindowsEvent
| where EventID == 4732 and EventData has "S-1-5-32-544"
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| where AccountType == "User"
| extend TargetSid = tostring(EventData.TargetSid)
| where TargetSid == "S-1-5-32-544"
| extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
| extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
| extend Activity="4732 - A member was added to a security-enabled local group."
| extend MemberSid = tostring(EventData.MemberSid)
| project GroupAddTime = TimeGenerated, GroupAddEventID = EventID, GroupAddActivity = Activity, Computer = toupper(Computer), GroupName = tolower(TargetAccount),
GroupSid = TargetSid, AccountThatAddedUser = SubjectAccount, SIDofAccountThatAddedUser = SubjectUserSid, AddedByAccountName = SubjectUserName, AddedByDomainName = SubjectDomainName,
CreatedUserSid = MemberSid
)
)
)
on CreatedUserSid
| project Computer, CreatedUserTime, CreatedUserEventID, CreatedUserActivity, CreatedUser, CreatedUserSid, CreatedUserAccountName, CreatedUserDomainName,
GroupAddTime, GroupAddEventID, GroupAddActivity, GroupName, GroupSid,
AccountUsedToCreateUser, SidofAccountUsedToCreateUser, CreatedByAccountName, CreatedByDomainName,
AccountThatAddedUser, SIDofAccountThatAddedUser, AddedByAccountName, AddedByDomainName
| 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" |
EventData | match |
| field:"EventData" kind:match value:"S-1-5-32-544" |
EventID | eq |
| field:"EventID" kind:eq |
TargetSid | eq |
| field:"TargetSid" kind:eq value:"S-1-5-32-544" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountThatAddedUser | project |
AccountUsedToCreateUser | project |
AddedByAccountName | project |
AddedByDomainName | project |
Computer | project |
CreatedByAccountName | project |
CreatedByDomainName | project |
CreatedUser | project |
CreatedUserAccountName | project |
CreatedUserActivity | project |
CreatedUserDomainName | project |
CreatedUserEventID | project |
CreatedUserSid | project |
CreatedUserTime | project |
GroupAddActivity | project |
GroupAddEventID | project |
GroupAddTime | project |
GroupName | project |
GroupSid | project |
SIDofAccountThatAddedUser | project |
SidofAccountUsedToCreateUser | project |
HostName | extend |
HostNameDomain | extend |
Pathlock TDnR - SE16N Direct Table Change Documents
#Detects direct table data changes made via SE16N (Table Browser) in SAP, forwarded by Pathlock Threat Detection and Response. SE16N changes bypass normal application workflows and audit trails, making them a high-risk activity that could indicate data manipulation, log tampering, or unauthorized direct database modifications.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment | |
| Exfiltration |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c55
name: Pathlock TDnR - SE16N Direct Table Change Documents
kind: Scheduled
description: >-
Detects direct table data changes made via SE16N (Table Browser) in SAP, forwarded by Pathlock
Threat Detection and Response. SE16N changes bypass normal application workflows and audit trails,
making them a high-risk activity that could indicate data manipulation, log tampering,
or unauthorized direct database modifications.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Exfiltration
relevantTechniques:
- T1562
- T1048
query: |
Pathlock_TDnR_CL
| where DataSource == "SE16N_CHANGEDOCS"
| 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 == "SE16N_CHANGEDOCS"
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:"SE16N_CHANGEDOCS" |
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 |
Possible SignIn from Azure Backdoor
#Identifies when a user adds an unverified domain as an authentication method, followed by a sign-in from a user the newly added domain. Threat actors may add custom domains to create a backdoor to your tenant. It's important to monitor whenever custom domains are added to the tenant.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Add unverified domain |
| Entra ID | SigninLogs sign-in event: Successful sign-in |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Domain Federation Trust Settings Modified (Kusto)
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure AD New Custom Domain Added (Splunk)
- Azure AD New Federated Domain Added (Splunk)
- Azure Domain Federation Settings Modified (Sigma)
Rule Body
id: fa00014c-c5f4-4715-8f5b-ba567e19e41e
name: Possible SignIn from Azure Backdoor
description: |
'Identifies when a user adds an unverified domain as an authentication method, followed by a sign-in from a user the newly added domain. Threat actors may add custom domains to create a backdoor to your tenant. It's important to monitor whenever custom domains are added to the tenant.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- AuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1098
query: |
// Microsoft Entra ID Backdoors: Identity Federation
//Ref: https://www.inversecos.com/2021/11/how-to-detect-azure-active-directory.html
AuditLogs
| where OperationName == "Add unverified domain"
| where Result == "success"
| extend InitiatedBy = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend DomainName = tostring(TargetResources[0].displayName)
| summarize DomainAddedTime = min(TimeGenerated), ModifiedProperties = make_set(parse_json(TargetResources[0].modifiedProperties),1048576) by InitiatedBy, DomainName
| join kind=inner (
SigninLogs
| where ResultType == "0"
| extend UserDomain = tostring(parse_json(split(UserPrincipalName,"@",1)[0]))
| summarize SignInTime = min(TimeGenerated) by UserPrincipalName, IPAddress, tostring(LocationDetails),AppDisplayName,ResourceDisplayName,UserDomain
) on $left.DomainName == $right.UserDomain
// Getting UserName and Domain
| extend Name = split(UserPrincipalName,"@",0), Domain = split(UserPrincipalName,"@",1)
| mv-expand Name,Domain
entityMappings:
- entityType: Account
fieldMappings:
- identifier: NTDomain
columnName: UserDomain
- identifier: FullName
columnName: UserPrincipalName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
customDetails:
AppDisplayName: AppDisplayName
ResourceDisplayName: ResourceDisplayName
DomainAdded: DomainName
InitiatedBy: InitiatedBy
ModifiedProperties: ModifiedProperties
DomainAddedTime: DomainAddedTime
SignInTime: SignInTime
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where OperationName == "Add unverified domain"
Stage 3: where
| where Result == "success"
Stage 4: extend
| extend InitiatedBy = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
Stage 5: extend
| extend DomainName = tostring(TargetResources[0].displayName)
Stage 6: summarize
| summarize DomainAddedTime = min(TimeGenerated), ModifiedProperties = make_set(parse_json(TargetResources[0].modifiedProperties),1048576) by InitiatedBy, DomainName
Stage 7: join
| join kind=inner (
SigninLogs
| where ResultType == "0"
| extend UserDomain = tostring(parse_json(split(UserPrincipalName,"@",1)[0]))
| summarize SignInTime = min(TimeGenerated) by UserPrincipalName, IPAddress, tostring(LocationDetails),AppDisplayName,ResourceDisplayName,UserDomain
) on $left.DomainName == $right.UserDomain
Stage 8: extend
extend Domain, Name
Stage 9: mv-expand
mv-expand Name
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | eq |
| field:"OperationName" kind:eq value:"Add unverified domain" |
Result | eq |
| field:"Result" kind:eq value:"success" |
ResultType | eq |
| field:"ResultType" kind:eq value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DomainAddedTime | summarize |
DomainName | summarize |
InitiatedBy | summarize |
ModifiedProperties | summarize |
Domain | extend |
Name | extend |
PRODAFT USTA - Compromised credential used in successful sign-in
#Correlates corporate credentials exposed in PRODAFT USTA Account Takeover Prevention tickets with successful Microsoft Entra ID sign-ins for the same user. A successful sign-in by an account whose credentials are known to be compromised is a strong signal of account takeover. Requires the Microsoft Entra ID (SigninLogs) connector. Username matching is best-effort (case-insensitive) because USTA may record either an email or a bare username.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Credential Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Entra ID | SigninLogs sign-in event: Successful sign-in |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Cisco - firewall block but success logon to Microsoft Entra ID (Kusto)
Rule Body
id: 5da571b5-54f6-42c2-8c3e-27edd987ceec
name: PRODAFT USTA - Compromised credential used in successful sign-in
description: |
'Correlates corporate credentials exposed in PRODAFT USTA Account Takeover Prevention
tickets with successful Microsoft Entra ID sign-ins for the same user. A successful
sign-in by an account whose credentials are known to be compromised is a strong signal
of account takeover. Requires the Microsoft Entra ID (SigninLogs) connector. Username
matching is best-effort (case-insensitive) because USTA may record either an email or a
bare username.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: PRODAFTUstaATPCCPDefinition
dataTypes:
- PRODAFTUstaCompromisedCredentials_CL
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- InitialAccess
relevantTechniques:
- T1078
- T1555
query: |
let lookback = 14d;
let detection = 1h;
let compromised =
PRODAFTUstaCompromisedCredentials
| where Created > ago(lookback)
| where IsCorporate == true
| where isnotempty(Username)
| extend MatchName = tolower(Username)
| summarize arg_max(TimeGenerated, TicketId, CompanyName, InfectionDate, Source) by MatchName;
SigninLogs
| where TimeGenerated > ago(detection)
| where ResultType == 0
| where isnotempty(UserPrincipalName)
| extend MatchName = tolower(UserPrincipalName)
| join kind=inner compromised on MatchName
| project
TimeGenerated,
UserPrincipalName,
IPAddress,
Location,
AppDisplayName,
ClientAppUsed,
TicketId,
CompanyName,
Source,
InfectionDate
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
customDetails:
Ticket: TicketId
Company: CompanyName
Source: Source
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1d
matchingMethod: Selected
groupByEntities:
- Account
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let lookback = 14d;
let detection = 1h;
Let Binding: compromised
let compromised = PRODAFTUstaCompromisedCredentials
| where Created > ago(lookback)
| where IsCorporate == true
| where isnotempty(Username)
| extend MatchName = tolower(Username)
| summarize arg_max(TimeGenerated, TicketId, CompanyName, InfectionDate, Source) by MatchName;
Stage 1: source
SigninLogs
Stage 2: where
| where TimeGenerated > ago(detection)
Stage 3: where
| where ResultType == 0
Stage 4: where
| where isnotempty(UserPrincipalName)
Stage 5: extend
| extend MatchName = tolower(UserPrincipalName)
Stage 6: join
| join kind=inner compromised on MatchName
Stage 7: project
project AppDisplayName, ClientAppUsed, CompanyName, IPAddress, InfectionDate, Location, Source, TicketId, TimeGenerated, UserPrincipalName
Stage 8: summarize aggregation inside the join branch
summarize by MatchName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IsCorporate | eq |
| field:"IsCorporate" kind:eq value:"true" |
ResultType | eq |
| field:"ResultType" kind:eq value:"0" |
UserPrincipalName | is_not_null | field:"UserPrincipalName" kind:is_not_null | |
Username | is_not_null | field:"Username" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MatchName | summarize |
Qakbot Discovery Activies
#This query searches for injected processes launching discovery activity. Qakbot has been observed leading to ransomware in numerous instances. It looks for discovery commands such as net.exe, whoami.exe, nslookup.exe, netstat.exe, arp.exe, and ping.exe.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Stealth | |
| Discovery |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents action any: Process activity |
Rule Body
id: ba9db6b2-3d05-42ae-8aee-3a15bbe29f27
name: Qakbot Discovery Activies
description: |
This query searches for injected processes launching discovery activity. Qakbot has been observed leading to ransomware in numerous instances. It looks for discovery commands such as net.exe, whoami.exe, nslookup.exe, netstat.exe, arp.exe, and ping.exe.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Discovery
- Execution
relevantTechniques:
- T1140
- T1010
- T1059
tags:
- Ransomware
- Qakbot
- Discovery
query: |
DeviceProcessEvents
| where InitiatingProcessFileName in~('mobsync.exe','explorer.exe')
| where (FileName =~ 'net.exe' and InitiatingProcessCommandLine has_all('view','/all'))
or (FileName =~ 'whoami.exe' and InitiatingProcessCommandLine has '/all')
or (FileName =~ 'nslookup.exe' and InitiatingProcessCommandLine has_all('querytype=ALL','timeout=10'))
or (FileName =~ 'netstat.exe' and InitiatingProcessCommandLine has '-nao')
or (FileName =~ 'arp.exe' and InitiatingProcessCommandLine has '-a')
or (FileName =~ 'ping.exe' and InitiatingProcessCommandLine has '-t' and InitiatingProcessCommandLine endswith '127.0.0.1')
| summarize DiscoveryCommands = dcount(InitiatingProcessCommandLine), make_set(InitiatingProcessFileName), make_set(FileName), make_set(InitiatingProcessCommandLine) by DeviceId, DeviceName, bin(TimeGenerated, 5m)
| where DiscoveryCommands >= 3
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
DeviceProcessEvents
Stage 2: where
| where InitiatingProcessFileName in~('mobsync.exe','explorer.exe')
Stage 3: where
| where (FileName =~ 'net.exe' and InitiatingProcessCommandLine has_all('view','/all'))
or (FileName =~ 'whoami.exe' and InitiatingProcessCommandLine has '/all')
or (FileName =~ 'nslookup.exe' and InitiatingProcessCommandLine has_all('querytype=ALL','timeout=10'))
or (FileName =~ 'netstat.exe' and InitiatingProcessCommandLine has '-nao')
or (FileName =~ 'arp.exe' and InitiatingProcessCommandLine has '-a')
or (FileName =~ 'ping.exe' and InitiatingProcessCommandLine has '-t' and InitiatingProcessCommandLine endswith '127.0.0.1')
Stage 4: summarize
| summarize DiscoveryCommands = dcount(InitiatingProcessCommandLine), make_set(InitiatingProcessFileName), make_set(FileName), make_set(InitiatingProcessCommandLine) by DeviceId, DeviceName, bin(TimeGenerated, 5m)
Stage 5: where
| where DiscoveryCommands >= 3
Stage 6: extend
| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =DeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))DeviceNameStage 7: extend
| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =DeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))""Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DiscoveryCommands | ge |
| field:"DiscoveryCommands" kind:ge value:"3" |
FileName | eq |
| field:"process_basename" kind:eq |
InitiatingProcessCommandLine | ends_with |
| field:"ParentCommandLine" kind:ends_with value:"127.0.0.1" |
InitiatingProcessCommandLine | match |
| field:"ParentCommandLine" kind:match |
InitiatingProcessFileName | in |
| field:"parent_process_basename" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceId | summarize |
DeviceName | summarize |
DiscoveryCommands | summarize |
HostName | extend |
DnsDomain | extend |
Tenant Quota Deleted
#Detects when a tenant quota is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: 16183d97-c348-4f72-a943-27e80c9dfbd5
name: Tenant Quota Deleted
description: Detects when a tenant quota 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 == 24170\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 == 24170
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:"24170" |
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 |
TIE Golden Ticket
#'Searches for Golden Ticket attacks.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 216e12dd-165a-4537-b241-32e1bd3330c7
name: TIE Golden Ticket
description: |
'Searches for Golden Ticket attacks.'
severity: High
requiredDataConnectors:
- connectorId: TenableIE
dataTypes:
- Tenable_IE_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1558.001
query: |
// For the query to work properly, make sure you have imported the afad_parser.yaml parser into the workspace
// Retrieve the parser here: https://aka.ms/sentinel-TenableApp-afad-parser
// Then, create the Kusto Function with alias afad_parser
afad_parser
| where MessageType == 2 and Codename == "Golden Ticket"
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 2 and Codename == "Golden Ticket"
Stage 3: extend
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Codename | eq |
| field:"Codename" kind:eq value:"Golden Ticket" |
MessageType | eq |
| field:"MessageType" kind:eq value:"2" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
UniFi Site Manager: System log shipping disabled
#Identifies when a UniFi host transitions cloudSystemLogState from enabled to disabled. This defense-evasion signal may indicate an attacker silencing audit trails.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: f32950bc-6553-4c03-2686-a9c29ef318e8
name: "UniFi Site Manager: System log shipping disabled"
description: |
Identifies when a UniFi host transitions cloudSystemLogState from enabled to disabled. This defense-evasion signal may indicate an attacker silencing audit trails.
severity: High
status: Available
requiredDataConnectors:
- connectorId: UniFiSiteManagerConnectorDefinition
dataTypes:
- Unifi_SiteManager_Hosts_CL
queryFrequency: 15m
queryPeriod: 45m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
subTechniques:
- T1562.006
- T1562.008
query: |
let prev = Unifi_SiteManager_Hosts_CL
| where TimeGenerated between (ago(30m) .. ago(15m))
| summarize arg_max(TimeGenerated, *) by Id
| project id_s = Id, prevState = tostring(ReportedState.cloudSystemLogState);
Unifi_SiteManager_Hosts_CL
| where TimeGenerated > ago(15m)
| summarize arg_max(TimeGenerated, *) by Id
| extend id_s = Id,
SiteName = tostring(ReportedState.name),
currentState = tostring(ReportedState.cloudSystemLogState)
| join kind=inner prev on id_s
| where currentState == 'disabled' and prevState == 'enabled'
| extend Activity = strcat('Cloud system log shipping was disabled on host ', SiteName, ' (', prevState, ' -> ', currentState, ')')
| project TimeGenerated, Id, SiteName, Activity, prevState, currentState
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Id
- identifier: DnsDomain
columnName: SiteName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT12H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.1
Stages and Predicates
Let Binding: prev
let prev = Unifi_SiteManager_Hosts_CL
| where TimeGenerated between (ago(30m) .. ago(15m))
| summarize arg_max(TimeGenerated, *) by Id
| project id_s = Id, prevState = tostring(ReportedState.cloudSystemLogState);
Stage 1: source
Unifi_SiteManager_Hosts_CL
Stage 2: where
| where TimeGenerated > ago(15m)
Stage 3: summarize
| summarize arg_max(TimeGenerated, *) by Id
Stage 4: extend
| extend id_s = Id,
SiteName = tostring(ReportedState.name),
currentState = tostring(ReportedState.cloudSystemLogState)
Stage 5: join
| join kind=inner prev on id_s
Stage 6: where
where currentState == "disabled" and prevState == "enabled"
Stage 7: extend
extend Activity
Stage 8: project
project Activity, Id, SiteName, TimeGenerated, currentState, prevState
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
currentState | eq |
| field:"currentState" kind:eq value:"disabled" |
prevState | eq |
| field:"prevState" kind:eq value:"enabled" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Activity | project |
Id | project |
SiteName | project |
TimeGenerated | project |
currentState | project |
prevState | project |