Detection rules › Kusto
Kusto rules: 1u
Backup Proxy Deleted
#Detects when a backup proxy is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: b8527d36-4cba-49c1-9cab-de9cdc3de879
name: Backup Proxy Deleted
description: Detects when a backup proxy 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 == 27900\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 == 27900
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:"27900" |
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 |
BloodHound Attack Path Finding - AllExtended Privileges on Tier Zero Objects
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - AllExtended Privileges on Tier Zero Objects
Rule Body
id: 234ba956-2361-4db2-a9d4-c4c89fb2ed55
name: BloodHound Attack Path Finding - AllExtended Privileges on Tier Zero Objects
description: BloodHound Attack Path Finding - AllExtended Privileges on Tier Zero
Objects
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "T0AllExtendedRights"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "T0AllExtendedRights"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0AllExtendedRights" |
Cisco Umbrella - Connection to non-corporate private network
#'IP addresses of broadband links that usually indicates users attempting to access their home network, for example for a remote session to a home computer.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control | No specific technique |
| Exfiltration | No specific technique |
Rule Body
id: c9b6d281-b96b-4763-b728-9a04b9fe1246
name: Cisco Umbrella - Connection to non-corporate private network
description: |
'IP addresses of broadband links that usually indicates users attempting to access their home network, for example for a remote session to a home computer.'
severity: Medium
requiredDataConnectors:
- connectorId: CiscoUmbrellaDataConnector
dataTypes:
- Cisco_Umbrella_proxy_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
- Exfiltration
query: |
let lbtime = 10m;
Cisco_Umbrella
| where TimeGenerated > ago(lbtime)
| where EventType == 'proxylogs'
| where DvcAction =~ 'Allowed'
| where UrlCategory has_any ('Dynamic and Residential', 'Personal VPN')
| project TimeGenerated, SrcIpAddr, Identities
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Identities
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.1.1
kind: Scheduled
Stages and Predicates
Parameters
let lbtime = 10m;
Stage 1: source
Cisco_Umbrella
Stage 2: where
| where TimeGenerated > ago(lbtime)
Stage 3: where
| where EventType == 'proxylogs'
Stage 4: where
| where DvcAction =~ 'Allowed'
Stage 5: where
| where UrlCategory has_any ('Dynamic and Residential', 'Personal VPN')
Stage 6: project
| project TimeGenerated, SrcIpAddr, Identities
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DvcAction | eq |
| field:"DvcAction" kind:eq value:"Allowed" |
EventType | eq |
| field:"EventType" kind:eq value:"proxylogs" |
UrlCategory | match |
| field:"UrlCategory" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Identities | project |
SrcIpAddr | project |
TimeGenerated | project |
Corelight - Forced External Outbound SMB
#'Detects SMB requests that originate internally and communicate with an external IP address.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 73f23aa2-5cc4-4507-940b-75c9092e9e01
name: Corelight - Forced External Outbound SMB
description: |
'Detects SMB requests that originate internally and communicate with an external IP address.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Corelight
dataTypes:
- corelight_conn
- corelight_conn_red
- corelight_conn_long
- corelight_conn_agg
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1187
query: |
union isfuzzy=true corelight_conn, corelight_conn_agg
| where local_orig == true
| where local_resp == false
| where history hasprefix 'Sh' and (service_dynamic hasprefix 'smb' or service_string hasprefix 'smb')
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: id_orig_h
version: 2.2.0
kind: Scheduled
Stages and Predicates
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: corelight_conn, corelight_conn_agg
Leg 1: corelight_conn
Leg 2: corelight_conn_agg
Applied to the Combined Result
| where local_orig == true | where local_resp == false | where history hasprefix 'Sh' and (service_dynamic hasprefix 'smb' or service_string hasprefix 'smb')
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
history | starts_with |
| field:"history" kind:starts_with value:"Sh" |
local_orig | eq |
| field:"local_orig" kind:eq value:"true" |
local_resp | eq |
| field:"local_resp" kind:eq value:"false" |
service_dynamic | starts_with |
| field:"service_dynamic" kind:starts_with value:"smb" |
service_string | starts_with |
| field:"service_string" kind:starts_with value:"smb" |
Cyble Vision Alerts Pastebin
#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.
'Cyble detected a paste containing references to monitored keywords or domains. Pastebin/Gist exposure may indicate data leakage or threat actor activity referencing the organization.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: dd37e041-3973-482a-aa8c-f484b4178940
name: Cyble Vision Alerts Pastebin
description: |
'Cyble detected a paste containing references to monitored keywords or domains. Pastebin/Gist exposure may indicate data leakage or threat actor activity referencing the organization.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- Reconnaissance
relevantTechniques:
- T1591
query: |
Alerts_pastebin
| where Service == "pastebin"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: Pastebin/Gist Exposure for Keyword {{KeywordName}}
alertDescriptionFormat: |
A public paste has been detected referencing monitored keyword {{KeywordName}}. Paste Author {{PB_Author}}. URL {{PB_Data_URL}}. This may indicate sensitive information exposure or monitoring by external actors.
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
PasteAuthor: PB_Author
PasteUpdatedOn: PB_ContentUpdatedOn
PasteURL: PB_Data_URL
PasteID: PB_Data_PasteID
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: MA_Domain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: MA_DomainURL
- entityType: Account
fieldMappings:
- identifier: Name
columnName: KeywordName
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Domain
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_pastebin
Stage 2: where
| where Service == "pastebin"
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:"pastebin" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
Cyble Vision Alerts Social Media Monitoring
#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 and CybleVision 'social_media_monitoring' alerts into a single incident.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Resource Development |
Rule Body
id: 231c2c16-3742-4cfb-a8e1-c1a7d09f080a
name: Cyble Vision Alerts Social Media Monitoring
description: |
'Detects and CybleVision 'social_media_monitoring' alerts into a single incident.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: false
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- Reconnaissance
- ResourceDevelopment
relevantTechniques:
- T1595
- T1585
- T1589
subTechniques: []
query: |
Alerts_social_media_monitoring
| where Service == "social_media_monitoring"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: CybleVision Social Media Monitoring ({{SM_CreatorName}})
alertDescriptionFormat: |
Social media monitoring alert detected. Category {{SM_Category}}. Source {{SM_SourceData}}. Creator {{SM_CreatorName}}.
alertDynamicProperties: []
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
CreatorName: SM_CreatorName
Category: SM_Category
Source: SM_SourceData
Sentiment: SM_SentimentData
ContentHash: SM_ContentHash
Domains: SM_Domains
Subdomains: SM_Subdomains
URLS: SM_Urls
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: SM_CreatorName
- entityType: URL
fieldMappings:
- identifier: Url
columnName: SM_Urls
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IP
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_social_media_monitoring
Stage 2: where
| where Service == "social_media_monitoring"
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:"social_media_monitoring" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
Email access via active sync
#This query detects attempts to add attacker devices as allowed IDs for active sync using the Set-CASMailbox command. This technique was seen in relation to Solorigate attack but the results can indicate potential malicious activity used in different attacks. - Note that this query can be changed to use the KQL "has_all" operator, which hasn't yet been documented officially, but will be soon. In short, "has_all" will only match when the referenced field has all strings in the list. - Refer to Set-CASMailbox syntax: https://docs.microsoft.com/powershell/module/exchange/set-casmailbox?view=exchange-ps
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
Rule Body
id: 2f561e20-d97b-4b13-b02d-18b34af6e87c
name: Email access via active sync
description: |
This query detects attempts to add attacker devices as allowed IDs for active sync using the Set-CASMailbox command.
This technique was seen in relation to Solorigate attack but the results can indicate potential malicious activity used in different attacks.
- Note that this query can be changed to use the KQL "has_all" operator, which hasn't yet been documented officially, but will be soon.
In short, "has_all" will only match when the referenced field has all strings in the list.
- Refer to Set-CASMailbox syntax: https://docs.microsoft.com/powershell/module/exchange/set-casmailbox?view=exchange-ps
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1068
- T1078
tags:
- Solorigate
- NOBELIUM
query: |
let timeframe = 1d;
let cmdList = dynamic(["Set-CASMailbox","ActiveSyncAllowedDeviceIDs","add"]);
(union isfuzzy=true
(
SecurityEvent
| where TimeGenerated >= ago(timeframe)
| where EventID == 4688
| where CommandLine has_all (cmdList)
| project Type, TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, Process, ParentProcessName, CommandLine
| extend timestamp = TimeGenerated, AccountEntity = Account, HostEntity = Computer
),
( WindowsEvent
| where TimeGenerated >= ago(timeframe)
| where EventID == 4688
| where EventData has_all (cmdList)
| extend CommandLine = tostring(EventData.CommandLine)
| where CommandLine has_all (cmdList)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| project Type, TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, Process, ParentProcessName, CommandLine
| extend timestamp = TimeGenerated, AccountEntity = Account, HostEntity = Computer
),
(
DeviceProcessEvents
| where TimeGenerated >= ago(timeframe)
| where InitiatingProcessCommandLine has_all (cmdList)
| project Type, TimeGenerated, DeviceName, AccountName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessParentFileName, InitiatingProcessCommandLine
| extend timestamp = TimeGenerated, AccountDomain = InitiatingProcessAccountDomain, AccountName = InitiatingProcessAccountName, HostEntity = DeviceName
),
(
Event
| where TimeGenerated > ago(timeframe)
| where Source == "Microsoft-Windows-Sysmon"
| where EventID == 1
| extend EventData = parse_xml(EventData).DataItem.EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key=tostring(['@Name']), Value=['#text']
| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
| where TimeGenerated >= ago(timeframe)
| where CommandLine has_all (cmdList)
| extend Type = strcat(Type, ": ", Source)
| project Type, TimeGenerated, Computer, User, Process, ParentImage, CommandLine
| extend timestamp = TimeGenerated, AccountEntity = User, HostEntity = Computer
)
)
| extend HostName = tostring(split(HostEntity, ".")[0]), DomainIndex = toint(indexof(HostEntity, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(HostEntity, DomainIndex + 1), HostEntity)
| extend AccountName = tostring(split(AccountEntity, @'\')[1]), AccountDomain = tostring(split(AccountEntity, @'\')[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountEntity
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: HostEntity
- 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 - Threat Protection" ]
Stages and Predicates
Parameters
let timeframe = 1d;
let cmdList = dynamic(["Set-CASMailbox","ActiveSyncAllowedDeviceIDs","add"]);
Union isfuzzy=true (4 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, WindowsEvent, DeviceProcessEvents, Event
Leg 1: SecurityEvent
SecurityEvent
| where TimeGenerated >= ago(timeframe)
| where EventID == 4688
| where CommandLine has_all (cmdList)
| project Type, TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, Process, ParentProcessName, CommandLine
| extend timestamp = TimeGenerated, AccountEntity = Account, HostEntity = Computer
Leg 2: WindowsEvent
WindowsEvent
| where TimeGenerated >= ago(timeframe)
| where EventID == 4688
| where EventData has_all (cmdList)
| extend CommandLine = tostring(EventData.CommandLine)
| where CommandLine has_all (cmdList)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| project Type, TimeGenerated, Computer, Account, SubjectDomainName, SubjectUserName, Process, ParentProcessName, CommandLine
| extend timestamp = TimeGenerated, AccountEntity = Account, HostEntity = Computer
Leg 3: DeviceProcessEvents
DeviceProcessEvents
| where TimeGenerated >= ago(timeframe)
| where InitiatingProcessCommandLine has_all (cmdList)
| project Type, TimeGenerated, DeviceName, AccountName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessParentFileName, InitiatingProcessCommandLine
| extend timestamp = TimeGenerated, AccountDomain = InitiatingProcessAccountDomain, AccountName = InitiatingProcessAccountName, HostEntity = DeviceName
Leg 4: Event
Event
| where TimeGenerated > ago(timeframe)
| where Source == "Microsoft-Windows-Sysmon"
| where EventID == 1
| extend EventData = parse_xml(EventData).DataItem.EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key=tostring(['@Name']), Value=['#text']
| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
| where TimeGenerated >= ago(timeframe)
| where CommandLine has_all (cmdList)
| extend Type = strcat(Type, ": ", Source)
| project Type, TimeGenerated, Computer, User, Process, ParentImage, CommandLine
| extend timestamp = TimeGenerated, AccountEntity = User, HostEntity = Computer
Applied to the Combined Result
| extend HostName = tostring(split(HostEntity, ".")[0]), DomainIndex = toint(indexof(HostEntity, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(HostEntity, DomainIndex + 1), HostEntity)
| extend AccountName = tostring(split(AccountEntity, @'\')[1]), AccountDomain = tostring(split(AccountEntity, @'\')[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CommandLine | match |
| field:"CommandLine" kind:match |
EventData | match |
| field:"EventData" kind:match |
EventID | eq |
| field:"EventID" kind:eq |
InitiatingProcessCommandLine | match |
| field:"ParentCommandLine" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CommandLine | project |
Computer | project |
ParentImage | project |
Process | project |
TimeGenerated | project |
Type | project |
User | project |
AccountEntity | extend |
HostEntity | extend |
timestamp | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
AccountDomain | extend |
AccountName | extend |
Guardian- Content Safety Profanity Policy Violation Detection
#'This alert creates an incident when Content Safety Profanity Policy Violation detected from the Guardian.'
Rule Body
id: de3ce72f-7826-4be1-ab1a-87a8299c54f6
name: Guardian- Content Safety Profanity Policy Violation Detection
description: |
'This alert creates an incident when Content Safety Profanity Policy Violation detected from the Guardian.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'JSON'
| where Severity =~ 'Low'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Content Safety Profanity Policy Violation detection.
alertDescriptionFormat: |
This query detects Content Safety Profanity Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'JSON'
Stage 3: where
| where Severity =~ 'Low'
Indicators
These rows show field, operator, and value matches.
Midnight Blizzard - Script payload stored in Registry
#This query identifies when a process execution command-line indicates that a registry value is written to allow for later execution a malicious script References: https://www.microsoft.com/security/blog/2021/03/04/goldmax-goldfinder-sibot-analyzing-nobelium-malware/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 4688: A new process has been created. |
Rule Body
id: 00cb180c-08a8-4e55-a276-63fb1442d5b5
name: Midnight Blizzard - Script payload stored in Registry
description: |
'This query identifies when a process execution command-line indicates that a registry value is written to allow for later execution a malicious script
References: https://www.microsoft.com/security/blog/2021/03/04/goldmax-goldfinder-sibot-analyzing-nobelium-malware/'
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1059
tags:
- Midnight Blizzard
query: |
let cmdTokens0 = dynamic(['vbscript','jscript']);
let cmdTokens1 = dynamic(['mshtml','RunHTMLApplication']);
let cmdTokens2 = dynamic(['Execute','CreateObject','RegRead','window.close']);
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated >= ago(14d)
| where EventID == 4688
| where CommandLine has @'\Microsoft\Windows\CurrentVersion'
| where not(CommandLine has_any (@'\Software\Microsoft\Windows\CurrentVersion\Run', @'\Software\Microsoft\Windows\CurrentVersion\RunOnce'))
// If you are receiving false positives, then it may help to make the query more strict by uncommenting one or both of the lines below to refine the matches
//| where CommandLine has_any (cmdTokens0)
//| where CommandLine has_all (cmdTokens1)
| where CommandLine has_all (cmdTokens2)
| project TimeGenerated, Computer, Account, Process, NewProcessName, CommandLine, ParentProcessName, _ResourceId
),
(WindowsEvent
| where TimeGenerated >= ago(14d)
| where EventID == 4688 and EventData has_all(cmdTokens2) and EventData has @'\Microsoft\Windows\CurrentVersion'
| where not(EventData has_any (@'\Software\Microsoft\Windows\CurrentVersion\Run', @'\Software\Microsoft\Windows\CurrentVersion\RunOnce'))
| extend CommandLine = tostring(EventData.CommandLine)
| where CommandLine has @'\Microsoft\Windows\CurrentVersion'
| where not(CommandLine has_any (@'\Software\Microsoft\Windows\CurrentVersion\Run', @'\Software\Microsoft\Windows\CurrentVersion\RunOnce'))
// If you are receiving false positives, then it may help to make the query more strict by uncommenting one or both of the lines below to refine the matches
//| where CommandLine has_any (cmdTokens0)
//| where CommandLine has_all (cmdTokens1)
| where CommandLine has_all (cmdTokens2)
| extend Account = strcat(EventData.SubjectDomainName,"\\", EventData.SubjectUserName)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| project TimeGenerated, Computer, Account, Process, NewProcessName, CommandLine, ParentProcessName, _ResourceId)
| extend Name = tostring(split(Account, "\\")[1]), NTDomain = tostring(split(Account, "\\")[0])
| extend DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')), HostName = tostring(split(Computer, '.', 0)[0]))
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
- identifier: Name
columnName: Name
- identifier: NTDomain
columnName: NTDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.1.5
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Shain
support:
tier: Community
categories:
domains: [ "Security - Threat Intelligence" ]
Stages and Predicates
Parameters
let cmdTokens0 = dynamic(['vbscript','jscript']);
let cmdTokens1 = dynamic(['mshtml','RunHTMLApplication']);
let cmdTokens2 = dynamic(['Execute','CreateObject','RegRead','window.close']);
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 TimeGenerated >= ago(14d)
| where EventID == 4688
| where CommandLine has @'\Microsoft\Windows\CurrentVersion'
| where not(CommandLine has_any (@'\Software\Microsoft\Windows\CurrentVersion\Run', @'\Software\Microsoft\Windows\CurrentVersion\RunOnce'))
| where CommandLine has_all (cmdTokens2)
| project TimeGenerated, Computer, Account, Process, NewProcessName, CommandLine, ParentProcessName, _ResourceId
Leg 2: WindowsEvent
WindowsEvent
| where TimeGenerated >= ago(14d)
| where EventID == 4688 and EventData has_all(cmdTokens2) and EventData has @'\Microsoft\Windows\CurrentVersion'
| where not(EventData has_any (@'\Software\Microsoft\Windows\CurrentVersion\Run', @'\Software\Microsoft\Windows\CurrentVersion\RunOnce'))
| extend CommandLine = tostring(EventData.CommandLine)
| where CommandLine has @'\Microsoft\Windows\CurrentVersion'
| where not(CommandLine has_any (@'\Software\Microsoft\Windows\CurrentVersion\Run', @'\Software\Microsoft\Windows\CurrentVersion\RunOnce'))
| where CommandLine has_all (cmdTokens2)
| extend Account = strcat(EventData.SubjectDomainName,"\\", EventData.SubjectUserName)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| project TimeGenerated, Computer, Account, Process, NewProcessName, CommandLine, ParentProcessName, _ResourceId
Applied to the Combined Result
| extend Name = tostring(split(Account, "\\")[1]), NTDomain = tostring(split(Account, "\\")[0])
| extend DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')), HostName = tostring(split(Computer, '.', 0)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CommandLine | match |
| field:"CommandLine" kind:match |
EventData | match |
| field:"EventData" kind:match |
EventID | eq |
| field:"EventID" kind:eq value:"4688" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
CommandLine | match | \Software\Microsoft\Windows\CurrentVersion\Run, \Software\Microsoft\Windows\CurrentVersion\RunOnce | excludes:CommandLine field:"CommandLine" value:"\Software\Microsoft\Windows\CurrentVersion\Run" field:"CommandLine" value:"\Software\Microsoft\Windows\CurrentVersion\RunOnce" |
EventData | match | \Software\Microsoft\Windows\CurrentVersion\Run, \Software\Microsoft\Windows\CurrentVersion\RunOnce | excludes:EventData field:"EventData" value:"\Software\Microsoft\Windows\CurrentVersion\Run" field:"EventData" value:"\Software\Microsoft\Windows\CurrentVersion\RunOnce" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
CommandLine | project |
Computer | project |
NewProcessName | project |
ParentProcessName | project |
Process | project |
TimeGenerated | project |
_ResourceId | project |
NTDomain | extend |
Name | extend |
DnsDomain | extend |
HostName | extend |
Mimecast Audit - Logon Authentication Failed
#Detects threat when logon authentication failure found in audit
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: f00197ab-491f-41e7-9e22-a7003a4c1e54
name: Mimecast Audit - Logon Authentication Failed
description: Detects threat when logon authentication failure found in audit
displayName: Mimecast Audit - Logon Authentication Failed
severity: High
requiredDataConnectors:
- connectorId: MimecastAuditAPI
dataTypes:
- MimecastAudit
enabled: true
query: |
MimecastAudit
| where ['Source IP'] !="" and ['Audit Type'] == "Logon Authentication Failed"
| extend SourceIp = ['Source IP']
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 3
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- Discovery
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1110
alertRuleTemplateName:
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P7D
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
customDetails:
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIp
- entityType: Mailbox
fieldMappings:
- identifier: MailboxPrimaryAddress
columnName: User
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: Application
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
MimecastAudit
Stage 2: where
| where ['Source IP'] !="" and ['Audit Type'] == "Logon Authentication Failed"
Stage 3: extend
| extend SourceIp = ['Source IP']
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Audit Type | eq |
| field:"Audit Type" kind:eq value:"Logon Authentication Failed" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SourceIp | extend |
Mimecast Targeted Threat Protection - Impersonation Protect
#'Detects a maliciously tagged impersonation.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: c048fa06-0d50-4626-ae82-a6cea812d9c4
name: Mimecast Targeted Threat Protection - Impersonation Protect
description: |
'Detects a maliciously tagged impersonation.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: MimecastTTPAPI
dataTypes:
- MimecastTTPImpersonation
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
- Collection
- Discovery
relevantTechniques:
- T1114
query: |
MimecastTTPImpersonation
| where ['Tagged Malicious'] == true
| extend SenderAddress = ['Sender Address'],
SenderIPAddress = ['Sender IP Address'],
RecipientAddress = ['Recipient Address']
suppressionDuration: 5h
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P7D
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: MailMessage
fieldMappings:
- identifier: Sender
columnName: SenderAddress
- identifier: SenderIP
columnName: SenderIPAddress
- identifier: Recipient
columnName: RecipientAddress
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
MimecastTTPImpersonation
Stage 2: where
| where ['Tagged Malicious'] == true
Stage 3: extend
| extend SenderAddress = ['Sender Address'],
SenderIPAddress = ['Sender IP Address'],
RecipientAddress = ['Recipient Address']
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Tagged Malicious | eq |
| field:"Tagged Malicious" kind:eq value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
RecipientAddress | extend |
SenderAddress | extend |
SenderIPAddress | extend |
No traffic on Sensor Detected (Microsoft Defender for IoT)
#'This alert leverages Defender for IoT to detect that a sensor can no longer detect the network traffic, which indicates that the system is potentially insecure.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Inhibit Response Function |
Rule Body
id: 208c3f5b-3ba2-49b5-9bca-c44e58cd5fd3
name: No traffic on Sensor Detected (Microsoft Defender for IoT)
description: |
'This alert leverages Defender for IoT to detect that a sensor can no longer detect the network traffic, which indicates that the system is potentially insecure.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: IoT
dataTypes:
- SecurityAlert (ASC for IoT)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InhibitResponseFunction
relevantTechniques:
- T0881
query: |
SecurityAlert
| where ProviderName == "IoTSecurity"
| where AlertName == "No Traffic Detected on Sensor Interface"
| extend ExtendedProperties = parse_json(ExtendedProperties)
| where tostring(ExtendedProperties.isNew) == "True"
| extend DeviceId = tostring(ExtendedProperties.DeviceId),
SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress),
DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress),
RemediationSteps = tostring(parse_json(RemediationSteps)[0]),
Protocol = tostring(ExtendedProperties.Protocol),
AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)
| project
TimeGenerated,
DeviceId,
ProductName,
ProductComponentName,
AlertSeverity,
AlertName,
Description,
Protocol,
SourceDeviceAddress,
DestDeviceAddress,
RemediationSteps,
Tactics,
Entities,
VendorOriginalId,
AlertLink,
AlertManagementUri,
Techniques
entityMappings:
sentinelEntitiesMappings:
- columnName: Entities
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
Sensor: DeviceId
Protocol: Protocol
VendorOriginalId: VendorOriginalId
AlertManagementUri: AlertManagementUri
alertDetailsOverride:
alertDisplayNameFormat: (MDIoT) {{AlertName}}
alertDescriptionFormat: (MDIoT) {{Description}}
alertTacticsColumnName: Tactics
alertSeverityColumnName: AlertSeverity
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: RemediationSteps
value: RemediationSteps
- alertProperty: Techniques
value: Techniques
- alertProperty: ProductComponentName
value: ProductComponentName
- alertProperty: AlertLink
value: AlertLink
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityAlert
Stage 2: where
| where ProviderName == "IoTSecurity"
Stage 3: where
| where AlertName == "No Traffic Detected on Sensor Interface"
Stage 4: extend
| extend ExtendedProperties = parse_json(ExtendedProperties)
Stage 5: where
| where tostring(ExtendedProperties.isNew) == "True"
Stage 6: extend
| extend DeviceId = tostring(ExtendedProperties.DeviceId),
SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress),
DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress),
RemediationSteps = tostring(parse_json(RemediationSteps)[0]),
Protocol = tostring(ExtendedProperties.Protocol),
AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)
Stage 7: project
| project
TimeGenerated,
DeviceId,
ProductName,
ProductComponentName,
AlertSeverity,
AlertName,
Description,
Protocol,
SourceDeviceAddress,
DestDeviceAddress,
RemediationSteps,
Tactics,
Entities,
VendorOriginalId,
AlertLink,
AlertManagementUri,
Techniques
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertName | eq |
| field:"AlertName" kind:eq value:"No Traffic Detected on Sensor Interface" |
ExtendedProperties.isNew | eq |
| field:"ExtendedProperties.isNew" kind:eq value:"True" |
ProviderName | eq |
| field:"ProviderName" kind:eq value:"IoTSecurity" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertLink | project |
AlertManagementUri | project |
AlertName | project |
AlertSeverity | project |
Description | project |
DestDeviceAddress | project |
DeviceId | project |
Entities | project |
ProductComponentName | project |
ProductName | project |
Protocol | project |
RemediationSteps | project |
SourceDeviceAddress | project |
Tactics | project |
Techniques | project |
TimeGenerated | project |
VendorOriginalId | project |
NRT First access credential added to Application or Service Principal where no credential was present
#This will alert when an admin or app owner account adds a new credential to an Application or Service Principal where there was no previous verify KeyCredential associated. If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential. Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
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] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles (Kusto)
- Added Credentials to Existing Application (Sigma)
- Added Owner To Application (Sigma)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Granted Microsoft Permissions (Sigma)
- App Granted Privileged Delegated Or App Permissions (Sigma)
- Application AppID Uri Configuration Changes (Sigma)
Rule Body
id: b6988c32-4f3b-4a45-8313-b46b33061a74
name: NRT First access credential added to Application or Service Principal where no credential was present
description: |
'This will alert when an admin or app owner account adds a new credential to an Application or Service Principal where there was no previous verify KeyCredential associated.
If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential.
Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
tactics:
- DefenseEvasion
relevantTechniques:
- T1550.001
tags:
- Solorigate
- NOBELIUM
query: |
AuditLogs
| where OperationName has_any ("Add service principal", "Certificates and secrets management")
| where Result =~ "success"
| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "Application"
| extend targetDisplayName = tostring(TargetResource.displayName),
targetId = tostring(TargetResource.id),
targetType = tostring(TargetResource.type),
keyEvents = TargetResource.modifiedProperties
)
| mv-apply Property = keyEvents on
(
where Property.displayName =~ "KeyDescription"
| extend new_value_set = parse_json(tostring(Property.newValue)),
old_value_set = parse_json(tostring(Property.oldValue))
)
| where old_value_set == "[]"
| mv-expand new_value_set
| parse new_value_set with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *
| where keyUsage == "Verify"
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
| 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))
//| where targetType =~ "Application" // or targetType =~ "ServicePrincipal"
| project-away new_value_set, old_value_set
| project-reorder TimeGenerated, OperationName, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId
| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
entityMappings:
- 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.6
kind: NRT
metadata:
source:
kind: Community
author:
name: Samik Roy
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
threatAnalysisTactics: [ "DefenseEvasion" ]
threatAnalysisTechniques: [ "T1550.001" ]
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where OperationName has_any ("Add service principal", "Certificates and secrets management")
Stage 3: where
| where Result =~ "success"
Stage 4: where
| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
Stage 5: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "Application"
| extend targetDisplayName = tostring(TargetResource.displayName),
targetId = tostring(TargetResource.id),
targetType = tostring(TargetResource.type),
keyEvents = TargetResource.modifiedProperties
)
Stage 6: kusto:mv-apply
| mv-apply Property = keyEvents on
(
where Property.displayName =~ "KeyDescription"
| extend new_value_set = parse_json(tostring(Property.newValue)),
old_value_set = parse_json(tostring(Property.oldValue))
)
Stage 7: where
| where old_value_set == "[]"
Stage 8: mv-expand
| mv-expand new_value_set
Stage 9: parse
| parse new_value_set with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *
Stage 10: where
| where keyUsage == "Verify"
Stage 11: kusto:mv-apply
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend UserAgent = tostring(AdditionalDetail.value)
)
Stage 12: extend (5 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
Stage 13: project-away
| project-away new_value_set, old_value_set
Stage 14: project-reorder
| project-reorder TimeGenerated, OperationName, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId
Stage 15: extend
| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AdditionalDetail.key | eq |
| field:"AdditionalDetail.key" kind:eq value:"User-Agent" |
InitiatedBy.app.displayName | match |
| field:"InitiatedBy.app.displayName" kind:match value:"@" |
InitiatedBy.user.userPrincipalName | match |
| field:"InitiatedBy.user.userPrincipalName" kind:match value:"@" |
OperationName | match |
| field:"OperationName" kind:match |
Property.displayName | eq |
| field:"Property.displayName" kind:eq value:"KeyDescription" |
Result | eq |
| field:"Result" kind:eq value:"success" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"Application" |
keyUsage | eq |
| field:"keyUsage" kind:eq value:"Verify" |
old_value_set | eq |
| field:"old_value_set" kind:eq value:"[]" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InitiatingAppName | extend |
InitiatingAppServicePrincipalId | extend |
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIpAddress | extend |
InitiatedByName | extend |
InitiatedByUPNSuffix | extend |
Synqly Alert Event
#Creates one Microsoft Sentinel alert for each qualifying Synqly-attributed ASIM Alert Event row. Native ASIM values take precedence, with retained OCSF data used to recover investigation context when normalized fields are empty. Replayed source rows can create additional alerts, and Microsoft Sentinel or Defender XDR may correlate related alerts into a shared incident.
Rule Body
id: 3192085a-e97e-440f-acb7-9227622949a4
name: Synqly Alert Event
description: |
Creates one Microsoft Sentinel alert for each qualifying Synqly-attributed ASIM Alert Event row. Native ASIM values take precedence, with retained OCSF data used to recover investigation context when normalized fields are empty. Replayed source rows can create additional alerts, and Microsoft Sentinel or Defender XDR may correlate related alerts into a shared incident.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SynqlyIntegrationConnector
dataTypes:
- ASimAlertEventLogs
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 1h
suppressionEnabled: false
tactics: []
relevantTechniques: []
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5M
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: "{{SynqlyAlertName}}"
alertDescriptionFormat: "{{SynqlyAlertDescription}}"
alertSeverityColumnName: SynqlyAlertSeverity
alertTacticsColumnName: SynqlyTactics
alertDynamicProperties:
- alertProperty: Techniques
value: AllAttackTechniques
- alertProperty: ConfidenceLevel
value: SynqlyConfidenceLevel
- alertProperty: ConfidenceScore
value: SynqlyConfidenceScore
- alertProperty: RemediationSteps
value: SynqlyRemediation
- alertProperty: AlertLink
value: SynqlyAlertLink
customDetails:
SourceAlertId: SynqlyAlertId
SourceVendor: SourceVendor
SourceProduct: SourceProduct
ProductVersion: ProductVersion
OriginalSeverity: OriginalSeverity
AttackTechniques: AllAttackTechniques
SourceRuleId: SourceRuleId
SourceIp: SourceIp
DestinationIp: DestinationIp
DeviceIp: DeviceIp
PrimaryIpRole: PrimaryIpRole
DestinationDomain: DestinationDomain
FilePath: FileValue
SourceUrl: EntityUrl
MalwareName: MalwareName
query: |
ASimAlertEventLogs
| where tostring(AdditionalFields._ConnectorId) == "SynqlySentinelASIMConnector"
| extend OCSF = coalesce(todynamic(AdditionalFields.OCSF), dynamic({}))
| mv-apply Evidence = array_concat(coalesce(todynamic(OCSF.evidences), dynamic([])), dynamic([{}])) on (
mv-apply EvidenceHash = array_concat(coalesce(todynamic(Evidence.data.file.hashes), dynamic([])), coalesce(todynamic(Evidence.data.process.hashes), dynamic([])), dynamic([{}])) on (
summarize
OcsfSHA256 = take_anyif(tostring(EvidenceHash.value), toupper(tostring(EvidenceHash.algorithm)) == "SHA256"),
OcsfSHA1 = take_anyif(tostring(EvidenceHash.value), toupper(tostring(EvidenceHash.algorithm)) == "SHA1"),
OcsfMD5 = take_anyif(tostring(EvidenceHash.value), toupper(tostring(EvidenceHash.algorithm)) == "MD5")
)
| summarize
OcsfUserName = take_anyif(tostring(Evidence.data.process.user.name), isnotempty(tostring(Evidence.data.process.user.name))),
OcsfUserSid = take_anyif(tostring(Evidence.data.process.user.uid), isnotempty(tostring(Evidence.data.process.user.uid))),
OcsfUserDomain = take_anyif(tostring(Evidence.data.process.user.domain), isnotempty(tostring(Evidence.data.process.user.domain))),
OcsfProcessId = take_anyif(tostring(Evidence.data.process.pid), isnotempty(tostring(Evidence.data.process.pid))),
OcsfProcessCommandLine = take_anyif(tostring(Evidence.data.process.cmd_line), isnotempty(tostring(Evidence.data.process.cmd_line))),
OcsfFileName = take_anyif(tostring(Evidence.data.file.name), isnotempty(tostring(Evidence.data.file.name))),
OcsfFilePath = take_anyif(tostring(Evidence.data.file.path), isnotempty(tostring(Evidence.data.file.path))),
OcsfSourceHost = take_anyif(tostring(Evidence.data.network.src_endpoint.host.hostname), isnotempty(tostring(Evidence.data.network.src_endpoint.host.hostname))),
OcsfSourceIp = take_anyif(tostring(Evidence.data.network.src_endpoint.ip), isnotempty(tostring(Evidence.data.network.src_endpoint.ip))),
OcsfDestinationIp = take_anyif(tostring(Evidence.data.network.dst_endpoint.ip), isnotempty(tostring(Evidence.data.network.dst_endpoint.ip))),
OcsfDestinationDomain = take_anyif(tostring(Evidence.data.network.dst_endpoint.domain), isnotempty(tostring(Evidence.data.network.dst_endpoint.domain))),
OcsfSHA256 = take_anyif(OcsfSHA256, isnotempty(OcsfSHA256)),
OcsfSHA1 = take_anyif(OcsfSHA1, isnotempty(OcsfSHA1)),
OcsfMD5 = take_anyif(OcsfMD5, isnotempty(OcsfMD5))
)
| mv-apply Attack = array_concat(coalesce(todynamic(OCSF.attacks), dynamic([])), dynamic([{}])) on (
extend TacticId = tostring(Attack.tactic.uid), TechniqueId = tostring(Attack.technique.uid), SubTechniqueId = tostring(Attack.sub_technique.uid)
| extend TacticName = case(
TacticId == "TA0043", "Reconnaissance", TacticId == "TA0042", "ResourceDevelopment",
TacticId == "TA0001", "InitialAccess", TacticId == "TA0002", "Execution",
TacticId == "TA0003", "Persistence", TacticId == "TA0004", "PrivilegeEscalation",
TacticId == "TA0005", "DefenseEvasion", TacticId == "TA0006", "CredentialAccess",
TacticId == "TA0007", "Discovery", TacticId == "TA0008", "LateralMovement",
TacticId == "TA0009", "Collection", TacticId == "TA0010", "Exfiltration",
TacticId == "TA0011", "CommandAndControl", TacticId == "TA0040", "Impact", "")
| summarize
OcsfTactics = make_set_if(TacticName, isnotempty(TacticName), 14),
OcsfTechniques = make_set_if(TechniqueId, isnotempty(TechniqueId), 50),
OcsfSubTechniques = make_set_if(SubTechniqueId, isnotempty(SubTechniqueId), 50)
)
| extend
ActorUser = todynamic(OCSF.actor.process.user),
Malware = todynamic(OCSF.malware[0]),
NativeConfidenceValue = toreal(ThreatConfidence),
OcsfConfidenceValue = toreal(OCSF.confidence_score),
NativeConfidenceLabel = tolower(trim(" ", tostring(ThreatOriginalConfidence))),
OcsfConfidenceLabel = tolower(trim(" ", tostring(OCSF.confidence))),
NativeSourceId = coalesce(tostring(EventUid), tostring(AlertId), tostring(EventOriginalUid)),
OcsfSourceId = coalesce(tostring(OCSF.finding_info.uid), tostring(OCSF.uid)),
SourceIdMaterial = tostring(pack_array(tostring(OCSF.class_uid), tostring(OCSF.type_uid), tostring(OCSF.activity_id), tostring(EventStartTime), tostring(EventEndTime), tostring(EventVendor), tostring(EventProduct), tostring(EventMessage), tostring(OCSF["time"])))
| extend
SynqlyAlertId = coalesce(NativeSourceId, OcsfSourceId, strcat("generated-", hash_sha256(SourceIdMaterial))),
SynqlyAlertName = coalesce(tostring(AlertName), tostring(OCSF.finding_info["title"]), tostring(OCSF.class_name), "Synqly normalized alert event"),
SynqlyAlertDescription = coalesce(tostring(EventMessage), tostring(AlertDescription), tostring(OCSF.message), "Synqly normalized alert event"),
SeverityValue = tolower(coalesce(tostring(EventSeverity), tostring(EventOriginalSeverity), tostring(OCSF.severity))),
SynqlyTactics = coalesce(tostring(AttackTactics), strcat_array(OcsfTactics, ",")),
AllAttackTechniques = coalesce(tostring(AttackTechniques), strcat_array(array_concat(OcsfTechniques, OcsfSubTechniques), ",")),
SynqlyConfidenceScore = coalesce(
iff(NativeConfidenceValue between (0.0 .. 100.0), NativeConfidenceValue / 100.0, real(null)),
iff(OcsfConfidenceValue between (0.0 .. 100.0), OcsfConfidenceValue / 100.0, real(null))),
SynqlyConfidenceLevel = case(
NativeConfidenceLabel == "high", "High",
NativeConfidenceLabel == "low", "Low",
NativeConfidenceLabel == "unknown", "Unknown",
isnotempty(NativeConfidenceLabel), "",
OcsfConfidenceLabel == "high", "High",
OcsfConfidenceLabel == "low", "Low",
OcsfConfidenceLabel == "unknown", "Unknown",
""),
SynqlyRemediation = coalesce(tostring(AttackRemediationSteps), strcat_array(todynamic(OCSF.remediation.kb_articles), "; ")),
SynqlyAlertLink = coalesce(tostring(EventReportUrl), tostring(OCSF.finding_info.src_url)),
SourceVendor = coalesce(tostring(EventVendor), tostring(OCSF.metadata.product.vendor_name)),
SourceProduct = coalesce(tostring(EventProduct), tostring(OCSF.metadata.product.name)),
ProductVersion = coalesce(tostring(EventProductVersion), tostring(OCSF.metadata.product.version)),
OriginalSeverity = coalesce(tostring(EventOriginalSeverity), tostring(OCSF.severity)),
SourceRuleId = coalesce(
tostring(Rule),
tostring(OCSF.finding_info.analytic.uid),
iff(isnotempty(tostring(RuleName)) and not(tostring(RuleName) matches regex @"^\d+$"), tostring(RuleName), "")),
AccountName = coalesce(tostring(Username), tostring(User), tostring(ActorUser.name), OcsfUserName),
AccountSid = coalesce(iff(tolower(tostring(UserIdType)) contains "sid", tostring(UserId), ""), tostring(ActorUser.uid), OcsfUserSid),
AccountDomain = coalesce(tostring(UserScope), tostring(ActorUser.domain), OcsfUserDomain),
NativeDeviceHost = coalesce(tostring(DvcHostname), tostring(DvcFQDN)),
NativeDeviceHostDomain = coalesce(tostring(DvcDomain), extract(@"^[^.]+\.(.+)$", 1, tostring(DvcFQDN)), extract(@"^[^.]+\.(.+)$", 1, tostring(DvcHostname))),
DeviceIp = tostring(DvcIpAddr),
DestinationDomain = OcsfDestinationDomain,
EntityUrl = coalesce(tostring(Url), tostring(OCSF.finding_info.src_url)),
FileValue = coalesce(tostring(FilePath), OcsfFilePath),
ProcessIdValue = coalesce(tostring(ProcessId), OcsfProcessId),
ProcessCommandLineValue = coalesce(tostring(ProcessCommandLine), OcsfProcessCommandLine),
MalwareName = coalesce(tostring(ThreatName), tostring(Malware.name)),
FileHashValue = coalesce(tostring(FileSHA256), OcsfSHA256, tostring(FileSHA1), OcsfSHA1, tostring(FileMD5), OcsfMD5)
| extend
SourceIp = OcsfSourceIp,
DestinationIp = OcsfDestinationIp,
HostValue = coalesce(NativeDeviceHost, OcsfSourceHost),
HostDomainValue = iff(isnotempty(NativeDeviceHost), NativeDeviceHostDomain, extract(@"^[^.]+\.(.+)$", 1, OcsfSourceHost))
| extend
PrimaryIp = coalesce(SourceIp, DestinationIp, tostring(DvcIpAddr)),
PrimaryIpRole = case(isnotempty(SourceIp), "Source", isnotempty(DestinationIp), "Destination", isnotempty(tostring(DvcIpAddr)), "Device", "")
| extend
SynqlyAlertSeverity = case(
SeverityValue == "informational", "Informational",
SeverityValue == "low", "Low",
SeverityValue == "medium", "Medium",
SeverityValue in ("high", "critical", "fatal"), "High",
"Medium"),
HostNameValue = coalesce(extract(@"^([^.]+)", 1, HostValue), HostValue),
FileHashAlgorithm = case(
isnotempty(FileSHA256) or isnotempty(OcsfSHA256), "SHA256",
isnotempty(FileSHA1) or isnotempty(OcsfSHA1), "SHA1",
isnotempty(FileMD5) or isnotempty(OcsfMD5), "MD5",
"")
| project
TimeGenerated,
SynqlyAlertId,
SynqlyAlertName,
SynqlyAlertDescription,
SynqlyAlertSeverity,
SynqlyTactics,
AllAttackTechniques,
SynqlyConfidenceLevel,
SynqlyConfidenceScore,
SynqlyRemediation,
SynqlyAlertLink,
SourceVendor,
SourceProduct,
ProductVersion,
OriginalSeverity,
SourceRuleId,
AccountName,
AccountSid,
AccountDomain,
HostNameValue,
HostDomainValue,
SourceIp,
DestinationIp,
DeviceIp,
PrimaryIp,
PrimaryIpRole,
DestinationDomain,
EntityUrl,
FileValue,
FileHashAlgorithm,
FileHashValue,
ProcessIdValue,
ProcessCommandLineValue,
MalwareName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: Sid
columnName: AccountSid
- identifier: NTDomain
columnName: AccountDomain
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostNameValue
- identifier: DnsDomain
columnName: HostDomainValue
- entityType: IP
fieldMappings:
- identifier: Address
columnName: PrimaryIp
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: FileHashAlgorithm
- identifier: Value
columnName: FileHashValue
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ProcessIdValue
- identifier: CommandLine
columnName: ProcessCommandLineValue
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
ASimAlertEventLogs
Stage 2: where
| where tostring(AdditionalFields._ConnectorId) == "SynqlySentinelASIMConnector"
Stage 3: extend
| extend OCSF = coalesce(todynamic(AdditionalFields.OCSF), dynamic({}))
Stage 4: kusto:mv-apply
| mv-apply Evidence = array_concat(coalesce(todynamic(OCSF.evidences), dynamic([])), dynamic([{}])) on (
mv-apply EvidenceHash = array_concat(coalesce(todynamic(Evidence.data.file.hashes), dynamic([])), coalesce(todynamic(Evidence.data.process.hashes), dynamic([])), dynamic([{}])) on (
summarize
OcsfSHA256 = take_anyif(tostring(EvidenceHash.value), toupper(tostring(EvidenceHash.algorithm)) == "SHA256"),
OcsfSHA1 = take_anyif(tostring(EvidenceHash.value), toupper(tostring(EvidenceHash.algorithm)) == "SHA1"),
OcsfMD5 = take_anyif(tostring(EvidenceHash.value), toupper(tostring(EvidenceHash.algorithm)) == "MD5")
)
| summarize
OcsfUserName = take_anyif(tostring(Evidence.data.process.user.name), isnotempty(tostring(Evidence.data.process.user.name))),
OcsfUserSid = take_anyif(tostring(Evidence.data.process.user.uid), isnotempty(tostring(Evidence.data.process.user.uid))),
OcsfUserDomain = take_anyif(tostring(Evidence.data.process.user.domain), isnotempty(tostring(Evidence.data.process.user.domain))),
OcsfProcessId = take_anyif(tostring(Evidence.data.process.pid), isnotempty(tostring(Evidence.data.process.pid))),
OcsfProcessCommandLine = take_anyif(tostring(Evidence.data.process.cmd_line), isnotempty(tostring(Evidence.data.process.cmd_line))),
OcsfFileName = take_anyif(tostring(Evidence.data.file.name), isnotempty(tostring(Evidence.data.file.name))),
OcsfFilePath = take_anyif(tostring(Evidence.data.file.path), isnotempty(tostring(Evidence.data.file.path))),
OcsfSourceHost = take_anyif(tostring(Evidence.data.network.src_endpoint.host.hostname), isnotempty(tostring(Evidence.data.network.src_endpoint.host.hostname))),
OcsfSourceIp = take_anyif(tostring(Evidence.data.network.src_endpoint.ip), isnotempty(tostring(Evidence.data.network.src_endpoint.ip))),
OcsfDestinationIp = take_anyif(tostring(Evidence.data.network.dst_endpoint.ip), isnotempty(tostring(Evidence.data.network.dst_endpoint.ip))),
OcsfDestinationDomain = take_anyif(tostring(Evidence.data.network.dst_endpoint.domain), isnotempty(tostring(Evidence.data.network.dst_endpoint.domain))),
OcsfSHA256 = take_anyif(OcsfSHA256, isnotempty(OcsfSHA256)),
OcsfSHA1 = take_anyif(OcsfSHA1, isnotempty(OcsfSHA1)),
OcsfMD5 = take_anyif(OcsfMD5, isnotempty(OcsfMD5))
)
Stage 5: kusto:mv-apply
| mv-apply Attack = array_concat(coalesce(todynamic(OCSF.attacks), dynamic([])), dynamic([{}])) on (
extend TacticId = tostring(Attack.tactic.uid), TechniqueId = tostring(Attack.technique.uid), SubTechniqueId = tostring(Attack.sub_technique.uid)
| extend TacticName = case(
TacticId == "TA0043", "Reconnaissance", TacticId == "TA0042", "ResourceDevelopment",
TacticId == "TA0001", "InitialAccess", TacticId == "TA0002", "Execution",
TacticId == "TA0003", "Persistence", TacticId == "TA0004", "PrivilegeEscalation",
TacticId == "TA0005", "DefenseEvasion", TacticId == "TA0006", "CredentialAccess",
TacticId == "TA0007", "Discovery", TacticId == "TA0008", "LateralMovement",
TacticId == "TA0009", "Collection", TacticId == "TA0010", "Exfiltration",
TacticId == "TA0011", "CommandAndControl", TacticId == "TA0040", "Impact", "")
| summarize
OcsfTactics = make_set_if(TacticName, isnotempty(TacticName), 14),
OcsfTechniques = make_set_if(TechniqueId, isnotempty(TechniqueId), 50),
OcsfSubTechniques = make_set_if(SubTechniqueId, isnotempty(SubTechniqueId), 50)
)
Stage 6: extend (5 consecutive steps)
| extend
ActorUser = todynamic(OCSF.actor.process.user),
Malware = todynamic(OCSF.malware[0]),
NativeConfidenceValue = toreal(ThreatConfidence),
OcsfConfidenceValue = toreal(OCSF.confidence_score),
NativeConfidenceLabel = tolower(trim(" ", tostring(ThreatOriginalConfidence))),
OcsfConfidenceLabel = tolower(trim(" ", tostring(OCSF.confidence))),
NativeSourceId = coalesce(tostring(EventUid), tostring(AlertId), tostring(EventOriginalUid)),
OcsfSourceId = coalesce(tostring(OCSF.finding_info.uid), tostring(OCSF.uid)),
SourceIdMaterial = tostring(pack_array(tostring(OCSF.class_uid), tostring(OCSF.type_uid), tostring(OCSF.activity_id), tostring(EventStartTime), tostring(EventEndTime), tostring(EventVendor), tostring(EventProduct), tostring(EventMessage), tostring(OCSF["time"])))
| extend
SynqlyAlertId = coalesce(NativeSourceId, OcsfSourceId, strcat("generated-", hash_sha256(SourceIdMaterial))),
SynqlyAlertName = coalesce(tostring(AlertName), tostring(OCSF.finding_info["title"]), tostring(OCSF.class_name), "Synqly normalized alert event"),
SynqlyAlertDescription = coalesce(tostring(EventMessage), tostring(AlertDescription), tostring(OCSF.message), "Synqly normalized alert event"),
SeverityValue = tolower(coalesce(tostring(EventSeverity), tostring(EventOriginalSeverity), tostring(OCSF.severity))),
SynqlyTactics = coalesce(tostring(AttackTactics), strcat_array(OcsfTactics, ",")),
AllAttackTechniques = coalesce(tostring(AttackTechniques), strcat_array(array_concat(OcsfTechniques, OcsfSubTechniques), ",")),
SynqlyConfidenceScore = coalesce(
iff(NativeConfidenceValue between (0.0 .. 100.0), NativeConfidenceValue / 100.0, real(null)),
iff(OcsfConfidenceValue between (0.0 .. 100.0), OcsfConfidenceValue / 100.0, real(null))),
SynqlyConfidenceLevel = case(
NativeConfidenceLabel == "high", "High",
NativeConfidenceLabel == "low", "Low",
NativeConfidenceLabel == "unknown", "Unknown",
isnotempty(NativeConfidenceLabel), "",
OcsfConfidenceLabel == "high", "High",
OcsfConfidenceLabel == "low", "Low",
OcsfConfidenceLabel == "unknown", "Unknown",
""),
SynqlyRemediation = coalesce(tostring(AttackRemediationSteps), strcat_array(todynamic(OCSF.remediation.kb_articles), "; ")),
SynqlyAlertLink = coalesce(tostring(EventReportUrl), tostring(OCSF.finding_info.src_url)),
SourceVendor = coalesce(tostring(EventVendor), tostring(OCSF.metadata.product.vendor_name)),
SourceProduct = coalesce(tostring(EventProduct), tostring(OCSF.metadata.product.name)),
ProductVersion = coalesce(tostring(EventProductVersion), tostring(OCSF.metadata.product.version)),
OriginalSeverity = coalesce(tostring(EventOriginalSeverity), tostring(OCSF.severity)),
SourceRuleId = coalesce(
tostring(Rule),
tostring(OCSF.finding_info.analytic.uid),
iff(isnotempty(tostring(RuleName)) and not(tostring(RuleName) matches regex @"^\d+$"), tostring(RuleName), "")),
AccountName = coalesce(tostring(Username), tostring(User), tostring(ActorUser.name), OcsfUserName),
AccountSid = coalesce(iff(tolower(tostring(UserIdType)) contains "sid", tostring(UserId), ""), tostring(ActorUser.uid), OcsfUserSid),
AccountDomain = coalesce(tostring(UserScope), tostring(ActorUser.domain), OcsfUserDomain),
NativeDeviceHost = coalesce(tostring(DvcHostname), tostring(DvcFQDN)),
NativeDeviceHostDomain = coalesce(tostring(DvcDomain), extract(@"^[^.]+\.(.+)$", 1, tostring(DvcFQDN)), extract(@"^[^.]+\.(.+)$", 1, tostring(DvcHostname))),
DeviceIp = tostring(DvcIpAddr),
DestinationDomain = OcsfDestinationDomain,
EntityUrl = coalesce(tostring(Url), tostring(OCSF.finding_info.src_url)),
FileValue = coalesce(tostring(FilePath), OcsfFilePath),
ProcessIdValue = coalesce(tostring(ProcessId), OcsfProcessId),
ProcessCommandLineValue = coalesce(tostring(ProcessCommandLine), OcsfProcessCommandLine),
MalwareName = coalesce(tostring(ThreatName), tostring(Malware.name)),
FileHashValue = coalesce(tostring(FileSHA256), OcsfSHA256, tostring(FileSHA1), OcsfSHA1, tostring(FileMD5), OcsfMD5)
| extend
SourceIp = OcsfSourceIp,
DestinationIp = OcsfDestinationIp,
HostValue = coalesce(NativeDeviceHost, OcsfSourceHost),
HostDomainValue = iff(isnotempty(NativeDeviceHost), NativeDeviceHostDomain, extract(@"^[^.]+\.(.+)$", 1, OcsfSourceHost))
| extend
PrimaryIp = coalesce(SourceIp, DestinationIp, tostring(DvcIpAddr)),
PrimaryIpRole = case(isnotempty(SourceIp), "Source", isnotempty(DestinationIp), "Destination", isnotempty(tostring(DvcIpAddr)), "Device", "")
| extend
SynqlyAlertSeverity = case(
SeverityValue == "informational", "Informational",
SeverityValue == "low", "Low",
SeverityValue == "medium", "Medium",
SeverityValue in ("high", "critical", "fatal"), "High",
"Medium"),
HostNameValue = coalesce(extract(@"^([^.]+)", 1, HostValue), HostValue),
FileHashAlgorithm = case(
isnotempty(FileSHA256) or isnotempty(OcsfSHA256), "SHA256",
isnotempty(FileSHA1) or isnotempty(OcsfSHA1), "SHA1",
isnotempty(FileMD5) or isnotempty(OcsfMD5), "MD5",
"")
Stage 7: project
| project
TimeGenerated,
SynqlyAlertId,
SynqlyAlertName,
SynqlyAlertDescription,
SynqlyAlertSeverity,
SynqlyTactics,
AllAttackTechniques,
SynqlyConfidenceLevel,
SynqlyConfidenceScore,
SynqlyRemediation,
SynqlyAlertLink,
SourceVendor,
SourceProduct,
ProductVersion,
OriginalSeverity,
SourceRuleId,
AccountName,
AccountSid,
AccountDomain,
HostNameValue,
HostDomainValue,
SourceIp,
DestinationIp,
DeviceIp,
PrimaryIp,
PrimaryIpRole,
DestinationDomain,
EntityUrl,
FileValue,
FileHashAlgorithm,
FileHashValue,
ProcessIdValue,
ProcessCommandLineValue,
MalwareName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AdditionalFields._ConnectorId | eq |
| field:"AdditionalFields._ConnectorId" kind:eq value:"SynqlySentinelASIMConnector" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountDomain | project |
AccountName | project |
AccountSid | project |
AllAttackTechniques | project |
DestinationDomain | project |
DestinationIp | project |
DeviceIp | project |
EntityUrl | project |
FileHashAlgorithm | project |
FileHashValue | project |
FileValue | project |
HostDomainValue | project |
HostNameValue | project |
MalwareName | project |
OriginalSeverity | project |
PrimaryIp | project |
PrimaryIpRole | project |
ProcessCommandLineValue | project |
ProcessIdValue | project |
ProductVersion | project |
SourceIp | project |
SourceProduct | project |
SourceRuleId | project |
SourceVendor | project |
SynqlyAlertDescription | project |
SynqlyAlertId | project |
SynqlyAlertLink | project |
SynqlyAlertName | project |
SynqlyAlertSeverity | project |
SynqlyConfidenceLevel | project |
SynqlyConfidenceScore | project |
SynqlyRemediation | project |
SynqlyTactics | project |
TimeGenerated | project |
Uniqkey - Departing employee credential export
#Correlates the archival, deletion or detachment of a Uniqkey employee account with data exports performed by that same employee during the preceding 14 days. An employee who exports credentials shortly before losing access is a classic insider-risk scenario, and organization credentials touched by the export should be considered for rotation. The action filter matches the offboarding action identifiers from the Uniqkey audit-log catalog, with a name-based fallback for future action variants.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: cee6bbae-e1ee-4b65-9782-6afce5c330e6
name: Uniqkey - Departing employee credential export
version: 1.0.0
kind: Scheduled
description: |-
Correlates the archival, deletion or detachment of a Uniqkey employee account with data exports performed by that same employee during the preceding 14 days. An employee who exports credentials shortly before losing access is a classic insider-risk scenario, and organization credentials touched by the export should be considered for rotation. The action filter matches the offboarding action identifiers from the Uniqkey audit-log catalog, with a name-based fallback for future action variants.
severity: High
requiredDataConnectors:
- connectorId: UniqkeyEventsConnector
dataTypes:
- UniqkeyEvents_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- Exfiltration
relevantTechniques:
- T1555
query: |-
let lookback = 14d;
let frequency = 1h;
let exports =
UniqkeyEvents_CL
| where TimeGenerated > ago(lookback)
| where Category == "data_export"
| project ExportTime = TimeGenerated, ActorId, ActorEmail, ExportAction = Action, ExportedTargetType = TargetType, ExportedTargetName = TargetName, SrcIpAddr;
let removalActionIds = dynamic([
"a2e18fcb-4d28-4f53-b2e3-24f03d0e188e", // Archive employee account
"319c8a05-abd5-4b13-b51f-d6198578e61c", // Archive bulk employee
"f1ed588f-975b-4bbe-83e9-e7a5c23e647f", // Hard delete employee
"f8b81b6c-0082-4ed8-916e-83350f932e89", // Permanent delete employee account
"4361dd86-0f70-47e0-91de-071a81d49834" // Detach bulk employee account
]);
UniqkeyEvents_CL
| where TimeGenerated > ago(frequency)
| where Category == "account_management"
| where tolower(ActionId) in (removalActionIds) or Action has_any ("archive", "delete", "detach")
| where TargetType == "employee"
| project RemovalTime = TimeGenerated, RemovalAction = Action, RemovedEmployeeId = TargetId, RemovedEmployeeName = TargetName, RemovalActor = ActorEmail
| join kind=inner exports on $left.RemovedEmployeeId == $right.ActorId
| project RemovalTime, RemovalAction, RemovedEmployeeName, RemovalActor, ExportTime, ExportAction, ExportedTargetType, ExportedTargetName, ActorEmail, SrcIpAddr
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorEmail
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let lookback = 14d;
let frequency = 1h;
Let Binding: exports
let exports = UniqkeyEvents_CL
| where TimeGenerated > ago(lookback)
| where Category == "data_export"
| project ExportTime = TimeGenerated, ActorId, ActorEmail, ExportAction = Action, ExportedTargetType = TargetType, ExportedTargetName = TargetName, SrcIpAddr;
Let Binding: removalActionIds
let removalActionIds = dynamic([
"a2e18fcb-4d28-4f53-b2e3-24f03d0e188e",
"319c8a05-abd5-4b13-b51f-d6198578e61c",
"f1ed588f-975b-4bbe-83e9-e7a5c23e647f",
"f8b81b6c-0082-4ed8-916e-83350f932e89",
"4361dd86-0f70-47e0-91de-071a81d49834"
]);
Stage 1: source
UniqkeyEvents_CL
Stage 2: where
| where TimeGenerated > ago(frequency)
Stage 3: where
| where Category == "account_management"
Stage 4: where
| where tolower(ActionId) in (removalActionIds) or Action has_any ("archive", "delete", "detach")
Stage 5: where
| where TargetType == "employee"
Stage 6: project
| project RemovalTime = TimeGenerated, RemovalAction = Action, RemovedEmployeeId = TargetId, RemovedEmployeeName = TargetName, RemovalActor = ActorEmail
Stage 7: join
| join kind=inner exports on $left.RemovedEmployeeId == $right.ActorId
Stage 8: project
project ActorEmail, ExportAction, ExportTime, ExportedTargetName, ExportedTargetType, RemovalAction, RemovalActor, RemovalTime, RemovedEmployeeName, SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | match |
| field:"Action" kind:match |
ActionId | in |
| field:"ActionId" kind:in |
Category | eq |
| field:"Category" kind:eq |
TargetType | eq |
| field:"TargetType" kind:eq value:"employee" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorEmail | project |
ExportAction | project |
ExportTime | project |
ExportedTargetName | project |
ExportedTargetType | project |
RemovalAction | project |
RemovalActor | project |
RemovalTime | project |
RemovedEmployeeName | project |
SrcIpAddr | project |