Detection rules › Kusto
Kusto rules: 08
Awake Security - High Severity Matches By Device
#This query searches for devices with high severity event(s).
Rule Body
id: d5e012c2-29ba-4a02-a813-37b928aafe2d
name: Awake Security - High Severity Matches By Device
description: This query searches for devices with high severity event(s).
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
CommonSecurityLog
| where DeviceVendor == "Arista Networks" and DeviceProduct == "Awake Security" and toint(LogSeverity) > 6
| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), Models=make_set(Activity), ASPMatchURLs=make_set(DeviceCustomString2), SourceIPs=make_set(SourceIP),
DestinationIPs=make_set(DestinationIP), ModelMatchCount=sum(EventCount), MaxSeverity=max(todecimal (LogSeverity)) by SourceHostName
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: Awake Security - High Severity Matches On Device {{SourceHostName}}
alertDescriptionFormat: |
Device {{SourceHostName}} matched the following high-severity Awake model(s):
{{Models}}
The destination IPs associated with these matches were:
{{DestinationIPs}}
alertTacticsColumnName: null
alertSeverityColumnName: MaxSeverity
customDetails:
Matched_Models: Models
Matches_ASP_URLs: ASPMatchURLs
Device: SourceHostName
Matches_Count: ModelMatchCount
Matches_Max_Severity: MaxSeverity
Matches_Dest_IPs: DestinationIPs
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SourceHostName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIPs
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: true
lookbackDuration: 3d
matchingMethod: Selected
groupByEntities:
- Host
groupByAlertDetails: []
groupByCustomDetails:
- Device
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
CommonSecurityLog
Stage 2: where
| where DeviceVendor == "Arista Networks" and DeviceProduct == "Awake Security" and toint(LogSeverity) > 6
Stage 3: summarize
| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), Models=make_set(Activity), ASPMatchURLs=make_set(DeviceCustomString2), SourceIPs=make_set(SourceIP),
DestinationIPs=make_set(DestinationIP), ModelMatchCount=sum(EventCount), MaxSeverity=max(todecimal (LogSeverity)) by SourceHostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceProduct | eq |
| field:"DeviceProduct" kind:eq value:"Awake Security" |
DeviceVendor | eq |
| field:"DeviceVendor" kind:eq value:"Arista Networks" |
LogSeverity | gt |
| field:"LogSeverity" kind:gt value:"6" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ASPMatchURLs | summarize |
DestinationIPs | summarize |
EndTime | summarize |
MaxSeverity | summarize |
ModelMatchCount | summarize |
Models | summarize |
SourceHostName | summarize |
SourceIPs | summarize |
StartTime | summarize |
AWSCloudTrail - S3 object publicly exposed
#Detects S3 object ACL changes that expose objects to public or broadly authenticated users. This can result in unintended data disclosure from object-level permissions.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: 09f2a28b-3286-4268-9e2f-33805f104e5d
name: AWSCloudTrail - S3 object publicly exposed
description: |
Detects S3 object ACL changes that expose objects to public or broadly authenticated users.
This can result in unintended data disclosure from object-level permissions.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1537
query: |
AWSCloudTrail
| where EventName == "PutObjectAcl" and isempty(ErrorCode) and isempty(ErrorMessage)
| extend Grant = parse_json(tostring((parse_json(RequestParameters).AccessControlPolicy))).AccessControlList.Grant
| mvexpand Grant
| extend cannedacl = parse_json(tostring((parse_json(RequestParameters))))
| extend URI = parse_json(Grant).Grantee.URI, type = parse_json(Grant).Grantee.["xsi:type"], xamzacl = parse_json(cannedacl).["x-amz-acl"]
| where (type == "Group" and (URI endswith "AllUsers" or URI endswith "AuthenticatedUsers"))
or xamzacl in ("authenticated-read","public-read","public-read-write")
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- identifier: CloudAppAccountId
columnName: RecipientAccountId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIpAddress
customDetails:
EventName: EventName
URI: URI
xamzacl: xamzacl
RecipientAccountId: RecipientAccountId
alertDetailsOverride:
alertDisplayNameFormat: 'AWS S3 object public ACL exposure by {{AccountName}}'
alertDescriptionFormat: 'Detected {{EventName}} event from {{SourceIpAddress}} applying public object ACL settings in account {{RecipientAccountId}}.'
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName == "PutObjectAcl" and isempty(ErrorCode) and isempty(ErrorMessage)
Stage 3: extend
| extend Grant = parse_json(tostring((parse_json(RequestParameters).AccessControlPolicy))).AccessControlList.Grant
Stage 4: mv-expand
| mvexpand Grant
Stage 5: extend
| extend cannedacl = parse_json(tostring((parse_json(RequestParameters))))
Stage 6: extend
| extend URI = parse_json(Grant).Grantee.URI, type = parse_json(Grant).Grantee.["xsi:type"], xamzacl = parse_json(cannedacl).["x-amz-acl"]
Stage 7: where
| where (type == "Group" and (URI endswith "AllUsers" or URI endswith "AuthenticatedUsers"))
or xamzacl in ("authenticated-read","public-read","public-read-write")
Stage 8: extend (4 consecutive steps)
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ErrorCode | is_null | field:"aws::errorCode" kind:is_null | |
ErrorMessage | is_null | field:"aws::errorMessage" kind:is_null | |
EventName | eq |
| field:"aws::eventName" kind:eq value:"PutObjectAcl" |
URI | ends_with |
| field:"URI" kind:ends_with |
type | eq |
| field:"type" kind:eq value:"Group" |
xamzacl | in |
| field:"xamzacl" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Grant | extend |
cannedacl | extend |
URI | extend |
type | extend |
xamzacl | extend |
UserIdentityArn | extend |
UserName | extend |
AccountName | extend |
AccountUPNSuffix | extend |
BloodHound Attack Path Finding - Non-Tier Zero Principal Trusted for Unconstrained Delegation
#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 - Non-Tier Zero Principal Trusted for Unconstrained Delegation
Rule Body
id: bc26d218-89a4-40ba-88b6-76e5141b5731
name: BloodHound Attack Path Finding - Non-Tier Zero Principal Trusted for Unconstrained
Delegation
description: BloodHound Attack Path Finding - Non-Tier Zero Principal Trusted for
Unconstrained Delegation
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 == "T0CoerceToTGT"
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 == "T0CoerceToTGT"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0CoerceToTGT" |
CiscoISE - Command executed with the highest privileges by new user
#'Detects command execution with PrivilegeLevel - 15 by user for wich there was no such activity detected earlier.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation |
Rule Body
id: e71890a2-5f61-4790-b1ed-cf1d92d3e398
name: CiscoISE - Command executed with the highest privileges by new user
description: |
'Detects command execution with PrivilegeLevel - 15 by user for wich there was no such activity detected earlier.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Persistence
- PrivilegeEscalation
- DefenseEvasion
- Execution
relevantTechniques:
- T1133
- T1548
- T1059
query: |
let lbperiod = 14d;
let lbtime = 15m;
let knownPrivUsers =
CiscoISEEvent
| where TimeGenerated between (ago(lbperiod) .. ago(lbtime))
| where PrivilegeLevel == '15'
| summarize makelist(DstUserName)
;
CiscoISEEvent
| where TimeGenerated > ago(lbtime)
| where PrivilegeLevel == '15'
| where DstUserName !in (knownPrivUsers)
| project TimeGenerated, SrcIpAddr, DstIpAddr, DstUserName, CmdSet
| extend AccountCustomEntity = DstUserName
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let lbperiod = 14d;
let lbtime = 15m;
Let Binding: knownPrivUsers
let knownPrivUsers = CiscoISEEvent
| where TimeGenerated between (ago(lbperiod) .. ago(lbtime))
| where PrivilegeLevel == '15'
| summarize makelist(DstUserName);
Stage 1: source
CiscoISEEvent
Stage 2: where
| where TimeGenerated > ago(lbtime)
Stage 3: where
| where PrivilegeLevel == '15'
Stage 4: where
| where DstUserName !in (knownPrivUsers)
Stage 5: project
| project TimeGenerated, SrcIpAddr, DstIpAddr, DstUserName, CmdSet
Stage 6: extend
| extend AccountCustomEntity = DstUserName
Stage 7: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PrivilegeLevel | eq |
| field:"PrivilegeLevel" kind:eq value:"15" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DstUserName | eq | knownPrivUsers | excludes:DstUserName field:"DstUserName" value:"knownPrivUsers" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CmdSet | project |
DstIpAddr | project |
DstUserName | project |
SrcIpAddr | project |
TimeGenerated | project |
AccountCustomEntity | extend |
IPCustomEntity | extend |
Cloud Gateway Pool Deleted
#Detects when a cloud gateway pool is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: 73e1d89b-79a6-4ab0-b3bb-5e564fcf10f1
name: Cloud Gateway Pool Deleted
description: Detects when a cloud gateway pool 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 == 24143\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 == 24143
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:"24143" |
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 |
Cyble Vision Alerts Bitbucket
#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 exposed secrets in Bitbucket repositories using the Alerts_bit_bucket parser. Creates one incident per matched secret. Includes decoder, detector, commit, file, and repository context for SOC triage.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | |
| Discovery | |
| Exfiltration |
Rule Body
id: f3c25011-4509-41c8-be27-35d891531c39
name: Cyble Vision Alerts Bitbucket
description: |
'Detects exposed secrets in Bitbucket repositories using the Alerts_bit_bucket parser. Creates one incident per matched secret. Includes decoder, detector, commit, file, and repository context for SOC triage.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- CredentialAccess
- Exfiltration
- Discovery
relevantTechniques:
- T1552
- T1537
- T1083
query: |
Alerts_bit_bucket
| where Service == "bit_bucket"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: Secret Exposure in Bitbucket {{BB_File}} ({{BB_DetectorName}})
alertDescriptionFormat: |
A sensitive secret was detected in Bitbucket repository {{BB_Repository}}. File {{BB_File}}:{{BB_Line}}. Investigator should verify exposure, rotate credentials and remediate impacted systems.
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
BB_DetectorName: BB_DetectorName
BB_Raw: BB_Raw
BB_File: BB_File
BB_Line: BB_Line
BB_Commit: BB_Commit
BB_Email: BB_Email
BB_Repository: BB_Repository
BB_Link: BB_Link
BB_Verified: BB_Verified
BB_RotationGuide: BB_RotationGuide
entityMappings:
- entityType: Url
fieldMappings:
- identifier: Url
columnName: BB_Repository
- entityType: Url
fieldMappings:
- identifier: Url
columnName: BB_Link
- entityType: File
fieldMappings:
- identifier: Name
columnName: BB_File
- identifier: Directory
columnName: KeywordName
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_bit_bucket
Stage 2: where
| where Service == "bit_bucket"
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:"bit_bucket" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
Execution 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 execute shell commands (e.g. for running illegitimate code).'
MITRE ATT&CK Coverage
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)
- Firewall errors stateful anomaly on database (Kusto)
- Firewall rule manipulation attempts 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: 3367fd5e-44b3-4746-a9a5-dc15c8202490
name: Execution 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 execute shell commands (e.g. for running illegitimate code).'
severity: Medium
requiredDataConnectors:
- connectorId: AzureSql
dataTypes:
- AzureDiagnostics
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Execution
relevantTechniques:
- T1059
- T1059.001
- T1059.003
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('xp_cmdshell', 'ps.exe', 'powershell', 'cmd.exe', 'msiexec', '<script>'); // 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: 'Execution attempts stateful anomaly on database {{Database}}'
alertDescriptionFormat: 'An anomaly was detected on database {{Database}} with {{countStatementsWithHotwords}} statements with monitored hot words (xp_cmdshell, powershell, cmd.exe etc.) in the last hour. Investigate the database activity for potential malicious attempts to execute shell commands or run illegitimate code.'
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 hotwords = pack_array('xp_cmdshell', 'ps.exe', 'powershell', 'cmd.exe', 'msiexec', '<script>');
let processedData is inlined into the numbered stages below.
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 |
GCP Audit Logs - Storage Bucket Made Public
#Detects when a Google Cloud Storage bucket is made publicly accessible by granting permissions to allUsers or allAuthenticatedUsers. Making buckets public can expose sensitive data to unauthorized access and may indicate a misconfiguration or malicious activity. Adversaries may make buckets public to exfiltrate data or as part of a data exposure attack. This rule monitors setIamPermissions operations that add public access roles to storage buckets.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Collection | |
| Exfiltration |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GCP | storage.setIamPermissions: Set IAM permissions on bucket |
Rules Detecting the Same Action
These rules filter on the same operation.
- GCP Storage Bucket Opened To Public (YARA-L)
Rule Body
id: 3a8d7f9e-4b2c-4e5d-8c6b-9f1a3d5e8c7b
name: GCP Audit Logs - Storage Bucket Made Public
description: |
'Detects when a Google Cloud Storage bucket is made publicly accessible by granting permissions to allUsers or allAuthenticatedUsers.
Making buckets public can expose sensitive data to unauthorized access and may indicate a misconfiguration or malicious activity.
Adversaries may make buckets public to exfiltrate data or as part of a data exposure attack.
This rule monitors setIamPermissions operations that add public access roles to storage buckets.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: GCPAuditLogsDefinition
dataTypes:
- GCPAuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- InitialAccess
- Exfiltration
relevantTechniques:
- T1530
- T1078.004
- T1567.002
tags:
- GCP
- Storage
- Data Exposure
- Cloud Security
query: |
GCPAuditLogs
| where ServiceName == "storage.googleapis.com"
| where MethodName == "storage.setIamPermissions"
| where GCPResourceType == "gcs_bucket"
| extend
ServiceDataJson = parse_json(ServiceData),
RequestMetadataJson = parse_json(RequestMetadata),
AuthInfoJson = parse_json(AuthenticationInfo),
AuthzInfoJson = parse_json(AuthorizationInfo)
| extend PolicyDelta = ServiceDataJson.policyDelta.bindingDeltas
| mv-expand PolicyDelta
| extend
Action = tostring(PolicyDelta.action),
Member = tostring(PolicyDelta.member),
Role = tostring(PolicyDelta.role)
| where Action == "ADD"
| where Member in~ ("allUsers", "allAuthenticatedUsers")
| extend
BucketName = extract(@"buckets/([^/]+)", 1, GCPResourceName),
CallerIpAddress = tostring(RequestMetadataJson.callerIp),
UserAgent = tostring(RequestMetadataJson.callerSuppliedUserAgent),
AuthEmail = tostring(AuthInfoJson.principalEmail),
Permission = tostring(AuthzInfoJson[0].permission),
PermissionGranted = tostring(AuthzInfoJson[0].granted)
| extend
PublicAccessType = case(
Member =~ "allUsers", "Public to Everyone",
Member =~ "allAuthenticatedUsers", "Public to All Authenticated Users",
"Unknown"),
AccountName = tostring(split(PrincipalEmail, "@")[0]),
AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])
| project TimeGenerated,
PrincipalEmail,
AuthEmail,
ProjectId,
BucketName,
ResourceName = GCPResourceName,
PublicAccessType,
Member,
Role,
CallerIpAddress,
UserAgent,
MethodName,
ServiceName,
Severity,
Permission,
PermissionGranted,
LogName,
InsertId,
AccountName,
AccountUPNSuffix
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: PrincipalEmail
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CallerIpAddress
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: ProjectId
- identifier: InstanceName
columnName: ResourceName
customDetails:
ProjectId: ProjectId
BucketName: BucketName
ResourceName: ResourceName
PublicAccessType: PublicAccessType
RoleGranted: Role
UserAgent: UserAgent
Permission: Permission
alertDetailsOverride:
alertDisplayNameFormat: "GCP Storage Bucket {{BucketName}} Made Public by {{PrincipalEmail}}"
alertDescriptionFormat: |-
User {{PrincipalEmail}} made storage bucket {{BucketName}} publicly accessible in project {{ProjectId}}.
This may expose sensitive data to unauthorized access. Investigate immediately to determine if this action was authorized and assess potential data exposure.
Review bucket contents and access logs for any unauthorized access attempts.
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GCPAuditLogs
Stage 2: where
| where ServiceName == "storage.googleapis.com"
Stage 3: where
| where MethodName == "storage.setIamPermissions"
Stage 4: where
| where GCPResourceType == "gcs_bucket"
Stage 5: extend
| extend
ServiceDataJson = parse_json(ServiceData),
RequestMetadataJson = parse_json(RequestMetadata),
AuthInfoJson = parse_json(AuthenticationInfo),
AuthzInfoJson = parse_json(AuthorizationInfo)
Stage 6: extend
| extend PolicyDelta = ServiceDataJson.policyDelta.bindingDeltas
Stage 7: mv-expand
| mv-expand PolicyDelta
Stage 8: extend
| extend
Action = tostring(PolicyDelta.action),
Member = tostring(PolicyDelta.member),
Role = tostring(PolicyDelta.role)
Stage 9: where
| where Action == "ADD"
Stage 10: where
| where Member in~ ("allUsers", "allAuthenticatedUsers")
Stage 11: extend
| extend
BucketName = extract(@"buckets/([^/]+)", 1, GCPResourceName),
CallerIpAddress = tostring(RequestMetadataJson.callerIp),
UserAgent = tostring(RequestMetadataJson.callerSuppliedUserAgent),
AuthEmail = tostring(AuthInfoJson.principalEmail),
Permission = tostring(AuthzInfoJson[0].permission),
PermissionGranted = tostring(AuthzInfoJson[0].granted)
Stage 12: extend
| extend
PublicAccessType = case(
Member =~ "allUsers", "Public to Everyone",
Member =~ "allAuthenticatedUsers", "Public to All Authenticated Users",
"Unknown"),
AccountName = tostring(split(PrincipalEmail, "@")[0]),
AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])
PublicAccessType =Member =~ "allUsers""Public to Everyone"Member =~ "allAuthenticatedUsers""Public to All Authenticated Users""Unknown"Stage 13: project
| project TimeGenerated,
PrincipalEmail,
AuthEmail,
ProjectId,
BucketName,
ResourceName = GCPResourceName,
PublicAccessType,
Member,
Role,
CallerIpAddress,
UserAgent,
MethodName,
ServiceName,
Severity,
Permission,
PermissionGranted,
LogName,
InsertId,
AccountName,
AccountUPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"ADD" |
GCPResourceType | eq |
| field:"gcp::resource_type" kind:eq value:"gcs_bucket" |
Member | in |
| field:"Member" kind:in |
MethodName | eq |
| field:"gcp::method_name" kind:eq value:"storage.setIamPermissions" |
ServiceName | eq |
| field:"ServiceName" kind:eq value:"storage.googleapis.com" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
AccountUPNSuffix | project |
AuthEmail | project |
BucketName | project |
CallerIpAddress | project |
InsertId | project |
LogName | project |
Member | project |
MethodName | project |
Permission | project |
PermissionGranted | project |
PrincipalEmail | project |
ProjectId | project |
PublicAccessType | project |
ResourceName | project |
Role | project |
ServiceName | project |
Severity | project |
TimeGenerated | project |
UserAgent | project |
Global VM Exclusions Deleted
#Detects when a VM is removed from global exclusions in Veeam Backup & Replication. This might indicate unauthorized changes.
Rule Body
id: 141d7e3c-a72b-49f7-9e64-57f60c2fa927
name: Global VM Exclusions Deleted
description: Detects when a VM is removed from global exclusions in Veeam Backup &
Replication. This might indicate unauthorized changes.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 40500\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n UserName = 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 == 40500
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = 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:"40500" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
GSA - TI URL Entity
#This query identifies URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in GSA NetworkAccessTraffic.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 347c6cb3-33d2-4753-b7f6-eab946a8cd51
name: GSA - TI URL Entity
description: |
This query identifies URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in GSA NetworkAccessTraffic.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- NetworkAccessTrafficLogs
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let GSANetworkAccessTraffic_ = NetworkAccessTraffic
| where TimeGenerated >= ago(dt_lookBack)
| where TrafficType == "internet"
| extend DestinationUrlToLower = tolower(DestinationUrl)
| project-rename GSANetworkAccessTraffic_TimeGenerated = TimeGenerated;
let GSANetworkAccessTrafficUrls =
GSANetworkAccessTraffic_
| where isnotempty(DestinationUrl)
| extend Url = tolower(DestinationUrl)
| distinct Url;
ThreatIntelIndicators
| extend IndicatorType = replace(@'[\[\]"]', "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "url"
| extend Url = ObservableValue
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| where TimeGenerated >= ago(ioc_lookBack)
| extend Url = tolower(Url)
| where Url in (GSANetworkAccessTrafficUrls)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| extend Description = tostring(parse_json(Data).description)
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
| project-reorder *, Tags, TrafficLightProtocolLevel, Url, Type
| join kind=innerunique (GSANetworkAccessTraffic_) on $left.Url == $right.DestinationUrlToLower
| where isempty(ValidUntil) or GSANetworkAccessTraffic_TimeGenerated < ValidUntil
| extend GSAThreatType = ThreatType
| summarize GSANetworkAccessTraffic_TimeGenerated = arg_max(GSANetworkAccessTraffic_TimeGenerated, *) by Id, DestinationUrl
| project-reorder *,
Tags,
Confidence,
TrafficLightProtocolLevel,
Type,
UserPrincipalName,
UserId,
DeviceId,
InitiatingProcessName,
GSAThreatType,
DestinationIp,
DestinationFqdn,
DestinationUrl,
DestinationPort,
SourceIp,
SourcePort,
HttpUserAgent
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: UserPrincipalName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIp
- entityType: URL
fieldMappings:
- identifier: Url
columnName: DestinationUrl
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: InitiatingProcessName
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Let Binding: GSANetworkAccessTraffic_
let GSANetworkAccessTraffic_ = NetworkAccessTraffic
| where TimeGenerated >= ago(dt_lookBack)
| where TrafficType == "internet"
| extend DestinationUrlToLower = tolower(DestinationUrl)
| project-rename GSANetworkAccessTraffic_TimeGenerated = TimeGenerated;
Let Binding: GSANetworkAccessTrafficUrls
let GSANetworkAccessTrafficUrls = GSANetworkAccessTraffic_
| where isnotempty(DestinationUrl)
| extend Url = tolower(DestinationUrl)
| distinct Url;
Stage 1: source
ThreatIntelIndicators
Stage 2: extend
| extend IndicatorType = replace(@'[\[\]"]', "", tostring(split(ObservableKey, ":", 0)))
Stage 3: where
| where IndicatorType == "url"
Stage 4: extend
| extend Url = ObservableValue
Stage 5: extend
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
Stage 6: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 7: extend
| extend Url = tolower(Url)
Stage 8: where
| where Url in (GSANetworkAccessTrafficUrls)
Stage 9: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
Stage 10: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 11: extend
| extend Description = tostring(parse_json(Data).description)
Stage 12: where
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
Stage 13: project-reorder
| project-reorder *, Tags, TrafficLightProtocolLevel, Url, Type
Stage 14: join
| join kind=innerunique (GSANetworkAccessTraffic_) on $left.Url == $right.DestinationUrlToLower
Stage 15: where
where (GSANetworkAccessTraffic_TimeGenerated < ValidUntil or isempty(ValidUntil))
Stage 16: extend
extend GSAThreatType
Stage 17: summarize
summarize GSANetworkAccessTraffic_TimeGenerated by Id, DestinationUrl
Stage 18: project-reorder
project-reorder
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"url" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
TrafficType | eq |
| field:"TrafficType" kind:eq value:"internet" |
Url | in |
| field:"Url" kind:in value:"GSANetworkAccessTrafficUrls" |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Description | contains | State: falsepos; | excludes:Description field:"Description" value:"State: falsepos;" |
Description | contains | State: inactive; | excludes:Description field:"Description" value:"State: inactive;" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DestinationUrl | summarize |
GSANetworkAccessTraffic_TimeGenerated | summarize |
Id | summarize |
Guardian- Privacy Protection PII Policy Violation Detection
#'This alert creates an incident when Privacy Protection PII Policy Violation detected from the Guardian'
Rule Body
id: d5065600-617b-4a16-b58a-4a46da9c1afd
name: Guardian- Privacy Protection PII Policy Violation Detection
description: |
'This alert creates an incident when Privacy Protection PII Policy Violation detected from the Guardian'
severity: High
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'PII Detection'
| where Severity =~ 'High'
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- Privacy Protection PII Policy Violation detection
alertDescriptionFormat: |
This query detects Privacy Protection PII 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 =~ 'PII Detection'
Stage 3: where
| where Severity =~ 'High'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"PII Detection" |
Severity | eq |
| field:"Severity" kind:eq value:"High" |
High Number of Urgent Vulnerabilities Detected
#'This Creates an incident when a host has a high number of Urgent, severity 5, vulnerabilities detected.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 3edb7215-250b-40c0-8b46-79093949242d
name: High Number of Urgent Vulnerabilities Detected
description: |
'This Creates an incident when a host has a high number of Urgent, severity 5, vulnerabilities detected.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: QualysVMLogsCCPDefinition
dataTypes:
- QualysHostDetection
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
query: |
let threshold = 10;
QualysHostDetection
| where Severity == "5"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by NetBios, IPAddress
| where count_ >= threshold
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: NetBios
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 10;
Stage 1: source
QualysHostDetection
Stage 2: where
| where Severity == "5"
Stage 3: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by NetBios, IPAddress
Stage 4: where
| where count_ >= threshold
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Severity | eq |
| field:"Severity" kind:eq value:"5" |
count_ | ge |
| field:"count_" kind:ge value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTime | summarize |
IPAddress | summarize |
NetBios | summarize |
StartTime | summarize |
Hunt for compromised browser extensions
#This hunting rule can be used to find devices using known compromised browser extensions. It is created based on the threat researched linked in the reference section.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceInfo action any: Device information |
Rule Body
let extensions = datatable (Id:string, VulnVersion:string) [
"nnpnnpemnckcfdebeekibpiijlicmpom", "2.0.1",
"kkodiihpgodmdankclfibbiphjkfdenh", "1.16.2",
"oaikpkmjciadfpddlpjjdapglcihgdle", "1.0.12",
"dpggmcodlahmljkhlmpgpdcffdaoccni", "1.1.1",
"acmfnomgphggonodopogfbmkneepfgnh", "4.00",
"mnhffkhmpnefgklngfmlndmkimimbphc", "4.40",
"cedgndijpacnfbdggppddacngjfdkaca", "0.0.11",
"bbdnohkpnbkdkmnkddobeafboooinpla", "1.0.1",
"egmennebgadmncfjafcemlecimkepcle", "2.2.7",
"bibjgkidgpfbblifamdlkdlhgihmfohh", "0.1.3",
"befflofjcniongenjmbkgkoljhgliihe", "2.13.0",
"pkgciiiancapdlpcbppfkmeaieppikkk", "1.3.7",
"llimhhconnjiflfimocjggfjdlmlhblm", "1.5.7",
"oeiomhmbaapihbilkfkhmlajkeegnjhe", "3.18.0",
"pajkjnmeojmbapicmbpliphjmcekeaac", "24.10.4",
"ndlbedplllcgconngcnfmkadhokfaaln", "2.22.6",
"epdjhgbipjpbbhoccdeipghoihibnfja", "1.4",
"cplhlgabfijoiabgkigdafklbhhdkahj", "1.0.161",
"jiofmdifioeejeilfkpegipdjiopiekl", "1.1.61",
"hihblcmlaaademjlakdpicchbjnnnkbo", "3.0.2",
"llimhhconnjiflfimocjggfjdlmlhblm", "1.5.7",
"ekpkdmohpdnebfedjjfklhpefgpgaaji", "1.3",
"epikoohpebngmakjinphfiagogjcnddm", "2.7.3",
"miglaibdlgminlepgeifekifakochlka", "1.4.5",
"eanofdhdfbcalhflpbdipkjjkoimeeod", "1.4.9",
"ogbhbgkiojdollpjbhbamafmedkeockb", "1.8.1",
"bgejafhieobnfpjlpcjjggoboebonfcg", "1.1.1",
"igbodamhgjohafcenbcljfegbipdfjpk", "2.3",
"mbindhfolmpijhodmgkloeeppmkhpmhc", "1.44",
"hodiladlefdpcbemnbbcpclbmknkiaem", "3.1.3",
"lbneaaedflankmgmfbmaplggbmjjmbae", "1.3.8",
"eaijffijbobmnonfhilihbejadplhddo", "2.4",
"hmiaoahjllhfgebflooeeefeiafpkfde", "1.0.0"
];
DeviceTvmBrowserExtensions
// Find devices using vulnerable extensions
| join kind=inner extensions on $left.ExtensionId == $right.Id
| extend IntVersion = parse_version(ExtensionVersion), IntVulnVursion = parse_version(VulnVersion)
| where IntVersion <= IntVulnVursion and IsActivated == "true"
// Join for more device info
| join kind=inner (
DeviceInfo
| where Timestamp > ago(7d)
) on DeviceId
| distinct DeviceName, DeviceId, BrowserName, ExtensionName, ExtensionDescription, ExtensionVersion, ExtensionRisk, VulnVersion
Stages and Predicates
Let Binding: extensions
let extensions = datatable (Id:string, VulnVersion:string) [
"nnpnnpemnckcfdebeekibpiijlicmpom", "2.0.1",
"kkodiihpgodmdankclfibbiphjkfdenh", "1.16.2",
"oaikpkmjciadfpddlpjjdapglcihgdle", "1.0.12",
"dpggmcodlahmljkhlmpgpdcffdaoccni", "1.1.1",
"acmfnomgphggonodopogfbmkneepfgnh", "4.00",
"mnhffkhmpnefgklngfmlndmkimimbphc", "4.40",
"cedgndijpacnfbdggppddacngjfdkaca", "0.0.11",
"bbdnohkpnbkdkmnkddobeafboooinpla", "1.0.1",
"egmennebgadmncfjafcemlecimkepcle", "2.2.7",
"bibjgkidgpfbblifamdlkdlhgihmfohh", "0.1.3",
"befflofjcniongenjmbkgkoljhgliihe", "2.13.0",
"pkgciiiancapdlpcbppfkmeaieppikkk", "1.3.7",
"llimhhconnjiflfimocjggfjdlmlhblm", "1.5.7",
"oeiomhmbaapihbilkfkhmlajkeegnjhe", "3.18.0",
"pajkjnmeojmbapicmbpliphjmcekeaac", "24.10.4",
"ndlbedplllcgconngcnfmkadhokfaaln", "2.22.6",
"epdjhgbipjpbbhoccdeipghoihibnfja", "1.4",
"cplhlgabfijoiabgkigdafklbhhdkahj", "1.0.161",
"jiofmdifioeejeilfkpegipdjiopiekl", "1.1.61",
"hihblcmlaaademjlakdpicchbjnnnkbo", "3.0.2",
"llimhhconnjiflfimocjggfjdlmlhblm", "1.5.7",
"ekpkdmohpdnebfedjjfklhpefgpgaaji", "1.3",
"epikoohpebngmakjinphfiagogjcnddm", "2.7.3",
"miglaibdlgminlepgeifekifakochlka", "1.4.5",
"eanofdhdfbcalhflpbdipkjjkoimeeod", "1.4.9",
"ogbhbgkiojdollpjbhbamafmedkeockb", "1.8.1",
"bgejafhieobnfpjlpcjjggoboebonfcg", "1.1.1",
"igbodamhgjohafcenbcljfegbipdfjpk", "2.3",
"mbindhfolmpijhodmgkloeeppmkhpmhc", "1.44",
"hodiladlefdpcbemnbbcpclbmknkiaem", "3.1.3",
"lbneaaedflankmgmfbmaplggbmjjmbae", "1.3.8",
"eaijffijbobmnonfhilihbejadplhddo", "2.4",
"hmiaoahjllhfgebflooeeefeiafpkfde", "1.0.0"
];
Stage 1: source
DeviceTvmBrowserExtensions
Stage 2: join
join kind=inner (extensions) on $left.ExtensionId, $right.Id
Stage 3: extend
extend IntVersion, IntVulnVursion
Stage 4: where
where IntVersion <= IntVulnVursion and IsActivated == true
Stage 5: join
| join kind=inner (
DeviceInfo
| where Timestamp > ago(7d)
) on DeviceId
Stage 6: distinct
distinct BrowserName, DeviceId, DeviceName, ExtensionDescription, ExtensionName, ExtensionRisk, ExtensionVersion, VulnVersion
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IsActivated | eq |
| field:"IsActivated" kind:eq value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IntVersion | extend |
IntVulnVursion | extend |
Insider Risk_Risky User Access By Application
#This alert evaluates Microsoft Entra ID Sign in risk via Machine Learning correlations in the basket operator. The basket threshold is adjustable, and the default is set to .01. There is an optional configuration to configure the percentage rates. The correlations are designed to leverage machine learning to identify patterns of risky user application access. There is an option for configuration of correlations against Microsoft Sentinel watchlists. For more information, see Tutorial: Use risk detections for user sign-ins to trigger Microsoft Entra ID Multi-Factor Authentication or password changes
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Telemetry Coverage
Rule Body
id: 15386bba-dc70-463f-a09f-d392e7731c63
name: Insider Risk_Risky User Access By Application
description: |
'This alert evaluates Microsoft Entra ID Sign in risk via Machine Learning correlations in the basket operator. The basket threshold is adjustable, and the default is set to .01. There is an optional configuration to configure the percentage rates. The correlations are designed to leverage machine learning to identify patterns of risky user application access. There is an option for configuration of correlations against Microsoft Sentinel watchlists. For more information, see [Tutorial: Use risk detections for user sign-ins to trigger Microsoft Entra ID Multi-Factor Authentication or password changes](https://docs.microsoft.com/azure/active-directory/authentication/tutorial-risk-based-sspr-mfa)'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1204
query: |
SigninLogs
| where RiskState == "atRisk"
| project UserPrincipalName, Location, AppDisplayName, RiskState
| evaluate basket(0.01) // Adjust & Tune Thresholds within Organzational Requirements
// | where Percent > 50 // Adjust & Tune Thresholds within Organzational Requirements
| where UserPrincipalName <> ""
| where AppDisplayName <> ""
| project Percent, UserPrincipalName, Location, AppDisplayName, RiskState
// | lookup kind=inner _GetWatchlist('<Your Watchlist Name>') on $left.UserPrincipalName == $right.SearchKey
| sort by Percent desc
| extend AccountName = tostring(split(UserPrincipalName, "@")[0]), AccountUPNSuffix = tostring(split(UserPrincipalName, "@")[1])
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: true
lookbackDuration: 3d
matchingMethod: Selected
groupByEntities:
- Account
version: 1.1.4
kind: Scheduled
Stages and Predicates
Stage 1: source
SigninLogs
Stage 2: where
| where RiskState == "atRisk"
Stage 3: project
| project UserPrincipalName, Location, AppDisplayName, RiskState
Stage 4: evaluate
| evaluate basket(0.01)
Stage 5: where
| where UserPrincipalName <> ""
Stage 6: where
| where AppDisplayName <> ""
Stage 7: project
| project Percent, UserPrincipalName, Location, AppDisplayName, RiskState
Stage 8: sort
| sort by Percent desc
Stage 9: extend
| extend AccountName = tostring(split(UserPrincipalName, "@")[0]), AccountUPNSuffix = tostring(split(UserPrincipalName, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
RiskState | eq |
| field:"RiskState" kind:eq value:"atRisk" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppDisplayName | project |
Location | project |
Percent | project |
RiskState | project |
UserPrincipalName | project |
AccountName | extend |
AccountUPNSuffix | extend |
M2131_RecommendedDatatableUnhealthy
#'This alert is designed to monitor recommended data tables aligned to the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when a recommended data table hasn't been observed in over 48 hours.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: c61b167a-59ae-42af-bc98-36c78c5acb5c
name: M2131_RecommendedDatatableUnhealthy
description: |
'This alert is designed to monitor recommended data tables aligned to the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when a recommended data table hasn't been observed in over 48 hours.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
let M2131Mapping = datatable(DataTable:string, MaturityLevel:string)
[
"SigninLogs", "Event Logging (EL0)",
"AADManagedIdentitySignInLogs", "Event Logging (EL0)",
"AADServicePrincipalSignInLogs", "Event Logging (EL0)",
"StorageBlobLogs", "Event Logging (EL0)",
"StorageFileLogs", "Event Logging (EL0)",
"AzureMetrics", "Event Logging (EL0)",
"AuditLogs", "Event Logging (EL0)",
"IdentityInfo", "Event Logging (EL0)",
"CommonSecurityLog", "Event Logging (EL0)",
"ThreatIntelligenceIndicator", "Event Logging (EL0)",
"DeviceNetworkInfo", "Event Logging (EL0)",
"DnsEvents", "Event Logging (EL0)",
"DeviceNetworkEvents", "Event Logging (EL0)",
"AzureDiagnostics", "Event Logging (EL0)",
"Usage", "Event Logging (EL0)",
"SecurityIncident", "Event Logging (EL0)",
"SecurityAlert", "Event Logging (EL0)",
"AzureActivity", "Event Logging (EL0)",
"Heartbeat", "Event Logging (EL0)",
"OfficeActivity", "Event Logging (EL0)",
"SecurityEvent", "Event Logging (EL0)",
"Syslog", "Event Logging (EL0)",
"AWSCloudTrail", "Event Logging (EL0)",
"GWorkspaceActivityReports", "Event Logging (EL0)",
"AWSGuardDuty", "Event Logging (EL0)",
"AWSVPCFlow", "Event Logging (EL0)",
"Perf", "Basic Event Logging (EL1)",
"SentinelHealth", "Basic Event Logging (EL1)",
"DeviceLogonEvents", "Basic Event Logging (EL1)",
"DeviceEvents", "Basic Event Logging (EL1)",
"DeviceNetworkEvents", "Basic Event Logging (EL1)",
"DeviceFileEvents", "Basic Event Logging (EL1)",
"DeviceRegistryEvents", "Basic Event Logging (EL1)",
"DeviceProcessEvents", "Basic Event Logging (EL1)",
"VMConnection", "Basic Event Logging (EL1)",
"EmailEvents", "Basic Event Logging (EL1)",
"ThreatIntelligenceIndicator", "Basic Event Logging (EL1)",
"SecurityRecommendation", "Basic Event Logging (EL1)",
"DeviceProcessEvents", "Basic Event Logging (EL1)",
"ConfigurationData", "Basic Event Logging (EL1)",
"ConfigurationChange", "Basic Event Logging (EL1)",
"GatewayDiagnosticLog", "Basic Event Logging (EL1)",
"TunnelDiagnosticLog", "Basic Event Logging (EL1)",
"IKEDiagnosticLog", "Basic Event Logging (EL1)",
"RouteDiagnosticLog", "Basic Event Logging (EL1)",
"PS2DiagnosticLog", "Basic Event Logging (EL1)",
"Event", "Basic Event Logging (EL1)",
"SqlAtpStatus", "Basic Event Logging (EL1)",
"ConstainerInstanceLog_CL", "Basic Event Logging (EL1)",
"ContainerEvent_CL", "Basic Event Logging (EL1)",
"InsightsMetrics", "Intermediate Event Logging (EL2)",
"EmailUrlInfo", "Intermediate Event Logging (EL2)",
"EmailAttachmentInfo", "Intermediate Event Logging (EL2)",
"InformationProtectionLogs_CL", "Intermediate Event Logging (EL2)",
"CloudAppEvents", "Intermediate Event Logging (EL2)",
"ContainerInventory", "Intermediate Event Logging (EL2)",
"Update", "Advanced Event Logging (EL3)",
"BehaviorAnalytics", "Advanced Event Logging (EL3)",
"Anomalies", "Advanced Event Logging (EL3)",
"SecurityRegulatoryCompliance", "Advanced Event Logging (EL3)"
];
let LastLogTime = Usage
| summarize LastLog_Time = arg_max(TimeGenerated, *) by DataType;
Usage
| summarize last_log = datetime_diff("day",now(), max(TimeGenerated)) by DataType
| where last_log > 0
| join kind=inner (LastLogTime) on DataType
| project DataTable = DataType, ['Last Log Received'] = last_log, LastLog_Time
| where ['Last Log Received'] > 2
| join kind=inner (M2131Mapping) on DataTable
| project-away DataTable1
| order by ['Last Log Received'] desc
| extend CloudApplication = DataTable
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: DataTable
version: 1.0.0
kind: Scheduled
Stages and Predicates
Let Binding: M2131Mapping
let M2131Mapping = datatable(DataTable:string, MaturityLevel:string)
[
"SigninLogs", "Event Logging (EL0)",
"AADManagedIdentitySignInLogs", "Event Logging (EL0)",
"AADServicePrincipalSignInLogs", "Event Logging (EL0)",
"StorageBlobLogs", "Event Logging (EL0)",
"StorageFileLogs", "Event Logging (EL0)",
"AzureMetrics", "Event Logging (EL0)",
"AuditLogs", "Event Logging (EL0)",
"IdentityInfo", "Event Logging (EL0)",
"CommonSecurityLog", "Event Logging (EL0)",
"ThreatIntelligenceIndicator", "Event Logging (EL0)",
"DeviceNetworkInfo", "Event Logging (EL0)",
"DnsEvents", "Event Logging (EL0)",
"DeviceNetworkEvents", "Event Logging (EL0)",
"AzureDiagnostics", "Event Logging (EL0)",
"Usage", "Event Logging (EL0)",
"SecurityIncident", "Event Logging (EL0)",
"SecurityAlert", "Event Logging (EL0)",
"AzureActivity", "Event Logging (EL0)",
"Heartbeat", "Event Logging (EL0)",
"OfficeActivity", "Event Logging (EL0)",
"SecurityEvent", "Event Logging (EL0)",
"Syslog", "Event Logging (EL0)",
"AWSCloudTrail", "Event Logging (EL0)",
"GWorkspaceActivityReports", "Event Logging (EL0)",
"AWSGuardDuty", "Event Logging (EL0)",
"AWSVPCFlow", "Event Logging (EL0)",
"Perf", "Basic Event Logging (EL1)",
"SentinelHealth", "Basic Event Logging (EL1)",
"DeviceLogonEvents", "Basic Event Logging (EL1)",
"DeviceEvents", "Basic Event Logging (EL1)",
"DeviceNetworkEvents", "Basic Event Logging (EL1)",
"DeviceFileEvents", "Basic Event Logging (EL1)",
"DeviceRegistryEvents", "Basic Event Logging (EL1)",
"DeviceProcessEvents", "Basic Event Logging (EL1)",
"VMConnection", "Basic Event Logging (EL1)",
"EmailEvents", "Basic Event Logging (EL1)",
"ThreatIntelligenceIndicator", "Basic Event Logging (EL1)",
"SecurityRecommendation", "Basic Event Logging (EL1)",
"DeviceProcessEvents", "Basic Event Logging (EL1)",
"ConfigurationData", "Basic Event Logging (EL1)",
"ConfigurationChange", "Basic Event Logging (EL1)",
"GatewayDiagnosticLog", "Basic Event Logging (EL1)",
"TunnelDiagnosticLog", "Basic Event Logging (EL1)",
"IKEDiagnosticLog", "Basic Event Logging (EL1)",
"RouteDiagnosticLog", "Basic Event Logging (EL1)",
"PS2DiagnosticLog", "Basic Event Logging (EL1)",
"Event", "Basic Event Logging (EL1)",
"SqlAtpStatus", "Basic Event Logging (EL1)",
"ConstainerInstanceLog_CL", "Basic Event Logging (EL1)",
"ContainerEvent_CL", "Basic Event Logging (EL1)",
"InsightsMetrics", "Intermediate Event Logging (EL2)",
"EmailUrlInfo", "Intermediate Event Logging (EL2)",
"EmailAttachmentInfo", "Intermediate Event Logging (EL2)",
"InformationProtectionLogs_CL", "Intermediate Event Logging (EL2)",
"CloudAppEvents", "Intermediate Event Logging (EL2)",
"ContainerInventory", "Intermediate Event Logging (EL2)",
"Update", "Advanced Event Logging (EL3)",
"BehaviorAnalytics", "Advanced Event Logging (EL3)",
"Anomalies", "Advanced Event Logging (EL3)",
"SecurityRegulatoryCompliance", "Advanced Event Logging (EL3)"
];
Let Binding: LastLogTime
let LastLogTime = Usage
| summarize LastLog_Time = arg_max(TimeGenerated, *) by DataType;
Stage 1: source
Usage
Stage 2: summarize
| summarize last_log = datetime_diff("day",now(), max(TimeGenerated)) by DataType
Stage 3: where
| where last_log > 0
Stage 4: join
| join kind=inner (LastLogTime) on DataType
Stage 5: project
project DataTable, Last Log Received, LastLog_Time
Stage 6: where
where ["Last Log Received"] > 2
Stage 7: join
join kind=inner (M2131Mapping) on DataTable
Stage 8: project-away
project-away DataTable1
Stage 9: sort
sort by Last Log Received
Stage 10: extend
extend CloudApplication
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Last Log Received | gt |
| field:"Last Log Received" kind:gt value:"2" |
last_log | gt |
| field:"last_log" kind:gt value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataTable | project |
Last Log Received | project |
LastLog_Time | project |
CloudApplication | extend |
Multi-Factor Authentication User Locked
#Detects when the allowed number of multi-factor authentication attempts is exceeded for a user.
Rule Body
id: ebdd9cf8-c41c-460e-95d8-e5bc3cd9763e
name: Multi-Factor Authentication User Locked
description: Detects when the allowed number of multi-factor authentication attempts
is exceeded for a user.
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 == 40206\n| project\n Date =\
\ format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n UserName = user,\n MessageDetails = Description,\n\
\ Severity = SeverityDescription\n| project Date, DataSource, EventId, UserName,MessageDetails,Severity"
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 == 40206
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
MessageDetails = Description,
Severity = SeverityDescription
Stage 4: project
| project Date, DataSource, EventId, UserName,MessageDetails,Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"40206" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
Pathlock TDnR - Outgoing Spool Print Job Events
#Detects recording of outgoing spool print jobs in SAP, forwarded by Pathlock Threat Detection and Response. Anomalous print job activity may indicate unauthorized printing of sensitive documents, data exfiltration via print-to-file operations, or misuse of SAP output management.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c60
name: Pathlock TDnR - Outgoing Spool Print Job Events
kind: Scheduled
description: >-
Detects recording of outgoing spool print jobs in SAP, forwarded by Pathlock Threat Detection
and Response. Anomalous print job activity may indicate unauthorized printing of sensitive
documents, data exfiltration via print-to-file operations, or misuse of SAP output management.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1048
query: |
Pathlock_TDnR_CL
| where DataSource == "SPOOL_OUTPUT_REQUEST"
| 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 == "SPOOL_OUTPUT_REQUEST"
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:"SPOOL_OUTPUT_REQUEST" |
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 |
SailPointIdentityNowEventType
#'Created to detect failed events of particular type from SailPointIDN_Events.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 48bb92e2-bad4-4fd4-9684-26cb188299b7
name: SailPointIdentityNowEventType
description: |
'Created to detect failed events of particular type from SailPointIDN_Events.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SailPointIdentityNow
dataTypes:
- SailPointIDN_Events
- connectorId: SailPointIdentityNowConnector
dataTypes:
- SailPointIDN_Events
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1133
query: |
declare query_parameters(lbperiod:timespan = 14d, type:string = "ACCESS_ITEM");
SailPointIDN_Events
| where TimeGenerated > ago(lbperiod)
| where EventType == type
| where Status == "FAILED"
| sort by Created
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: TechnicalName
version: 1.1.0
kind: Scheduled
Stages and Predicates
Stage 1: source
declare query_parameters(lbperiod:timespan = 14d, type:string = "ACCESS_ITEM");
SailPointIDN_Events
Stage 2: where
| where TimeGenerated > ago(lbperiod)
Stage 3: where
| where EventType == type
Stage 4: where
| where Status == "FAILED"
Stage 5: sort
| sort by Created
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Status | eq |
| field:"Status" kind:eq value:"FAILED" |
Scale-Out Backup Repository Deleted
#Detects when a scale-out backup repository is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: 4dc85727-573c-4722-8173-026e2a1d20db
name: Scale-Out Backup Repository Deleted
description: Detects when a scale-out backup repository is deleted from Veeam Backup
& Replication. This might indicate unauthorized removal of critical components.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 30200\n| extend Name = extract(\"\
Name=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ UserName = user,\n [\"Object Name\"] = Name,\n MessageDetails = Description,\n\
\ Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 30200
Stage 3: extend
| extend Name = extract("Name=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
["Object Name"] = Name,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"30200" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Object Name | project |
Severity | project |
UserName | project |
Shadow Credentials Added to Account
#This query searches for modifications to the 'msDS-KeyCredentialLink' property in Active Directory, introduced in Windows Server 2016. There are two different events which contain information to detect such changes 5136 and 4662. This detection uses the 5136, which is the preferred event to use.
Known False Positives
- The property can also be used for legitimate purposes. However, the legitimate use of the property is limited in most environments.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
References
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5136: A directory service object was modified. |
Rule Body
let timeframe = 2*1h;
let RuleId = "0275";
let DedupFields = dynamic(["TimeGenerated", "SubjectUserName", "Computer"]);
SecurityEvent
| where ingestion_time() >= ago(timeframe)
| where EventID == 5136
| extend AttributeName = extract("<Data Name=\"AttributeLDAPDisplayName\">(.*?)</Data>", 1, EventData)
| extend ObjectDN = extract("<Data Name=\"ObjectDN\">(.*?)</Data>", 1, EventData)
| extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">(.*?)</Data>", 1, EventData)
| where AttributeName has "msDS-KeyCredentialLink"
| where not(SubjectUserName endswith "$" and ObjectDN startswith strcat("CN=", replace_string(SubjectUserName, "$", ""), ",")) // Machine account changing its own msDS-KeyCredentialLink.
| extend HostName=tostring(split(Computer,".")[0]),DnsDomain=iif(Computer contains ".", substring(Computer, indexof(Computer, ".") + 1, strlen(Computer)),"")
| extend AccountName=iif(Account contains @"\",tostring(split(Account,@"\")[1]),Account),AccountDomain=iif(Account contains @"\",tostring(split(Account,@"\")[0]),"")
// Begin environment-specific filter.
// End environment-specific filter.
// Begin de-duplication logic.
| extend DedupFieldValues=pack_all()
| mv-apply e=DedupFields to typeof(string) on (
extend DedupValue=DedupFieldValues[tostring(e)]
| order by e // Sorting is required to ensure make_list is deterministic.
| summarize DedupValues=make_list(DedupValue)
)
| extend DedupEntity=strcat_array(DedupValues, "|")
| project-away DedupFieldValues, DedupValues
| join kind=leftanti (
SecurityAlert
| where AlertName has RuleId and ProviderName has "ASI"
| where TimeGenerated >= ago(timeframe)
| extend DedupEntity = tostring(parse_json(tostring(parse_json(ExtendedProperties)["Custom Details"])).DedupEntity[0])
| project DedupEntity
) on DedupEntity
// End de-duplication logic.
Stages and Predicates
Parameters
let timeframe = 2*1h;
let RuleId = "0275";
let DedupFields = dynamic(["TimeGenerated", "SubjectUserName", "Computer"]);
Stage 1: source
SecurityEvent
Stage 2: where
| where ingestion_time() >= ago(timeframe)
Stage 3: where
| where EventID == 5136
Stage 4: extend (3 consecutive steps)
| extend AttributeName = extract("<Data Name=\"AttributeLDAPDisplayName\">(.*?)</Data>", 1, EventData)
| extend ObjectDN = extract("<Data Name=\"ObjectDN\">(.*?)</Data>", 1, EventData)
| extend SubjectUserName = extract("<Data Name=\"SubjectUserName\">(.*?)</Data>", 1, EventData)
Stage 5: where
| where AttributeName has "msDS-KeyCredentialLink"
Stage 6: where
| where not(SubjectUserName endswith "$" and ObjectDN startswith strcat("CN=", replace_string(SubjectUserName, "$", ""), ","))
Stage 7: extend (3 consecutive steps)
| extend HostName=tostring(split(Computer,".")[0]),DnsDomain=iif(Computer contains ".", substring(Computer, indexof(Computer, ".") + 1, strlen(Computer)),"")
| extend AccountName=iif(Account contains @"\",tostring(split(Account,@"\")[1]),Account),AccountDomain=iif(Account contains @"\",tostring(split(Account,@"\")[0]),"")
| extend DedupFieldValues=pack_all()
Stage 8: kusto:mv-apply
| mv-apply e=DedupFields to typeof(string) on (
extend DedupValue=DedupFieldValues[tostring(e)]
| order by e
| summarize DedupValues=make_list(DedupValue)
)
Stage 9: extend
| extend DedupEntity=strcat_array(DedupValues, "|")
Stage 10: project-away
| project-away DedupFieldValues, DedupValues
Stage 11: join (negated)
join kind=leftanti (SecurityAlert) on DedupEntity
Stage 12: summarize aggregation inside the mv-apply branch
summarize
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AttributeName | match |
| field:"AttributeName" kind:match value:"msDS-KeyCredentialLink" |
EventID | eq |
| field:"EventID" kind:eq value:"5136" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
SubjectUserName | ends_with | $ | excludes:SubjectUserName field:"SubjectUserName" value:"$" |
AlertName | match | 0275 | excludes:AlertName field:"AlertName" value:"0275" |
ProviderName | match | ASI | excludes:ProviderName field:"ProviderName" value:"ASI" |
Tailscale Premium: Unexpected exit-node egress
#Identifies when a node sends traffic via an exit node not used in the prior 7-day baseline. First-seen exit destinations from a node may indicate routing-policy drift, data exfiltration, or compromise.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control | |
| Exfiltration |
Rule Body
id: c1d2e3f4-1a2b-3c4d-5e6f-7a8b9c0d1e2f
name: "Tailscale Premium: Unexpected exit-node egress"
description: Identifies when a node sends traffic via an exit node not used in the prior 7-day baseline. First-seen exit destinations from a node may indicate routing-policy drift, data exfiltration, or compromise.
description-detailed: |
Identifies when a node sends traffic via an exit node not used in the prior 7-day baseline. First-seen exit destinations from a specific source may indicate unsanctioned egress, a compromised node pivoting, or a policy misconfiguration. Requires Tailscale Premium or Enterprise.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: TailscalePremiumCCF
dataTypes:
- Tailscale_Network_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
- Exfiltration
relevantTechniques:
- T1090
- T1041
query: |
let baseline = 7d;
let recent = 1h;
let recentEgress =
Tailscale_Network_CL
| where TimeGenerated > ago(recent)
| where HasExitTraffic
| mv-expand t = ExitTraffic
| extend Src = tostring(t.src), ExitDst = tostring(t.dst), Bytes = tolong(t.txBytes) + tolong(t.rxBytes)
| summarize FirstSeen = min(TimeGenerated), TotalBytes = sum(Bytes) by NodeId, SrcNodeName, SrcUser, SrcOs, Src, ExitDst;
let baselineEgress =
Tailscale_Network_CL
| where TimeGenerated between (ago(baseline + recent) .. ago(recent))
| where HasExitTraffic
| mv-expand t = ExitTraffic
| extend Src = tostring(t.src), ExitDst = tostring(t.dst)
| distinct NodeId, Src, ExitDst;
recentEgress
| join kind=leftanti baselineEgress on NodeId, Src, ExitDst
| extend TotalMB = round(TotalBytes / 1024.0 / 1024.0, 2)
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SrcNodeName
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SrcUser
- entityType: IP
fieldMappings:
- identifier: Address
columnName: Src
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0
Stages and Predicates
Parameters
let baseline = 7d;
let recent = 1h;
let recentEgress is inlined into the numbered stages below.
Let Binding: baselineEgress
let baselineEgress = Tailscale_Network_CL
| where TimeGenerated between (ago(baseline + recent) .. ago(recent))
| where HasExitTraffic
| mv-expand t = ExitTraffic
| extend Src = tostring(t.src), ExitDst = tostring(t.dst)
| distinct NodeId, Src, ExitDst;
Stages 1 to 6 define let recentEgress as the rule's main pipeline source. Stages 7 to 8 use it.
Stage 1: source
Tailscale_Network_CL
Stage 2: where
| where TimeGenerated > ago(recent)
Stage 3: where
| where HasExitTraffic
Stage 4: mv-expand
| mv-expand t = ExitTraffic
Stage 5: extend
| extend Src = tostring(t.src), ExitDst = tostring(t.dst), Bytes = tolong(t.txBytes) + tolong(t.rxBytes)
Stage 6: summarize
| summarize FirstSeen = min(TimeGenerated), TotalBytes = sum(Bytes) by NodeId, SrcNodeName, SrcUser, SrcOs, Src, ExitDst
Stage 7: join (negated)
join kind=leftanti (baselineEgress) on NodeId, Src, ExitDst
Stage 8: extend
extend TotalMB
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
HasExitTraffic | eq |
| field:"HasExitTraffic" kind:eq value:"true" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
HasExitTraffic | eq | true | excludes:HasExitTraffic field:"HasExitTraffic" value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ExitDst | summarize |
FirstSeen | summarize |
NodeId | summarize |
Src | summarize |
SrcNodeName | summarize |
SrcOs | summarize |
SrcUser | summarize |
TotalBytes | summarize |
TotalMB | extend |
Theom High Risks
#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.
"Creates Microsoft Sentinel incidents for high risk Theom alerts."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Stealth | |
| Credential Access | |
| Discovery | |
| Collection | |
| Command & Control | |
| Exfiltration | |
| Impact |
Rule Body
id: 74b80987-0a62-448c-8779-47b02e17d3cf
name: Theom High Risks
description: |
"Creates Microsoft Sentinel incidents for high risk Theom alerts."
severity: High
status : Available
requiredDataConnectors:
- connectorId: Theom
dataTypes:
- TheomAlerts_CL
queryPeriod: 5m
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- CommandAndControl
- CredentialAccess
- DefenseEvasion
- Discovery
- Exfiltration
- Impact
- Reconnaissance
relevantTechniques:
- T1592
- T1589
- T1070
- T1552
- T1619
- T1119
- T1560
- T1530
- T1213
- T1001
- T1041
- T1537
- T1485
- T1486
- T1565
query: |
TheomAlerts_CL
| where priority_s == "P2"
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: "Theom Alert ID: {{id_s}} "
alertDescriptionFormat: "\nSummary: {{summary_s}} \nAdditional info: {{details_s}}\nPlease investigate further on Theom UI at {{deepLink_s}}\n"
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: customProps_AssetName_s
- entityType: URL
fieldMappings:
- identifier: Url
columnName: deepLink_s
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
TheomAlerts_CL
Stage 2: where
| where priority_s == "P2"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
priority_s | eq |
| field:"priority_s" kind:eq value:"P2" |
TI Map Email entity to OfficeActivity
#'Identifies a match in OfficeActivity table from any Email IOC from TI'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 795d43a3-6edc-4c99-971f-00d05841e5ac
name: TI Map Email entity to OfficeActivity
description: |
'Identifies a match in OfficeActivity table from any Email IOC from TI'
severity: Medium
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
- 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;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
let OfficeEvents = materialize(
OfficeActivity
| where isnotempty(UserId)
| where TimeGenerated >= ago(dt_lookBack)
| where UserId matches regex emailregex
| project-rename OfficeActivity_TimeGenerated = TimeGenerated);
let OfficeActivityUPNs = OfficeEvents | distinct UserId = tolower(UserId) | summarize make_list(UserId);
ThreatIntelIndicators
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "email-addr"
| extend EmailSenderAddress = ObservableValue
| extend EmailSourceDomain = substring(EmailSenderAddress, indexof(EmailSenderAddress, "@") + 1, strlen(EmailSenderAddress) - indexof(EmailSenderAddress, "@") - 1)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
| where TimeGenerated >= ago(ioc_lookBack)
| where tolower(EmailSenderAddress) in (OfficeActivityUPNs)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| extend Description = tostring(parse_json(Data).description)
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
| project-reorder *, TrafficLightProtocolLevel, EmailSenderAddress, EmailSourceDomain, Type
// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
| join kind=innerunique (OfficeEvents) on $left.EmailSenderAddress == $right.UserId
| where OfficeActivity_TimeGenerated < ValidUntil
| summarize OfficeActivity_TimeGenerated = arg_max(OfficeActivity_TimeGenerated, *) by Id, UserId
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project OfficeActivity_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence, EmailSenderAddress, EmailSourceDomain, UserId, ClientIP, Operation, UserType, RecordType, OfficeWorkload, Parameters, Url
| extend Name = tostring(split(UserId, '@', 0)[0]), UPNSuffix = tostring(split(UserId, '@', 1)[0])
| extend timestamp = OfficeActivity_TimeGenerated
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserId
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.2.12
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
Let Binding: OfficeEvents
let OfficeEvents = materialize(
OfficeActivity
| where isnotempty(UserId)
| where TimeGenerated >= ago(dt_lookBack)
| where UserId matches regex emailregex
| project-rename OfficeActivity_TimeGenerated = TimeGenerated);
Let Binding: OfficeActivityUPNs
let OfficeActivityUPNs = OfficeEvents | distinct UserId = tolower(UserId) | summarize make_list(UserId);
Stage 1: source
ThreatIntelIndicators
Stage 2: extend
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
Stage 3: where
| where IndicatorType == "email-addr"
Stage 4: extend (4 consecutive steps)
| extend EmailSenderAddress = ObservableValue
| extend EmailSourceDomain = substring(EmailSenderAddress, indexof(EmailSenderAddress, "@") + 1, strlen(EmailSenderAddress) - indexof(EmailSenderAddress, "@") - 1)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
Stage 5: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 6: where
| where tolower(EmailSenderAddress) in (OfficeActivityUPNs)
Stage 7: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
Stage 8: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 9: extend
| extend Description = tostring(parse_json(Data).description)
Stage 10: where
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
Stage 11: project-reorder
| project-reorder *, TrafficLightProtocolLevel, EmailSenderAddress, EmailSourceDomain, Type
Stage 12: join
| join kind=innerunique (OfficeEvents) on $left.EmailSenderAddress == $right.UserId
Stage 13: where
where OfficeActivity_TimeGenerated < ValidUntil
Stage 14: summarize
summarize OfficeActivity_TimeGenerated by Id, UserId
Stage 15: extend
extend Description
Stage 16: extend
extend ActivityGroupNames
Stage 17: project
project ActivityGroupNames, ClientIP, Confidence, Description, EmailSenderAddress, EmailSourceDomain, Id, OfficeActivity_TimeGenerated, OfficeWorkload, Operation, Parameters, RecordType, Url, UserId, UserType, ValidUntil
Stage 18: extend
extend Name, UPNSuffix
Stage 19: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EmailSenderAddress | in |
| field:"EmailSenderAddress" kind:in value:"OfficeActivityUPNs" |
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"email-addr" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
UserId | is_not_null | field:"UserId" kind:is_not_null | |
UserId | regex_match |
| field:"UserId" kind:regex_match value:"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$" |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Description | contains | State: falsepos; | excludes:Description field:"Description" value:"State: falsepos;" |
Description | contains | State: inactive; | excludes:Description field:"Description" value:"State: inactive;" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
ClientIP | project |
Confidence | project |
Description | project |
EmailSenderAddress | project |
EmailSourceDomain | project |
Id | project |
OfficeActivity_TimeGenerated | project |
OfficeWorkload | project |
Operation | project |
Parameters | project |
RecordType | project |
Url | project |
UserId | project |
UserType | project |
ValidUntil | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
TI Map URL Entity to UrlClickEvents
#This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in UrlClickEvents.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | UrlClickEvents action any: URL click activity |
Rule Body
id: ad4fa1f2-2189-459c-9458-f77d2039d2f5
name: TI Map URL Entity to UrlClickEvents
description: |
'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in UrlClickEvents.'
severity: Medium
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- UrlClickEvents
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let UrlClickEvents_ = materialize(UrlClickEvents
| where TimeGenerated >= ago(dt_lookBack)
| extend UrlClickEvents_TimeGenerated = TimeGenerated);
let ChainReportID = UrlClickEvents_
| mv-expand todynamic(UrlChain)
| extend UrlChain = tolower(UrlChain)
| project ReportId, Url, UrlChain;
// Url is not always in UrlChain, so we need to check both
let ClickedUrls =
(union isfuzzy=false (ChainReportID), (ChainReportID | project Url = UrlChain))
| distinct Url
| summarize make_list(Url);
let TI = materialize(ThreatIntelIndicators
//extract key part of kv pair
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "url"
| extend Url = ObservableValue
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| where TimeGenerated >= ago(ioc_lookBack)
| where tolower(Url) in (ClickedUrls)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| project-rename TI_Url = Url, TI_Type = Type
);
(union isfuzzy=false (TI | join kind=innerunique (ChainReportID) on $left.TI_Url == $right.UrlChain),
(TI | join kind=innerunique (ChainReportID) on $left.TI_Url == $right.Url))
| project-away UrlChain
| join kind=innerunique (UrlClickEvents_) on ReportId
| where UrlClickEvents_TimeGenerated < ValidUntil
| summarize UrlClickEvents_TimeGenerated = arg_max(UrlClickEvents_TimeGenerated, *) by Id
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project UrlClickEvents_TimeGenerated, AccountUpn, Description, ActivityGroupNames, Id, Type, ValidUntil, Confidence, Url, NetworkMessageId
| extend timestamp = UrlClickEvents_TimeGenerated
| extend timestamp = UrlClickEvents_TimeGenerated, Name = tostring(split(AccountUpn, '@', 0)), UPNSuffix = tostring(split(AccountUpn, '@', 1))
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountUpn
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.0.5
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Let Binding: UrlClickEvents_
let UrlClickEvents_ = materialize(UrlClickEvents
| where TimeGenerated >= ago(dt_lookBack)
| extend UrlClickEvents_TimeGenerated = TimeGenerated);
Let Binding: ChainReportID
let ChainReportID = UrlClickEvents_
| mv-expand todynamic(UrlChain)
| extend UrlChain = tolower(UrlChain)
| project ReportId, Url, UrlChain;
Let Binding: ClickedUrls
let ClickedUrls = (union isfuzzy=false (ChainReportID), (ChainReportID | project Url = UrlChain))
| distinct Url
| summarize make_list(Url);
Union isfuzzy=false (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: TI, TI
Leg 1: TI
TI | join kind=innerunique (ChainReportID) on $left.TI_Url == $right.UrlChain
Leg 2: TI
TI | join kind=innerunique (ChainReportID) on $left.TI_Url == $right.Url
Applied to the Combined Result
| project-away UrlChain
| join kind=innerunique (UrlClickEvents_) on ReportId
| where UrlClickEvents_TimeGenerated < ValidUntil
| summarize UrlClickEvents_TimeGenerated = arg_max(UrlClickEvents_TimeGenerated, *) by Id
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project UrlClickEvents_TimeGenerated, AccountUpn, Description, ActivityGroupNames, Id, Type, ValidUntil, Confidence, Url, NetworkMessageId
| extend timestamp = UrlClickEvents_TimeGenerated
| extend timestamp = UrlClickEvents_TimeGenerated, Name = tostring(split(AccountUpn, '@', 0)), UPNSuffix = tostring(split(AccountUpn, '@', 1))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"url" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
Url | in |
| field:"Url" kind:in value:"ClickedUrls" |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountUpn | project |
ActivityGroupNames | project |
Confidence | project |
Description | project |
Id | project |
NetworkMessageId | project |
Type | project |
Url | project |
UrlClickEvents_TimeGenerated | project |
ValidUntil | project |
timestamp | extend |
Name | extend |
UPNSuffix | extend |
UniFi Site Manager: WAN uptime below 99%
#Identifies when site WAN uptime falls below 99 percent, an SLA-grade availability problem. Combine with the ISP Downtime rule for root-cause attribution.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 2bb29f25-833a-5544-4a8e-9bf6c4d8da56
name: "UniFi Site Manager: WAN uptime below 99%"
description: |
Identifies when site WAN uptime falls below 99 percent, an SLA-grade availability problem. Combine with the ISP Downtime rule for root-cause attribution.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: UniFiSiteManagerConnectorDefinition
dataTypes:
- Unifi_SiteManager_Sites_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1499
subTechniques:
- T1499.002
query: |
Unifi_SiteManager_Sites_CL
| where TimeGenerated > ago(1h)
| extend wanUptime = WanUptimePct
| where isnotnull(wanUptime) and wanUptime < 99.0
| summarize arg_max(TimeGenerated, *) by SiteId, SiteName
| extend Activity = strcat('WAN uptime ', round(wanUptime, 2), '% (below 99% SLA threshold)')
| project TimeGenerated, SiteId, SiteName, Activity, WanUptimePct = round(wanUptime, 2)
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SiteId
- identifier: DnsDomain
columnName: SiteName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.1
Stages and Predicates
Stage 1: source
Unifi_SiteManager_Sites_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: extend
| extend wanUptime = WanUptimePct
Stage 4: where
| where isnotnull(wanUptime) and wanUptime < 99.0
Stage 5: summarize
| summarize arg_max(TimeGenerated, *) by SiteId, SiteName
Stage 6: extend
| extend Activity = strcat('WAN uptime ', round(wanUptime, 2), '% (below 99% SLA threshold)')
Stage 7: project
| project TimeGenerated, SiteId, SiteName, Activity, WanUptimePct = round(wanUptime, 2)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
wanUptime | is_not_null | field:"wanUptime" kind:is_not_null | |
wanUptime | lt |
| field:"WanUptimePct" kind:lt value:"99.0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Activity | project |
SiteId | project |
SiteName | project |
TimeGenerated | project |
WanUptimePct | project |
User or Group Deleted
#Detects when a user or user group is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: d297961f-f8a3-4f84-865d-ec3a07641699
name: User or Group Deleted
description: Detects when a user or user group is deleted from Veeam Backup & Replication.
This might indicate unauthorized removal of critical components.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 31400\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 == 31400
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:"31400" |
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 |
Vectra AI Detect - Detections with High Severity
#'Create an incident for high severity malicious behavior detected by Vectra AI (Threat score superior to 7.0). The Severity is a mapping with the Threat score assigned to a detection. It ranges between 0 and 10. The severity_threshold variable can be adjusted as desired.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | |
| Discovery | |
| Lateral Movement | |
| Collection | |
| Command & Control | |
| Exfiltration | |
| Impact |
Rule Body
id: 39e48890-2c02-487e-aa9e-3ba494061798
name: Vectra AI Detect - Detections with High Severity
description: |
'Create an incident for high severity malicious behavior detected by Vectra AI (Threat score superior to 7.0).
The Severity is a mapping with the Threat score assigned to a detection. It ranges between 0 and 10.
The severity_threshold variable can be adjusted as desired.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- CredentialAccess
- Discovery
- LateralMovement
- Collection
- CommandAndControl
- Exfiltration
- Impact
relevantTechniques:
- T1003
- T1087
- T1021
- T1119
- T1071
- T1041
- T1499
query: |
// Edit this variable to only keep the tactics where an incident needs to be created (Defaults are: "COMMAND & CONTROL", "BOTNET ACTIVITY", "EXFILTRATION", "LATERAL MOVEMENT", "RECONNAISSANCE")
let configured_tactics = dynamic(["COMMAND & CONTROL", "BOTNET ACTIVITY", "EXFILTRATION", "LATERAL MOVEMENT", "RECONNAISSANCE"]);
//default threshold is 7 (meaning a threat score of 70)
let severity_threshold = 7.0;
//Map by default to High Severity in Sentinel
let Severity = "High";
CommonSecurityLog
| where DeviceVendor == "Vectra Networks"
| where DeviceProduct == "X Series"
| where DeviceEventClassID != "campaigns" and DeviceEventClassID != "hsc" and DeviceEventClassID != "audit" and DeviceEventClassID != "health" and DeviceEventClassID != "asc"
| extend Category = coalesce(
column_ifexists("DeviceEventCategory", ""),
extract("cat=(.+?)(;|$)", 1, AdditionalExtensions),
""
)
| project-rename threat_score = FlexNumber1
| project-rename certainty_score = FlexNumber2
| project-rename vectra_URL = DeviceCustomString4
| project-rename detection_name = DeviceEventClassID
| where todecimal(LogSeverity) >= severity_threshold
| extend Tactic = case( Category == "COMMAND & CONTROL", "CommandAndControl",
Category == "BOTNET ACTIVITY" , "Impact",
Category == "EXFILTRATION", "Exfiltration",
Category == "LATERAL MOVEMENT", "LateralMovement",
Category == "RECONNAISSANCE", "Discovery",
"UNKNOWN")
| extend level = case(threat_score == 0 and certainty_score == 0, "Info",
threat_score < 50 and certainty_score < 50, "Low",
threat_score < 50 and certainty_score >= 50, "Medium",
threat_score >= 50 and certainty_score <= 50, "High",
threat_score >= 50 and certainty_score >= 50, "Critical",
"UNKNOWN")
| extend Severity = case(level == "Info", "Informational",level == "Critical", "High", level)
| extend account = extract("account=(.+?);", 1, AdditionalExtensions)
| extend upn = iff(account matches regex ":", tostring(split(account,":")[1]) ,tostring(split(account,":")[0]))
| extend name = tostring(split(upn, "@")[0])
| extend upn_suffix = tostring(split(upn, "@")[1])
| extend source_entity = case( isnotempty(upn), upn,
isnotempty(SourceHostName), SourceHostName,
"UNKNWON")
| where Category in (configured_tactics)
| summarize arg_max(threat_score, *) by source_entity, Activity
| sort by TimeGenerated
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SourceHostName
- entityType: Account
fieldMappings:
- identifier: Name
columnName: name
- identifier: UPNSuffix
columnName: upn_suffix
alertDetailsOverride:
alertDisplayNameFormat: Vectra AI Detect - {{Activity}} detected
alertDescriptionFormat: Source entity is {{source_entity}} and category is {{Category}}. Threat score is {{threat_score}}.
alertSeverityColumnName: Severity
alertDynamicProperties:
- alertProperty: AlertLink
value: vectra_URL
- alertProperty: ProductName
value: DeviceProduct
- alertProperty: ProviderName
value: DeviceVendor
- alertProperty: ConfidenceScore
value: certainty_score
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: true
lookbackDuration: 7d
matchingMethod: AllEntities
customDetails:
AttackType: Activity
AttackCategory: Category
version: 1.1.0
kind: Scheduled
Stages and Predicates
Parameters
let configured_tactics = dynamic(["COMMAND & CONTROL", "BOTNET ACTIVITY", "EXFILTRATION", "LATERAL MOVEMENT", "RECONNAISSANCE"]);
let severity_threshold = 7.0;
let Severity = "High";
Stage 1: source
CommonSecurityLog
Stage 2: where
| where DeviceVendor == "Vectra Networks"
Stage 3: where
| where DeviceProduct == "X Series"
Stage 4: where
| where DeviceEventClassID != "campaigns" and DeviceEventClassID != "hsc" and DeviceEventClassID != "audit" and DeviceEventClassID != "health" and DeviceEventClassID != "asc"
Stage 5: extend
| extend Category = coalesce(
column_ifexists("DeviceEventCategory", ""),
extract("cat=(.+?)(;|$)", 1, AdditionalExtensions),
""
)
Stage 6: project-rename
| project-rename threat_score = FlexNumber1
Stage 7: project-rename
| project-rename certainty_score = FlexNumber2
Stage 8: project-rename
| project-rename vectra_URL = DeviceCustomString4
Stage 9: project-rename
| project-rename detection_name = DeviceEventClassID
Stage 10: where
| where todecimal(LogSeverity) >= severity_threshold
Stage 11: extend (8 consecutive steps)
| extend Tactic = case( Category == "COMMAND & CONTROL", "CommandAndControl",
Category == "BOTNET ACTIVITY" , "Impact",
Category == "EXFILTRATION", "Exfiltration",
Category == "LATERAL MOVEMENT", "LateralMovement",
Category == "RECONNAISSANCE", "Discovery",
"UNKNOWN")
| extend level = case(threat_score == 0 and certainty_score == 0, "Info",
threat_score < 50 and certainty_score < 50, "Low",
threat_score < 50 and certainty_score >= 50, "Medium",
threat_score >= 50 and certainty_score <= 50, "High",
threat_score >= 50 and certainty_score >= 50, "Critical",
"UNKNOWN")
| extend Severity = case(level == "Info", "Informational",level == "Critical", "High", level)
| extend account = extract("account=(.+?);", 1, AdditionalExtensions)
| extend upn = iff(account matches regex ":", tostring(split(account,":")[1]) ,tostring(split(account,":")[0]))
| extend name = tostring(split(upn, "@")[0])
| extend upn_suffix = tostring(split(upn, "@")[1])
| extend source_entity = case( isnotempty(upn), upn,
isnotempty(SourceHostName), SourceHostName,
"UNKNWON")
Tactic =Category == "COMMAND & CONTROL""CommandAndControl"Category == "BOTNET ACTIVITY""Impact"Category == "EXFILTRATION""Exfiltration"Category == "LATERAL MOVEMENT""LateralMovement"Category == "RECONNAISSANCE""Discovery""UNKNOWN"Stage 12: where
| where Category in (configured_tactics)
Stage 13: summarize
| summarize arg_max(threat_score, *) by source_entity, Activity
Stage 14: sort
| sort by TimeGenerated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | in |
| field:"Category" kind:in |
DeviceEventClassID | ne |
| field:"DeviceEventClassID" kind:ne |
DeviceProduct | eq |
| field:"DeviceProduct" kind:eq value:"X Series" |
DeviceVendor | eq |
| field:"DeviceVendor" kind:eq value:"Vectra Networks" |
LogSeverity | ge |
| field:"LogSeverity" kind:ge value:"7.0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Activity | summarize |
source_entity | summarize |
VMware ESXi - Multiple Failed Shell Login via SSH
#Identifies a failed ESXi Shell login via SSH in a short TimeFrame. This could be suspicious activity especially if this alert is seen triggering many times within a short time frame which could be evidence of a brute-force attack. TriggerThreshold can be adapted.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 22d177d5-588c-4f1a-a332-2695f52079bb
name: VMware ESXi - Multiple Failed Shell Login via SSH
description: |
Identifies a failed ESXi Shell login via SSH in a short TimeFrame. This could be suspicious activity especially if this alert is seen triggering many times within a short time frame which could be evidence of a brute-force attack. TriggerThreshold can be adapted.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
// To Adapt to your environment
let triggerThreshold = 10;
VMwareESXi
| where SyslogMessage has "sshd"
| where SyslogMessage contains "authentication failure"
| extend SrcUsername = extract(@'\[info\]\s+\[(.*?)\]', 1, SyslogMessage)
| extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)
| extend SrcIpAddr = extract(@'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)
| summarize make_set(SrcUsername),count() by SrcIpAddr,DstHostname
| where count_ >= triggerThreshold
| extend HostCustomEntity = DstHostname
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: HostCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let triggerThreshold = 10;
Stage 1: source
VMwareESXi
Stage 2: where
| where SyslogMessage has "sshd"
Stage 3: where
| where SyslogMessage contains "authentication failure"
Stage 4: extend (3 consecutive steps)
| extend SrcUsername = extract(@'\[info\]\s+\[(.*?)\]', 1, SyslogMessage)
| extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)
| extend SrcIpAddr = extract(@'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 1, SyslogMessage)
Stage 5: summarize
| summarize make_set(SrcUsername),count() by SrcIpAddr,DstHostname
Stage 6: where
| where count_ >= triggerThreshold
Stage 7: extend
| extend HostCustomEntity = DstHostname
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
SyslogMessage | contains |
| field:"SyslogMessage" kind:contains value:"authentication failure" |
SyslogMessage | match |
| field:"SyslogMessage" kind:match value:"sshd" |
count_ | ge |
| field:"count_" kind:ge value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstHostname | summarize |
SrcIpAddr | summarize |
HostCustomEntity | extend |