Detection rules › Kusto
Kusto rules: 3a
1Password - Changes to SSO configuration
#This will alert when changes have been made to the SSO configuration. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same. Ref: https://1password.com/ Ref: https://github.com/securehats/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| 1Password | any: SSO (catch-all) |
Rule Body
id: 9406f5ab-1197-4db9-8042-9f3345be061c
name: 1Password - Changes to SSO configuration
version: 1.0.1
kind: Scheduled
description: |-
This will alert when changes have been made to the SSO configuration. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same.
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:
- T1556
query: |-
OnePasswordEventLogs_CL
| where log_source == "auditevents"
| where action has_any("enblsso", "disblsso", "chngpsso", "chngasso", "chngdsso", "addgsso", "delgsso")
| where object_type == "sso"
| extend
ActorUsername = actor_details.email
, SrcIpAddr = session.ip
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
suppressionDuration: 5h
alertDetailsOverride:
alertDynamicProperties: []
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUsername
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
Stages and Predicates
Stage 1: source
OnePasswordEventLogs_CL
Stage 2: where
| where log_source == "auditevents"
Stage 3: where
| where action has_any("enblsso", "disblsso", "chngpsso", "chngasso", "chngdsso", "addgsso", "delgsso")
Stage 4: where
| where object_type == "sso"
Stage 5: extend
| extend
ActorUsername = actor_details.email
, SrcIpAddr = session.ip
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | match |
| field:"action" kind:match |
log_source | eq |
| field:"log_source" kind:eq value:"auditevents" |
object_type | eq |
| field:"object_type" kind:eq value:"sso" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorUsername | extend |
SrcIpAddr | extend |
Apache - Request to sensitive files
#'Detects request to sensitive files.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: d1c52578-3afc-11ec-8d3d-0242ac130003
name: Apache - Request to sensitive files
description: |
'Detects request to sensitive files.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- ApacheHTTPServer_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1189
query: |
let forbidden_files = dynamic(['shadow', 'passwd', 'id_rsa']);
ApacheHTTPServer
| where isnotempty(UrlOriginal)
| extend File = extract(@'(.*\/)?(.*)', 2, tostring(UrlOriginal))
| where File in~ (forbidden_files)
| extend FileCustomEntity = File, UrlCustomEntity = UrlOriginal
entityMappings:
- entityType: File
fieldMappings:
- identifier: Name
columnName: FileCustomEntity
- entityType: URL
fieldMappings:
- identifier: Url
columnName: UrlCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let forbidden_files = dynamic(['shadow', 'passwd', 'id_rsa']);
Stage 1: source
ApacheHTTPServer
Stage 2: where
| where isnotempty(UrlOriginal)
Stage 3: extend
| extend File = extract(@'(.*\/)?(.*)', 2, tostring(UrlOriginal))
Stage 4: where
| where File in~ (forbidden_files)
Stage 5: extend
| extend FileCustomEntity = File, UrlCustomEntity = UrlOriginal
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
File | in |
| field:"File" kind:in |
UrlOriginal | is_not_null | field:"UrlOriginal" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
File | extend |
FileCustomEntity | extend |
UrlCustomEntity | extend |
ASR Bypassing Writing Executable Content
#The query checks for any file which has been created/written by an Office application and shortly after renamed to one of the deny-listed "executable extensions" which are text files. (e.g. ps1, .js, .vbs).
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceFileEvents action FileRenamed: File renamed |
Rule Body
id: efe4efef-5ca7-4b51-a53e-0e96492ce97a
name: ASR Bypassing Writing Executable Content
description: |
The query checks for any file which has been created/written by an Office application and shortly after renamed to one of the deny-listed "executable extensions" which are text files. (e.g. ps1, .js, .vbs).
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceFileEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1211
query: |
let timeframe = 1d;
let executableExtensions = dynamic([".js", ".hta", ".lnk", ".application", ".vb", ".vba", ".vbs", ".ps", ".ps1", ".bat", ".cmd"]);
DeviceFileEvents
| where Timestamp >= ago(timeframe)
| where InitiatingProcessFileName in~ ("winword.exe", "excel.exe", "outlook.exe", "powerpnt.exe")
| where ActionType == "FileRenamed"
// The mv-apply is less performance compared to doing a full written out !endswith. We kept the mv-apply since We don't hit any limits and get more readable/maintainable code.
| mv-apply ext=executableExtensions to typeof(string) on
(
where PreviousFileName !endswith ext and FileName endswith ext
)
| project-reorder PreviousFileName, FileName
// Begin allow-list.
// End allow-list.
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: InitiatingProcessAccountSid
- identifier: Name
columnName: InitiatingProcessAccountName
- identifier: NTDomain
columnName: InitiatingProcessAccountDomain
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: InitiatingProcessCommandLine
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = 1d;
let executableExtensions = dynamic([".js", ".hta", ".lnk", ".application", ".vb", ".vba", ".vbs", ".ps", ".ps1", ".bat", ".cmd"]);
Stage 1: source
DeviceFileEvents
Stage 2: where
| where Timestamp >= ago(timeframe)
Stage 3: where
| where InitiatingProcessFileName in~ ("winword.exe", "excel.exe", "outlook.exe", "powerpnt.exe")
Stage 4: where
| where ActionType == "FileRenamed"
Stage 5: kusto:mv-apply
| mv-apply ext=executableExtensions to typeof(string) on
(
where PreviousFileName !endswith ext and FileName endswith ext
)
Stage 6: project-reorder
| project-reorder PreviousFileName, FileName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"FileRenamed" |
FileName | ends_with |
| field:"FileName" kind:ends_with value:"ext" |
InitiatingProcessFileName | in |
| field:"process_basename" kind:in |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
PreviousFileName | ends_with | ext | excludes:PreviousFileName field:"PreviousFileName" value:"ext" |
Azure Secure Score Self Service Password Reset
#'This query searches for requires you to setup Microsoft Entra ID Connect. Microsoft Entra ID Connect is free with all Azure Subscriptions'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 114120B2-AAA0-4C4E-BDF1-2EE178465047
name: Azure Secure Score Self Service Password Reset
description: |
'This query searches for requires you to setup Microsoft Entra ID Connect.
Microsoft Entra ID Connect is free with all Azure Subscriptions'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SenservaPro
dataTypes:
- SenservaPro_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1529
query: |
SenservaPro_CL
| where ControlName_s == 'AzureSecureScoreSelfServicePasswordReset'
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: ControlName_s
- identifier: AadTenantId
columnName: TenantId
- identifier: DisplayName
columnName: TenantDisplayName_s
- entityType: SecurityGroup
fieldMappings:
- identifier: DistinguishedName
columnName: Group_s
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: SourceSystem
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
SenservaPro_CL
Stage 2: where
| where ControlName_s == 'AzureSecureScoreSelfServicePasswordReset'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ControlName_s | eq |
| field:"ControlName_s" kind:eq value:"AzureSecureScoreSelfServicePasswordReset" |
BloodHound Attack Path Finding - RDP Users on Tier Zero Computers
#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 - RDP Users on Tier Zero Computers
Rule Body
id: 0eefbca4-0dde-4e72-afaa-dbacc131d07a
name: BloodHound Attack Path Finding - RDP Users on Tier Zero Computers
description: BloodHound Attack Path Finding - RDP Users on Tier Zero Computers
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 == "T0RDP"
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 == "T0RDP"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0RDP" |
Brute force attack against user credentials (Uses Authentication Normalization)
#Identifies evidence of brute force activity against a user based on multiple authentication failures and at least one successful authentication within a given time window. Note that the query does not enforce any sequence, and does not require the successful authentication to occur last. The default failure threshold is 10, success threshold is 1, and the default time window is 20 minutes. To use this analytics rule, make sure you have deployed the ASIM normalization parsers
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry Coverage
Rule Body
id: a6c435a2-b1a0-466d-b730-9f8af69262e8
name: Brute force attack against user credentials (Uses Authentication Normalization)
description: |
'Identifies evidence of brute force activity against a user based on multiple authentication failures and at least one successful authentication within a given time window.
Note that the query does not enforce any sequence, and does not require the successful authentication to occur last.
The default failure threshold is 10, success threshold is 1, and the default time window is 20 minutes.
To use this analytics rule, make sure you have deployed the [ASIM normalization parsers](https://aka.ms/ASimAuthentication)'
severity: Medium
requiredDataConnectors: []
queryFrequency: 20m
queryPeriod: 20m
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
tags:
- Id: 28b42356-45af-40a6-a0b4-a554cdfd5d8a
version: 1.0.0
- Schema: ASIMAuthentication
SchemaVersion: 0.1.0
query: |
let failureCountThreshold = 10;
let successCountThreshold = 1;
// let authenticationWindow = 20m; // Implicit in the analytic rule query period
imAuthentication
| where TargetUserType != "NonInteractive"
| summarize
StartTime = min(TimeGenerated),
EndTime = max(TimeGenerated),
IpAddresses = make_set (SrcDvcIpAddr, 100),
ReportedBy = make_set (strcat (EventVendor, "/", EventProduct), 100),
FailureCount = countif(EventResult=='Failure'),
SuccessCount = countif(EventResult=='Success')
by
TargetUserId, TargetUsername, TargetUserType
| where FailureCount >= failureCountThreshold and SuccessCount >= successCountThreshold
| extend
IpAddresses = strcat_array(IpAddresses, ", "),
ReportedBy = strcat_array(ReportedBy, ", ")
| extend
Name = iif(
TargetUsername contains "@"
, tostring(split(TargetUsername, '@', 0)[0])
, TargetUsername
),
UPNSuffix = iif(
TargetUsername contains "@"
, tostring(split(TargetUsername, '@', 1)[0])
, ""
)
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUserName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
customDetails:
IpAddresses: IpAddresses
ReportedBy: ReportedBy
version: 1.2.5
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Ofer Shezaf
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Parameters
let failureCountThreshold = 10;
let successCountThreshold = 1;
Stage 1: source
imAuthentication
Stage 2: where
| where TargetUserType != "NonInteractive"
Stage 3: summarize
| summarize
StartTime = min(TimeGenerated),
EndTime = max(TimeGenerated),
IpAddresses = make_set (SrcDvcIpAddr, 100),
ReportedBy = make_set (strcat (EventVendor, "/", EventProduct), 100),
FailureCount = countif(EventResult=='Failure'),
SuccessCount = countif(EventResult=='Success')
by
TargetUserId, TargetUsername, TargetUserType
Stage 4: where
| where FailureCount >= failureCountThreshold and SuccessCount >= successCountThreshold
Stage 5: extend
| extend
IpAddresses = strcat_array(IpAddresses, ", "),
ReportedBy = strcat_array(ReportedBy, ", ")
Stage 6: extend
| extend
Name = iif(
TargetUsername contains "@"
, tostring(split(TargetUsername, '@', 0)[0])
, TargetUsername
),
UPNSuffix = iif(
TargetUsername contains "@"
, tostring(split(TargetUsername, '@', 1)[0])
, ""
)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
FailureCount | ge |
| field:"FailureCount" kind:ge value:"10" |
SuccessCount | ge |
| field:"SuccessCount" kind:ge value:"1" |
TargetUserType | ne |
| field:"TargetUserType" kind:ne value:"NonInteractive" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTime | summarize |
FailureCount | summarize |
IpAddresses | extend |
ReportedBy | extend |
StartTime | summarize |
SuccessCount | summarize |
TargetUserId | summarize |
TargetUserType | summarize |
TargetUsername | summarize |
Name | extend |
UPNSuffix | extend |
Cisco Duo - New access device
#'Detects new access device.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: f05271b6-26a5-49cf-ad73-4a202fba6eb6
name: Cisco Duo - New access device
description: |
'Detects new access device.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CiscoDuoAuthConnectorDefinition
dataTypes:
- DuoAuthentication_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
DuoAuthentication_CL
| where Result =~ 'success'
| where isnotempty(AccessDeviceIp)
| summarize dvc_ip = make_set(AccessDeviceIp) by UserName
| join (DuoAuthentication_CL
| where Result =~ 'success') on UserName
| where dvc_ip !has AccessDeviceIp
| extend IPCustomEntity = AccessDeviceIp, AccountCustomEntity = UserName
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
DuoAuthentication_CL
Stage 2: where
| where Result =~ 'success'
Stage 3: where
| where isnotempty(AccessDeviceIp)
Stage 4: summarize
| summarize dvc_ip = make_set(AccessDeviceIp) by UserName
Stage 5: join
| join (DuoAuthentication_CL
| where Result =~ 'success') on UserName
Stage 6: where
where dvc_ip =~ AccessDeviceIp
Stage 7: extend
extend AccountCustomEntity, IPCustomEntity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccessDeviceIp | is_not_null | field:"AccessDeviceIp" kind:is_not_null | |
Result | eq |
| field:"Result" kind:eq value:"success" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserName | summarize |
dvc_ip | summarize |
AccountCustomEntity | extend |
IPCustomEntity | extend |
Corelight - Multiple Compressed Files Transferred over HTTP
#'Detects compressed archives transferre over HTTP.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: 4e55e306-3022-43a1-870a-41c4d5116079
name: Corelight - Multiple Compressed Files Transferred over HTTP
description: |
'Detects compressed archives transferre over HTTP.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Corelight
dataTypes:
- corelight_http
- corelight_http_red
- corelight_http2
- corelight_http_agg
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1567
query: |
let threshold = 25;
union isfuzzy=true corelight_http, corelight_http_agg
| where method in~ ('POST', 'PUT')
| where isempty(referrer)
| where orig_mime_types has_any ('application/vnd.ms-cab-compressed', 'application/warc', 'application/x-7z-compressed', 'application/x-ace', 'application/x-arc', 'application/x-archive', 'application/x-arj', 'application/x-compress', 'application/x-cpio', 'application/x-dmg', 'application/x-eet', 'application/x-gzip', 'application/x-lha', 'application/x-lrzip', 'application/x-lz4', 'application/x-lzma', 'application/x-lzh', 'application/x-lzip', 'application/x-rar', 'application/x-rpm', 'application/x-stuffit', 'application/x-tar', 'application/x-xz', 'application/x-zoo', 'application/zip')
| summarize count() by uri, id_orig_h, bin(TimeGenerated, 15m)
| where count_ > threshold
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: id_orig_h
version: 2.2.0
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 25;
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: corelight_http, corelight_http_agg
Leg 1: corelight_http
Leg 2: corelight_http_agg
Applied to the Combined Result
| where method in~ ('POST', 'PUT') | where isempty(referrer) | where orig_mime_types has_any ('application/vnd.ms-cab-compressed', 'application/warc', 'application/x-7z-compressed', 'application/x-ace', 'application/x-arc', 'application/x-archive', 'application/x-arj', 'application/x-compress', 'application/x-cpio', 'application/x-dmg', 'application/x-eet', 'application/x-gzip', 'application/x-lha', 'application/x-lrzip', 'application/x-lz4', 'application/x-lzma', 'application/x-lzh', 'application/x-lzip', 'application/x-rar', 'application/x-rpm', 'application/x-stuffit', 'application/x-tar', 'application/x-xz', 'application/x-zoo', 'application/zip') | summarize count() by uri, id_orig_h, bin(TimeGenerated, 15m) | where count_ > threshold
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
count_ | gt |
| field:"count_" kind:gt value:"25" |
method | in |
| field:"method" kind:in |
orig_mime_types | match |
| field:"orig_mime_types" kind:match |
referrer | is_null | field:"referrer" kind:is_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
id_orig_h | summarize |
uri | summarize |
CYFIRMA - Medium severity Malicious Network Indicators with Block 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 rule detects network-based indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Block' and no specified role. These indicators may be associated with infrastructure used in phishing campaigns, malware, or initial access via watering hole or drive-by compromise techniques."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Execution | |
| Impact |
Rule Body
id: 4e7d1851-5aab-478d-b348-4b83dc2b03d9
name: CYFIRMA - Medium severity Malicious Network Indicators with Block Action Rule
description: |
"This rule detects network-based indicators such as IP addresses, domains, and URLs reported by CYFIRMA threat intelligence with a recommended action of 'Block' and no specified role.
These indicators may be associated with infrastructure used in phishing campaigns, malware, or initial access via watering hole or drive-by compromise techniques."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
// Network Indicators with Block Action Rule
let timeFrame= 5m;
CyfirmaIndicators_CL
| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and (isempty(Roles) or not(Roles has_any ('Command & Control', 'Malware', 'TOR', 'Trojan', 'Phishing')))
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
| mv-expand extensionKeys
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- InitialAccess
- Execution
- Reconnaissance
- Impact
relevantTechniques:
- T1566
- T1189
- T1204
- T1595
- T1498
- T1566.002
- T1204.001
- T1595.002
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence Malicious Network Indicators with Block Action Rule - {{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
IPAbuse: IPAbuse
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv4
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv6
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
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 ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and (isempty(Roles) or not(Roles has_any ('Command & Control', 'Malware', 'TOR', 'Trojan', 'Phishing')))
Stage 3: extend (6 consecutive steps)
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 6: project
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
pattern | contains | file:hashes | excludes:pattern field:"pattern" value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ConfidenceScore | project |
Country | project |
Description | project |
Domain | project |
IPAbuse | project |
IPv4 | project |
IPv6 | project |
IndicatorID | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
URL | project |
created | project |
modified | project |
name | project |
valid_from | project |
Dataverse - User bulk retrieval outside normal activity
#Identifies users retrieving significantly more records from Dataverse than they have previously in the past 2 weeks.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | any: Dataverse and model-driven apps audit (catch-all) |
Rule Body
id: 08cb7ffc-59c6-4e7d-88e0-327371c9431b
kind: Scheduled
name: Dataverse - User bulk retrieval outside normal activity
description: Identifies users retrieving significantly more records from Dataverse
than they have previously in the past 2 weeks.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: Dataverse
dataTypes:
- DataverseActivity
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1048
query: |
let baseline_time = 14d;
let detection_time = 1d;
DataverseActivity
| where TimeGenerated between(ago(baseline_time) .. ago(detection_time - 1d))
| where Message == "RetrieveMultiple"
| extend numQueryCount = todouble(QueryResults)
| extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
| extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))
| summarize sum(QueryCount) by UserId
| extend HistoricalBaseline = sum_QueryCount
| join kind=inner (
DataverseActivity
| where TimeGenerated > ago(detection_time)
| where Message == "RetrieveMultiple"
| extend numQueryCount = todouble(QueryResults)
| extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
| extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))
| summarize sum(QueryCount) by UserId
| extend CurrentExportRate = sum_QueryCount)
on UserId
| where CurrentExportRate > HistoricalBaseline
| project UserId, HistoricalBaseline, CurrentExportRate
| join kind=inner(
DataverseActivity
| where TimeGenerated > ago(detection_time)
| where Message == "RetrieveMultiple"
| extend numQueryCount = todouble(QueryResults)
| extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
| extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1)))
on UserId
| summarize
QuerySizes = make_set(QueryCount),
MostRecentQuery = max(TimeGenerated),
IPs = make_set(ClientIp),
UserAgents = make_set(UserAgent),
Entities = make_set(EntityName),
Queries = make_set(Query)
by UserId, InstanceUrl, HistoricalBaseline, CurrentExportRate
| extend
AccountName = tostring(split(UserId, '@')[0]),
UPNSuffix = tostring(split(UserId, '@')[1]),
CloudAppId = int(32780)
| project
MostRecentQuery,
UserId,
IPs,
UserAgents,
InstanceUrl,
Queries,
QuerySizes,
Entities,
HistoricalBaseline,
CurrentExportRate,
AccountName,
UPNSuffix,
CloudAppId
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: CloudAppId
- identifier: InstanceName
columnName: InstanceUrl
alertDetailsOverride:
alertDisplayNameFormat: Dataverse - Bulk record retrieval outside of normal activity
alertDescriptionFormat: '{{UserId}} exported {{CurrentExportRate}} records, far
beyond the historical baseline of {{{HistoricalBaseline}}.'
customDetails: {}
version: 3.2.0
Stages and Predicates
Parameters
let baseline_time = 14d;
let detection_time = 1d;
Stage 1: source
DataverseActivity
Stage 2: where
| where TimeGenerated between(ago(baseline_time) .. ago(detection_time - 1d))
Stage 3: where
| where Message == "RetrieveMultiple"
Stage 4: extend (3 consecutive steps)
| extend numQueryCount = todouble(QueryResults)
| extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
| extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))
Stage 5: summarize
| summarize sum(QueryCount) by UserId
Stage 6: extend
| extend HistoricalBaseline = sum_QueryCount
Stage 7: join
| join kind=inner (
DataverseActivity
| where TimeGenerated > ago(detection_time)
| where Message == "RetrieveMultiple"
| extend numQueryCount = todouble(QueryResults)
| extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
| extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))
| summarize sum(QueryCount) by UserId
| extend CurrentExportRate = sum_QueryCount)
on UserId
Stage 8: where
where CurrentExportRate > HistoricalBaseline
Stage 9: project
project CurrentExportRate, HistoricalBaseline, UserId
Stage 10: join
| join kind=inner(
DataverseActivity
| where TimeGenerated > ago(detection_time)
| where Message == "RetrieveMultiple"
| extend numQueryCount = todouble(QueryResults)
| extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
| extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1)))
on UserId
Stage 11: summarize
summarize Entities, IPs, MostRecentQuery, Queries, QuerySizes, UserAgents by UserId, InstanceUrl, HistoricalBaseline, CurrentExportRate
Stage 12: extend
extend AccountName, CloudAppId, UPNSuffix
Stage 13: project
project AccountName, CloudAppId, CurrentExportRate, Entities, HistoricalBaseline, IPs, InstanceUrl, MostRecentQuery, Queries, QuerySizes, UPNSuffix, UserAgents, UserId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Message | eq |
| field:"Message" kind:eq value:"RetrieveMultiple" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
CloudAppId | project |
CurrentExportRate | project |
Entities | project |
HistoricalBaseline | project |
IPs | project |
InstanceUrl | project |
MostRecentQuery | project |
Queries | project |
QuerySizes | project |
UPNSuffix | project |
UserAgents | project |
UserId | project |
Encryption Password Deleted
#Detects when an encryption password is deleted in Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: a2c6fe48-0d3d-416b-991c-a750ec50d7eb
name: Encryption Password Deleted
description: Detects when an encryption password is deleted in 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 == 31800\n| extend Hint = extract(\"\
Hint=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ UserName = user,\n [\"Hint\"] = Hint,\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 == 31800
Stage 3: extend
| extend Hint = extract("Hint=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
["Hint"] = Hint,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"31800" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
Hint | project |
MessageDetails | project |
Severity | project |
UserName | project |
Firewall rule manipulation attempts stateful anomaly on database
#'This query detects batches of distinct SQL queries that execute (or attempt to) commands that could indicate potential security issues - such as attempts to manipulate firewall rules (e.g. for allowing malicious access to the database).'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment |
Rules Detecting the Same Action
These rules filter on the same operation.
- Affected rows stateful anomaly on database (Kusto)
- Credential errors stateful anomaly on database (Kusto)
- Drop attempts stateful anomaly on database (Kusto)
- Execution attempts stateful anomaly on database (Kusto)
- Firewall errors stateful anomaly on database (Kusto)
- OLE object manipulation attempts stateful anomaly on database (Kusto)
- Outgoing connection attempts stateful anomaly on database (Kusto)
- Response rows stateful anomaly on database (Kusto)
Rule Body
id: 05030ca6-ef66-42ca-b672-2e84d4aaf5d7
name: Firewall rule manipulation attempts stateful anomaly on database
description: |
'This query detects batches of distinct SQL queries that execute (or attempt to) commands that could indicate potential security issues - such as attempts to manipulate firewall rules (e.g. for allowing malicious access to the database).'
severity: Medium
requiredDataConnectors:
- connectorId: AzureSql
dataTypes:
- AzureDiagnostics
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
- Persistence
relevantTechniques:
- T1098
- T1562
tags:
- SQL
query: |
let monitoredStatementsThreshold = 1; // Minimal number of monitored statements in the slice to trigger an anomaly.
let trainingSlicesThreshold = 5; // The maximal amount of slices with monitored statements in the training window before anomaly detection is throttled.
let timeSliceSize = 1h; // The size of the single timeSlice for individual aggregation.
let detectionWindow = 1h; // The size of the recent detection window for detecting anomalies.
let trainingWindow = detectionWindow + 14d; // The size of the training window before the detection window for learning the normal state.
let hotwords = pack_array('sp_set_firewall_rule', 'sp_set_database_firewall_rule', 'sp_delete_database_firewall_rule', 'sp_delete_firewall_rule', 'sys.firewall_rules', 'sys.database_firewall_rules'); // List of monitored hot words.
let processedData = materialize (
AzureDiagnostics
| where TimeGenerated >= ago(trainingWindow)
| where Category == 'SQLSecurityAuditEvents' and action_id_s has_any ("RCM", "BCM") // Keep only SQL affected rows
| project TimeGenerated, PrincipalName = server_principal_name_s, ClientIp = client_ip_s, HostName = host_name_s, ResourceId,
ApplicationName = application_name_s, ActionName = action_name_s, Database = strcat(LogicalServerName_s, '/', database_name_s),
IsSuccess = succeeded_s, AffectedRows = affected_rows_d,
ResponseRows = response_rows_d, Statement = statement_s,
Error = case( additional_information_s has 'error_code', toint(extract("<error_code>([0-9.]+)", 1, additional_information_s))
, additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+)", 1, additional_information_s))
, 0),
State = case( additional_information_s has 'error_state', toint(extract("<error_state>([0-9.]+)", 1, additional_information_s))
, additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+), Level ([0-9.]+)", 2, additional_information_s))
, 0),
AdditionalInfo = additional_information_s, timeSlice = floor(TimeGenerated, timeSliceSize)
| extend hasHotword = iff(Statement has_any (hotwords), 1, 0)
| summarize countEvents = count(), countStatements = dcount(Statement)
, countStatementsWithHotwords = dcountif(Statement, hasHotword == 1)
, countFailedStatementsWithHotwords = dcountif(Statement, (hasHotword == 1) and (Error > 0))
, countSuccessfulStatementsWithHotwords = dcountif(Statement, ((hasHotword == 1)) and (Error == 0))
, anyMonitoredStatement = anyif(Statement, (hasHotword == 1))
, anySuccessfulMonitoredStatement = anyif(Statement, (hasHotword == 1) and (Error == 0))
, anyInfo = anyif(AdditionalInfo, hasHotword == 1)
, hotWord = anyif(extract(strcat_array(hotwords, '|'), 0, tolower(Statement)), hasHotword == 1)
by Database, ClientIp, ApplicationName, PrincipalName, timeSlice,HostName,ResourceId
| extend WindowType = case( timeSlice >= ago(detectionWindow), 'detection',
(ago(trainingWindow) <= timeSlice and timeSlice < ago(detectionWindow)), 'training', 'other')
| where WindowType in ('detection', 'training'));
let trainingSet =
processedData
| where WindowType == 'training'
| summarize countSlicesWithHotwords = dcountif(timeSlice, countStatementsWithHotwords >= monitoredStatementsThreshold)
by Database;
processedData
| where WindowType == 'detection'
| join kind = inner (trainingSet) on Database
| extend IsHotwordAnomalyOnStatement = iff(((countStatementsWithHotwords >= monitoredStatementsThreshold) and (countSlicesWithHotwords <= trainingSlicesThreshold)), true, false)
, anomalyScore = round(countStatementsWithHotwords/monitoredStatementsThreshold, 0)
| where IsHotwordAnomalyOnStatement == 'true'
| project TimeGenerated = timeSlice, Database, ClientIp, ApplicationName, PrincipalName, HostName, ResourceId, countEvents, countStatements, countStatementsWithHotwords, anyMonitoredStatement, anyInfo, anomalyScore, hotWord
| extend Name = tostring(split(PrincipalName,'@',0)[0]), UPNSuffix = tostring(split(PrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: ApplicationName
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: ResourceId
customDetails:
HotWords: hotWord
alertDetailsOverride:
alertDisplayNameFormat: 'Firewall rule manipulation attempts stateful anomaly on database {{Database}}'
alertDescriptionFormat: 'An anomaly was detected on database {{Database}} with {{countStatementsWithHotwords}} statements with monitored hot words in the last hour. Investigate the database activity for potential malicious attempts to manipulate firewall rules or perform other unauthorized actions.'
version: 1.1.2
kind: Scheduled
Stages and Predicates
Parameters
let monitoredStatementsThreshold = 1;
let trainingSlicesThreshold = 5;
let timeSliceSize = 1h;
let detectionWindow = 1h;
let trainingWindow = detectionWindow + 14d;
let processedData is inlined into the numbered stages below.
Let Binding: hotwords
let hotwords = pack_array('sp_set_firewall_rule', 'sp_set_database_firewall_rule', 'sp_delete_database_firewall_rule', 'sp_delete_firewall_rule', 'sys.firewall_rules', 'sys.database_firewall_rules');
Let Binding: trainingSet
let trainingSet = processedData
| where WindowType == 'training'
| summarize countSlicesWithHotwords = dcountif(timeSlice, countStatementsWithHotwords >= monitoredStatementsThreshold)
by Database;
Stages 1 to 8 define let processedData as the rule's main pipeline source. Stages 9 to 14 use it.
Stage 1: source
AzureDiagnostics
Stage 2: where
| where TimeGenerated >= ago(trainingWindow)
Stage 3: where
| where Category == 'SQLSecurityAuditEvents' and action_id_s has_any ("RCM", "BCM")
Stage 4: project
| project TimeGenerated, PrincipalName = server_principal_name_s, ClientIp = client_ip_s, HostName = host_name_s, ResourceId,
ApplicationName = application_name_s, ActionName = action_name_s, Database = strcat(LogicalServerName_s, '/', database_name_s),
IsSuccess = succeeded_s, AffectedRows = affected_rows_d,
ResponseRows = response_rows_d, Statement = statement_s,
Error = case( additional_information_s has 'error_code', toint(extract("<error_code>([0-9.]+)", 1, additional_information_s))
, additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+)", 1, additional_information_s))
, 0),
State = case( additional_information_s has 'error_state', toint(extract("<error_state>([0-9.]+)", 1, additional_information_s))
, additional_information_s has 'failure_reason', toint(extract("<failure_reason>Err ([0-9.]+), Level ([0-9.]+)", 2, additional_information_s))
, 0),
AdditionalInfo = additional_information_s, timeSlice = floor(TimeGenerated, timeSliceSize)
Stage 5: extend
| extend hasHotword = iff(Statement has_any (hotwords), 1, 0)
hasHotword =Statement has "hotwords"10Stage 6: summarize
| summarize countEvents = count(), countStatements = dcount(Statement)
, countStatementsWithHotwords = dcountif(Statement, hasHotword == 1)
, countFailedStatementsWithHotwords = dcountif(Statement, (hasHotword == 1) and (Error > 0))
, countSuccessfulStatementsWithHotwords = dcountif(Statement, ((hasHotword == 1)) and (Error == 0))
, anyMonitoredStatement = anyif(Statement, (hasHotword == 1))
, anySuccessfulMonitoredStatement = anyif(Statement, (hasHotword == 1) and (Error == 0))
, anyInfo = anyif(AdditionalInfo, hasHotword == 1)
, hotWord = anyif(extract(strcat_array(hotwords, '|'), 0, tolower(Statement)), hasHotword == 1)
by Database, ClientIp, ApplicationName, PrincipalName, timeSlice,HostName,ResourceId
Stage 7: extend
| extend WindowType = case( timeSlice >= ago(detectionWindow), 'detection',
(ago(trainingWindow) <= timeSlice and timeSlice < ago(detectionWindow)), 'training', 'other')
WindowType =/* macro: (timeSlice >= ago(detectionWindow)) */'detection'/* macro: (ago(trainingWindow) <= timeSlice) */'training''other'Stage 8: where
| where WindowType in ('detection', 'training')
Stage 9: where
processedData
| where WindowType == 'detection'
Stage 10: join
| join kind = inner (trainingSet) on Database
Stage 11: extend
extend IsHotwordAnomalyOnStatement, anomalyScore
IsHotwordAnomalyOnStatement =countStatementsWithHotwords >= monitoredStatementsThreshold and countSlicesWithHotwords <= trainingSlicesThresholdtruefalseStage 12: where
where IsHotwordAnomalyOnStatement == true
Stage 13: project
project ApplicationName, ClientIp, Database, HostName, PrincipalName, ResourceId, TimeGenerated, anomalyScore, anyInfo, anyMonitoredStatement, countEvents, countStatements, countStatementsWithHotwords, hotWord
Stage 14: extend
extend Name, UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"SQLSecurityAuditEvents" |
IsHotwordAnomalyOnStatement | eq |
| field:"IsHotwordAnomalyOnStatement" kind:eq value:"true" |
WindowType | eq |
| field:"WindowType" kind:eq |
WindowType | in |
| field:"WindowType" kind:in |
action_id_s | match |
| field:"action_id_s" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ApplicationName | project |
ClientIp | project |
Database | project |
HostName | project |
PrincipalName | project |
ResourceId | project |
TimeGenerated | project |
anomalyScore | project |
anyInfo | project |
anyMonitoredStatement | project |
countEvents | project |
countStatements | project |
countStatementsWithHotwords | project |
hotWord | project |
Name | extend |
UPNSuffix | extend |
HoneyLabs TI Map URL Entity to CommonSecurityLog
#'Identifies requests in CommonSecurityLog to malware loader or command-and-control URLs that HoneyLabs pulled out of payloads captured by its honeypot sensors. A hit usually means a host inside your estate reached out to attacker-controlled infrastructure, so treat it as post-compromise until proven otherwise. Indicator Confidence reflects how much evidence HoneyLabs holds (90 = 100+ observed attacks, 60 = a single sighting); raise the threshold in the query to trade coverage for precision.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: afe2a5ff-f4fe-4dee-8b6b-fd28d29d6738
name: HoneyLabs TI Map URL Entity to CommonSecurityLog
version: 1.0.0
kind: Scheduled
description: |
'Identifies requests in CommonSecurityLog to malware loader or command-and-control URLs that HoneyLabs pulled out of payloads captured by its honeypot sensors. A hit usually means a host inside your estate reached out to attacker-controlled infrastructure, so treat it as post-compromise until proven otherwise. Indicator Confidence reflects how much evidence HoneyLabs holds (90 = 100+ observed attacks, 60 = a single sighting); raise the threshold in the query to trade coverage for precision.'
severity: High
requiredDataConnectors:
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: CEF
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
- Execution
relevantTechniques:
- T1071
- T1105
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let HoneyLabs_URLs = ThreatIntelIndicators
| where TimeGenerated >= ago(ioc_lookBack)
| where IsActive == true and ValidUntil > now()
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where SourceSystem startswith 'HoneyLabs'
| where ObservableKey == 'url:value'
| extend TI_urlEntity = tostring(ObservableValue),
HoneyLabsConfidence = Confidence,
IndicatorDescription = tostring(Data.description);
HoneyLabs_URLs
| join kind=innerunique (
CommonSecurityLog
| where TimeGenerated >= ago(dt_lookBack)
| where isnotempty(RequestURL)
| extend CommonSecurityLog_TimeGenerated = TimeGenerated
) on $left.TI_urlEntity == $right.RequestURL
| where CommonSecurityLog_TimeGenerated < ValidUntil
| summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by Id, TI_urlEntity
| project CommonSecurityLog_TimeGenerated, IndicatorDescription, Id, ValidUntil,
HoneyLabsConfidence, TI_urlEntity, SourceIP, DestinationIP, DeviceName, DeviceAction,
SourceUserName, RequestClientApplication
| extend timestamp = CommonSecurityLog_TimeGenerated
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: TI_urlEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let HoneyLabs_URLs is inlined into the numbered stages below.
Stages 1 to 7 define let HoneyLabs_URLs as the rule's main pipeline source. Stages 8 to 12 use it.
Stage 1: source
ThreatIntelIndicators
Stage 2: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 3: where
| where IsActive == true and ValidUntil > now()
Stage 4: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
Stage 5: where
| where SourceSystem startswith 'HoneyLabs'
Stage 6: where
| where ObservableKey == 'url:value'
Stage 7: extend
| extend TI_urlEntity = tostring(ObservableValue),
HoneyLabsConfidence = Confidence,
IndicatorDescription = tostring(Data.description)
Stage 8: join
HoneyLabs_URLs
| join kind=innerunique (
CommonSecurityLog
| where TimeGenerated >= ago(dt_lookBack)
| where isnotempty(RequestURL)
| extend CommonSecurityLog_TimeGenerated = TimeGenerated
) on $left.TI_urlEntity == $right.RequestURL
Stage 9: where
where CommonSecurityLog_TimeGenerated < ValidUntil
Stage 10: summarize
summarize CommonSecurityLog_TimeGenerated by Id, TI_urlEntity
Stage 11: project
project CommonSecurityLog_TimeGenerated, DestinationIP, DeviceAction, DeviceName, HoneyLabsConfidence, Id, IndicatorDescription, RequestClientApplication, SourceIP, SourceUserName, TI_urlEntity, ValidUntil
Stage 12: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
ObservableKey | eq |
| field:"ObservableKey" kind:eq value:"url:value" |
RequestURL | is_not_null | field:"RequestURL" kind:is_not_null | |
SourceSystem | starts_with |
| field:"SourceSystem" kind:starts_with value:"HoneyLabs" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CommonSecurityLog_TimeGenerated | project |
DestinationIP | project |
DeviceAction | project |
DeviceName | project |
HoneyLabsConfidence | project |
Id | project |
IndicatorDescription | project |
RequestClientApplication | project |
SourceIP | project |
SourceUserName | project |
TI_urlEntity | project |
ValidUntil | project |
timestamp | extend |
Imperva - Request from unexpected countries
#'Detects request attempts from unexpected countries.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 58300723-22e0-4096-b33a-aa9b992c3564
name: Imperva - Request from unexpected countries
description: |
'Detects request attempts from unexpected countries.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: ImpervaWAFCloudAPI
dataTypes:
- ImpervaWAFCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let bl_country = dynamic(['CH', 'KR']);
ImpervaWAFCloud
| where Country in (bl_country)
| where DvcAction !startswith 'REQ_BLOCKED' or DvcAction !startswith 'REQ_BAD_'
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let bl_country = dynamic(['CH', 'KR']);
Stage 1: source
ImpervaWAFCloud
Stage 2: where
| where Country in (bl_country)
Stage 3: where
| where DvcAction !startswith 'REQ_BLOCKED' or DvcAction !startswith 'REQ_BAD_'
Stage 4: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Country | in |
| field:"Country" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
Mail redirect via ExO transport rule
#Identifies when Exchange Online transport rule configured to forward emails. This could be an adversary mailbox configured to collect mail from multiple user accounts.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection | |
| Exfiltration |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: 500415fb-bba7-4227-a08a-9857fb61b6a7
name: Mail redirect via ExO transport rule
description: |
'Identifies when Exchange Online transport rule configured to forward emails.
This could be an adversary mailbox configured to collect mail from multiple user accounts.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity (Exchange)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- Exfiltration
relevantTechniques:
- T1114
- T1020
query: |
OfficeActivity
| where OfficeWorkload == "Exchange"
| where Operation in~ ("New-TransportRule", "Set-TransportRule")
| mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)))
| extend RuleName = case(
Operation =~ "Set-TransportRule", OfficeObjectId,
Operation =~ "New-TransportRule", ParsedParameters.Name,
"Unknown")
| mv-expand ExpandedParameters = todynamic(Parameters)
| where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value)
| extend RedirectTo = ExpandedParameters.Value
| extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0]
| extend From = ParsedParameters.From
| project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserId
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 2.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
OfficeActivity
Stage 2: where
| where OfficeWorkload == "Exchange"
Stage 3: where
| where Operation in~ ("New-TransportRule", "Set-TransportRule")
Stage 4: kusto:mv-apply
| mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)))
Stage 5: extend
| extend RuleName = case(
Operation =~ "Set-TransportRule", OfficeObjectId,
Operation =~ "New-TransportRule", ParsedParameters.Name,
"Unknown")
RuleName =Operation =~ "Set-TransportRule"OfficeObjectIdOperation =~ "New-TransportRule"ParsedParameters.Name"Unknown"Stage 6: mv-expand
| mv-expand ExpandedParameters = todynamic(Parameters)
Stage 7: where
| where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value)
Stage 8: extend (3 consecutive steps)
| extend RedirectTo = ExpandedParameters.Value
| extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0]
| extend From = ParsedParameters.From
Stage 9: project
| project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters
Stage 10: extend
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
Stage 11: summarize aggregation inside the mv-apply branch
summarize
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ExpandedParameters.Name | in |
| field:"ExpandedParameters.Name" kind:in |
ExpandedParameters.Value | is_not_null | field:"ExpandedParameters.Value" kind:is_not_null | |
OfficeWorkload | eq |
| field:"m365::Workload" kind:eq value:"Exchange" |
Operation | in |
| field:"Operation" kind:in |
NRT GitHub Two Factor Auth Disable
#Two-factor authentication is a process where a user is prompted during the sign-in process for an additional form of identification, such as to enter a code on their cellphone or to provide a fingerprint scan. Two factor authentication reduces the risk of account takeover. Attacker will want to disable such security tools in order to go undetected.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GitHub | Audit log action org.disable_two_factor_requirement: 2FA requirement disabled for organization |
Rules Detecting the Same Action
These rules filter on the same operation.
- GitHub Enterprise Disable 2FA Requirement (Splunk)
- Github High Risk Configuration Disabled (Sigma)
- GitHub Org Authentication Method Changed (Panther)
- GitHub Organizations Disable 2FA Requirement (Splunk)
- GitHub Two Factor Auth Disable (Kusto)
- GitHub Two-Factor Authentication Requirement Disabled (YARA-L)
Rule Body
id: 594c653d-719a-4c23-b028-36e3413e632e
name: NRT GitHub Two Factor Auth Disable
description: |
'Two-factor authentication is a process where a user is prompted during the sign-in process for an additional form of identification, such as to enter a code on their cellphone or to provide a fingerprint scan. Two factor authentication reduces the risk of account takeover. Attacker will want to disable such security tools in order to go undetected. '
severity: Medium
requiredDataConnectors: []
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
GitHubAuditData
| where Action == "org.disable_two_factor_requirement"
| project TimeGenerated, Action, Actor, Country, Repository
| extend Name = iif(Actor contains "@", split(Actor, "@")[0], Actor)
| extend UPNSuffix = iif(Actor contains "@", split(Actor, "@")[1], "")
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Actor
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
version: 1.0.3
kind: NRT
Stages and Predicates
Stage 1: source
GitHubAuditData
Stage 2: where
| where Action == "org.disable_two_factor_requirement"
Stage 3: project
| project TimeGenerated, Action, Actor, Country, Repository
Stage 4: extend
| extend Name = iif(Actor contains "@", split(Actor, "@")[0], Actor)
Stage 5: extend
| extend UPNSuffix = iif(Actor contains "@", split(Actor, "@")[1], "")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"org.disable_two_factor_requirement" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
Actor | project |
Country | project |
Repository | project |
TimeGenerated | project |
Name | extend |
UPNSuffix | extend |
Pathlock TDnR - Pathlock Security Radar Internal Events
#Detects internal events generated by the Pathlock Security Radar platform, forwarded to Microsoft Sentinel. These events include platform health indicators, configuration changes, and self-monitoring alerts that may require SOC attention.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c34
name: Pathlock TDnR - Pathlock Security Radar Internal Events
kind: Scheduled
description: >-
Detects internal events generated by the Pathlock Security Radar platform, forwarded to Microsoft
Sentinel. These events include platform health indicators, configuration changes, and
self-monitoring alerts that may require SOC attention.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
Pathlock_TDnR_CL
| where DataSource == "INTERNAL"
| 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 == "INTERNAL"
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:"INTERNAL" |
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 |
Pathlock TDnR - SAP Read Access Logging Audit
#Detects SAP Read Access Logging (RAL) audit changelog events, forwarded by Pathlock Threat Detection and Response. RAL audit events capture changes to which sensitive data fields are being logged, and modifications may indicate attempts to disable read access monitoring for sensitive data.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c43
name: Pathlock TDnR - SAP Read Access Logging Audit
kind: Scheduled
description: >-
Detects SAP Read Access Logging (RAL) audit changelog events, forwarded by Pathlock Threat
Detection and Response. RAL audit events capture changes to which sensitive data fields are being
logged, and modifications may indicate attempts to disable read access monitoring for sensitive data.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
relevantTechniques:
- T1213
query: |
Pathlock_TDnR_CL
| where DataSource == "RAL_AUDIT"
| 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 == "RAL_AUDIT"
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:"RAL_AUDIT" |
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 |
Silverfort - NoPacBreach Incident
#'The NoPac vulnerability involves privilege escalation, allowing attackers to gain unauthorized access and potentially take control of an entire Active Directory domain'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Rule Body
id: bdfd2c45-10a0-44e7-a90a-ba7b6bdd9ff2
name: Silverfort - NoPacBreach Incident
description: |
'The NoPac vulnerability involves privilege escalation, allowing attackers to gain unauthorized access and potentially take control of an entire Active Directory domain'
severity: High
requiredDataConnectors:
- connectorId: SilverfortAma
dataTypes:
- CommonSecurityLog
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1068 #Exploitation for Privilege Escalation
- T1548 #Abuse Elevation Control Mechanism
query: |-
CommonSecurityLog
| where DeviceVendor has 'Silverfort'
| where DeviceProduct has 'Admin Console'
| where DeviceEventClassID == "NewIncident"
| where Message has "NoPacBreach"
| extend UserName = parse_json(replace('^""|""$', '', Message))['userName']
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: UserName
queryPeriod: 15m
queryFrequency: 15m
triggerOperator: gt
triggerThreshold: 0
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CommonSecurityLog
Stage 2: where
| where DeviceVendor has 'Silverfort'
Stage 3: where
| where DeviceProduct has 'Admin Console'
Stage 4: where
| where DeviceEventClassID == "NewIncident"
Stage 5: where
| where Message has "NoPacBreach"
Stage 6: extend
| extend UserName = parse_json(replace('^""|""$', '', Message))['userName']
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceEventClassID | eq |
| field:"DeviceEventClassID" kind:eq value:"NewIncident" |
DeviceProduct | match |
| field:"DeviceProduct" kind:match value:"Admin Console" |
DeviceVendor | match |
| field:"DeviceVendor" kind:match value:"Silverfort" |
Message | match |
| field:"Message" kind:match value:"NoPacBreach" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserName | extend |
Tape Erase Job Started
#Detects when tape erase operations start. This might indicate data destruction activity.
Rule Body
id: 06ba509c-606e-4967-baee-21815ae61f8e
name: Tape Erase Job Started
description: Detects when tape erase operations start. This might indicate data destruction
activity.
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 == 115\n| extend JobName = extract(\"\
JobName=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ [\"Job Name\"] = JobName,\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 == 115
Stage 3: extend
| extend JobName = extract("JobName=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["Job Name"] = JobName,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"115" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
Job Name | project |
MessageDetails | project |
Severity | project |
TI Map Domain entity to EmailUrlInfo
#Identifies a match in EmailUrlInfo table from any Domain IOC from TI.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
Rule Body
id: b56e2290-c65b-45a5-9636-3651e85bbe5d
name: TI Map Domain entity to EmailUrlInfo
description: |
'Identifies a match in EmailUrlInfo table from any Domain IOC from TI.'
severity: Medium
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- EmailUrlInfo
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
let dt_lookBack = 1h; // Define the lookback period for email data as 1 hour
let ioc_lookBack = 14d; // Define the lookback period for threat intelligence data as 14 days
let EmailUrlInfo_ = EmailUrlInfo| where isnotempty(Url) or isnotempty(UrlDomain) // Filter for non-empty URLs or URL domains
| where TimeGenerated >= ago(dt_lookBack) // Filter for records within the lookback period
| extend Url = tolower(Url), UrlDomain = tolower(UrlDomain) // Convert URLs and domains to lowercase
| extend EmailUrlInfo_TimeGenerated = TimeGenerated; // Create a new column for the time generated
let EmailEvents_ = EmailEvents
| where TimeGenerated >= ago(dt_lookBack); // Filter email events within the lookback period
let TI_Urls = ThreatIntelIndicators
| where TimeGenerated >= ago(ioc_lookBack) // Filter threat intelligence indicators within the lookback period
| summarize arg_max(TimeGenerated, *) by Id, ObservableValue // Get latest record per Id FIRST
| where IsActive == true and ValidUntil > now() // THEN filter for active indicators that haven't expired
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where isnotempty(IndicatorType) and IndicatorType == "url"
| extend Url = tolower(ObservableValue)
| where isnotempty(Url) // Filter for non-empty URLs
| join kind=innerunique (EmailUrlInfo_) on Url // Join with email URL info on URL
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract("ActivityGroup:([^,]+)", 1, tostring(parse_json(Data).labels))
| project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence, Url, UrlLocation, NetworkMessageId; // Select relevant columns
let TI_Domains = ThreatIntelIndicators
| where TimeGenerated >= ago(ioc_lookBack) // Filter threat intelligence indicators within the lookback period
| where isnotempty(ObservableValue) // Filter for non-empty domain names
| extend DomainName = tolower(ObservableValue) // Convert domain names to lowercase
| where ObservableKey == "domain-name:value"
| summarize arg_max(TimeGenerated, *) by Id, ObservableValue // Get latest record per Id FIRST
| where IsActive == true // THEN filter for active indicators
| extend TrafficLightProtocolLevel = AdditionalFields.TLPLevel
| extend IndicatorId = tostring(split(Id, "--")[2]);
let TI_Domains_UrlInfo = TI_Domains
| project-reorder *, IsActive, Tags, TrafficLightProtocolLevel, DomainName, Type
| join kind=innerunique (EmailUrlInfo_) on $left.DomainName == $right.UrlDomain // Join with email URL info on domain name
| extend ExpirationDateTime = ValidUntil
| extend Description = Data.description
| extend ThreatType = Data.type
| extend ConfidenceScore = Confidence
| extend ActivityGroupNames = replace_string(tostring(parse_json(tostring(Data.labels))[0]),"ActivityGroup:","")
| where ExpirationDateTime > now() // Filter for indicators that haven't expired
| project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, UrlDomain, UrlLocation, NetworkMessageId; // Select relevant columns
union TI_Urls, TI_Domains_UrlInfo // Combine URL and domain threat intelligence data
| extend timestamp = EmailUrlInfo_TimeGenerated // Add a timestamp column
| join kind=inner (EmailEvents_) on NetworkMessageId // Join with email events on network message ID
| where DeliveryAction !has "Blocked" // Filter out blocked delivery actions
| extend Name = tostring(split(RecipientEmailAddress, '@', 0)[0]), UPNSuffix = tostring(split(RecipientEmailAddress, '@', 1)[0]); // Extract name and UPN suffix from recipient email address"
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: RecipientEmailAddress
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.0.7
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Let Binding: EmailUrlInfo_
let EmailUrlInfo_ = EmailUrlInfo| where isnotempty(Url) or isnotempty(UrlDomain)
| where TimeGenerated >= ago(dt_lookBack)
| extend Url = tolower(Url), UrlDomain = tolower(UrlDomain)
| extend EmailUrlInfo_TimeGenerated = TimeGenerated;
Let Binding: EmailEvents_
let EmailEvents_ = EmailEvents
| where TimeGenerated >= ago(dt_lookBack);
Union (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: TI_Urls, TI_Domains_UrlInfo
Leg 1: TI_Urls
Leg 2: TI_Domains_UrlInfo
Applied to the Combined Result
| extend timestamp = EmailUrlInfo_TimeGenerated | join kind=inner (EmailEvents_) on NetworkMessageId | where DeliveryAction !has "Blocked" | extend Name = tostring(split(RecipientEmailAddress, '@', 0)[0]), UPNSuffix = tostring(split(RecipientEmailAddress, '@', 1)[0]);
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"url" |
IndicatorType | is_not_null | field:"IndicatorType" kind:is_not_null | |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
ObservableKey | eq |
| field:"ObservableKey" kind:eq value:"domain-name:value" |
ObservableValue | is_not_null | field:"ObservableValue" kind:is_not_null | |
Url | is_not_null | field:"Url" kind:is_not_null | |
UrlDomain | is_not_null | field:"UrlDomain" kind:is_not_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DeliveryAction | match | Blocked | excludes:DeliveryAction field:"DeliveryAction" value:"Blocked" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
ConfidenceScore | project |
Description | project |
EmailUrlInfo_TimeGenerated | project |
ExpirationDateTime | project |
IndicatorId | project |
NetworkMessageId | project |
ThreatType | project |
UrlDomain | project |
UrlLocation | project |
timestamp | extend |
Name | extend |
UPNSuffix | extend |
TIE LSASS Memory
#'Searches for OS Credentials dumping attacks.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 7851f57c-98b6-43c6-9747-9bb7cf11f21c
name: TIE LSASS Memory
description: |
'Searches for OS Credentials dumping attacks.'
severity: High
requiredDataConnectors:
- connectorId: TenableIE
dataTypes:
- Tenable_IE_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1003.001
query: |
// For the query to work properly, make sure you have imported the afad_parser.yaml parser into the workspace
// Retrieve the parser here: https://aka.ms/sentinel-TenableApp-afad-parser
// Then, create the Kusto Function with alias afad_parser
afad_parser
| where MessageType == 2 and Codename == "OS Credential Dumping: LSASS Memory"
| 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 == "OS Credential Dumping: LSASS Memory"
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:"OS Credential Dumping: LSASS Memory" |
MessageType | eq |
| field:"MessageType" kind:eq value:"2" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
Whisper Security - Tor Exit Node Communication
#Identifies network traffic communicating with known Tor exit nodes. While Tor usage may be legitimate in some environments, unexpected Tor communication can indicate data exfiltration, C2 tunneling, or policy violations.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: f5567c93-91de-577a-b35e-c2807715493d
name: Whisper Security - Tor Exit Node Communication
description: |
Identifies network traffic communicating with known Tor exit nodes. While Tor usage may be legitimate in some environments, unexpected Tor communication can indicate data exfiltration, C2 tunneling, or policy violations.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: WhisperSecurityConnector
dataTypes:
- WhisperThreatIntel_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1090
query: |
// MITRE ATT&CK: T1090.003 - Proxy: Multi-hop Proxy
// Tactic: Command and Control
// Detects communication with Tor exit nodes using Whisper threat intelligence
let timeRange = 1d;
let torIndicators = WhisperThreatIntel_CL
| where TimeGenerated > ago(timeRange)
| where isTor == true
| project indicator, threatScore, threatLevel, feedNames, lastSeen;
let CommonSecurityLogSafe = union isfuzzy=true CommonSecurityLog, (datatable(TimeGenerated:datetime, DestinationIP:string, SourceIP:string, DeviceProduct:string, DeviceAction:string, DeviceVendor:string, Activity:string)[]);
CommonSecurityLogSafe
| where TimeGenerated > ago(timeRange)
| where isnotempty(DestinationIP)
| join kind=inner (torIndicators) on $left.DestinationIP == $right.indicator
| extend SourceIP = SourceIP, DestinationIP = DestinationIP
| project TimeGenerated, SourceIP, DestinationIP, DeviceProduct, DeviceAction, DeviceVendor, Activity, threatScore, threatLevel, feedNames
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DestinationIP
customDetails:
ThreatScore: threatScore
ThreatLevel: threatLevel
FeedNames: feedNames
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let timeRange = 1d;
Let Binding: torIndicators
let torIndicators = WhisperThreatIntel_CL
| where TimeGenerated > ago(timeRange)
| where isTor == true
| project indicator, threatScore, threatLevel, feedNames, lastSeen;
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: CommonSecurityLog, datatable(TimeGenerated:datetime,
Leg 1: CommonSecurityLog
Leg 2: datatable(TimeGenerated:datetime,
(datatable(TimeGenerated:datetime, DestinationIP:string, SourceIP:string, DeviceProduct:string, DeviceAction:string, DeviceVendor:string, Activity:string)[])
Applied to the Combined Result
| where TimeGenerated > ago(timeRange) | where isnotempty(DestinationIP) | join kind=inner (torIndicators) on $left.DestinationIP == $right.indicator | extend SourceIP = SourceIP, DestinationIP = DestinationIP | project TimeGenerated, SourceIP, DestinationIP, DeviceProduct, DeviceAction, DeviceVendor, Activity, threatScore, threatLevel, feedNames
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DestinationIP | is_not_null | field:"DestinationIP" kind:is_not_null | |
isTor | eq |
| field:"isTor" kind:eq value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Activity | project |
DestinationIP | project |
DeviceAction | project |
DeviceProduct | project |
DeviceVendor | project |
SourceIP | project |
TimeGenerated | project |
feedNames | project |
threatLevel | project |
threatScore | project |
Zero Networks Segement - Machine Removed from protection
#'Detects when a machine is removed from protection.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: a4ce12ca-d01d-460a-b15e-6c74ef328b82
name: Zero Networks Segement - Machine Removed from protection
description: |
'Detects when a machine is removed from protection.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: ZeroNetworksSegmentAuditFunction
dataTypes:
- ZNSegmentAudit_CL
- connectorId: ZeroNetworksSegmentAuditNativePoller
dataTypes:
- ZNSegmentAuditNativePoller_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
ZNSegmentAudit
| where AuditTypeId == 4
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: PerformedByName
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DestinationEntityName
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
ZNSegmentAudit
Stage 2: where
| where AuditTypeId == 4
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AuditTypeId | eq |
| field:"AuditTypeId" kind:eq value:"4" |