Detection rules › Kusto
Kusto rules: 23
Access Token Manipulation - Create Process with Token
#This query detects the use of the 'runas' command and checks whether the account used to elevate privileges isn't the user's own admin account. Additionally, it will match this event to the logon events - to check whether it has been successful as well as augment the event with the new SID.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Stealth |
Telemetry Coverage
Rule Body
id: 8df80270-b4fa-4a7a-931e-8d17c0b321ae
name: Access Token Manipulation - Create Process with Token
description: |
This query detects the use of the 'runas' command and checks whether the account used to elevate privileges isn't the user's own admin account.
Additionally, it will match this event to the logon events - to check whether it has been successful as well as augment the event with the new SID.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
- DeviceLogonEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
- DefenseEvasion
relevantTechniques:
- T1134.002
query: |
let RunAsProcess=DeviceProcessEvents
| where FileName =~ "runas.exe"
// You can choose to filter out the local admin account. This is based on convention. Here, we assume that localadmin accounts
// end with _ladmin (RID 500 / LAPS).
| where not(AccountName has_any("_ladmin"))
// De-obfuscate the commandline used.
| extend CleanProcessCommandLine=parse_command_line(tostring(ProcessCommandLine), "windows")
// Exclude a user running something on their system through their admin account.
| where CleanProcessCommandLine !contains strcat(AccountName, "_adm") // Replace this with your admin account naming convention.
// Exclude local admin account activities by, for instance, the servicedesk that uses the LAPS provisioned account. This is optional.
// Disable the line below if the number of false positives is acceptable.
| where not(CleanProcessCommandLine has_any (":_ladmin")) // Replace this with your local RID500/LAPS account.
// Extract the username for the elevation action.
| extend ElevatedAccountName=extract("user:([a-zA-Z0-9\\\\]+)",1,tostring(CleanProcessCommandLine))
// Strip the domain suffix.
| extend CleanElevatedAccountName= trim("(.*\\\\)",ElevatedAccountName);
RunAsProcess
| join kind=leftouter (
DeviceLogonEvents
| project-rename CleanElevatedAccountName = AccountName
) on CleanElevatedAccountName,DeviceId
| project-rename ElevatedActionType=ActionType1,ElevatedAccountSid=AccountSid1
| project TimeGenerated,DeviceId,DeviceName,FileName,FolderPath,ProcessCommandLine,SHA256,ProcessIntegrityLevel,AccountDomain,AccountName,AccountSid, LogonId, InitiatingProcessFileName,InitiatingProcessFolderPath,InitiatingProcessCommandLine,CleanProcessCommandLine,ElevatedAccountName,CleanElevatedAccountName,ElevatedActionType,LogonType,ElevatedAccountSid
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: AccountSid
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountDomain
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: ProcessCommandLine
version: 1.0.0
kind: Scheduled
Stages and Predicates
let RunAsProcess is inlined into the numbered stages below.
Stage 1: source
let RunAsProcess
Stage 2: source
DeviceProcessEvents
Stage 3: where
where FileName =~ "runas.exe"
Stage 4: where
where not (AccountName has "_ladmin")
Stage 5: extend
extend CleanProcessCommandLine
Stage 6: where
where CleanProcessCommandLine !contains strcat(AccountName, "_adm")
Stage 7: where
where not (CleanProcessCommandLine has ":_ladmin")
Stage 8: extend
extend ElevatedAccountName
Stage 9: extend
extend CleanElevatedAccountName
Stage 10: join
join kind=leftouter (DeviceLogonEvents) on CleanElevatedAccountName, DeviceId
Stage 11: project-rename
project-rename
Stage 12: project
project AccountDomain, AccountName, AccountSid, CleanElevatedAccountName, CleanProcessCommandLine, DeviceId, DeviceName, ElevatedAccountName, ElevatedAccountSid, ElevatedActionType, FileName, FolderPath, InitiatingProcessCommandLine, InitiatingProcessFileName, InitiatingProcessFolderPath, LogonId, LogonType, ProcessCommandLine, ProcessIntegrityLevel, SHA256, TimeGenerated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
FileName | eq |
| field:"FileName" kind:eq value:"runas.exe" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
AccountName | match | _ladmin | excludes:AccountName field:"AccountName" value:"_ladmin" |
CleanProcessCommandLine | match | :_ladmin | excludes:CleanProcessCommandLine field:"CleanProcessCommandLine" value:":_ladmin" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountDomain | project |
AccountName | project |
AccountSid | project |
CleanElevatedAccountName | project |
CleanProcessCommandLine | project |
DeviceId | project |
DeviceName | project |
ElevatedAccountName | project |
ElevatedAccountSid | project |
ElevatedActionType | project |
FileName | project |
FolderPath | project |
InitiatingProcessCommandLine | project |
InitiatingProcessFileName | project |
InitiatingProcessFolderPath | project |
LogonId | project |
LogonType | project |
ProcessCommandLine | project |
ProcessIntegrityLevel | project |
SHA256 | project |
TimeGenerated | project |
Azure Key Vault access TimeSeries anomaly
#'Identifies a sudden increase in count of Azure Key Vault secret or vault access operations by CallerIPAddress. The query leverages a built-in KQL anomaly detection algorithm to find large deviations from baseline Azure Key Vault access patterns. Any sudden increase in the count of Azure Key Vault accesses can be an indication of adversary dumping credentials via automated methods. If you are seeing any noise, try filtering known source(IP/Account) and user-agent combinations. TimeSeries Reference Blog: https://techcommunity.microsoft.com/t5/azure-sentinel/looking-for-unknown-anomalies-what-is-normal-time-series/ba-p/555052'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 0914adab-90b5-47a3-a79f-7cdcac843aa7
name: Azure Key Vault access TimeSeries anomaly
description: |
'Identifies a sudden increase in count of Azure Key Vault secret or vault access operations by CallerIPAddress. The query leverages a built-in KQL anomaly detection algorithm to find large deviations from baseline Azure Key Vault access patterns.
Any sudden increase in the count of Azure Key Vault accesses can be an indication of adversary dumping credentials via automated methods. If you are seeing any noise, try filtering known source(IP/Account) and user-agent combinations.
TimeSeries Reference Blog: https://techcommunity.microsoft.com/t5/azure-sentinel/looking-for-unknown-anomalies-what-is-normal-time-series/ba-p/555052'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: AzureKeyVault
dataTypes:
- KeyVaultData
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1003
query: |
let starttime = 14d;
let timeframe = 1d;
let scorethreshold = 3;
let baselinethreshold = 25;
// To avoid any False Positives, filtering using AppId is recommended. For example the AppId 509e4652-da8d-478d-a730-e9d4a1996ca4 has been added in the query as it corresponds
// to Azure Resource Graph performing VaultGet operations for indexing and syncing all tracked resources across Azure.
let Allowedappid = dynamic(["509e4652-da8d-478d-a730-e9d4a1996ca4"]);
let OperationList = dynamic(
["SecretGet", "KeyGet", "VaultGet"]);
let TimeSeriesData = AzureDiagnostics
| where TimeGenerated between (startofday(ago(starttime))..startofday(now()))
| where not((identity_claim_appid_g in (Allowedappid)) and OperationName == 'VaultGet')
| where ResourceType =~ "VAULTS" and ResultType =~ "Success"
| where OperationName in (OperationList)
| extend ResultType = column_ifexists("ResultType", "None"), CallerIPAddress = column_ifexists("CallerIPAddress", "None")
| where ResultType !~ "None" and isnotempty(ResultType)
| where CallerIPAddress !~ "None" and isnotempty(CallerIPAddress)
| project TimeGenerated, OperationName, Resource, CallerIPAddress
| make-series HourlyCount=count() on TimeGenerated from startofday(ago(starttime)) to startofday(now()) step timeframe by CallerIPAddress;
//Filter anomolies against TimeSeriesData
let TimeSeriesAlerts = TimeSeriesData
| extend (anomalies, score, baseline) = series_decompose_anomalies(HourlyCount, scorethreshold, -1, 'linefit')
| mv-expand HourlyCount to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double),score to typeof(double), baseline to typeof(long)
| where anomalies > 0 | extend AnomalyHour = TimeGenerated
| where baseline > baselinethreshold // Filtering low count events per baselinethreshold
| project CallerIPAddress, AnomalyHour, TimeGenerated, HourlyCount, baseline, anomalies, score;
let AnomalyHours = TimeSeriesAlerts | where TimeGenerated > ago(2d) | project TimeGenerated;
// Filter the alerts since specified timeframe
TimeSeriesAlerts
| where TimeGenerated > ago(2d)
// Join against base logs since specified timeframe to retrive records associated with the hour of anomoly
| join kind = innerunique (
AzureDiagnostics
| where TimeGenerated > ago(2d)
| where not((identity_claim_appid_g in (Allowedappid)) and OperationName == 'VaultGet')
| where ResourceType =~ "VAULTS" and ResultType =~ "Success"
| where OperationName in (OperationList)
| extend DateHour = bin(TimeGenerated, 1h) // create a new column and round to hour
| where DateHour in ((AnomalyHours)) //filter the dataset to only selected anomaly hours
| extend ResultType = column_ifexists("ResultType", "NoResultType")
| extend requestUri_s = column_ifexists("requestUri_s", "None"), identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g = column_ifexists("identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g", "None"),identity_claim_oid_g = column_ifexists("identity_claim_oid_g", ""),
identity_claim_upn_s = column_ifexists("identity_claim_upn_s", "")
| extend
CallerObjectId = iff(isempty(identity_claim_oid_g), identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g, identity_claim_oid_g),
CallerObjectUPN = iff(isempty(identity_claim_upn_s), identity_claim_http_schemas_xmlsoap_org_ws_2005_05_identity_claims_upn_s, identity_claim_upn_s)
| extend id_s = column_ifexists("id_s", "None"), CallerIPAddress = column_ifexists("CallerIPAddress", "None"), clientInfo_s = column_ifexists("clientInfo_s", "None")
| summarize PerOperationCount=count(), LatestAnomalyTime = arg_max(TimeGenerated,*) by bin(TimeGenerated,1h), Resource, OperationName, id_s, CallerIPAddress, identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g, identity_claim_oid_g, requestUri_s, clientInfo_s
) on CallerIPAddress
| extend
CallerObjectId = iff(isempty(identity_claim_oid_g), identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g, identity_claim_oid_g),
CallerObjectUPN = iff(isempty(identity_claim_upn_s), identity_claim_http_schemas_xmlsoap_org_ws_2005_05_identity_claims_upn_s, identity_claim_upn_s)
| summarize EventCount=count(), OperationNameList = make_set(OperationName,1000), RequestURLList = make_set(requestUri_s, 100), AccountList = make_set(CallerObjectId, 100), AccountMax = arg_max(CallerObjectId,*) by Resource, id_s, clientInfo_s, LatestAnomalyTime
| extend timestamp = LatestAnomalyTime
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountMax
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CallerIPAddress
version: 1.0.6
kind: Scheduled
Stages and Predicates
Parameters
let starttime = 14d;
let timeframe = 1d;
let scorethreshold = 3;
let baselinethreshold = 25;
let Allowedappid = dynamic(["509e4652-da8d-478d-a730-e9d4a1996ca4"]);
let TimeSeriesData and let TimeSeriesAlerts are inlined into the numbered stages below.
Let Binding: OperationList
let OperationList = dynamic(
["SecretGet", "KeyGet", "VaultGet"]);
Let Binding: AnomalyHours
let AnomalyHours = TimeSeriesAlerts | where TimeGenerated > ago(2d) | project TimeGenerated;
Stages 1 to 16 define let TimeSeriesAlerts as the rule's main pipeline source. Stages 17 to 21 use it.
Stage 1: source
AzureDiagnostics
Stage 2: where
| where TimeGenerated between (startofday(ago(starttime))..startofday(now()))
Stage 3: where
| where not((identity_claim_appid_g in (Allowedappid)) and OperationName == 'VaultGet')
Stage 4: where
| where ResourceType =~ "VAULTS" and ResultType =~ "Success"
Stage 5: where
| where OperationName in (OperationList)
Stage 6: extend
| extend ResultType = column_ifexists("ResultType", "None"), CallerIPAddress = column_ifexists("CallerIPAddress", "None")
Stage 7: where
| where ResultType !~ "None" and isnotempty(ResultType)
Stage 8: where
| where CallerIPAddress !~ "None" and isnotempty(CallerIPAddress)
Stage 9: project
| project TimeGenerated, OperationName, Resource, CallerIPAddress
The stages below score time-series anomalies (make-series, series_decompose_anomalies).
Stage 10: make-series
| make-series HourlyCount=count() on TimeGenerated from startofday(ago(starttime)) to startofday(now()) step timeframe by CallerIPAddress
Stage 11: extend
| extend (anomalies, score, baseline) = series_decompose_anomalies(HourlyCount, scorethreshold, -1, 'linefit')
Stage 12: mv-expand
| mv-expand HourlyCount to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double),score to typeof(double), baseline to typeof(long)
Stage 13: where
| where anomalies > 0
Stage 14: extend
| extend AnomalyHour = TimeGenerated
Stage 15: where
| where baseline > baselinethreshold
Stage 16: project
| project CallerIPAddress, AnomalyHour, TimeGenerated, HourlyCount, baseline, anomalies, score
Stage 17: where
TimeSeriesAlerts
| where TimeGenerated > ago(2d)
Stage 18: join
| join kind = innerunique (
AzureDiagnostics
| where TimeGenerated > ago(2d)
| where not((identity_claim_appid_g in (Allowedappid)) and OperationName == 'VaultGet')
| where ResourceType =~ "VAULTS" and ResultType =~ "Success"
| where OperationName in (OperationList)
| extend DateHour = bin(TimeGenerated, 1h)
| where DateHour in ((AnomalyHours))
| extend ResultType = column_ifexists("ResultType", "NoResultType")
| extend requestUri_s = column_ifexists("requestUri_s", "None"), identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g = column_ifexists("identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g", "None"),identity_claim_oid_g = column_ifexists("identity_claim_oid_g", ""),
identity_claim_upn_s = column_ifexists("identity_claim_upn_s", "")
| extend
CallerObjectId = iff(isempty(identity_claim_oid_g), identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g, identity_claim_oid_g),
CallerObjectUPN = iff(isempty(identity_claim_upn_s), identity_claim_http_schemas_xmlsoap_org_ws_2005_05_identity_claims_upn_s, identity_claim_upn_s)
| extend id_s = column_ifexists("id_s", "None"), CallerIPAddress = column_ifexists("CallerIPAddress", "None"), clientInfo_s = column_ifexists("clientInfo_s", "None")
| summarize PerOperationCount=count(), LatestAnomalyTime = arg_max(TimeGenerated,*) by bin(TimeGenerated,1h), Resource, OperationName, id_s, CallerIPAddress, identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g, identity_claim_oid_g, requestUri_s, clientInfo_s
) on CallerIPAddress
Stage 19: extend
extend CallerObjectId, CallerObjectUPN
CallerObjectId =isempty(identity_claim_oid_g)identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_gidentity_claim_oid_gCallerObjectUPN =isempty(identity_claim_upn_s)identity_claim_http_schemas_xmlsoap_org_ws_2005_05_identity_claims_upn_sidentity_claim_upn_sStage 20: summarize
summarize AccountList, AccountMax, EventCount, OperationNameList, RequestURLList by Resource, id_s, clientInfo_s, LatestAnomalyTime
Stage 21: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
OperationName | eq | VaultGet | excludes:OperationName field:"OperationName" value:"VaultGet" |
identity_claim_appid_g | eq | Allowedappid | excludes:identity_claim_appid_g field:"identity_claim_appid_g" value:"Allowedappid" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountList | summarize |
AccountMax | summarize |
EventCount | summarize |
LatestAnomalyTime | summarize |
OperationNameList | summarize |
RequestURLList | summarize |
Resource | summarize |
clientInfo_s | summarize |
id_s | summarize |
timestamp | extend |
Azure secure score one admin
#'This query searches for having 1 Global Administrator reduces the surface area of attack for your Azure tenant, but sets up a single point of failure for the whole tenant. Global Administrators have access to all aspects of Azure'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: F539B2A7-D9E7-4438-AA20-893BC61DF130
name: Azure secure score one admin
description: |
'This query searches for having 1 Global Administrator reduces the surface area of attack for your Azure tenant, but sets up a single point of failure for the whole tenant. Global Administrators have access to all aspects of Azure'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SenservaPro
dataTypes:
- SenservaPro_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1529
query: |
SenservaPro_CL
| where ControlName_s == 'AzureSecureScoreOneAdmin'
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: ControlName_s
- identifier: AadTenantId
columnName: TenantId
- identifier: DisplayName
columnName: TenantDisplayName_s
- entityType: SecurityGroup
fieldMappings:
- identifier: DistinguishedName
columnName: Group_s
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: SourceSystem
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
SenservaPro_CL
Stage 2: where
| where ControlName_s == 'AzureSecureScoreOneAdmin'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ControlName_s | eq |
| field:"ControlName_s" kind:eq value:"AzureSecureScoreOneAdmin" |
Bitglass - New risky user
#'Detects new risky user.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: a123668c-d907-41b9-bf3f-8cb4cd7b163a
name: Bitglass - New risky user
description: |
'Detects new risky user.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: Bitglass
dataTypes:
- Bitglass
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
Bitglass
| where EventType =~ 'access'
| where EventResultDetails has_all ('Added', 'Risky Users')
| extend AccountCustomEntity = User
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
Bitglass
Stage 2: where
| where EventType =~ 'access'
Stage 3: where
| where EventResultDetails has_all ('Added', 'Risky Users')
Stage 4: extend
| extend AccountCustomEntity = User
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventResultDetails | match |
| field:"EventResultDetails" kind:match |
EventType | eq |
| field:"EventType" kind:eq value:"access" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
Cloudflare - Bad client IP
#'Detects requests from IP with bad reputation index.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: a7ce6135-9d55-4f14-b058-adc2e920a4fb
name: Cloudflare - Bad client IP
description: |
'Detects requests from IP with bad reputation index.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CloudflareDataConnector
dataTypes:
- Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let ip_reputation = dynamic(['unknown', 'badHost', 'greylist', 'securityScanner', 'scan', 'tor']);
Cloudflare
| where ClientIPClass in~ (ip_reputation)
| extend CompleteUrl = strcat(HttpRequestHeaderHost,ClientRequestPath)
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: URL
fieldMappings:
- identifier: Url
columnName: CompleteUrl
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let ip_reputation = dynamic(['unknown', 'badHost', 'greylist', 'securityScanner', 'scan', 'tor']);
Stage 1: source
Cloudflare
Stage 2: where
| where ClientIPClass in~ (ip_reputation)
Stage 3: extend
| extend CompleteUrl = strcat(HttpRequestHeaderHost,ClientRequestPath)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ClientIPClass | in |
| field:"ClientIPClass" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CompleteUrl | extend |
Drop 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 drop tables or databases (e.g. for data vandalism).'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
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)
- Execution 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: 237c3855-138c-4588-a68f-b870abd3bfc9
name: Drop 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 drop tables or databases (e.g. for data vandalism).'
severity: Medium
requiredDataConnectors:
- connectorId: AzureSql
dataTypes:
- AzureDiagnostics
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Impact
relevantTechniques:
- T1485
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('drop table', 'drop database'); // 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: 'Drop attempts stateful anomaly on database {{Database}}'
alertDescriptionFormat: 'An anomaly was detected on database {{Database}} with {{countStatementsWithHotwords}} statements with monitored hot words (drop table, drop database etc.) in the last hour. Investigate the database activity for potential malicious attempts to drop tables or databases.'
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('drop table', 'drop database');
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 |
GitHub Security Vulnerability in Repository
#'This alerts when there is a new security vulnerability in a GitHub repository.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Privilege Escalation | |
| Stealth | |
| Credential Access | |
| Lateral Movement |
Rule Body
id: 5436f471-b03d-41cb-b333-65891f887c43
name: GitHub Security Vulnerability in Repository
description: |
'This alerts when there is a new security vulnerability in a GitHub repository.'
severity: Informational
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
- PrivilegeEscalation
- DefenseEvasion
- CredentialAccess
- LateralMovement
relevantTechniques:
- T1190
- T1203
- T1068
- T1211
- T1212
- T1210
query: |
GitHubRepo
| where Action == "vulnerabilityAlert"
| project TimeGenerated, DismmisedAt, Reason, vulnerableManifestFilename, Description, Link, PublishedAt, Severity, Summary
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Link
version: 1.0.3
kind: Scheduled
Stages and Predicates
Stage 1: source
GitHubRepo
Stage 2: where
| where Action == "vulnerabilityAlert"
Stage 3: project
| project TimeGenerated, DismmisedAt, Reason, vulnerableManifestFilename, Description, Link, PublishedAt, Severity, Summary
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"vulnerabilityAlert" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Description | project |
DismmisedAt | project |
Link | project |
PublishedAt | project |
Reason | project |
Severity | project |
Summary | project |
TimeGenerated | project |
vulnerableManifestFilename | project |
Netskope - Heavy Personal Cloud Storage Usage (Shadow IT)
#Detects heavy usage of personal cloud storage applications like personal Dropbox, Google Drive, OneDrive personal, etc. Indicates potential Shadow IT or data leakage risk.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection | |
| Exfiltration |
Rule Body
id: 272f9bca-5fd0-4413-b494-03b2d9f0bb9b
name: Netskope - Heavy Personal Cloud Storage Usage (Shadow IT)
description: |
Detects heavy usage of personal cloud storage applications like personal Dropbox, Google Drive, OneDrive personal, etc. Indicates potential Shadow IT or data leakage risk.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: NetskopeWebTxConnector
dataTypes:
- NetskopeWebTransactions_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
- Collection
relevantTechniques:
- T1567
- T1530
query: |
let heavyUsageThresholdMB = 500;
NetskopeWebTransactions_CL
| where TimeGenerated > ago(1h)
| where isnotempty(CsUsername) and isnotempty(XCsApp)
| where XCsApp has_any ('Dropbox', 'Google Drive', 'OneDrive', 'Box', 'iCloud', 'pCloud', 'MEGA', 'MediaFire', 'WeTransfer')
| where XCsAppInstanceTag contains 'Personal'
or XCsAppInstanceName contains 'Personal'
or XCsAppTags contains 'Unsanctioned'
or not(XCsAppTags contains 'Enterprise')
| summarize
TotalBytes = sum(Bytes),
UploadBytes = sum(CsBytes),
DownloadBytes = sum(ScBytes),
FileCount = dcount(XCsAppObjectName),
Files = make_set(XCsAppObjectName, 10),
Activities = make_set(XCsAppActivity),
EventCount = count()
by CsUsername, XCsApp, XCsAppCategory, XCsAppInstanceName, XCsAppTags, XCDevice, XCCountry
| extend
TotalMB = round(TotalBytes / 1048576.0, 2),
UploadMB = round(UploadBytes / 1048576.0, 2),
DownloadMB = round(DownloadBytes / 1048576.0, 2)
| where TotalMB > heavyUsageThresholdMB or FileCount > 50
| project
TimeGenerated = now(),
User = CsUsername,
CloudApplication = XCsApp,
AppCategory = XCsAppCategory,
AppInstance = XCsAppInstanceName,
AppTags = XCsAppTags,
TotalDataMB = TotalMB,
UploadMB,
DownloadMB,
FileCount,
Files,
Activities,
Device = XCDevice,
Country = XCCountry,
EventCount
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: User
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: CloudApplication
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let heavyUsageThresholdMB = 500;
Stage 1: source
NetskopeWebTransactions_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: where
| where isnotempty(CsUsername) and isnotempty(XCsApp)
Stage 4: where
| where XCsApp has_any ('Dropbox', 'Google Drive', 'OneDrive', 'Box', 'iCloud', 'pCloud', 'MEGA', 'MediaFire', 'WeTransfer')
Stage 5: where
| where XCsAppInstanceTag contains 'Personal'
or XCsAppInstanceName contains 'Personal'
or XCsAppTags contains 'Unsanctioned'
or not(XCsAppTags contains 'Enterprise')
Stage 6: summarize
| summarize
TotalBytes = sum(Bytes),
UploadBytes = sum(CsBytes),
DownloadBytes = sum(ScBytes),
FileCount = dcount(XCsAppObjectName),
Files = make_set(XCsAppObjectName, 10),
Activities = make_set(XCsAppActivity),
EventCount = count()
by CsUsername, XCsApp, XCsAppCategory, XCsAppInstanceName, XCsAppTags, XCDevice, XCCountry
Stage 7: extend
| extend
TotalMB = round(TotalBytes / 1048576.0, 2),
UploadMB = round(UploadBytes / 1048576.0, 2),
DownloadMB = round(DownloadBytes / 1048576.0, 2)
Stage 8: where
| where TotalMB > heavyUsageThresholdMB or FileCount > 50
Stage 9: project
| project
TimeGenerated = now(),
User = CsUsername,
CloudApplication = XCsApp,
AppCategory = XCsAppCategory,
AppInstance = XCsAppInstanceName,
AppTags = XCsAppTags,
TotalDataMB = TotalMB,
UploadMB,
DownloadMB,
FileCount,
Files,
Activities,
Device = XCDevice,
Country = XCCountry,
EventCount
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CsUsername | is_not_null | field:"CsUsername" kind:is_not_null | |
FileCount | gt |
| field:"FileCount" kind:gt value:"50" |
TotalMB | gt |
| field:"TotalMB" kind:gt value:"500" |
XCsApp | is_not_null | field:"XCsApp" kind:is_not_null | |
XCsApp | match |
| field:"XCsApp" kind:match |
XCsAppInstanceName | contains |
| field:"XCsAppInstanceName" kind:contains value:"Personal" |
XCsAppInstanceTag | contains |
| field:"XCsAppInstanceTag" kind:contains value:"Personal" |
XCsAppTags | contains |
| field:"XCsAppTags" kind:contains value:"Unsanctioned" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Activities | project |
AppCategory | project |
AppInstance | project |
AppTags | project |
CloudApplication | project |
Country | project |
Device | project |
DownloadMB | project |
EventCount | project |
FileCount | project |
Files | project |
TimeGenerated | project |
TotalDataMB | project |
UploadMB | project |
User | project |
SAP BTP - Cloud Integration artifact deployment
#Identifies deployment and undeployment of integration artifacts in SAP Cloud Integration. Integration flows are executable code that can process, transform, and route data between systems. Unauthorized artifact deployment could indicate: - Attacker deploying malicious integration flows for data exfiltration - Deployment of rogue code for persistent access - Undeployment of critical integrations causing denial of service
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Persistence |
Rule Body
id: a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d
kind: Scheduled
name: SAP BTP - Cloud Integration artifact deployment
description: |
Identifies deployment and undeployment of integration artifacts in SAP Cloud Integration.
Integration flows are executable code that can process, transform, and route data between
systems.
Unauthorized artifact deployment could indicate:
- Attacker deploying malicious integration flows for data exfiltration
- Deployment of rogue code for persistent access
- Undeployment of critical integrations causing denial of service
severity: High
status: Available
requiredDataConnectors:
- connectorId: SAPBTPAuditEvents
dataTypes:
- SAPBTPAuditLog_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
- Persistence
relevantTechniques:
- T1059
- T1546
query: |
SAPBTPAuditLog_CL
| where Category == "audit.configuration"
| extend objectType = tostring(Message.object.type)
| where objectType in ("Deployment", "Undeployment")
| extend attributes = todynamic(Message.attributes)
| mv-apply attr = attributes on (
summarize
SymbolicName = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "symbolicName"),
ArtifactId = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "id"),
ArtifactVersion = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "version"),
DeployedBy = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "deployedBy"),
UndeployedBy = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "undeployedBy"),
Creator = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "creator"),
TenantName = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "tenantName"),
RuntimeLocationId = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "runtimeLocationId")
)
| extend Actor = case(
isnotempty(DeployedBy), DeployedBy,
isnotempty(UndeployedBy), UndeployedBy,
isnotempty(Creator), Creator,
""
)
| extend ActionCategory = iff(objectType == "Deployment", "Deploy", "Undeploy"),
normalizedAction = iff(objectType == "Deployment", "deployed", "undeployed")
| extend MessageText = strcat("Integration artifact '", SymbolicName, "' (version ", ArtifactVersion, ") was ", normalizedAction, " in tenant ", TenantName)
| extend AccountName = iff(Actor has "@", tostring(split(Actor, "@")[0]), ""),
UPNSuffix = iff(Actor has "@", tostring(split(Actor, "@")[1]), "")
| project
UpdatedOn,
Actor,
AccountName,
UPNSuffix,
MessageText,
ArtifactName = SymbolicName,
ArtifactId,
ArtifactVersion,
ActionCategory,
ObjectType = objectType,
TenantName,
RuntimeLocationId,
Tenant,
CloudApp = "SAP Cloud Integration"
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: CloudApp
alertDetailsOverride:
alertDisplayNameFormat: 'SAP Cloud Integration: {{MessageText}}'
alertDescriptionFormat: |
{{MessageText}} by {{Actor}}.
This could indicate:
- Legitimate integration artifact deployment or maintenance
- Unauthorized deployment of malicious integration code
- Attacker undeploying security-relevant integrations
customDetails:
ArtifactName: ArtifactName
ArtifactId: ArtifactId
ArtifactVersion: ArtifactVersion
ActionCategory: ActionCategory
TenantName: TenantName
RuntimeLocationId: RuntimeLocationId
version: 1.1.1
Stages and Predicates
Stage 1: source
SAPBTPAuditLog_CL
Stage 2: where
| where Category == "audit.configuration"
Stage 3: extend
| extend objectType = tostring(Message.object.type)
Stage 4: where
| where objectType in ("Deployment", "Undeployment")
Stage 5: extend
| extend attributes = todynamic(Message.attributes)
Stage 6: kusto:mv-apply
| mv-apply attr = attributes on (
summarize
SymbolicName = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "symbolicName"),
ArtifactId = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "id"),
ArtifactVersion = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "version"),
DeployedBy = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "deployedBy"),
UndeployedBy = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "undeployedBy"),
Creator = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "creator"),
TenantName = take_anyif(tostring(attr.["new"]), tostring(attr.name) == "tenantName"),
RuntimeLocationId = take_anyif(tostring(coalesce(attr.["new"], attr.["old"])), tostring(attr.name) == "runtimeLocationId")
)
Stage 7: extend (4 consecutive steps)
| extend Actor = case(
isnotempty(DeployedBy), DeployedBy,
isnotempty(UndeployedBy), UndeployedBy,
isnotempty(Creator), Creator,
""
)
| extend ActionCategory = iff(objectType == "Deployment", "Deploy", "Undeploy"),
normalizedAction = iff(objectType == "Deployment", "deployed", "undeployed")
| extend MessageText = strcat("Integration artifact '", SymbolicName, "' (version ", ArtifactVersion, ") was ", normalizedAction, " in tenant ", TenantName)
| extend AccountName = iff(Actor has "@", tostring(split(Actor, "@")[0]), ""),
UPNSuffix = iff(Actor has "@", tostring(split(Actor, "@")[1]), "")
Actor =isnotempty(DeployedBy)DeployedByisnotempty(UndeployedBy)UndeployedByisnotempty(Creator)Creator""Stage 8: project
| project
UpdatedOn,
Actor,
AccountName,
UPNSuffix,
MessageText,
ArtifactName = SymbolicName,
ArtifactId,
ArtifactVersion,
ActionCategory,
ObjectType = objectType,
TenantName,
RuntimeLocationId,
Tenant,
CloudApp = "SAP Cloud Integration"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"audit.configuration" |
objectType | in |
| field:"objectType" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
ActionCategory | project |
Actor | project |
ArtifactId | project |
ArtifactName | project |
ArtifactVersion | project |
CloudApp | project |
MessageText | project |
ObjectType | project |
RuntimeLocationId | project |
Tenant | project |
TenantName | project |
UPNSuffix | project |
UpdatedOn | project |
Tomcat - Multiple empty requests from same IP
#'Detects multiple empty requests from same IP'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Impact |
Rule Body
id: 7c9a1026-4872-11ec-81d3-0242ac130003
name: Tomcat - Multiple empty requests from same IP
description: |
'Detects multiple empty requests from same IP'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- Tomcat_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Impact
relevantTechniques:
- T1190
- T1133
- T1499
query: |
let threshold = 50;
TomcatEvent
| where HttpResponseBodyBytes == 0
| summarize MultipleClientErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
| where MultipleClientErrors > threshold
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 50;
Stage 1: source
TomcatEvent
Stage 2: where
| where HttpResponseBodyBytes == 0
Stage 3: summarize
| summarize MultipleClientErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
Stage 4: where
| where MultipleClientErrors > threshold
Stage 5: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MultipleClientErrors | summarize |
SrcIpAddr | summarize |
IPCustomEntity | extend |
Ubiquiti - SSH from external source
#'Detects remote to local (R2L) SSH connection to internal host.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 0998a19d-8451-4cdd-8493-fc342816a197
name: Ubiquiti - SSH from external source
description: |
'Detects remote to local (R2L) SSH connection to internal host.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- Ubiquiti_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1133
query: |
UbiquitiAuditEvent
| where EventCategory =~ 'firewall'
| where ipv4_is_private(SrcIpAddr) == 'False'
| where ipv4_is_private(DstIpAddr)
| where DstPortNumber == '22'
| extend IPCustomEntity = DstIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
UbiquitiAuditEvent
Stage 2: where
| where EventCategory =~ 'firewall'
Stage 3: where
| where ipv4_is_private(SrcIpAddr) == 'False'
Stage 4: where
| where ipv4_is_private(DstIpAddr)
Stage 5: where
| where DstPortNumber == '22'
Stage 6: extend
| extend IPCustomEntity = DstIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DstIpAddr | cidr_match |
| field:"dest_ip" kind:cidr_match |
DstPortNumber | eq |
| field:"DstPortNumber" kind:eq value:"22" |
EventCategory | eq |
| field:"EventCategory" kind:eq value:"firewall" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
SrcIpAddr | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 | excludes:SrcIpAddr |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
Vaikora - Behavioral anomaly detected
#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.
Identifies AI agent behavioral anomalies flagged by Vaikora with an anomaly score of 0.7 or above, indicating significant deviation from the agent's established behavioral baseline.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Stealth |
Rule Body
id: e61258ec-1a7f-454c-95b5-458a6edb1ea4
name: Vaikora - Behavioral anomaly detected
description: |
Identifies AI agent behavioral anomalies flagged by Vaikora with an anomaly score of 0.7 or above, indicating significant deviation from the agent's established behavioral baseline.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: VaikoraSentinel
dataTypes:
- Vaikora_AgentSignals_CL
queryFrequency: 30m
queryPeriod: 1h
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
- DefenseEvasion
- Execution
relevantTechniques:
- T1059
- T1027
query: |
Vaikora_AgentSignals_CL
| where TimeGenerated > ago(1h)
| where is_anomaly_b == true
| where anomaly_score_d >= 0.7
| summarize
AnomalyCount = count(),
MaxAnomalyScore = max(anomaly_score_d),
AvgAnomalyScore = avg(anomaly_score_d),
AnomalyReasons = make_set(anomaly_reason_s),
ActionTypes = make_set(action_type_s)
by AgentId = agent_id_s, Severity = severity_s
| extend
ReasonList = strcat_array(AnomalyReasons, "; "),
ActionList = strcat_array(ActionTypes, ", ")
suppressionDuration: 30m
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: Selected
groupByEntities:
- Account
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
MaxAnomalyScore: MaxAnomalyScore
AvgAnomalyScore: AvgAnomalyScore
AnomalyCount: AnomalyCount
AnomalyReasons: ReasonList
ActionTypes: ActionList
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AgentId
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Vaikora_AgentSignals_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: where
| where is_anomaly_b == true
Stage 4: where
| where anomaly_score_d >= 0.7
Stage 5: summarize
| summarize
AnomalyCount = count(),
MaxAnomalyScore = max(anomaly_score_d),
AvgAnomalyScore = avg(anomaly_score_d),
AnomalyReasons = make_set(anomaly_reason_s),
ActionTypes = make_set(action_type_s)
by AgentId = agent_id_s, Severity = severity_s
Stage 6: extend
| extend
ReasonList = strcat_array(AnomalyReasons, "; "),
ActionList = strcat_array(ActionTypes, ", ")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
anomaly_score_d | ge |
| field:"anomaly_score_d" kind:ge value:"0.7" |
is_anomaly_b | eq |
| field:"is_anomaly_b" kind:eq value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActionTypes | summarize |
AgentId | summarize |
AnomalyCount | summarize |
AnomalyReasons | summarize |
AvgAnomalyScore | summarize |
MaxAnomalyScore | summarize |
Severity | summarize |
ActionList | extend |
ReasonList | extend |
Veeam ONE Immutability State
#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 changes in the immutability state of Veeam Backup & Replication repositories. This might indicate configuration changes that require review.
Rule Body
id: b5785a66-3722-4bdc-9d12-bb40f2cf4824
name: Veeam ONE Immutability State
description: Detects changes in the immutability state of Veeam Backup & Replication
repositories. This might indicate configuration changes that require review.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: VeeamCustomTablesDataConnector
dataTypes:
- VeeamOneTriggeredAlarms_CL
queryFrequency: 3h
queryPeriod: 3h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: VeeamOneTriggeredAlarms_CL | where PredefinedAlarmId == 376
version: 1.0.1
kind: Scheduled
customDetails:
PredefinedAlarmId: PredefinedAlarmId
TriggeredAlarmId: TriggeredAlarmId
Description: Description
Name: Name
VoneHostName: VoneHostName
TriggeredTime: TriggeredTime
Status: Status
Comment: Comment
ObjectId: ObjectId
ObjectName: ObjectName
ObjectType: ObjectType
Stages and Predicates
Stage 1: source
VeeamOneTriggeredAlarms_CL
Stage 2: where
| where PredefinedAlarmId == 376
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PredefinedAlarmId | eq |
| field:"PredefinedAlarmId" kind:eq value:"376" |
Zinc Actor IOCs files - October 2022
#Identifies a match across filename and commandline IOC's related to an actor tracked by Microsoft as Zinc. Reference: https://www.microsoft.com/security/blog/2022/09/29/zinc-weaponizing-open-source-software/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
Rule Body
id: 9a7f6651-801b-491c-a548-8b454b356eaa
name: Zinc Actor IOCs files - October 2022
description: |
'Identifies a match across filename and commandline IOC's related to an actor tracked by Microsoft as Zinc.
Reference: https://www.microsoft.com/security/blog/2022/09/29/zinc-weaponizing-open-source-software/'
severity: High
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceNetworkEvents
- DeviceFileEvents
- DeviceEvents
- DeviceProcessEvents
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1546
tags:
- Zinc
query: |
let iocs = externaldata(DateAdded:string,IoC:string,Type:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/ZincOctober2022IOCs.csv"] with (format="csv", ignoreFirstRecord=True);
let file_path = (iocs | where Type =~ "filepath" | project IoC);
let commandline = (iocs | where Type =~ "commandline" | project IoC);
(union isfuzzy=true
(DeviceNetworkEvents
| where InitiatingProcessFolderPath has_any (file_path) or InitiatingProcessCommandLine has_any (commandline)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, RemoteIP, RemoteUrl, LocalIP, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName
),
(Event
| where Source =~ "Microsoft-Windows-Sysmon"
| where EventID == 1
| extend EvData = parse_xml(EventData)
| extend EventDetail = EvData.DataItem.EventData.Data
| extend Image = EventDetail.[4].["#text"], CommandLine = EventDetail.[10].["#text"]
| where Image has_any (file_path) or CommandLine has_any (commandline)
| project TimeGenerated, EventDetail, UserName, Computer, Type, Source, CommandLine, Image
| extend Type = strcat(Type, ": ", Source)
| extend timestamp = TimeGenerated, HostEntity = Computer , AccountEntity = UserName, ProcessEntity = tostring(split(Image, '\\', -1)[-1])
),
(DeviceProcessEvents
| where ( InitiatingProcessCommandLine has_any (file_path)) or ( InitiatingProcessCommandLine has_any (commandline)) or (InitiatingProcessFolderPath has_any (file_path)) or (InitiatingProcessFolderPath has_any (commandline)) or (FolderPath has_any (file_path)) or (FolderPath has_any (commandline))
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessSHA256, FolderPath, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = InitiatingProcessAccountName, ProcessEntity = InitiatingProcessFileName
),
(DeviceFileEvents
| where (InitiatingProcessFolderPath has_any (file_path)) or (InitiatingProcessFolderPath has_any (commandline)) or (FolderPath has_any (file_path)) or (FolderPath has_any (commandline)) or ( InitiatingProcessCommandLine has_any (commandline)) or ( InitiatingProcessCommandLine has_any (file_path))
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, RequestAccountName, RequestSourceIP, InitiatingProcessSHA256, FolderPath, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = RequestAccountName, ProcessEntity = InitiatingProcessFileName, AlgorithmEntity = "SHA256", FileHashEntity = InitiatingProcessSHA256
),
(DeviceEvents
| where ( InitiatingProcessCommandLine has_any (file_path)) or ( InitiatingProcessCommandLine has_any (commandline)) or (InitiatingProcessFolderPath has_any (file_path)) or (InitiatingProcessFolderPath has_any (commandline)) or (FolderPath has_any (file_path)) or (FolderPath has_any (commandline))
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, FolderPath, Type
| extend CommandLine = InitiatingProcessCommandLine
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = InitiatingProcessAccountName, ProcessEntity = InitiatingProcessFileName
),
(SecurityEvent
| where EventID == 4688
| where ( CommandLine has_any (file_path)) or ( CommandLine has_any (commandline)) or (NewProcessName has_any (file_path)) or (NewProcessName has_any (commandline)) or (ParentProcessName has_any (file_path)) or (ParentProcessName has_any (commandline))
| project TimeGenerated, Computer, NewProcessName, ParentProcessName, Account, NewProcessId, Type
| extend timestamp = TimeGenerated, HostEntity = Computer , AccountEntity = Account, ProcessEntity = NewProcessName
)
)
| extend HostName = tostring(split(HostEntity, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(HostEntity, '.'), 1, -1), '.'))
| extend Name = tostring(split(AccountEntity, '@', 0)[0]), UPNSuffix = tostring(split(AccountEntity, '@', 1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: RemoteIP
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ProcessEntity
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: AlgorithmEntity
- identifier: Value
columnName: FileHashEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Let Binding: iocs
let iocs = externaldata(DateAdded:string,IoC:string,Type:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/ZincOctober2022IOCs.csv"] with (format="csv", ignoreFirstRecord=True);
Let Binding: file_path
let file_path = (iocs | where Type =~ "filepath" | project IoC);
Let Binding: commandline
let commandline = (iocs | where Type =~ "commandline" | project IoC);
Union isfuzzy=true (6 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: DeviceNetworkEvents, Event, DeviceProcessEvents, DeviceFileEvents, DeviceEvents, SecurityEvent
Leg 1: DeviceNetworkEvents
DeviceNetworkEvents
| where InitiatingProcessFolderPath has_any (file_path) or InitiatingProcessCommandLine has_any (commandline)
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, RemoteIP, RemoteUrl, LocalIP, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName
Leg 2: Event
Event
| where Source =~ "Microsoft-Windows-Sysmon"
| where EventID == 1
| extend EvData = parse_xml(EventData)
| extend EventDetail = EvData.DataItem.EventData.Data
| extend Image = EventDetail.[4].["#text"], CommandLine = EventDetail.[10].["#text"]
| where Image has_any (file_path) or CommandLine has_any (commandline)
| project TimeGenerated, EventDetail, UserName, Computer, Type, Source, CommandLine, Image
| extend Type = strcat(Type, ": ", Source)
| extend timestamp = TimeGenerated, HostEntity = Computer , AccountEntity = UserName, ProcessEntity = tostring(split(Image, '\\', -1)[-1])
Leg 3: DeviceProcessEvents
DeviceProcessEvents
| where ( InitiatingProcessCommandLine has_any (file_path)) or ( InitiatingProcessCommandLine has_any (commandline)) or (InitiatingProcessFolderPath has_any (file_path)) or (InitiatingProcessFolderPath has_any (commandline)) or (FolderPath has_any (file_path)) or (FolderPath has_any (commandline))
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessSHA256, FolderPath, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = InitiatingProcessAccountName, ProcessEntity = InitiatingProcessFileName
Leg 4: DeviceFileEvents
DeviceFileEvents
| where (InitiatingProcessFolderPath has_any (file_path)) or (InitiatingProcessFolderPath has_any (commandline)) or (FolderPath has_any (file_path)) or (FolderPath has_any (commandline)) or ( InitiatingProcessCommandLine has_any (commandline)) or ( InitiatingProcessCommandLine has_any (file_path))
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, RequestAccountName, RequestSourceIP, InitiatingProcessSHA256, FolderPath, Type
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = RequestAccountName, ProcessEntity = InitiatingProcessFileName, AlgorithmEntity = "SHA256", FileHashEntity = InitiatingProcessSHA256
Leg 5: DeviceEvents
DeviceEvents
| where ( InitiatingProcessCommandLine has_any (file_path)) or ( InitiatingProcessCommandLine has_any (commandline)) or (InitiatingProcessFolderPath has_any (file_path)) or (InitiatingProcessFolderPath has_any (commandline)) or (FolderPath has_any (file_path)) or (FolderPath has_any (commandline))
| project TimeGenerated, ActionType, DeviceId, DeviceName, InitiatingProcessAccountDomain, InitiatingProcessAccountName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessParentFileName, InitiatingProcessFileName, FolderPath, Type
| extend CommandLine = InitiatingProcessCommandLine
| extend timestamp = TimeGenerated, HostEntity = DeviceName , AccountEntity = InitiatingProcessAccountName, ProcessEntity = InitiatingProcessFileName
Leg 6: SecurityEvent
SecurityEvent
| where EventID == 4688
| where ( CommandLine has_any (file_path)) or ( CommandLine has_any (commandline)) or (NewProcessName has_any (file_path)) or (NewProcessName has_any (commandline)) or (ParentProcessName has_any (file_path)) or (ParentProcessName has_any (commandline))
| project TimeGenerated, Computer, NewProcessName, ParentProcessName, Account, NewProcessId, Type
| extend timestamp = TimeGenerated, HostEntity = Computer , AccountEntity = Account, ProcessEntity = NewProcessName
Applied to the Combined Result
| extend HostName = tostring(split(HostEntity, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(HostEntity, '.'), 1, -1), '.'))
| extend Name = tostring(split(AccountEntity, '@', 0)[0]), UPNSuffix = tostring(split(AccountEntity, '@', 1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CommandLine | match |
| field:"CommandLine" kind:match |
EventID | eq |
| field:"EventID" kind:eq |
FolderPath | match |
| field:"Image" kind:match |
Image | match |
| field:"Image" kind:match value:"file_path" |
InitiatingProcessCommandLine | match |
| field:"CommandLine" kind:match |
InitiatingProcessFolderPath | match |
| field:"ParentImage" kind:match |
NewProcessName | match |
| field:"Image" kind:match |
ParentProcessName | match |
| field:"ParentImage" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
Computer | project |
NewProcessId | project |
NewProcessName | project |
ParentProcessName | project |
TimeGenerated | project |
Type | project |
AccountEntity | extend |
HostEntity | extend |
ProcessEntity | extend |
timestamp | extend |
DnsDomain | extend |
HostName | extend |
Name | extend |
UPNSuffix | extend |