Detection rules › Kusto
Kusto rules: 2j
Apache - Requests to rare files
#'Shows requests to rare files'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 14d7e15e-3afb-11ec-8d3d-0242ac130003
name: Apache - Requests to rare files
description: |
'Shows requests to rare files'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- ApacheHTTPServer_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
let lb_period_start = 14d;
let lb_period_end = 1d;
let known_files = ApacheHTTPServer
| where TimeGenerated between (ago(lb_period_start) .. ago(lb_period_end))
| extend File = extract(@"(.*\/)?(.*)", 2, tostring(UrlOriginal))
| summarize makeset(File);
ApacheHTTPServer
| where TimeGenerated > ago(24h)
| extend File = extract(@"(.*\/)?(.*)", 2, tostring(UrlOriginal))
| where File in~ (known_files)
| extend FileCustomEntity = File, UrlCustomEntity = UrlOriginal
entityMappings:
- entityType: File
fieldMappings:
- identifier: Name
columnName: FileCustomEntity
- entityType: URL
fieldMappings:
- identifier: Url
columnName: UrlCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let lb_period_start = 14d;
let lb_period_end = 1d;
Let Binding: known_files
let known_files = ApacheHTTPServer
| where TimeGenerated between (ago(lb_period_start) .. ago(lb_period_end))
| extend File = extract(@"(.*\/)?(.*)", 2, tostring(UrlOriginal))
| summarize makeset(File);
Stage 1: source
ApacheHTTPServer
Stage 2: where
| where TimeGenerated > ago(24h)
Stage 3: extend
| extend File = extract(@"(.*\/)?(.*)", 2, tostring(UrlOriginal))
Stage 4: where
| where File in~ (known_files)
Stage 5: extend
| extend FileCustomEntity = File, UrlCustomEntity = UrlOriginal
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
File | in |
| field:"File" kind:in value:"known_files" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
File | extend |
FileCustomEntity | extend |
UrlCustomEntity | extend |
API - Anomaly Detection
#'42Crunch API protection anomaly detection'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: 2c59e609-e0a0-4e8e-adc5-ab4224be8a36
name: API - Anomaly Detection
description: |
'42Crunch API protection anomaly detection'
severity: Low
requiredDataConnectors:
- connectorId: 42CrunchAPIProtection
dataTypes:
- FortyTwoCrunchAPIProtection
- connectorId: FortyTwoCrunchAPIProtection
dataTypes:
- FortyTwoCrunchAPIProtection
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: SingleAlert
status: Available
tactics:
- Reconnaissance
relevantTechniques:
- T1593
- T1589
query: |
let infoRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m)
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where UriPath has "/api/users/info" and Status == 200;
let timestamp = toscalar(infoRec | top 1 by Timestamp desc | summarize by Timestamp);
let activityRec = FortyTwoCrunchAPIProtection
| where Timestamp < timestamp and TimeGenerated >= ago(5m)
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where UriPath has "/api/users/activity" and Status == 200;
activityRec
customDetails:
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIp
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InstanceName
version: 3.0.1
kind: Scheduled
Stages and Predicates
let activityRec is inlined into the numbered stages below.
Let Binding: infoRec
let infoRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m)
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where UriPath has "/api/users/info" and Status == 200;
Let Binding: timestamp
let timestamp = toscalar(infoRec | top 1 by Timestamp desc | summarize by Timestamp);
Stage 1: source
FortyTwoCrunchAPIProtection
Stage 2: where
| where Timestamp < timestamp and TimeGenerated >= ago(5m)
Stage 3: project-away
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
Stage 4: where
| where UriPath has "/api/users/activity" and Status == 200
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Status | eq |
| field:"Status" kind:eq value:"200" |
UriPath | match |
| field:"UriPath" kind:match value:"/api/users/activity" |
AWSCloudTrail - Amazon ECR image scanning disabled
#Identifies Amazon ECR image scanning being disabled. This change can reduce visibility into vulnerable container images and may indicate defense evasion or weakening of container security controls.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Telemetry Coverage
Rule Body
id: 19602494-94af-43c8-90ba-eb0e14999612
name: AWSCloudTrail - Amazon ECR image scanning disabled
description: |
Identifies Amazon ECR image scanning being disabled. This change can reduce visibility into vulnerable container images and may indicate defense evasion or weakening of container security controls.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562.001
query: |
AWSCloudTrail
| where EventName == "PutImageScanningConfiguration" and isempty(ErrorCode) and isempty(ErrorMessage)
| extend scanOnPush = parse_json(tostring((parse_json(RequestParameters).imageScanningConfiguration))).scanOnPush
| where scanOnPush == false
| 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]), "")
| distinct TimeGenerated, EventName, SourceIpAddress, UserIdentityArn, UserIdentityUserName, RecipientAccountId, AccountName, AccountUPNSuffix
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
UserIdentityArn: UserIdentityArn
UserIdentityUserName: UserIdentityUserName
RecipientAccountId: RecipientAccountId
alertDetailsOverride:
alertDisplayNameFormat: 'Amazon ECR image scanning disabled by {{AccountName}} from {{SourceIpAddress}}'
alertDescriptionFormat: 'Amazon ECR image scanning was disabled by {{AccountName}} from {{SourceIpAddress}} in account {{RecipientAccountId}}'
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName == "PutImageScanningConfiguration" and isempty(ErrorCode) and isempty(ErrorMessage)
Stage 3: extend
| extend scanOnPush = parse_json(tostring((parse_json(RequestParameters).imageScanningConfiguration))).scanOnPush
Stage 4: where
| where scanOnPush == false
Stage 5: 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]), "")
Stage 6: distinct
| distinct TimeGenerated, EventName, SourceIpAddress, UserIdentityArn, UserIdentityUserName, RecipientAccountId, AccountName, AccountUPNSuffix
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:"PutImageScanningConfiguration" |
scanOnPush | eq |
| field:"scanOnPush" kind:eq value:"false" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
scanOnPush | extend |
UserIdentityArn | extend |
UserName | extend |
AccountName | extend |
AccountUPNSuffix | extend |
BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC10 Scenario B Privileges
#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 Principals With ADCS ESC10 Scenario B Privileges
Rule Body
id: d9fb446e-58a9-4788-afee-66a2b5391347
name: BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC10 Scenario
B Privileges
description: BloodHound Attack Path Finding - Non-Tier Zero Principals With ADCS ESC10
Scenario B Privileges
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 == "T0ADCSESC10b"
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 == "T0ADCSESC10b"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0ADCSESC10b" |
Cisco SE - Malware execusion on host
#'Detects malware execution on host.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: aea4468e-6322-48b6-bd83-f9d300cce855
name: Cisco SE - Malware execusion on host
description: |
'Detects malware execution on host.'
severity: High
requiredDataConnectors:
- connectorId: CiscoSecureEndpoint
dataTypes:
- CiscoSecureEndpoint
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Execution
relevantTechniques:
- T1204.002
query: |
CiscoSecureEndpoint
| where EventMessage has 'Executed Malware'
| extend HostCustomEntity = DstHostname, MalwareCustomEntity = ThreatName
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostCustomEntity
- entityType: Malware
fieldMappings:
- identifier: Name
columnName: MalwareCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CiscoSecureEndpoint
Stage 2: where
| where EventMessage has 'Executed Malware'
Stage 3: extend
| extend HostCustomEntity = DstHostname, MalwareCustomEntity = ThreatName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | match |
| field:"EventMessage" kind:match value:"Executed Malware" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostCustomEntity | extend |
MalwareCustomEntity | extend |
Detect requests for an uncommon resources on the web (ASIM Web Session)
#'This detection mechanism examines connections made to a domain where only a single file is requested, which is considered unusual since most contemporary web applications require additional resources. Such activity is often associated with malware beaconing or tracking URLs delivered via emails. The query includes a sample set of popular web script extensions (scriptExtensions), which should be customized to align with the specific requirements of your environment'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: c99cf650-c53b-4c4c-9671-7d7500191a10
name: Detect requests for an uncommon resources on the web (ASIM Web Session)
description: |
'This detection mechanism examines connections made to a domain where only a single file is requested, which is considered unusual since most contemporary web applications require additional resources. Such activity is often associated with malware beaconing or tracking URLs delivered via emails.
The query includes a sample set of popular web script extensions (scriptExtensions), which should be customized to align with the specific requirements of your environment'
severity: Low
status: Available
tags:
- Schema: WebSession
SchemaVersion: 0.2.6
- RuleReference: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Zscaler%20Internet%20Access/Analytic%20Rules/Zscaler-LowVolumeDomainRequests.yaml
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1102
- T1071
query: |
let lookback = 1d;
let scriptExtensions = dynamic([".php", ".aspx", ".asp", ".cfml", ".py", ".sh", ".bash", ".pl"]);
//The number of URI's seen to be suspicious, higher = less likely to be suspicious
let uriThreshold = 1;
let ReqestCountThreshold = 10; // set minimum requests count to confirm repetitive connections
// Only look at connections that were allowed through the web proxy
_Im_WebSession (starttime=ago(lookback), eventresult='Success')
| project
SrcBytes,
DstBytes,
Url,
TimeGenerated,
DstIpAddr,
SrcIpAddr,
HttpRequestMethod,
HttpReferrer
| where not(ipv4_is_private(DstIpAddr)) // Only take traffic going to internet
| extend DestHostName = tostring(parse_url(Url)["Host"])
// Only look at connections where some data was exchanged.
| where SrcBytes > 0 and DstBytes > 0
// Extract Domain
| extend Domain = iif(countof(DestHostName, '.') >= 2, strcat(split(DestHostName, '.')[-2], '.', split(DestHostName, '.')[-1]), DestHostName)
| extend GetData = iff(Url contains "?", 1, 0)
| summarize
EventStartTime = min(TimeGenerated),
EventEndTime = max(TimeGenerated),
make_set(Url, 100),
make_set(DstIpAddr, 100),
make_set(SrcIpAddr, 100),
EventCount = count(),
make_set(HttpRequestMethod, 10),
max(GetData),
max(HttpReferrer)
by Domain
// Determine the number of URIs that have been visited for the domain
| extend destinationURICount = array_length(set_Url)
| where destinationURICount <= uriThreshold and EventCount > ReqestCountThreshold // check for repetitive requests for single resource only.
| where tostring(set_Url) has_any(scriptExtensions)
//Remove matches with referer
| where max_HttpReferrer == ""
//Keep requests where data was transferred either in a GET with parameters or a POST
| where set_HttpRequestMethod in~ ("POST") or max_GetData == 1
//Defeat email click tracking, may increase FN's while decreasing FP's
| where set_Url !has "click" and set_HttpRequestMethod !has "GET"
| mv-expand set_Url, set_DstIpAddr, set_SrcIpAddr
| extend
RequestURL = tostring(set_Url),
DestinationIP = tostring(set_DstIpAddr),
SourceIP = tostring(set_SrcIpAddr)
| project
EventStartTime,
EventEndTime,
SourceIP,
DestinationIP,
EventCount,
RequestURL,
set_HttpRequestMethod
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DestinationIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: RequestURL
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
EventStartTime: EventStartTime
EventEndTime: EventEndTime
EventCount: EventCount
alertDetailsOverride:
alertDisplayNameFormat: "User with IP '{{SourceIP}}' has been observed making request for a rare resource"
alertDescriptionFormat: "User requested (TotalEvents='{{EventCount}}') for URL '{{RequestURL}}' which contains a known script extension. The domain associated with this URL has not been accessed by any other user. This activity could be a potential beaconing activity to maintain control over compromised systems, receive instructions, or exfiltrate data"
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let lookback = 1d;
let scriptExtensions = dynamic([".php", ".aspx", ".asp", ".cfml", ".py", ".sh", ".bash", ".pl"]);
let uriThreshold = 1;
let ReqestCountThreshold = 10;
Stage 1: source
_Im_WebSession (starttime=ago(lookback), eventresult='Success')
Stage 2: project
| project
SrcBytes,
DstBytes,
Url,
TimeGenerated,
DstIpAddr,
SrcIpAddr,
HttpRequestMethod,
HttpReferrer
Stage 3: where
| where not(ipv4_is_private(DstIpAddr))
Stage 4: extend
| extend DestHostName = tostring(parse_url(Url)["Host"])
Stage 5: where
| where SrcBytes > 0 and DstBytes > 0
Stage 6: extend
| extend Domain = iif(countof(DestHostName, '.') >= 2, strcat(split(DestHostName, '.')[-2], '.', split(DestHostName, '.')[-1]), DestHostName)
Stage 7: extend
| extend GetData = iff(Url contains "?", 1, 0)
GetData =Url contains "?"10Stage 8: summarize
| summarize
EventStartTime = min(TimeGenerated),
EventEndTime = max(TimeGenerated),
make_set(Url, 100),
make_set(DstIpAddr, 100),
make_set(SrcIpAddr, 100),
EventCount = count(),
make_set(HttpRequestMethod, 10),
max(GetData),
max(HttpReferrer)
by Domain
Stage 9: extend
| extend destinationURICount = array_length(set_Url)
Stage 10: where
| where destinationURICount <= uriThreshold and EventCount > ReqestCountThreshold
Stage 11: where
| where tostring(set_Url) has_any(scriptExtensions)
Stage 12: where
| where max_HttpReferrer == ""
Stage 13: where
| where set_HttpRequestMethod in~ ("POST") or max_GetData == 1
Stage 14: where
| where set_Url !has "click" and set_HttpRequestMethod !has "GET"
Stage 15: mv-expand
| mv-expand set_Url, set_DstIpAddr, set_SrcIpAddr
Stage 16: extend
| extend
RequestURL = tostring(set_Url),
DestinationIP = tostring(set_DstIpAddr),
SourceIP = tostring(set_SrcIpAddr)
Stage 17: project
| project
EventStartTime,
EventEndTime,
SourceIP,
DestinationIP,
EventCount,
RequestURL,
set_HttpRequestMethod
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DstBytes | gt |
| field:"DstBytes" kind:gt value:"0" |
EventCount | gt |
| field:"EventCount" kind:gt value:"10" |
SrcBytes | gt |
| field:"SrcBytes" kind:gt value:"0" |
destinationURICount | le |
| field:"destinationURICount" kind:le value:"1" |
max_GetData | eq |
| field:"max_GetData" kind:eq value:"1" |
set_HttpRequestMethod | in |
| field:"set_HttpRequestMethod" kind:in value:"POST" |
set_Url | match |
| field:"set_Url" kind:match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DstIpAddr | 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:DstIpAddr |
set_HttpRequestMethod | match | GET | excludes:set_HttpRequestMethod field:"set_HttpRequestMethod" value:"GET" |
set_Url | match | click | excludes:set_Url field:"set_Url" value:"click" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DestinationIP | project |
EventCount | project |
EventEndTime | project |
EventStartTime | project |
RequestURL | project |
SourceIP | project |
set_HttpRequestMethod | project |
Google DNS - Possible data exfiltration
#'Detects possible data exfiltration.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: 705bed63-668f-4508-9d2d-26faf4010700
name: Google DNS - Possible data exfiltration
description: |
'Detects possible data exfiltration.'
severity: High
requiredDataConnectors:
- connectorId: GCPDNSDataConnector
dataTypes:
- GCPCloudDNS
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1567
query: |
GCPCloudDNS
| where Query has 'webhook.site'
| extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DNSCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GCPCloudDNS
Stage 2: where
| where Query has 'webhook.site'
Stage 3: extend
| extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Query | match |
| field:"Query" kind:match value:"webhook.site" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DNSCustomEntity | extend |
IPCustomEntity | extend |
Hunt Device Discovery Subnet Ranges
#This KQL query helps you identify which subnet ranges are behind the Microsoft Defender for Endpoint Device Discovery 'Monitored Networks' page. By using this query you can investigate if all of your corporate networks are being monitored and change monitored states effectivly. More information can be found in the references.
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceNetworkInfo action any: Device network configuration |
Rule Body
// OPTIONAL - Device cap used to ignore network with less then X devices in them
let device_cap = 0;
DeviceNetworkInfo
| where Timestamp > ago(7d)
// Ignore empty networks
| where ConnectedNetworks != ""
// Get networks data
| extend ConnectedNetworksExp = parse_json(ConnectedNetworks)
| mv-expand bagexpansion = array ConnectedNetworks=ConnectedNetworksExp
| extend NetworkName = tostring(ConnectedNetworks ["Name"]), NetworkDescription = tostring(ConnectedNetworks ["Description"]), NetworkCategory = tostring(ConnectedNetworks ["Category"])
// Get subnet data for IPv4 Addresses
| extend IPAddressesExp = parse_json(IPAddresses)
| mv-expand bagexpansion = array IPAddresses=IPAddressesExp
| extend IPAddress = tostring(IPAddresses ["IPAddress"]), SubnetPrefix = tolong(IPAddresses ["SubnetPrefix"])
| extend NetworkAddress = format_ipv4(IPAddress, SubnetPrefix)
| extend SubnetRange = strcat(NetworkAddress, "/", SubnetPrefix)
// Exclude IPv6 and APIPPA
| where SubnetPrefix <= 32
| where IPAddress !startswith "169.254"
// Ignore unidentified networks
| where not(NetworkName has_any ("Unidentified", "Identifying..."))
// Provide list
| distinct DeviceId, NetworkName, IPv4Dhcp, SubnetRange
| summarize Devices = count(), SubnetRanges = make_set(SubnetRange) by NetworkName, IPv4Dhcp
// Ignore network with very low device count
| where Devices >= device_cap
| sort by Devices desc
// OPTIONAL - Device cap used to ignore network with less then X devices in them
let device_cap = 0;
DeviceNetworkInfo
| where TimeGenerated > ago(7d)
// Ignore empty networks
| where ConnectedNetworks != ""
// Get networks data
| extend ConnectedNetworksExp = parse_json(ConnectedNetworks)
| mv-expand bagexpansion = array ConnectedNetworks=ConnectedNetworksExp
| extend NetworkName = tostring(ConnectedNetworks ["Name"]), NetworkDescription = tostring(ConnectedNetworks ["Description"]), NetworkCategory = tostring(ConnectedNetworks ["Category"])
// Get subnet data for IPv4 Addresses
| extend IPAddressesExp = parse_json(IPAddresses)
| mv-expand bagexpansion = array IPAddresses=IPAddressesExp
| extend IPAddress = tostring(IPAddresses ["IPAddress"]), SubnetPrefix = tolong(IPAddresses ["SubnetPrefix"])
| extend NetworkAddress = format_ipv4(IPAddress, SubnetPrefix)
| extend SubnetRange = strcat(NetworkAddress, "/", SubnetPrefix)
// Exclude IPv6 and APIPPA
| where SubnetPrefix <= 32
| where IPAddress !startswith "169.254"
// Ignore unidentified networks
| where not(NetworkName has_any ("Unidentified", "Identifying..."))
// Provide list
| distinct DeviceId, NetworkName, IPv4Dhcp, SubnetRange
| summarize Devices = count(), SubnetRanges = make_set(SubnetRange) by NetworkName, IPv4Dhcp
// Ignore network with very low device count
| where Devices >= device_cap
| sort by Devices desc
Stages and Predicates
Parameters
let device_cap = 0;
Stage 1: source
DeviceNetworkInfo
Stage 2: where
| where Timestamp > ago(7d)
Stage 3: where
| where ConnectedNetworks != ""
Stage 4: extend
| extend ConnectedNetworksExp = parse_json(ConnectedNetworks)
Stage 5: mv-expand
| mv-expand bagexpansion = array ConnectedNetworks=ConnectedNetworksExp
Stage 6: extend
| extend NetworkName = tostring(ConnectedNetworks ["Name"]), NetworkDescription = tostring(ConnectedNetworks ["Description"]), NetworkCategory = tostring(ConnectedNetworks ["Category"])
Stage 7: extend
| extend IPAddressesExp = parse_json(IPAddresses)
Stage 8: mv-expand
| mv-expand bagexpansion = array IPAddresses=IPAddressesExp
Stage 9: extend (3 consecutive steps)
| extend IPAddress = tostring(IPAddresses ["IPAddress"]), SubnetPrefix = tolong(IPAddresses ["SubnetPrefix"])
| extend NetworkAddress = format_ipv4(IPAddress, SubnetPrefix)
| extend SubnetRange = strcat(NetworkAddress, "/", SubnetPrefix)
Stage 10: where
| where SubnetPrefix <= 32
Stage 11: where
| where IPAddress !startswith "169.254"
Stage 12: where
| where not(NetworkName has_any ("Unidentified", "Identifying..."))
Stage 13: distinct
| distinct DeviceId, NetworkName, IPv4Dhcp, SubnetRange
Stage 14: summarize
| summarize Devices = count(), SubnetRanges = make_set(SubnetRange) by NetworkName, IPv4Dhcp
Stage 15: where
| where Devices >= device_cap
Stage 16: sort
| sort by Devices desc
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Devices | ge |
| field:"Devices" kind:ge value:"0" |
SubnetPrefix | le |
| field:"SubnetPrefix" kind:le value:"32" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
IPAddress | starts_with | 169.254 | excludes:IPAddress field:"IPAddress" value:"169.254" |
NetworkName | match | Unidentified, Identifying... | excludes:NetworkName field:"NetworkName" value:"Unidentified" field:"NetworkName" value:"Identifying..." |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Devices | summarize |
IPv4Dhcp | summarize |
NetworkName | summarize |
SubnetRanges | summarize |
Infoblox - IQ for TD Insight Detected - CDC Source
#'Infoblox IQ for Threat Defense Insight detected in logs sourced via Infoblox CDC. Customize scheduling, responses and more. This rule depends on a parser based on a Kusto Function to work as expected called InfobloxCDC_SOCInsights.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: d04f1963-df27-4127-b1ec-3d37148d65be
name: Infoblox - IQ for TD Insight Detected - CDC Source
description: |
'Infoblox IQ for Threat Defense Insight detected in logs sourced via Infoblox CDC. Customize scheduling, responses and more. This rule depends on a parser based on a Kusto Function to work as expected called [**InfobloxCDC_SOCInsights**](https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Infoblox/Parsers/InfobloxCDC_SOCInsights.yaml).'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: InfobloxSOCInsightsDataConnector_AMA
dataTypes:
- CommonSecurityLog (InfobloxCDC_SOCInsights)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1498
- T1565
query: |
InfobloxCDC_SOCInsights
| summarize arg_max(TimeGenerated, *) by InfobloxInsightID
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: ThreatIndicator
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: AssetHostName
customDetails:
InfobloxInsightID: InfobloxInsightID
Status: Status
Severity: Severity
SubType: InsightSubType
ThreatIndicator: ThreatIndicator
IndicatorCount: IndicatorCount
AssetHostName: AssetHostName
AssetCount: AssetCount
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: 'Infoblox - IQ for TD Insight - {{Name}}'
alertDescriptionFormat: 'Observed via CDC. {{Description}}'
alertSeverityColumnName: IncidentSeverity
incidentConfiguration:
createIncident: true
version: 1.1.0
kind: Scheduled
Stages and Predicates
Stage 1: source
InfobloxCDC_SOCInsights
Stage 2: summarize
| summarize arg_max(TimeGenerated, *) by InfobloxInsightID
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InfobloxInsightID | summarize |
Port Scan
#'Identifies a source IP scanning multiple open ports on Azure Firewall. This can indicate malicious scanning of ports by an attacker, trying to reveal open ports in the organization that can be compromised for initial access. Configurable Parameters: - Port scan time - the time range to look for multiple ports scanned. Default is set to 30 seconds. - Minimum different ports threshold - alert only if more than this number of ports scanned. Default is set to 100.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Discovery |
Rule Body
id: b2c5907b-1040-4692-9802-9946031017e8
name: Port Scan
description: |
'Identifies a source IP scanning multiple open ports on Azure Firewall. This can indicate malicious scanning of ports by an attacker, trying to reveal open ports in the organization that can be compromised for initial access.
Configurable Parameters:
- Port scan time - the time range to look for multiple ports scanned. Default is set to 30 seconds.
- Minimum different ports threshold - alert only if more than this number of ports scanned. Default is set to 100.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AzureFirewall
dataTypes:
- AzureDiagnostics
- AZFWApplicationRule
- AZFWNetworkRule
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
- Reconnaissance
relevantTechniques:
- T1046
- T1595.001
query: |
let MinimumDifferentPortsThreshold = 100;
let BinTime = 30s;
// Exclude known benign scanner IPs in your environment to reduce noise
let KnownScannerIPs = dynamic([]);
union isfuzzy=true(
AZFWApplicationRule
| where SourceIp !in (KnownScannerIPs)
| summarize AlertTimedCountPortsInBinTime = dcount(DestinationPort) by SourceIp, bin(TimeGenerated, BinTime), Fqdn
| where AlertTimedCountPortsInBinTime > MinimumDifferentPortsThreshold),
(AZFWNetworkRule
| where SourceIp !in (KnownScannerIPs)
| extend Fqdn = DestinationIp
| summarize AlertTimedCountPortsInBinTime = dcount(DestinationPort) by SourceIp, bin(TimeGenerated, BinTime), Fqdn
| where AlertTimedCountPortsInBinTime > MinimumDifferentPortsThreshold),
(AzureDiagnostics
| where OperationName == "AzureFirewallApplicationRuleLog" or OperationName == "AzureFirewallNetworkRuleLog"
| parse msg_s with * "from " SourceIp ":" SourcePort:int " to " Fqdn ":" DestinationPort:int *
| where isnotempty(Fqdn) and isnotempty(SourceIp)
| where SourceIp !in (KnownScannerIPs)
| summarize AlertTimedCountPortsInBinTime = dcount(DestinationPort) by SourceIp, bin(TimeGenerated, BinTime), Fqdn
| where AlertTimedCountPortsInBinTime > MinimumDifferentPortsThreshold)
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIp
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Fqdn
alertDetailsOverride:
alertDisplayNameFormat: 'Port Scan Detected from {{SourceIp}}'
alertDescriptionFormat: 'Source IP {{SourceIp}} has scanned {{AlertTimedCountPortsInBinTime}} different ports within a short time frame, which may indicate reconnaissance activity.'
version: 1.1.3
kind: Scheduled
Stages and Predicates
Parameters
let MinimumDifferentPortsThreshold = 100;
let BinTime = 30s;
let KnownScannerIPs = dynamic([]);
Union isfuzzy=true (3 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: AZFWApplicationRule, AZFWNetworkRule, AzureDiagnostics
Leg 1: AZFWApplicationRule
AZFWApplicationRule
| where SourceIp !in (KnownScannerIPs)
| summarize AlertTimedCountPortsInBinTime = dcount(DestinationPort) by SourceIp, bin(TimeGenerated, BinTime), Fqdn
| where AlertTimedCountPortsInBinTime > MinimumDifferentPortsThreshold
Leg 2: AZFWNetworkRule
AZFWNetworkRule
| where SourceIp !in (KnownScannerIPs)
| extend Fqdn = DestinationIp
| summarize AlertTimedCountPortsInBinTime = dcount(DestinationPort) by SourceIp, bin(TimeGenerated, BinTime), Fqdn
| where AlertTimedCountPortsInBinTime > MinimumDifferentPortsThreshold
Leg 3: AzureDiagnostics
AzureDiagnostics
| where OperationName == "AzureFirewallApplicationRuleLog" or OperationName == "AzureFirewallNetworkRuleLog"
| parse msg_s with * "from " SourceIp ":" SourcePort:int " to " Fqdn ":" DestinationPort:int *
| where isnotempty(Fqdn) and isnotempty(SourceIp)
| where SourceIp !in (KnownScannerIPs)
| summarize AlertTimedCountPortsInBinTime = dcount(DestinationPort) by SourceIp, bin(TimeGenerated, BinTime), Fqdn
| where AlertTimedCountPortsInBinTime > MinimumDifferentPortsThreshold
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertTimedCountPortsInBinTime | gt |
| field:"AlertTimedCountPortsInBinTime" kind:gt value:"100" |
Fqdn | is_not_null | field:"Fqdn" kind:is_not_null | |
OperationName | eq |
| field:"OperationName" kind:eq |
SourceIp | is_not_null | field:"src_ip" kind:is_not_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
SourceIp | eq | [] | excludes:SourceIp field:"SourceIp" value:"[]" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertTimedCountPortsInBinTime | summarize |
Fqdn | summarize |
SourceIp | summarize |
SailPointIdentityNowUserWithFailedEvent
#'Detects any failed event for a particular user.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 2a215222-bfc5-4858-a530-6d4088ebfa15
name: SailPointIdentityNowUserWithFailedEvent
description: |
'Detects any failed event for a particular user.'
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", actorName:string = "test.tester", targetName:string = "test.tester");
SailPointIDN_Events
| where TimeGenerated > ago(lbperiod)
| where EventType == type
| where Status == "FAILED"
| where ActorName == actorName
| where TargetName == targetName
| 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", actorName:string = "test.tester", targetName:string = "test.tester");
SailPointIDN_Events
Stage 2: where
| where TimeGenerated > ago(lbperiod)
Stage 3: where
| where EventType == type
Stage 4: where
| where Status == "FAILED"
Stage 5: where
| where ActorName == actorName
Stage 6: where
| where TargetName == targetName
Stage 7: 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" |
Sonrai Ticket Risk Accepted
#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.
'Checks if Sonrai tickets have had their risk accepted. It uses the action type to check if a ticket has had it's risk accepted'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Defense Impairment | |
| Credential Access | |
| Discovery | |
| Lateral Movement | |
| Collection | |
| Command & Control | |
| Exfiltration | |
| Impact |
Rule Body
id: 080191e8-271d-4ae6-85ce-c7bcd4b06b40
name: Sonrai Ticket Risk Accepted
description: |
'Checks if Sonrai tickets have had their risk accepted.
It uses the action type to check if a ticket has had it's risk accepted'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SonraiDataConnector
dataTypes:
- Sonrai_Tickets_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- CommandAndControl
- CredentialAccess
- DefenseEvasion
- Discovery
- Execution
- Exfiltration
- Impact
- InitialAccess
- LateralMovement
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1566
- T1059
- T1547
- T1548
- T1562
- T1003
- T1087
- T1021
- T1119
- T1071
- T1041
- T1499
query: |
Sonrai_Tickets_CL
| where action_d == 7
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: digest_criticalResourceName_s
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
ticketName: digest_title_s
ticketStatus: digest_status_s
ticketSeverity: digest_severityCategory_s
resourceLabel: digest_resourceLabel_s
resourceType: digest_resourceType_s
criticalResource: digest_criticalResourceName_s
ticketOrg: digest_org_s
alertDetailsOverride:
alertSeverityColumnName: digest_severityCategory_s
alertDisplayNameFormat: Risk Accepted - {{digest_ticketSrn_s}} - {{digest_ticketKeyName_s}}
alertDescriptionFormat: digest_ticketKeyDescription_s
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
Sonrai_Tickets_CL
Stage 2: where
| where action_d == 7
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action_d | eq |
| field:"action_d" kind:eq value:"7" |
Suspicious application consent similar to PwnAuth
#This will alert when a user consents to provide a previously-unknown Azure application with the same OAuth permissions used by the FireEye PwnAuth toolkit (https://github.com/fireeye/PwnAuth). The default permissions/scope for the PwnAuth toolkit are user.read, offline_access, mail.readwrite, mail.send, and files.read.all. Consent to applications with these permissions should be rare, especially as the knownApplications list is expanded. Public contributions to expand this filter are welcome! For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | |
| Lateral Movement |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- [Entra ID] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles (Kusto)
- Added Credentials to Existing Application (Sigma)
- Added Owner To Application (Sigma)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Granted Microsoft Permissions (Sigma)
- App Granted Privileged Delegated Or App Permissions (Sigma)
- Application AppID Uri Configuration Changes (Sigma)
Rule Body
id: 39198934-62a0-4781-8416-a81265c03fd6
name: Suspicious application consent similar to PwnAuth
description: |
'This will alert when a user consents to provide a previously-unknown Azure application with the same OAuth permissions used by the FireEye PwnAuth toolkit (https://github.com/fireeye/PwnAuth).
The default permissions/scope for the PwnAuth toolkit are user.read, offline_access, mail.readwrite, mail.send, and files.read.all.
Consent to applications with these permissions should be rare, especially as the knownApplications list is expanded. Public contributions to expand this filter are welcome!
For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CredentialAccess
- DefenseEvasion
relevantTechniques:
- T1528
- T1550
query: |
let detectionTime = 1d;
let joinLookback = 14d;
AuditLogs
| where TimeGenerated > ago(detectionTime)
| where LoggedByService =~ "Core Directory"
| where Category =~ "ApplicationManagement"
| where OperationName =~ "Consent to application"
| where TargetResources has "offline"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend AppDisplayName = tostring(TargetResource.displayName),
AppClientId = tostring(TargetResource.id),
props = TargetResource.modifiedProperties
)
| where AppClientId !in ((externaldata(knownAppClientId:string, knownAppDisplayName:string)[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/Microsoft.OAuth.KnownApplications.csv"] with (format="csv")))
| mv-apply ConsentFull = props on
(
where ConsentFull.displayName =~ "ConsentAction.Permissions"
)
| parse ConsentFull with * "ConsentType: " GrantConsentType ", Scope: " GrantScope1 "]" *
| where ConsentFull has_all ("user.read", "offline_access", "mail.readwrite", "mail.send", "files.read.all")
| where GrantConsentType != "AllPrincipals" // NOTE: we are ignoring if OAuth application was granted to all users via an admin - but admin due diligence should be audited occasionally
| extend GrantInitiatedByAppName = tostring(InitiatedBy.app.displayName)
| extend GrantInitiatedByAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend GrantInitiatedByUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend GrantInitiatedByAadUserId = tostring(InitiatedBy.user.id)
| extend GrantIpAddress = iff(isnotempty(InitiatedBy.user.ipAddress), tostring(InitiatedBy.user.ipAddress), tostring(InitiatedBy.app.ipAddress))
| extend GrantInitiatedBy = iff(isnotempty(GrantInitiatedByUserPrincipalName), GrantInitiatedByUserPrincipalName, GrantInitiatedByAppName)
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend GrantUserAgent = AdditionalDetail.value
)
| project TimeGenerated, GrantConsentType, GrantScope1, GrantInitiatedBy, AppDisplayName, GrantInitiatedByUserPrincipalName, GrantInitiatedByAadUserId, GrantInitiatedByAppName, GrantInitiatedByAppServicePrincipalId, GrantIpAddress, GrantUserAgent, AppClientId, OperationName, ConsentFull, CorrelationId
| join kind = leftouter (AuditLogs
| where TimeGenerated > ago(joinLookback)
| where LoggedByService =~ "Core Directory"
| where Category =~ "ApplicationManagement"
| where OperationName =~ "Add service principal"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend props = TargetResource.modifiedProperties,
AppClientId = tostring(TargetResource.id)
)
| mv-apply Property = props on
(
where Property.displayName =~ "AppAddress" and Property.newValue has "AddressType"
| extend AppReplyURLs = trim('"',tostring(Property.newValue))
)
| distinct AppClientId, tostring(AppReplyURLs)
)
on AppClientId
| join kind = innerunique (AuditLogs
| where TimeGenerated > ago(joinLookback)
| where LoggedByService =~ "Core Directory"
| where Category =~ "ApplicationManagement"
| where OperationName =~ "Add OAuth2PermissionGrant" or OperationName =~ "Add delegated permission grant"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal" and array_length(TargetResource.modifiedProperties) > 0 and isnotnull(TargetResource.displayName)
| extend GrantAuthentication = tostring(TargetResource.displayName)
)
| extend GrantOperation = OperationName
| project GrantAuthentication, GrantOperation, CorrelationId
) on CorrelationId
| project TimeGenerated, GrantConsentType, GrantScope1, GrantInitiatedBy, AppDisplayName, AppReplyURLs, GrantInitiatedByUserPrincipalName, GrantInitiatedByAadUserId, GrantInitiatedByAppName, GrantInitiatedByAppServicePrincipalId, GrantIpAddress, GrantUserAgent, AppClientId, GrantAuthentication, OperationName, GrantOperation, CorrelationId, ConsentFull
| extend timestamp = TimeGenerated, Name = tostring(split(GrantInitiatedByUserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(GrantInitiatedByUserPrincipalName,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: GrantInitiatedByUserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: GrantInitiatedByAadUserId
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: GrantInitiatedByAppServicePrincipalId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: GrantIpAddress
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let detectionTime = 1d;
let joinLookback = 14d;
Stage 1: source
AuditLogs
Stage 2: where
| where TimeGenerated > ago(detectionTime)
Stage 3: where
| where LoggedByService =~ "Core Directory"
Stage 4: where
| where Category =~ "ApplicationManagement"
Stage 5: where
| where OperationName =~ "Consent to application"
Stage 6: where
| where TargetResources has "offline"
Stage 7: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend AppDisplayName = tostring(TargetResource.displayName),
AppClientId = tostring(TargetResource.id),
props = TargetResource.modifiedProperties
)
Stage 8: where
| where AppClientId !in ((externaldata(knownAppClientId:string, knownAppDisplayName:string)[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/Microsoft.OAuth.KnownApplications.csv"] with (format="csv")))
Stage 9: kusto:mv-apply
| mv-apply ConsentFull = props on
(
where ConsentFull.displayName =~ "ConsentAction.Permissions"
)
Stage 10: parse
| parse ConsentFull with * "ConsentType: " GrantConsentType ", Scope: " GrantScope1 "]" *
Stage 11: where
| where ConsentFull has_all ("user.read", "offline_access", "mail.readwrite", "mail.send", "files.read.all")
Stage 12: where
| where GrantConsentType != "AllPrincipals"
Stage 13: extend (6 consecutive steps)
| extend GrantInitiatedByAppName = tostring(InitiatedBy.app.displayName)
| extend GrantInitiatedByAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend GrantInitiatedByUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend GrantInitiatedByAadUserId = tostring(InitiatedBy.user.id)
| extend GrantIpAddress = iff(isnotempty(InitiatedBy.user.ipAddress), tostring(InitiatedBy.user.ipAddress), tostring(InitiatedBy.app.ipAddress))
| extend GrantInitiatedBy = iff(isnotempty(GrantInitiatedByUserPrincipalName), GrantInitiatedByUserPrincipalName, GrantInitiatedByAppName)
Stage 14: kusto:mv-apply
| mv-apply AdditionalDetail = AdditionalDetails on
(
where AdditionalDetail.key =~ "User-Agent"
| extend GrantUserAgent = AdditionalDetail.value
)
Stage 15: project
| project TimeGenerated, GrantConsentType, GrantScope1, GrantInitiatedBy, AppDisplayName, GrantInitiatedByUserPrincipalName, GrantInitiatedByAadUserId, GrantInitiatedByAppName, GrantInitiatedByAppServicePrincipalId, GrantIpAddress, GrantUserAgent, AppClientId, OperationName, ConsentFull, CorrelationId
Stage 16: join
| join kind = leftouter (AuditLogs
| where TimeGenerated > ago(joinLookback)
| where LoggedByService =~ "Core Directory"
| where Category =~ "ApplicationManagement"
| where OperationName =~ "Add service principal"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend props = TargetResource.modifiedProperties,
AppClientId = tostring(TargetResource.id)
)
| mv-apply Property = props on
(
where Property.displayName =~ "AppAddress" and Property.newValue has "AddressType"
| extend AppReplyURLs = trim('"',tostring(Property.newValue))
)
| distinct AppClientId, tostring(AppReplyURLs)
)
on AppClientId
Stage 17: join
| join kind = innerunique (AuditLogs
| where TimeGenerated > ago(joinLookback)
| where LoggedByService =~ "Core Directory"
| where Category =~ "ApplicationManagement"
| where OperationName =~ "Add OAuth2PermissionGrant" or OperationName =~ "Add delegated permission grant"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal" and array_length(TargetResource.modifiedProperties) > 0 and isnotnull(TargetResource.displayName)
| extend GrantAuthentication = tostring(TargetResource.displayName)
)
| extend GrantOperation = OperationName
| project GrantAuthentication, GrantOperation, CorrelationId
) on CorrelationId
Stage 18: project
project AppClientId, AppDisplayName, AppReplyURLs, ConsentFull, CorrelationId, GrantAuthentication, GrantConsentType, GrantInitiatedBy, GrantInitiatedByAadUserId, GrantInitiatedByAppName, GrantInitiatedByAppServicePrincipalId, GrantInitiatedByUserPrincipalName, GrantIpAddress, GrantOperation, GrantScope1, GrantUserAgent, OperationName, TimeGenerated
Stage 19: extend
extend Name, UPNSuffix, timestamp
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppClientId | project |
AppDisplayName | project |
AppReplyURLs | project |
ConsentFull | project |
CorrelationId | project |
GrantAuthentication | project |
GrantConsentType | project |
GrantInitiatedBy | project |
GrantInitiatedByAadUserId | project |
GrantInitiatedByAppName | project |
GrantInitiatedByAppServicePrincipalId | project |
GrantInitiatedByUserPrincipalName | project |
GrantIpAddress | project |
GrantOperation | project |
GrantScope1 | project |
GrantUserAgent | project |
OperationName | project |
TimeGenerated | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
Tenable.ad privileged accounts issues
#'Searches for triggered Indicators of Exposures related to privileged accounts issues.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 353d6474-d795-4086-a179-ba1db4d8bbcb
name: Tenable.ad privileged accounts issues
description: |
'Searches for triggered Indicators of Exposures related to privileged accounts issues.'
severity: Low
requiredDataConnectors:
- connectorId: Tenable.ad
dataTypes:
- Tenable_ad_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
// For the query to work properly, make sure you have imported the afad_parser.kql parser into the workspace
// Retrieve the parser here: https://raw.githubusercontent.com/tenable/Azure-Sentinel/Tenable.ad-connector/Solutions/TenableAD/Parsers/afad_parser.kql
// Then, create the Kusto Function with alias afad_parser
let SeverityTable=datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
let codeNameList = datatable(Codename:string)["C-PRIV-ACCOUNTS-SPN", "C-NATIVE-ADM-GROUP-MEMBERS", "C-KRBTGT-PASSWORD", "C-PROTECTED-USERS-GROUP-UNUSED", "C-ADMINCOUNT-ACCOUNT-PROPS", "C-ADM-ACC-USAGE", "C-LAPS-UNSECURE-CONFIG", "C-DISABLED-ACCOUNTS-PRIV-GROUPS"];
afad_parser
| where MessageType == 0 and Codename in~ (codeNameList)
| lookup kind=leftouter SeverityTable on Severity
| order by Level
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Let Binding: SeverityTable
let SeverityTable = datatable(Severity:string,Level:int) [
"low", 1,
"medium", 2,
"high", 3,
"critical", 4
];
Let Binding: codeNameList
let codeNameList = datatable(Codename:string)["C-PRIV-ACCOUNTS-SPN", "C-NATIVE-ADM-GROUP-MEMBERS", "C-KRBTGT-PASSWORD", "C-PROTECTED-USERS-GROUP-UNUSED", "C-ADMINCOUNT-ACCOUNT-PROPS", "C-ADM-ACC-USAGE", "C-LAPS-UNSECURE-CONFIG", "C-DISABLED-ACCOUNTS-PRIV-GROUPS"];
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 0 and Codename in~ (codeNameList)
Stage 3: kusto:lookup
lookup kind=leftouter (SeverityTable) on Severity
Stage 4: sort
sort by Level
Stage 5: extend
extend DnsDomain, HostName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Codename | in |
| field:"Codename" kind:in value:"codeNameList" |
MessageType | eq |
| field:"MessageType" kind:eq value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
TI map File Hash to Security Event
#Identifies a match in Security Event data from any File Hash IOC from TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
Rule Body
id: a7427ed7-04b4-4e3b-b323-08b981b9b4bf
name: TI map File Hash to Security Event
description: |
'Identifies a match in Security Event data from any File Hash IOC from TI'
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
ThreatIntelligenceIndicator
| where isnotempty(FileHashValue)
| where TimeGenerated >= ago(ioc_lookBack)
| extend FileHashValue = toupper(FileHashValue)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
// 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 ( union isfuzzy=true
(SecurityEvent | where TimeGenerated >= ago(dt_lookBack)
| where EventID in ("8003","8002","8005")
| where isnotempty(FileHash)
| extend SecurityEvent_TimeGenerated = TimeGenerated, Event = EventID, FileHash = toupper(FileHash)
),
(WindowsEvent | where TimeGenerated >= ago(dt_lookBack)
| where EventID in ("8003","8002","8005")
| where isnotempty(EventData.FileHash)
| extend SecurityEvent_TimeGenerated = TimeGenerated, Event = EventID, FileHash = toupper(EventData.FileHash)
)
)
on $left.FileHashValue == $right.FileHash
| where SecurityEvent_TimeGenerated < ExpirationDateTime
| summarize SecurityEvent_TimeGenerated = arg_max(SecurityEvent_TimeGenerated, *) by IndicatorId, FileHash
| project SecurityEvent_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore,
Process, FileHash, Computer, Account, Event, FileHashValue, FileHashType
| extend NTDomain = tostring(split(Account, '\\', 0)[0]), Name = tostring(split(Account, '\\', 1)[0])
| extend HostName = tostring(split(Computer, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Computer, '.'), 1, -1), '.'))
| extend timestamp = SecurityEvent_TimeGenerated
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
- identifier: Name
columnName: Name
- identifier: NTDomain
columnName: NTDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
- entityType: FileHash
fieldMappings:
- identifier: Value
columnName: FileHashValue
- identifier: Algorithm
columnName: FileHashType
version: 1.4.6
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(FileHashValue)
Stage 3: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 4: extend
| extend FileHashValue = toupper(FileHashValue)
Stage 5: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 6: where
| where Active == true and ExpirationDateTime > now()
Stage 7: join
join kind=innerunique (...) on $left.FileHashValue, $right.FileHash
Stage 8: where
where SecurityEvent_TimeGenerated < ExpirationDateTime
Stage 9: summarize
summarize SecurityEvent_TimeGenerated by IndicatorId, FileHash
Stage 10: project
project Account, ActivityGroupNames, Computer, ConfidenceScore, Description, Event, ExpirationDateTime, FileHash, FileHashType, FileHashValue, IndicatorId, Process, SecurityEvent_TimeGenerated, ThreatType, Url
Stage 11: extend (3 consecutive steps)
extend DnsDomain, HostName, NTDomain, Name, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EventData.FileHash | is_not_null | field:"EventData.FileHash" kind:is_not_null | |
EventID | in |
| field:"EventID" kind:in |
FileHash | is_not_null | field:"FileHash" kind:is_not_null | |
FileHashValue | is_not_null | field:"FileHashValue" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
ActivityGroupNames | project |
Computer | project |
ConfidenceScore | project |
Description | project |
Event | project |
ExpirationDateTime | project |
FileHash | project |
FileHashType | project |
FileHashValue | project |
IndicatorId | project |
Process | project |
SecurityEvent_TimeGenerated | project |
ThreatType | project |
Url | project |
NTDomain | extend |
Name | extend |
DnsDomain | extend |
HostName | extend |
timestamp | extend |
Ubiquiti - Possible connection to cryptominning pool
#'Detects connections which may indicate that device is infected with cryptominer.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 7feb3c32-2a11-4eb8-a2d7-e3792b31cb80
name: Ubiquiti - Possible connection to cryptominning pool
description: |
'Detects connections which may indicate that device is infected with cryptominer.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
dataTypes:
- Ubiquiti_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
- T1095
- T1571
query: |
let susp_dst_ports = dynamic(['14433', '14444', '3333', '3334', '3335', '3336', '4444', '45560', '45700', '5555', '5556', '6666', '7777', '8788', '8888', '8899', '9999', '10034']);
UbiquitiAuditEvent
| where ipv4_is_private(SrcIpAddr)
| where ipv4_is_private(DstIpAddr) == 'False'
| where DstPortNumber in (susp_dst_ports) or (DnsQuery contains 'pool' and DnsQuery contains 'xmr')
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Let Binding: susp_dst_ports
let susp_dst_ports = dynamic(['14433', '14444', '3333', '3334', '3335', '3336', '4444', '45560', '45700', '5555', '5556', '6666', '7777', '8788', '8888', '8899', '9999', '10034']);
Stage 1: source
UbiquitiAuditEvent
Stage 2: where
| where ipv4_is_private(SrcIpAddr)
Stage 3: where
| where ipv4_is_private(DstIpAddr) == 'False'
Stage 4: where
| where DstPortNumber in (susp_dst_ports) or (DnsQuery contains 'pool' and DnsQuery contains 'xmr')
Stage 5: extend
| extend IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DnsQuery | contains |
| field:"DnsQuery" kind:contains |
DstPortNumber | in |
| field:"DstPortNumber" kind:in |
SrcIpAddr | cidr_match |
| field:"src_ip" kind:cidr_match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DstIpAddr | 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:DstIpAddr |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
IPCustomEntity | extend |
UniFi Site Manager: External WAN IP changed
#Identifies when a site reports more than one distinct WAN external IP within an hour, which may indicate ISP DHCP refresh, WAN reconfiguration, or routing hijack.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule Body
id: ef1a293a-9e2b-b087-7816-2610814ed2d4
name: "UniFi Site Manager: External WAN IP changed"
description: |
Identifies when a site reports more than one distinct WAN external IP within an hour, which may indicate ISP DHCP refresh, WAN reconfiguration, or routing hijack.
severity: High
status: Available
requiredDataConnectors:
- connectorId: UniFiSiteManagerConnectorDefinition
dataTypes:
- Unifi_SiteManager_Sites_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Reconnaissance
relevantTechniques:
- T1590
subTechniques:
- T1590.005
query: |
Unifi_SiteManager_Sites_CL
| where TimeGenerated > ago(1h)
| extend SiteName = tostring(Meta.name),
currentIp = tostring(SiteStatistics.wans.WAN.externalIp)
| where isnotempty(currentIp)
| summarize DistinctIps = make_set(currentIp), arg_max(TimeGenerated, currentIp) by SiteId, SiteName
| where array_length(DistinctIps) > 1
| extend Activity = strcat('WAN IP changed; observed: ', tostring(DistinctIps))
| project TimeGenerated, SiteId, SiteName, Activity, CurrentIp = currentIp, ObservedIps = DistinctIps
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CurrentIp
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SiteName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0
Stages and Predicates
Stage 1: source
Unifi_SiteManager_Sites_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: extend
| extend SiteName = tostring(Meta.name),
currentIp = tostring(SiteStatistics.wans.WAN.externalIp)
Stage 4: where
| where isnotempty(currentIp)
Stage 5: summarize
| summarize DistinctIps = make_set(currentIp), arg_max(TimeGenerated, currentIp) by SiteId, SiteName
Stage 6: where
| where array_length(DistinctIps) > 1
Stage 7: extend
| extend Activity = strcat('WAN IP changed; observed: ', tostring(DistinctIps))
Stage 8: project
| project TimeGenerated, SiteId, SiteName, Activity, CurrentIp = currentIp, ObservedIps = DistinctIps
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
currentIp | is_not_null | field:"currentIp" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Activity | project |
CurrentIp | project |
ObservedIps | project |
SiteId | project |
SiteName | project |
TimeGenerated | project |
Utimaco ESKM - Multiple KMIP authentication failures from same IP
#This query searches for 5 or more KMIP "Authentication Failure" events originating from the same client IP within a 15 minute window on Utimaco ESKM. This may indicate brute-force or credential-stuffing activity against the KMIP server.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 9b1a3b3e-7e16-4a3b-8a8f-7f1f2b1c0a01
name: Utimaco ESKM - Multiple KMIP authentication failures from same IP
description: |
This query searches for 5 or more KMIP "Authentication Failure" events originating from the same client IP within a 15 minute window on Utimaco ESKM. This may indicate brute-force or credential-stuffing activity against the KMIP server.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: UtimacoESKMConnector
dataTypes:
- UtimacoESKMKmipServerLogs_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
UtimacoESKMKmipServerLogs_CL
| where TimeGenerated > ago(15m)
| where Event == "Authentication Failure"
| where isnotempty(IP)
| summarize FailureCount = count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Users = make_set(User, 50) by IP
| where FailureCount >= 5
| extend IPCustomEntity = IP
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
UtimacoESKMKmipServerLogs_CL
Stage 2: where
| where TimeGenerated > ago(15m)
Stage 3: where
| where Event == "Authentication Failure"
Stage 4: where
| where isnotempty(IP)
Stage 5: summarize
| summarize FailureCount = count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Users = make_set(User, 50) by IP
Stage 6: where
| where FailureCount >= 5
Stage 7: extend
| extend IPCustomEntity = IP
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Event | eq |
| field:"Event" kind:eq value:"Authentication Failure" |
FailureCount | ge |
| field:"FailureCount" kind:ge value:"5" |
IP | is_not_null | field:"IP" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTime | summarize |
FailureCount | summarize |
IP | summarize |
StartTime | summarize |
Users | summarize |
IPCustomEntity | extend |