Detection rules › Kusto
Kusto rules: 3g
1Password - Manual account creation
#This will alert when a new account was created manually within 1Password. This should only be used when a 1Password integration via a SCIM Bridge has been implemented. Ref: https://support.1password.com/scim/ Ref: https://1password.com/ Ref: https://github.com/securehats/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| 1Password | invite-create: Create Invite |
Rule Body
id: 9a264487-bcb8-4c7f-a461-b289a46377b8
name: 1Password - Manual account creation
version: 1.0.0
kind: Scheduled
description: |-
This will alert when a new account was created manually within 1Password. This should only be used when a 1Password integration via a SCIM Bridge has been implemented.
Ref: https://support.1password.com/scim/
Ref: https://1password.com/
Ref: https://github.com/securehats/
severity: Medium
requiredDataConnectors:
- connectorId: 1Password
dataTypes:
- OnePasswordEventLogs_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1136
query: |-
OnePasswordEventLogs_CL
| where action == "create"
| where object_type == "invite"
| where actor_details.email !endswith "@1passwordserviceaccounts.com"
| extend
ActorUsername = actor_details.email
, SrcIpAddr = session.ip
, TargetUsername = aux_info
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AnyAlert
suppressionDuration: 1h
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUsername
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUsername
Stages and Predicates
Stage 1: source
OnePasswordEventLogs_CL
Stage 2: where
| where action == "create"
Stage 3: where
| where object_type == "invite"
Stage 4: where
| where actor_details.email !endswith "@1passwordserviceaccounts.com"
Stage 5: extend
| extend
ActorUsername = actor_details.email
, SrcIpAddr = session.ip
, TargetUsername = aux_info
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"create" |
object_type | eq |
| field:"object_type" kind:eq value:"invite" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
actor_details.email | ends_with | @1passwordserviceaccounts.com | excludes:actor_details.email field:"actor_details.email" value:"@1passwordserviceaccounts.com" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorUsername | extend |
SrcIpAddr | extend |
TargetUsername | extend |
Apache - Apache 2.4.49 flaw CVE-2021-41773
#'Detects using Apache 2.4.49 flaw CVE-2021-41773'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Lateral Movement |
Rule Body
id: 767f9dc4-3b01-11ec-8d3d-0242ac130003
name: Apache - Apache 2.4.49 flaw CVE-2021-41773
description: |
'Detects using Apache 2.4.49 flaw CVE-2021-41773'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- ApacheHTTPServer_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- LateralMovement
relevantTechniques:
- T1190
- T1133
- T1210
query: |
ApacheHTTPServer
| where HttpRequestMethod =~ "GET"
| where UrlOriginal has_all ("/cgi-bin/", "/%2e%2e/")
| where HttpStatusCode == "200"
| extend UrlCustomEntity = UrlOriginal
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: UrlCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
ApacheHTTPServer
Stage 2: where
| where HttpRequestMethod =~ "GET"
Stage 3: where
| where UrlOriginal has_all ("/cgi-bin/", "/%2e%2e/")
Stage 4: where
| where HttpStatusCode == "200"
Stage 5: extend
| extend UrlCustomEntity = UrlOriginal
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
HttpRequestMethod | eq |
| field:"HttpRequestMethod" kind:eq value:"GET" |
HttpStatusCode | eq |
| field:"HttpStatusCode" kind:eq value:"200" |
UrlOriginal | match |
| field:"UrlOriginal" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UrlCustomEntity | extend |
Archive Repository Deleted
#Detects when an archive repository is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: 6a90f177-dcaa-44ec-b6e6-723ee8408cb2
name: Archive Repository Deleted
description: Detects when an archive repository is deleted from Veeam Backup & Replication.
This might indicate unauthorized removal of critical components.
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 == 29900\n| extend Name = extract(\"\
Name=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ [\"User Name\"] = user,\n [\"Object Name\"] = Name,\n MessageDetails\
\ = Description,\n Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 29900
Stage 3: extend
| extend Name = extract("Name=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["User Name"] = user,
["Object Name"] = Name,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"29900" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Object Name | project |
Severity | project |
User Name | project |
CTE - Device Repeatedly Blocked Across Multiple Days
#A device has triggered URL blocks on 3 or more distinct days in the past week, suggesting a recurring risky browsing pattern or persistent exposure.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 8fbb095d-3a35-483b-b264-25b858a52f4d
name: CTE - Device Repeatedly Blocked Across Multiple Days
description: |
A device has triggered URL blocks on 3 or more distinct days in the past week, suggesting a recurring risky browsing pattern or persistent exposure.
severity: Medium
requiredDataConnectors:
- connectorId: cybereinforce_cte
dataTypes:
- CybereinforceCTE_CL
queryFrequency: 12h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1189
query: |
CybereinforceCTE_CL
| where EventType == "block.url"
| extend d = parse_json(Details)
| extend
DeviceId = tostring(d.deviceId),
HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
DeviceUser = tostring(d.deviceUser),
DeviceOs = tostring(d.deviceOs),
DeviceVersion = tostring(d.deviceVersion),
RuleId = tostring(d.ruleId),
RuleName = iff(isnotempty(tostring(d.ruleName)), tostring(d.ruleName), "Pattern Match"),
Pattern = tostring(d.pattern),
Url = tostring(d.url),
Action = tostring(d.action),
IsIoc = tobool(d.ioc),
ClientIp = tostring(split(tostring(d.clientIp), ":")[0]),
ClientPort = tostring(split(tostring(d.clientIp), ":")[1]),
UserAgent = tostring(d.userAgent)
| summarize BlockDays=dcount(startofday(TimeGenerated)), BlockCount=count(), SampleUrls=make_set(Url, 20) by DeviceId, HostName, DeviceUser, DeviceOs, TenantId
| where BlockDays >= 3
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CybereinforceCTE_CL
Stage 2: where
| where EventType == "block.url"
Stage 3: extend
| extend d = parse_json(Details)
Stage 4: extend
| extend
DeviceId = tostring(d.deviceId),
HostName = iff(isnotempty(tostring(d.hostname)), tostring(d.hostname), strcat("cte-device-", substring(EntityId, 0, 8))),
DeviceUser = tostring(d.deviceUser),
DeviceOs = tostring(d.deviceOs),
DeviceVersion = tostring(d.deviceVersion),
RuleId = tostring(d.ruleId),
RuleName = iff(isnotempty(tostring(d.ruleName)), tostring(d.ruleName), "Pattern Match"),
Pattern = tostring(d.pattern),
Url = tostring(d.url),
Action = tostring(d.action),
IsIoc = tobool(d.ioc),
ClientIp = tostring(split(tostring(d.clientIp), ":")[0]),
ClientPort = tostring(split(tostring(d.clientIp), ":")[1]),
UserAgent = tostring(d.userAgent)
HostName =isnotempty(tostring(d.hostname))tostring(d.hostname)strcat("cte-device-", substring(EntityId, 0, 8))RuleName =isnotempty(tostring(d.ruleName))tostring(d.ruleName)"Pattern Match"Stage 5: summarize
| summarize BlockDays=dcount(startofday(TimeGenerated)), BlockCount=count(), SampleUrls=make_set(Url, 20) by DeviceId, HostName, DeviceUser, DeviceOs, TenantId
Stage 6: where
| where BlockDays >= 3
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
BlockDays | ge |
| field:"BlockDays" kind:ge value:"3" |
EventType | eq |
| field:"EventType" kind:eq value:"block.url" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
BlockCount | summarize |
BlockDays | summarize |
DeviceId | summarize |
DeviceOs | summarize |
DeviceUser | summarize |
HostName | summarize |
SampleUrls | summarize |
TenantId | summarize |
Cyble Vision Alerts Flash Report
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
'Detects new threat intelligence flash reports from CybleVision. Extracts company-level context and report identifiers for triage.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: 2c86652a-bbbe-4a32-8b1c-4b53aad0750e
name: Cyble Vision Alerts Flash Report
description: |
'Detects new threat intelligence flash reports from CybleVision. Extracts company-level context and report identifiers for triage.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: PT5H
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- Reconnaissance
relevantTechniques:
- T1598
- T1589
subTechniques: []
query: |
Alerts_flash_report
| where Service == "flash_report"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: CybleVision Flash Report {{KeywordName}}
alertDescriptionFormat: |
A flash threat report has been published. Report ID {{FR_ReportID}} For Company {{FR_ForCompany}} Description {{Description}}
alertDynamicProperties: []
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
FlashReportID: FR_ReportID
ForCompany: FR_ForCompany
Tags: FR_Tags
entityMappings:
- entityType: Account
fieldMappings:
- identifier: ObjectGuid
columnName: FR_ReportID
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_flash_report
Stage 2: where
| where Service == "flash_report"
Stage 3: extend
| extend MappedSeverity = Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"flash_report" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
Cyble Vision Alerts IOC'S
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
'Detects malicious Indicators of Compromise such as IPs, domains, URLs, and hashes. Extracts IOC type, behaviour tags, risk rating, and timestamps using Alerts_IOCs parser. Triggers an incident with mapped entities, severity, and details.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Discovery | |
| Command & Control | |
| Impact |
Rule Body
id: c8cf42d5-8684-435f-9c4d-9dd0cc47eaec
name: Cyble Vision Alerts IOC'S
description: |
'Detects malicious Indicators of Compromise such as IPs, domains, URLs, and hashes. Extracts IOC type, behaviour tags, risk rating, and timestamps using Alerts_IOCs parser. Triggers an incident with mapped entities, severity, and details.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: PT5H
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- Reconnaissance
- InitialAccess
- Discovery
- CommandAndControl
- Impact
relevantTechniques:
- T1595
- T1133
- T1046
- T1071
- T1486
subTechniques: []
query: |
Alerts_iocs
| where Service == "iocs"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: CybleVision IOC Detected {{IOC_Value}}
alertDescriptionFormat: |
Type {{IOC_Type}} Behaviour {{IOC_BehaviourTags}} Risk Rating {{IOC_RiskRating}}'
alertDynamicProperties: []
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
IOC: IOC_Value
IOCType: IOC_Type
IOCBehaviour: IOC_BehaviourTags
IOCRiskRating: IOC_RiskRating
IOCConfidentRating: IOC_ConfidentRating
IOCAttackName: IOC_AttackName
FirstSeen: IOC_FirstSeen
LastSeen: IOC_LastSeen
Reference: IOC_ReferenceLink
UUID: IOC_UUID
Tags: IOC_Tags
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IOC_Value
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_iocs
Stage 2: where
| where Service == "iocs"
Stage 3: extend
| extend MappedSeverity = Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"iocs" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
CYFIRMA - High severity 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 query retrieves file hash indicators marked for Monitoring, with no assigned role, from the CyfirmaIndicators_CL table. It extracts MD5, SHA1, and SHA256 hashes and includes threat metadata for use in preventive security controls such as EDRs, threat intel platforms."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Stealth | |
| Impact |
Rule Body
id: 952b7d77-a848-4888-a638-62fe877eb55d
name: CYFIRMA - High severity File Hash Indicators with Monitor Action Rule
description: |
"This query retrieves file hash indicators marked for Monitoring, with no assigned role, from the CyfirmaIndicators_CL table.
It extracts MD5, SHA1, and SHA256 hashes and includes threat metadata for use in preventive security controls such as EDRs, threat intel platforms."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
// 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 (isempty(Roles) or not(Roles has_any ('Malware', '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,
SHA1,
SHA256,
Algo_MD5,
Algo_SHA1,
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:
- Execution
- InitialAccess
- DefenseEvasion
- Impact
relevantTechniques:
- T1204
- T1566
- T1027
- T1486
- T1566.001
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence 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 (isempty(Roles) or not(Roles has_any ('Malware', '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,
SHA1,
SHA256,
Algo_MD5,
Algo_SHA1,
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 | is_null | field:"Roles" kind:is_null | |
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 |
Dataminr - urgent alerts detected
#'Rule helps to detect whenever there is an alert found which has urgent alert-type in Dataminr.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: "64a46029-3236-4d03-b5df-207366a623f1"
name: "Dataminr - urgent alerts detected"
description: |
'Rule helps to detect whenever there is an alert found which has urgent alert-type in Dataminr.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: DataminrPulseAlerts
dataTypes:
- DataminrPulseAlerts
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1546
query: |
DataminrPulseAlerts
| where EventSource in~ ('Greynoise', 'Shodan', 'VirusTotal', 'alienvault open threat exchange', 'urlscan')
and AlertType == "Urgent"
incidentConfiguration:
createIncident: true
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: 'Dataminr Alert: {{AlertId}}'
alertDescriptionFormat: '{{Caption}}'
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: PostLink
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
DataminrPulseAlerts
Stage 2: where
| where EventSource in~ ('Greynoise', 'Shodan', 'VirusTotal', 'alienvault open threat exchange', 'urlscan')
and AlertType == "Urgent"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertType | eq |
| field:"AlertType" kind:eq value:"Urgent" |
EventSource | in |
| field:"aws::eventSource" kind:in |
Dataverse - Suspicious security role modifications
#Identifies an unusual pattern of events whereby a new role is created followed by the creator adding members to the role and subsequently removing the member or deleting the role after a short time period.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Privilege Escalation |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Dataverse and model-driven apps audit (catch-all) |
Rule Body
id: e44a58b2-b63a-4eb9-92da-85660d73495c
kind: Scheduled
name: Dataverse - Suspicious security role modifications
description: Identifies an unusual pattern of events whereby a new role is created
followed by the creator adding members to the role and subsequently removing the
member or deleting the role after a short time period.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Dataverse
dataTypes:
- DataverseActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1404
- T1626
- T1548
query: |
let role_create_watch_period = 2d;
let query_frequency = 1h;
let role_create_add_events= DataverseActivity
| where Message == "Create" and EntityName == "role"
| mv-expand Role = Fields
| extend RoleName = Role.Value
| where Role.Name == "name"
| mv-expand Role = Fields
| extend RoleCreateTime = TimeGenerated, RoleId = tostring(Role.Value)
| where Role.Name == "roleid"
| join kind=inner (
DataverseActivity
| where Message == "Associate" and EntityName == "systemuser"
| mv-expand Role = Fields
| where Role.Name == "role"
| extend RoleMemberAddedTime = TimeGenerated, MemberAddedRoleId = tostring(Role.Value))
on $left.RoleId == $right.MemberAddedRoleId, InstanceUrl, UserId
| where RoleMemberAddedTime between (RoleCreateTime .. (RoleCreateTime + role_create_watch_period));
let remove_role_member_events = DataverseActivity
| where TimeGenerated >= ago(query_frequency)
| where Message == "Disassociate" and EntityName == "systemuser"
| mv-expand Role = Fields
| where Role.Name == "role"
| extend ActionTime = TimeGenerated, MemberRemovedRoleId = tostring(Role.Value);
let role_delete_events = DataverseActivity
| where TimeGenerated >= ago(query_frequency)
| where Message == "Delete" and EntityName == "role"
| extend DeletedRoleID = EntityId, Action = "Role deleted within defined time window"
| project Action, ActionTime = TimeGenerated, UserId, ClientIp, DeletedRoleID, InstanceUrl;
let role_member_removals = role_create_add_events
| join kind=inner (remove_role_member_events) on $left.RoleId == $right.MemberRemovedRoleId
| where ActionTime between (RoleCreateTime .. (RoleCreateTime + role_create_watch_period))
| extend Action = "Role membership removed within defined time window";
let role_deletions = role_create_add_events
| join kind=inner (role_delete_events) on $left.RoleId == $right.DeletedRoleID
| where ActionTime between (RoleCreateTime .. (RoleCreateTime + role_create_watch_period));
union isfuzzy=true role_member_removals, role_deletions
| extend
CloudAppId = int(32780),
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1])
| project
UserId,
InstanceUrl,
ClientIp,
Action,
RoleCreateTime,
RoleName,
ActionTime,
CloudAppId,
AccountName,
UPNSuffix
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: CloudAppId
- identifier: InstanceName
columnName: InstanceUrl
alertDetailsOverride:
alertDisplayNameFormat: Dataverse - suspicious role modifications in {{InstanceUrl}}
alertDescriptionFormat: 'The following action ocurred following role modifications
changes in {{InstanceUrl}}: {{Action}}.'
alertSeverityColumnName: Severity
version: 3.2.0
Stages and Predicates
Parameters
let role_create_watch_period = 2d;
let query_frequency = 1h;
Let Binding: remove_role_member_events
let remove_role_member_events = DataverseActivity
| where TimeGenerated >= ago(query_frequency)
| where Message == "Disassociate" and EntityName == "systemuser"
| mv-expand Role = Fields
| where Role.Name == "role"
| extend ActionTime = TimeGenerated, MemberRemovedRoleId = tostring(Role.Value);
Let Binding: role_delete_events
let role_delete_events = DataverseActivity
| where TimeGenerated >= ago(query_frequency)
| where Message == "Delete" and EntityName == "role"
| extend DeletedRoleID = EntityId, Action = "Role deleted within defined time window"
| project Action, ActionTime = TimeGenerated, UserId, ClientIp, DeletedRoleID, InstanceUrl;
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: role_member_removals, role_deletions
Leg 1: role_member_removals
Leg 2: role_deletions
Applied to the Combined Result
| extend
CloudAppId = int(32780),
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1]) | project
UserId,
InstanceUrl,
ClientIp,
Action,
RoleCreateTime,
RoleName,
ActionTime,
CloudAppId,
AccountName,
UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EntityName | eq |
| field:"EntityName" kind:eq |
Message | eq |
| field:"Message" kind:eq |
Role.Name | eq |
| field:"Role.Name" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
Action | project |
ActionTime | project |
ClientIp | project |
CloudAppId | project |
InstanceUrl | project |
RoleCreateTime | project |
RoleName | project |
UPNSuffix | project |
UserId | project |
Detect Print Processors Registry Driver Key Creation/Modification
#This analytic rule detects any registry value creation or modification of print processor registry Driver key. This will load the executable at startup with print spooler service. This could be an indication of a persistence attempt by an adversary.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rule Body
id: 7edde3d4-9859-4a00-b93c-b19ddda55320
name: Detect Print Processors Registry Driver Key Creation/Modification
description: |
This analytic rule detects any registry value creation or modification of print processor registry Driver key. This will load the executable at startup with print spooler service. This could be an indication of a persistence attempt by an adversary.
severity: Medium
status: Available
tags:
- Schema: _ASim_RegistryEvent
SchemaVersion: 0.1.2
requiredDataConnectors:
- connectorId: CrowdStrikeFalconEndpointProtection
dataTypes:
- CommonSecurityLog
- connectorId: MicrosoftThreatProtection
dataTypes:
- SecurityAlert
- connectorId: SentinelOne
dataTypes:
- SentinelOne_CL
- connectorId: VMwareCarbonBlack
dataTypes:
- CarbonBlackEvents_CL
- connectorId: CiscoSecureEndpoint
dataTypes:
- CiscoSecureEndpoint_CL
- connectorId: TrendMicroApexOne
dataTypes:
- TMApexOneEvent
- connectorId: TrendMicroApexOneAma
dataTypes:
- TMApexOneEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1547
query: |
// Print Processor Registry Key RegEx
let printProcessorRegistryRegEx = @'HKEY_LOCAL_MACHINE\\SYSTEM\\[A-Za-z0-9]*ControlSet[A-Za-z0-9]*\\Control\\Print\\Environments\\Windows\s[A-Za-z0-9]+\\Print Processors\\[A-Za-z0-9]+\\Driver';
_ASim_RegistryEvent
| where EventType in ('RegistryValueSet', 'RegistryKeyCreated')
| where RegistryKey matches regex printProcessorRegistryRegEx
| project
TimeGenerated,
DvcHostname,
ActorUsername,
ActorUsernameType,
ActingProcessId,
ActingProcessName,
ActingProcessCommandLine,
RegistryKey,
RegistryValue,
RegistryValueType,
RegistryValueData
| extend HostName = tostring(split(DvcHostname, '.')[0])
| extend DnsDomain = tostring(strcat_array(array_slice(split(DvcHostname, '.'), 1, -1), '.'))
| extend Username = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[1]), ActorUsername)
| extend NTDomain = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[0]), ActorUsername)
| extend Username = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[0]), Username)
| extend UPNSuffix = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[1]), '')
| extend RegHive = tostring(split(RegistryKey, '\\')[0]), RegKey = tostring(strcat_array(array_slice(split(RegistryKey, '\\'), 1, -1), '\\'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- identifier: NTDomain
columnName: NTDomain
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Username
- identifier: UPNSuffix
columnName: UPNSuffix
- identifier: NTDomain
columnName: NTDomain
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ActingProcessId
- identifier: CommandLine
columnName: ActingProcessCommandLine
- entityType: RegistryKey
fieldMappings:
- identifier: Hive
columnName: RegHive
- identifier: Key
columnName: RegKey
- entityType: RegistryValue
fieldMappings:
- identifier: Name
columnName: RegistryValue
- identifier: Value
columnName: RegistryValueData
- identifier: ValueType
columnName: RegistryValueType
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: "Print Processor Registry Driver Keys Modified on {{DvcHostname}} ({{DvcIpAddr}}) by ({{ActorUsername}})"
alertDescriptionFormat: "Process {{ActingProcessName}} ProcessId: ({{ActingProcessId}}) modified registry driver key {{RegistryKey}}."
version: 1.0.0
kind: Scheduled
Stages and Predicates
Let Binding: printProcessorRegistryRegEx
let printProcessorRegistryRegEx = @'HKEY_LOCAL_MACHINE\\SYSTEM\\[A-Za-z0-9]*ControlSet[A-Za-z0-9]*\\Control\\Print\\Environments\\Windows\s[A-Za-z0-9]+\\Print Processors\\[A-Za-z0-9]+\\Driver';
Stage 1: source
_ASim_RegistryEvent
Stage 2: where
| where EventType in ('RegistryValueSet', 'RegistryKeyCreated')
Stage 3: where
| where RegistryKey matches regex printProcessorRegistryRegEx
Stage 4: project
| project
TimeGenerated,
DvcHostname,
ActorUsername,
ActorUsernameType,
ActingProcessId,
ActingProcessName,
ActingProcessCommandLine,
RegistryKey,
RegistryValue,
RegistryValueType,
RegistryValueData
Stage 5: extend (7 consecutive steps)
| extend HostName = tostring(split(DvcHostname, '.')[0])
| extend DnsDomain = tostring(strcat_array(array_slice(split(DvcHostname, '.'), 1, -1), '.'))
| extend Username = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[1]), ActorUsername)
| extend NTDomain = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[0]), ActorUsername)
| extend Username = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[0]), Username)
| extend UPNSuffix = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[1]), '')
| extend RegHive = tostring(split(RegistryKey, '\\')[0]), RegKey = tostring(strcat_array(array_slice(split(RegistryKey, '\\'), 1, -1), '\\'))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | in |
| field:"EventType" kind:in |
RegistryKey | regex_match |
| field:"TargetObject" kind:regex_match value:"HKEY_LOCAL_MACHINE\SYSTEM\[A-Za-z0-9]*ControlSet[A-Za-z0-9]*\Control\Print\Environments\Windows\s[A-Za-z0-9]+\Print Processors\[A-Za-z0-9]+\Driver" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActingProcessCommandLine | project |
ActingProcessId | project |
ActingProcessName | project |
ActorUsername | project |
ActorUsernameType | project |
DvcHostname | project |
RegistryKey | project |
RegistryValue | project |
RegistryValueData | project |
RegistryValueType | project |
TimeGenerated | project |
HostName | extend |
DnsDomain | extend |
Username | extend |
NTDomain | extend |
UPNSuffix | extend |
RegHive | extend |
RegKey | extend |
Guest Users Invited to Tenant by New Inviters
#Detects when a Guest User is added by a user account that has not been seen adding a guest in the previous 14 days. Monitoring guest accounts and the access they are provided is important to detect potential account abuse. Accounts added should be investigated to ensure the activity was legitimate. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Invite external user |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created and Deleted in Short Timeframe (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Account created or deleted by non-approved user (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- Authentication Method Changed for Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
Rule Body
id: 572e75ef-5147-49d9-9d65-13f2ed1e3a86
name: Guest Users Invited to Tenant by New Inviters
description: |
'Detects when a Guest User is added by a user account that has not been seen adding a guest in the previous 14 days.
Monitoring guest accounts and the access they are provided is important to detect potential account abuse.
Accounts added should be investigated to ensure the activity was legitimate.
Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-failed-unusual-sign-ins'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1078.004
tags:
- AADSecOpsGuide
query: |
let inviting_users = (AuditLogs
| where TimeGenerated between(ago(14d)..ago(1d))
| where OperationName =~ "Invite external user"
| where Result =~ "success"
| extend InitiatingUserPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| where isnotempty(InitiatingUserPrincipalName)
| summarize by InitiatingUserPrincipalName);
AuditLogs
| where TimeGenerated > ago(1d)
| where OperationName =~ "Invite external user"
| where Result =~ "success"
| extend InitiatingUserPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| where isnotempty(InitiatingUserPrincipalName) and InitiatingUserPrincipalName !in (inviting_users)
| extend TargetUserPrincipalName = tostring(TargetResources[0].userPrincipalName)
| extend TargetAadUserId = tostring(TargetResources[0].id)
| extend invitingUser = InitiatingUserPrincipalName, invitedUserPrincipalName = TargetUserPrincipalName
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])
| project-reorder TimeGenerated, OperationName, Result, TargetUserPrincipalName, TargetAadUserId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUserPrincipalName
- identifier: Name
columnName: TargetAccountName
- identifier: UPNSuffix
columnName: TargetAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: TargetAadUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIPAddress
version: 1.1.1
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Let Binding: inviting_users
let inviting_users = (AuditLogs
| where TimeGenerated between(ago(14d)..ago(1d))
| where OperationName =~ "Invite external user"
| where Result =~ "success"
| extend InitiatingUserPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| where isnotempty(InitiatingUserPrincipalName)
| summarize by InitiatingUserPrincipalName);
Stage 1: source
AuditLogs
Stage 2: where
| where TimeGenerated > ago(1d)
Stage 3: where
| where OperationName =~ "Invite external user"
Stage 4: where
| where Result =~ "success"
Stage 5: extend (3 consecutive steps)
| extend InitiatingUserPrincipalName = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
Stage 6: where
| where isnotempty(InitiatingUserPrincipalName) and InitiatingUserPrincipalName !in (inviting_users)
Stage 7: extend (5 consecutive steps)
| extend TargetUserPrincipalName = tostring(TargetResources[0].userPrincipalName)
| extend TargetAadUserId = tostring(TargetResources[0].id)
| extend invitingUser = InitiatingUserPrincipalName, invitedUserPrincipalName = TargetUserPrincipalName
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])
Stage 8: project-reorder
| project-reorder TimeGenerated, OperationName, Result, TargetUserPrincipalName, TargetAadUserId, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
InitiatingUserPrincipalName | is_not_null | field:"azure_ad::initiated_by_user_upn" kind:is_not_null | |
OperationName | eq |
| field:"OperationName" kind:eq value:"Invite external user" |
Result | eq |
| field:"Result" kind:eq value:"success" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
InitiatingUserPrincipalName | eq | inviting_users | excludes:InitiatingUserPrincipalName field:"InitiatingUserPrincipalName" value:"inviting_users" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIPAddress | extend |
TargetUserPrincipalName | extend |
TargetAadUserId | extend |
invitedUserPrincipalName | extend |
invitingUser | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
TargetAccountName | extend |
TargetAccountUPNSuffix | extend |
Hunt for Defender for Identity NNR issues
#This query can help you in finding Network Name Resolution health issues of Microsoft Defender for Identity. NNR is a critical component which is used to get more information on IP addresses seen by MDI. Without NNR proparly working, MDI can throw a lot of False Positive alerts.
References
Telemetry Coverage
Rule Body
let networks = DeviceNetworkInfo
// Expand all IPs
| mv-expand todynamic(IPAddresses)
// Get the network address related to the IP
| extend NetworkAddress = format_ipv4(tostring(IPAddresses.IPAddress), tolong(IPAddresses.SubnetPrefix))
// Build the IP with the CIDR notation
| extend IPAddress = strcat(tostring(IPAddresses.IPAddress), "/", tolong(IPAddresses.SubnetPrefix))
// Save the Prefix as an extra property
| extend Prefix = tostring(IPAddresses.SubnetPrefix)
// Make a set of all the IP's belonging to the same subnet
| summarize make_set(IPAddress) by NetworkAddress, Prefix
// Count how many IPs there are in one subnet
| extend CountIPs = array_length(set_IPAddress)
| extend Joiner = 1;
// Network Information
let network_info = DeviceNetworkInfo
| mv-expand todynamic(IPAddresses)
| extend IPAddress = tostring(IPAddresses.IPAddress);
// Ports used in NNR
let nnr_ports = dynamic(["3389", "135", "137"]);
let mdi_servers = dynamic([]);
// Query network connections
DeviceNetworkEvents
// Get events from Defender for Identity sensors - fill in mdi-servers variable for more complete results
| where InitiatingProcessFileName == "Microsoft.Tri.Sensor.exe" or DeviceName has_any (mdi_servers)
// Check traffic for NNR ports
| where RemotePort in (nnr_ports)
// Join the network info for more destination context
| join kind=inner network_info on $left.RemoteIP == $right.IPAddress
// Get distinct values
| project-rename RemoteDeviceName = DeviceName1
| distinct DeviceName, ActionType, RemoteIP, RemotePort, RemoteDeviceName
// Join all network addresses
| extend Joiner = 1
| join kind=inner networks on Joiner
// Check if remote ip is in a certain network address
| extend NetworkAddrPrefix = strcat(NetworkAddress, "/", Prefix)
| where ipv4_is_in_range(RemoteIP, NetworkAddrPrefix)
// Create Object to reuse later
| extend Obj = pack(
"DeviceName", DeviceName,
"NetworkAddrPrefix", NetworkAddrPrefix,
"RemotePort", RemotePort,
"RemoteIP", RemoteIP
)
// Count amount of failed and succeeded logins
| summarize FailedConnections = countif(ActionType == "ConnectionFailed"),
SucceededConnections = countif(ActionType == "ConnectionSuccess") by tostring(Obj)
// Extract the columns from the object again
| extend Obj = todynamic(Obj)
// Save the properties for later use
| extend DeviceName = tostring(Obj.DeviceName),
NetworkAddrPrefix = tostring(Obj.NetworkAddrPrefix),
RemotePort = tostring(Obj.RemotePort),
RemoteIP = tostring(Obj.RemoteIP)
// Create a new object to save the amount of failed and succeeded attempts per IP
| extend Obj = pack(
"RemoteIP", RemoteIP,
"SucceededConnections", SucceededConnections,
"FailedConnections", FailedConnections
)
// Create a list of the remote ips and their connections by MDI sensor, destination subnet and RemoteIP
// Subnets with only fails on both ports will fail in NNR
| summarize ConnectionDetails = make_set(Obj),
TotalSucceededConnections = sum(SucceededConnections),
TotalFailedConnections = sum(FailedConnections) by DeviceName, NetworkAddrPrefix, RemotePort
// Filter out /32 addresses
| where NetworkAddrPrefix !contains "/32"
// Sorting
| sort by TotalFailedConnections desc
// Reorder
| project-reorder DeviceName, NetworkAddrPrefix, RemotePort, TotalSucceededConnections, TotalFailedConnections, ConnectionDetails
Stages and Predicates
Parameters
let nnr_ports = dynamic(["3389", "135", "137"]);
let mdi_servers = dynamic([]);
Let Binding: networks
let networks = DeviceNetworkInfo
| mv-expand todynamic(IPAddresses)
| extend NetworkAddress = format_ipv4(tostring(IPAddresses.IPAddress), tolong(IPAddresses.SubnetPrefix))
| extend IPAddress = strcat(tostring(IPAddresses.IPAddress), "/", tolong(IPAddresses.SubnetPrefix))
| extend Prefix = tostring(IPAddresses.SubnetPrefix)
| summarize make_set(IPAddress) by NetworkAddress, Prefix
| extend CountIPs = array_length(set_IPAddress)
| extend Joiner = 1;
Let Binding: network_info
let network_info = DeviceNetworkInfo
| mv-expand todynamic(IPAddresses)
| extend IPAddress = tostring(IPAddresses.IPAddress);
Stage 1: source
let networks
Stage 2: source
let network_info
Stage 3: source
DeviceNetworkEvents
Stage 4: where
where (DeviceName has "[]" or InitiatingProcessFileName == "Microsoft.Tri.Sensor.exe")
Stage 5: where
where RemotePort in (135, 137, 3389)
Stage 6: join
join kind=inner (network_info) on $left.RemoteIP, $right.IPAddress
Stage 7: project-rename
project-rename
Stage 8: distinct
distinct ActionType, DeviceName, RemoteDeviceName, RemoteIP, RemotePort
Stage 9: extend
extend Joiner
Stage 10: join
join kind=inner (networks) on Joiner
Stage 11: extend
extend NetworkAddrPrefix
Stage 12: where
where /* macro: ipv4_is_in_range(RemoteIP, NetworkAddrPrefix) */
Stage 13: extend
extend Obj
Stage 14: summarize
summarize FailedConnections, SucceededConnections
Stage 15: extend (3 consecutive steps)
extend DeviceName, NetworkAddrPrefix, Obj, RemoteIP, RemotePort
Stage 16: summarize
summarize ConnectionDetails, TotalFailedConnections, TotalSucceededConnections by DeviceName, NetworkAddrPrefix, RemotePort
Stage 17: where
where not (NetworkAddrPrefix contains "/32")
Stage 18: sort
sort by TotalFailedConnections
Stage 19: project-reorder
project-reorder
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceName | match |
| field:"DeviceName" kind:match value:"[]" |
InitiatingProcessFileName | eq |
| field:"InitiatingProcessFileName" kind:eq value:"Microsoft.Tri.Sensor.exe" |
RemotePort | in |
| field:"RemotePort" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
NetworkAddrPrefix | contains | /32 | excludes:NetworkAddrPrefix field:"NetworkAddrPrefix" value:"/32" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ConnectionDetails | summarize |
DeviceName | summarize |
NetworkAddrPrefix | summarize |
RemotePort | summarize |
TotalFailedConnections | summarize |
TotalSucceededConnections | summarize |
Modification of Accessibility Features
#Adversaries may establish persistence and/or elevate privileges by executing malicious content triggered by accessibility features. Windows contains accessibility features that may be launched with a key combination before a user has logged in (ex: when the user is on the Windows logon screen). An adversary can modify the way these programs are launched to get a command prompt or backdoor without logging in to the system. Two common accessibility programs are C:\Windows\System32\sethc.exe, launched when the shift key is pressed five times and C:\Windows\System32\utilman.exe, launched when the Windows + U key combination is pressed. The sethc.exe program is often referred to as "sticky keys", and has been used by adversaries for unauthenticated access through a remote desktop login screen. [1] Ref: https://attack.mitre.org/techniques/T1546/008/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Sysmon | Event ID 1: Process creation |
Rule Body
id: d714ef62-1a56-4779-804f-91c4158e528d
name: Modification of Accessibility Features
description: |
'Adversaries may establish persistence and/or elevate privileges by executing malicious content triggered by accessibility features. Windows contains accessibility features that may be launched with a key combination before a user has logged in (ex: when the user is on the Windows logon screen). An adversary can modify the way these programs are launched to get a command prompt or backdoor without logging in to the system.
Two common accessibility programs are C:\Windows\System32\sethc.exe, launched when the shift key is pressed five times and C:\Windows\System32\utilman.exe, launched when the Windows + U key combination is pressed. The sethc.exe program is often referred to as "sticky keys", and has been used by adversaries for unauthenticated access through a remote desktop login screen. [1]
Ref: https://attack.mitre.org/techniques/T1546/008/'
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1546.008
query: |
let ImagesList = dynamic (["sethc.exe","utilman.exe","osk.exe","Magnify.exe","Narrator.exe","DisplaySwitch.exe","AtBroker.exe"]);
let OriginalFileNameList = dynamic (["sethc.exe","utilman.exe","osk.exe","Magnify.exe","Narrator.exe","DisplaySwitch.exe","AtBroker.exe","SR.exe","utilman2.exe","ScreenMagnifier.exe"]);
Event
| where EventLog == "Microsoft-Windows-Sysmon/Operational" and EventID==1
| parse EventData with * 'ProcessId">' ProcessId "<" * 'Image">' Image "<" * 'OriginalFileName">' OriginalFileName "<" *
| where Image has_any (ImagesList) and not (OriginalFileName has_any (OriginalFileNameList))
| parse EventData with * 'ProcessGuid">' ProcessGuid "<" * 'Description">' Description "<" * 'CommandLine">' CommandLine "<" * 'CurrentDirectory">' CurrentDirectory "<" * 'User">' User "<" * 'LogonGuid">' LogonGuid "<" * 'Hashes">' Hashes "<" * 'ParentProcessGuid">' ParentProcessGuid "<" * 'ParentImage">' ParentImage "<" * 'ParentCommandLine">' ParentCommandLine "<" * 'ParentUser">' ParentUser "<" *
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventID, Computer, User, ParentImage, ParentProcessGuid, ParentCommandLine, ParentUser, Image, ProcessId, ProcessGuid, CommandLine, Description, OriginalFileName, CurrentDirectory, Hashes
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(User, "\\")[1]), AccountNTDomain = tostring(split(User, "\\")[0])
| extend ImageFileName = tostring(split(Image, "\\")[-1])
| extend ImageDirectory = replace_string(Image, ImageFileName, "")
| project-away DomainIndex
entityMappings:
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: CommandLine
- identifier: ProcessId
columnName: ProcessId
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountNTDomain
- entityType: File
fieldMappings:
- identifier: Name
columnName: ImageFileName
- identifier: Directory
columnName: ImageDirectory
version: 1.0.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Vasileios Paschalidis
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let ImagesList = dynamic (["sethc.exe","utilman.exe","osk.exe","Magnify.exe","Narrator.exe","DisplaySwitch.exe","AtBroker.exe"]);
Let Binding: OriginalFileNameList
let OriginalFileNameList = dynamic (["sethc.exe","utilman.exe","osk.exe","Magnify.exe","Narrator.exe","DisplaySwitch.exe","AtBroker.exe","SR.exe","utilman2.exe","ScreenMagnifier.exe"]);
Stage 1: source
Event
Stage 2: where
| where EventLog == "Microsoft-Windows-Sysmon/Operational" and EventID==1
Stage 3: parse
| parse EventData with * 'ProcessId">' ProcessId "<" * 'Image">' Image "<" * 'OriginalFileName">' OriginalFileName "<" *
Stage 4: where
| where Image has_any (ImagesList) and not (OriginalFileName has_any (OriginalFileNameList))
Stage 5: parse
| parse EventData with * 'ProcessGuid">' ProcessGuid "<" * 'Description">' Description "<" * 'CommandLine">' CommandLine "<" * 'CurrentDirectory">' CurrentDirectory "<" * 'User">' User "<" * 'LogonGuid">' LogonGuid "<" * 'Hashes">' Hashes "<" * 'ParentProcessGuid">' ParentProcessGuid "<" * 'ParentImage">' ParentImage "<" * 'ParentCommandLine">' ParentCommandLine "<" * 'ParentUser">' ParentUser "<" *
Stage 6: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventID, Computer, User, ParentImage, ParentProcessGuid, ParentCommandLine, ParentUser, Image, ProcessId, ProcessGuid, CommandLine, Description, OriginalFileName, CurrentDirectory, Hashes
Stage 7: extend (5 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(User, "\\")[1]), AccountNTDomain = tostring(split(User, "\\")[0])
| extend ImageFileName = tostring(split(Image, "\\")[-1])
| extend ImageDirectory = replace_string(Image, ImageFileName, "")
Stage 8: project-away
| project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | eq |
| field:"EventID" kind:eq value:"1" |
EventLog | eq |
| field:"EventLog" kind:eq value:"Microsoft-Windows-Sysmon/Operational" |
Image | match |
| field:"Image" kind:match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
OriginalFileName | match | sethc.exe, utilman.exe, osk.exe, Magnify.exe, Narrator.exe, DisplaySwitch.exe, AtBroker.exe, SR.exe, utilman2.exe, ScreenMagnifier.exe | excludes:OriginalFileName |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CommandLine | summarize |
Computer | summarize |
CurrentDirectory | summarize |
Description | summarize |
EndTime | summarize |
EventID | summarize |
Hashes | summarize |
Image | summarize |
OriginalFileName | summarize |
ParentCommandLine | summarize |
ParentImage | summarize |
ParentProcessGuid | summarize |
ParentUser | summarize |
ProcessGuid | summarize |
ProcessId | summarize |
StartTime | summarize |
User | summarize |
HostName | extend |
HostNameDomain | extend |
AccountNTDomain | extend |
AccountName | extend |
ImageFileName | extend |
ImageDirectory | extend |
Pathlock TDnR - User Access Management Password Resets
#Detects password reset events from the Pathlock User Access Management module in SAP, forwarded to Microsoft Sentinel. Unexpected or bulk password resets may indicate account takeover preparation, credential stuffing followup, or unauthorized use of administrative password reset capabilities.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c53
name: Pathlock TDnR - User Access Management Password Resets
kind: Scheduled
description: >-
Detects password reset events from the Pathlock User Access Management module in SAP,
forwarded to Microsoft Sentinel. Unexpected or bulk password resets may indicate account
takeover preparation, credential stuffing followup, or unauthorized use of administrative
password reset capabilities.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- Persistence
relevantTechniques:
- T1098
- T1078
query: |
Pathlock_TDnR_CL
| where DataSource == "SAST_UAM_PWR"
| 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 == "SAST_UAM_PWR"
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:"SAST_UAM_PWR" |
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 |
Ransom Protect Detected a Ransomware Attack
#Monitors CTERA platform to detect potential ransomware attacks detected by CTERA Ransom Protect AI engine.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 7a075edf-1cf2-4038-ba9c-c354db6409de
name: Ransom Protect Detected a Ransomware Attack
description: 'Monitors CTERA platform to detect potential ransomware attacks detected by CTERA Ransom Protect AI engine.'
severity: High
status: Available
kind: NRT # Set to NRT for near real-time alerting
requiredDataConnectors:
- connectorId: CTERA
dataTypes:
- Syslog
tactics:
- Impact
relevantTechniques:
- T1486
query: |
Syslog
| where SyslogMessage contains "Ransomware incident detected"
| extend
Portal = extract("portal:(\\w+)", 1, SyslogMessage),
EdgeFiler = extract("Edge Filer:(\\w+-\\d+)", 1, SyslogMessage),
IP = extract("\\(IP:([0-9.]+)\\)", 1, SyslogMessage),
User = extract("user:(\\w+)", 1, SyslogMessage),
IncidentType = extract("Incident type:(\\w+)", 1, SyslogMessage),
StartTime = extract("started at \"([^\"]+)\"", 1, SyslogMessage),
EndTime = extract("ended at \"([^\"]+)\"", 1, SyslogMessage)
| project TimeGenerated, Portal, EdgeFiler, IP, User, IncidentType, StartTime, EndTime
suppressionDuration: PT5H
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertnameFormat: 'CTERA Ransom Protect Detected a Ransomware Attack.'
alertDescriptionFormat: CTERA Ransom Protect Detected a Ransomware Attack at {{TimeGenerated}}.
customDetails:
EdgeFiler: EdgeFiler
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: EdgeFiler
version: 1.0.2
Stages and Predicates
Stage 1: source
Syslog
Stage 2: where
| where SyslogMessage contains "Ransomware incident detected"
Stage 3: extend
| extend
Portal = extract("portal:(\\w+)", 1, SyslogMessage),
EdgeFiler = extract("Edge Filer:(\\w+-\\d+)", 1, SyslogMessage),
IP = extract("\\(IP:([0-9.]+)\\)", 1, SyslogMessage),
User = extract("user:(\\w+)", 1, SyslogMessage),
IncidentType = extract("Incident type:(\\w+)", 1, SyslogMessage),
StartTime = extract("started at \"([^\"]+)\"", 1, SyslogMessage),
EndTime = extract("ended at \"([^\"]+)\"", 1, SyslogMessage)
Stage 4: project
| project TimeGenerated, Portal, EdgeFiler, IP, User, IncidentType, StartTime, EndTime
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
SyslogMessage | contains |
| field:"SyslogMessage" kind:contains value:"Ransomware incident detected" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EdgeFiler | project |
EndTime | project |
IP | project |
IncidentType | project |
Portal | project |
StartTime | project |
TimeGenerated | project |
User | project |
SailPointIdentityNowFailedEvents
#'Detects all events with status failed.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: c3835197-fd07-447e-a0ac-7540d51a1f64
name: SailPointIdentityNowFailedEvents
description: |
'Detects all events with status failed.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SailPointIdentityNow
dataTypes:
- SailPointIDN_Events
- connectorId: SailPointIdentityNowConnector
dataTypes:
- SailPointIDN_Events
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1133
query: |
declare query_parameters(lbperiod:timespan = 14d);
SailPointIDN_Events
| where TimeGenerated > ago(lbperiod) and Status == "FAILED"
| project TechnicalName, Operation, EventType
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: TechnicalName
version: 1.1.0
kind: Scheduled
Stages and Predicates
Stage 1: source
declare query_parameters(lbperiod:timespan = 14d);
SailPointIDN_Events
Stage 2: where
| where TimeGenerated > ago(lbperiod) and Status == "FAILED"
Stage 3: project
| project TechnicalName, Operation, EventType
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Status | eq |
| field:"Status" kind:eq value:"FAILED" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventType | project |
Operation | project |
TechnicalName | project |
Tenable.ad Golden Ticket
#'Searches for Golden Ticket attacks.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: d1abda25-f88a-429a-8163-582533cd0def
name: Tenable.ad Golden Ticket
description: |
'Searches for Golden Ticket attacks.'
severity: High
requiredDataConnectors:
- connectorId: Tenable.ad
dataTypes:
- Tenable_ad_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.kql parser into the workspace
// Retrieve the parser here: https://raw.githubusercontent.com/tenable/Azure-Sentinel/Tenable.ad-connector/Solutions/TenableAD/Parsers/afad_parser.kql
// 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 |
Tenable.ad Password issues
#'Searches for triggered Indicators of Exposures related to password issues.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 2518b57f-1a8b-44ea-935d-7dc1cfe4f918
name: Tenable.ad Password issues
description: |
'Searches for triggered Indicators of Exposures related to password issues.'
severity: Low
requiredDataConnectors:
- connectorId: Tenable.ad
dataTypes:
- Tenable_ad_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
// For the query to work properly, make sure you have imported the afad_parser.kql parser into the workspace
// Retrieve the parser here: https://raw.githubusercontent.com/tenable/Azure-Sentinel/Tenable.ad-connector/Solutions/TenableAD/Parsers/afad_parser.kql
// Then, create the Kusto Function with alias afad_parser
let SeverityTable=datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
let codeNameList = datatable(Codename:string)["C-CLEARTEXT-PASSWORD", "C-PASSWORD-DONT-EXPIRE", "C-USER-REVER-PWDS", "C-PASSWORD-POLICY", "C-USER-PASSWORD", "C-KRBTGT-PASSWORD", "C-AAD-SSO-PASSWORD", "C-REVER-PWD-GPO"];
afad_parser
| where MessageType == 0 and Codename in~ (codeNameList)
| lookup kind=leftouter SeverityTable on Severity
| order by Level
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Let Binding: SeverityTable
let SeverityTable = datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
Let Binding: codeNameList
let codeNameList = datatable(Codename:string)["C-CLEARTEXT-PASSWORD", "C-PASSWORD-DONT-EXPIRE", "C-USER-REVER-PWDS", "C-PASSWORD-POLICY", "C-USER-PASSWORD", "C-KRBTGT-PASSWORD", "C-AAD-SSO-PASSWORD", "C-REVER-PWD-GPO"];
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 0 and Codename in~ (codeNameList)
Stage 3: kusto:lookup
lookup kind=leftouter (SeverityTable) on Severity
Stage 4: sort
sort by Level
Stage 5: extend
extend DnsDomain, HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Codename | in |
| field:"Codename" kind:in value:"codeNameList" |
MessageType | eq |
| field:"MessageType" kind:eq value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |