Detection rules › Kusto
Kusto rules: 1k
Apache - Multiple server errors from single IP
#'Detects multiple server errors from one source in short timeframe'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Impact |
Rule Body
id: 1bf246a2-3af9-11ec-8d3d-0242ac130003
name: Apache - Multiple server errors from single IP
description: |
'Detects multiple server errors from one source in short timeframe'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- ApacheHTTPServer_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
- InitialAccess
relevantTechniques:
- T1498
- T1190
- T1133
query: |
let threshold = 100;
ApacheHTTPServer
| where HttpStatusCode >= 500 and HttpStatusCode <= 599
| summarize MultipleServerErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
| where MultipleServerErrors > threshold
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 100;
Stage 1: source
ApacheHTTPServer
Stage 2: where
| where HttpStatusCode >= 500 and HttpStatusCode <= 599
Stage 3: summarize
| summarize MultipleServerErrors = count() by SrcIpAddr, bin(TimeGenerated, 5m)
Stage 4: where
| where MultipleServerErrors > 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 |
|---|---|
MultipleServerErrors | summarize |
SrcIpAddr | summarize |
IPCustomEntity | extend |
API - Password Cracking
#'42Crunch API protection against password cracking'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: d951d64d-0ecd-4675-8c79-6c870d5f72ac
name: API - Password Cracking
description: |
'42Crunch API protection against password cracking'
severity: High
requiredDataConnectors:
- connectorId: 42CrunchAPIProtection
dataTypes:
- FortyTwoCrunchAPIProtection
- connectorId: FortyTwoCrunchAPIProtection
dataTypes:
- FortyTwoCrunchAPIProtection
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: SingleAlert
status: Available
tactics:
- CredentialAccess
relevantTechniques:
- T1110
- T1555
- T1187
query: |
let loginRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m)
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where UriPath has "/api/login?user=" and Status == 403;
let recCount = iff((toscalar(loginRec | count) > 10), 1, 0);
loginRec | take recCount
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 loginRec is inlined into the numbered stages below.
Let Binding: recCount
let recCount = iff((toscalar(loginRec | count) > 10), 1, 0);
Stages 1 to 4 define let loginRec as the rule's main pipeline source. Stage 5 uses it.
Stage 1: source
FortyTwoCrunchAPIProtection
Stage 2: where
| where 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/login?user=" and Status == 403
Stage 5: take
loginRec
| take recCount
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Status | eq |
| field:"Status" kind:eq value:"403" |
UriPath | match |
| field:"UriPath" kind:match value:"/api/login?user=" |
BloodHound Attack Path Finding - Admins on Tier Zero Computers
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - Admins on Tier Zero Computers
Rule Body
id: b0b747f2-938a-4f13-b7d1-9def60a719bd
name: BloodHound Attack Path Finding - Admins on Tier Zero Computers
description: BloodHound Attack Path Finding - Admins on Tier Zero Computers
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: "BHEAttackPathsData_CL \n| where Finding == \"T0Admins\""
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 == "T0Admins"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0Admins" |
Cloudflare - WAF Allowed threat
#'Detects WAF "Allowed" action on threat events.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: f53fe2a9-96b5-454c-827e-cf1764a67fb1
name: Cloudflare - WAF Allowed threat
description: |
'Detects WAF "Allowed" action on threat events.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CloudflareDataConnector
dataTypes:
- Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
Cloudflare
| where isnotempty(WAFRuleID) or isnotempty(WAFRuleMessage)
| where WAFAction =~ 'Allow'
| 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
Stage 1: source
Cloudflare
Stage 2: where
| where isnotempty(WAFRuleID) or isnotempty(WAFRuleMessage)
Stage 3: where
| where WAFAction =~ 'Allow'
Stage 4: extend
| extend CompleteUrl = strcat(HttpRequestHeaderHost,ClientRequestPath)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
WAFAction | eq |
| field:"WAFAction" kind:eq value:"Allow" |
WAFRuleID | is_not_null | field:"WAFRuleID" kind:is_not_null | |
WAFRuleMessage | is_not_null | field:"WAFRuleMessage" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CompleteUrl | extend |
Common Event Format (CEF) via AMA - Critical or High Severity Detections by User
#'Creates an incident when a large number of Critical or High severity CrowdStrike Falcon sensor detections is triggered by a single user within 1 hour. The rule uses the CrowdStrikeFalconEventStream table, filters for DetectionSummaryEvent records with Severity set to Critical or High, and alerts when detections for a single DstUserName exceed the configured threshold of 15. Review DstHostName, SrcIpAddr, FileName, FileHash, and Message for investigation context.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment | |
| Impact |
Rule Body
id: 4465ebde-b381-45f7-ad08-7d818070a11c
name: Common Event Format (CEF) via AMA - Critical or High Severity Detections by User
description: |
'Creates an incident when a large number of Critical or High severity CrowdStrike Falcon sensor detections is triggered by a single user within 1 hour. The rule uses the CrowdStrikeFalconEventStream table, filters for DetectionSummaryEvent records with Severity set to Critical or High, and alerts when detections for a single DstUserName exceed the configured threshold of 15. Review DstHostName, SrcIpAddr, FileName, FileHash, and Message for investigation context.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
- DefenseEvasion
relevantTechniques:
- T1489
- T1562
query: |
let timeframe = 1h;
let threshold = 15; // update threshold value based on organization's preference
let NotableEvents = CrowdStrikeFalconEventStream
| where TimeGenerated > ago(timeframe)
| where EventType == "DetectionSummaryEvent"
| where Severity in ("Critical", "High")
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), Total = count() by DstUserName, DstHostName, SrcIpAddr, FileName, FileHash, Message
| where Total > threshold;
NotableEvents
| extend timestamp = StartTimeUtc, AccountCustomEntity = DstUserName, HostCustomEntity = DstHostName, IPCustomEntity = SrcIpAddr, FileHashCustomEntity = FileHash, FileHashAlgo = "MD5"
| project timestamp, StartTimeUtc, EndTimeUtc, DstUserName, DstHostName, SrcIpAddr, FileName, FileHash, FileHashAlgo, Message, Total, AccountCustomEntity, HostCustomEntity, IPCustomEntity, FileHashCustomEntity
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: HostCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: FileHashAlgo
- identifier: Value
columnName: FileHashCustomEntity
alertDetailsOverride:
alertDisplayNameFormat: 'CrowdStrike critical/high detections by user: {{DstUserName}}'
alertDescriptionFormat: User {{DstUserName}} generated {{Total}} critical/high detections from host {{DstHostName}}.
customDetails:
DstUserName: DstUserName
DstHostName: DstHostName
SrcIpAddr: SrcIpAddr
FileName: FileName
FileHash: FileHash
Total: Total
version: 1.0.6
kind: Scheduled
Stages and Predicates
Parameters
let timeframe = 1h;
let threshold = 15;
let NotableEvents is inlined into the numbered stages below.
Stages 1 to 6 define let NotableEvents as the rule's main pipeline source. Stages 7 to 8 use it.
Stage 1: source
CrowdStrikeFalconEventStream
Stage 2: where
| where TimeGenerated > ago(timeframe)
Stage 3: where
| where EventType == "DetectionSummaryEvent"
Stage 4: where
| where Severity in ("Critical", "High")
Stage 5: summarize
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), Total = count() by DstUserName, DstHostName, SrcIpAddr, FileName, FileHash, Message
Stage 6: where
| where Total > threshold
Stage 7: extend
NotableEvents
| extend timestamp = StartTimeUtc, AccountCustomEntity = DstUserName, HostCustomEntity = DstHostName, IPCustomEntity = SrcIpAddr, FileHashCustomEntity = FileHash, FileHashAlgo = "MD5"
Stage 8: project
| project timestamp, StartTimeUtc, EndTimeUtc, DstUserName, DstHostName, SrcIpAddr, FileName, FileHash, FileHashAlgo, Message, Total, AccountCustomEntity, HostCustomEntity, IPCustomEntity, FileHashCustomEntity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"DetectionSummaryEvent" |
Severity | in |
| field:"Severity" kind:in |
Total | gt |
| field:"Total" kind:gt value:"15" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | project |
DstHostName | project |
DstUserName | project |
EndTimeUtc | project |
FileHash | project |
FileHashAlgo | project |
FileHashCustomEntity | project |
FileName | project |
HostCustomEntity | project |
IPCustomEntity | project |
Message | project |
SrcIpAddr | project |
StartTimeUtc | project |
Total | project |
timestamp | project |
CyberArkEPM - Process started from different locations
#'Detects when process started from different locations on a host.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Stealth |
Rule Body
id: 0d4e62da-0a64-4532-b93e-28cd2940c300
name: CyberArkEPM - Process started from different locations
description: |
'Detects when process started from different locations on a host.'
severity: Medium
requiredDataConnectors:
- connectorId: CyberArkEPM
dataTypes:
- CyberArkEPM
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
- DefenseEvasion
relevantTechniques:
- T1204
- T1036
query: |
CyberArkEPM
| where EventSubType != 'AttackAttempt'
| extend bin_path = tolower(extract(@'\A(.*)(\\|/)', 1, ActingProcessName))
| summarize p = makeset(bin_path) by ActingProcessFileInternalName, DvcId
| where array_length(p) > 1
| extend FileCustomEntity = ActingProcessFileInternalName
entityMappings:
- entityType: File
fieldMappings:
- identifier: Name
columnName: FileCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CyberArkEPM
Stage 2: where
| where EventSubType != 'AttackAttempt'
Stage 3: extend
| extend bin_path = tolower(extract(@'\A(.*)(\\|/)', 1, ActingProcessName))
Stage 4: summarize
| summarize p = makeset(bin_path) by ActingProcessFileInternalName, DvcId
Stage 5: where
| where array_length(p) > 1
Stage 6: extend
| extend FileCustomEntity = ActingProcessFileInternalName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventSubType | ne |
| field:"EventSubType" kind:ne value:"AttackAttempt" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActingProcessFileInternalName | summarize |
DvcId | summarize |
p | summarize |
FileCustomEntity | extend |
Detect executable drops via Azure custom script extension
#This detection rule flags when the Custom Script extension service on a machine is dropping executable files. This might indicate that an actor is trying to drop malware or beacons via a compromised cloud admin account. In the most legitimate cases administrators are pushing only PowerShell or Shell scripts, although these can also contain malicious content. Be aware of this gap in the below detection rule.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Lateral Movement |
References
Telemetry Coverage
Rule Body
// Executable extensions we want to flag (you can also add .ps1 and .sh)
let win_executable_extensions = dynamic([".dll", ".exe", ".msi", ".bat", ".cmd", ".com", ".vbs", ".wsf", ".scr", ".cpl"]);
DeviceFileEvents
| where TimeGenerated > ago(1h)
// Search for file created events by Arc Custom Script Handler
| where ActionType == "FileCreated"
| where InitiatingProcessFileName =~ "customscripthandler.exe"
// Get the file type
| extend FileType = tostring(parse_json(AdditionalFields).FileType)
// Flag on extension or executable file type
| where FileName has_any (win_executable_extensions) or
FileType contains "Executable"
Stages and Predicates
Parameters
let win_executable_extensions = dynamic([".dll", ".exe", ".msi", ".bat", ".cmd", ".com", ".vbs", ".wsf", ".scr", ".cpl"]);
Stage 1: source
DeviceFileEvents
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: where
| where ActionType == "FileCreated"
Stage 4: where
| where InitiatingProcessFileName =~ "customscripthandler.exe"
Stage 5: extend
| extend FileType = tostring(parse_json(AdditionalFields).FileType)
Stage 6: where
| where FileName has_any (win_executable_extensions) or
FileType contains "Executable"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"FileCreated" |
FileName | match |
| field:"file_name" kind:match |
FileType | contains |
| field:"FileType" kind:contains value:"Executable" |
InitiatingProcessFileName | eq |
| field:"process_basename" kind:eq value:"customscripthandler.exe" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
FileType | extend |
Netskope - Impossible Travel Detection (Two Countries in Less Than 1 Hour)
#Detects when a user accesses resources from two distinct countries within less than 1 hour, indicating potential credential compromise or VPN abuse.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: d1b88716-3cd4-4585-a9a2-2dd2c9b04ecb
name: Netskope - Impossible Travel Detection (Two Countries in Less Than 1 Hour)
description: |
Detects when a user accesses resources from two distinct countries within less than 1 hour, indicating potential credential compromise or VPN abuse.
severity: High
status: Available
requiredDataConnectors:
- connectorId: NetskopeWebTxConnector
dataTypes:
- NetskopeWebTransactions_CL
queryFrequency: 1h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CredentialAccess
relevantTechniques:
- T1078
query: |
let timeWindow = 1h;
NetskopeWebTransactions_CL
| where TimeGenerated > ago(1h)
| where isnotempty(CsUsername) and isnotempty(XCCountry)
| summarize
Countries = make_set(XCCountry),
Locations = make_set(XCLocation),
IPs = make_set(CIp),
MinTime = min(TimeGenerated),
MaxTime = max(TimeGenerated),
EventCount = count()
by CsUsername, bin(TimeGenerated, timeWindow)
| where array_length(Countries) > 1
| extend TimeDiffMinutes = datetime_diff('minute', MaxTime, MinTime)
| where TimeDiffMinutes < 60
| project
TimeGenerated,
User = CsUsername,
DistinctCountries = Countries,
Locations,
SourceIPs = IPs,
TimeDifferenceMinutes = TimeDiffMinutes,
EventCount,
AlertDetails = strcat('User ', CsUsername, ' accessed from ', array_length(Countries), ' countries within ', TimeDiffMinutes, ' minutes')
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: User
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let timeWindow = 1h;
Stage 1: source
NetskopeWebTransactions_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: where
| where isnotempty(CsUsername) and isnotempty(XCCountry)
Stage 4: summarize
| summarize
Countries = make_set(XCCountry),
Locations = make_set(XCLocation),
IPs = make_set(CIp),
MinTime = min(TimeGenerated),
MaxTime = max(TimeGenerated),
EventCount = count()
by CsUsername, bin(TimeGenerated, timeWindow)
Stage 5: where
| where array_length(Countries) > 1
Stage 6: extend
| extend TimeDiffMinutes = datetime_diff('minute', MaxTime, MinTime)
Stage 7: where
| where TimeDiffMinutes < 60
Stage 8: project
| project
TimeGenerated,
User = CsUsername,
DistinctCountries = Countries,
Locations,
SourceIPs = IPs,
TimeDifferenceMinutes = TimeDiffMinutes,
EventCount,
AlertDetails = strcat('User ', CsUsername, ' accessed from ', array_length(Countries), ' countries within ', TimeDiffMinutes, ' minutes')
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CsUsername | is_not_null | field:"CsUsername" kind:is_not_null | |
TimeDiffMinutes | lt |
| field:"TimeDiffMinutes" kind:lt value:"60" |
XCCountry | is_not_null | field:"XCCountry" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertDetails | project |
DistinctCountries | project |
EventCount | project |
Locations | project |
SourceIPs | project |
TimeDifferenceMinutes | project |
TimeGenerated | project |
User | project |
NordPass - Domain data detected in breach
#This will alert you when Data Breach Scanner discovers data related to your organization's domains on the dark web. !This rule should be enabled only by the organizations that have set up Data Breach Scanner in NordPass.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: e3f2b6c9-df0c-4b36-a376-bb2762e4dbdc
name: NordPass - Domain data detected in breach
kind: Scheduled
version: 1.0.0
displayName: Domain data detected in breach
description: |
This will alert you when Data Breach Scanner discovers data related to your organization's domains on the dark web.
!This rule should be enabled only by the organizations that have set up Data Breach Scanner in NordPass.
severity: High
query: |
NordPassEventLogs_CL
| where event_type == "breach"
| where action == "domain_breached"
| extend TargetEmail = user_email
requiredDataConnectors:
- connectorId: NordPass
dataTypes:
- NordPassEventLogs_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1020
incidentConfiguration:
createIncident: false
entityMappings:
- entityType: Mailbox
fieldMappings:
- identifier: MailboxPrimaryAddress
columnName: TargetEmail
Stages and Predicates
Stage 1: source
NordPassEventLogs_CL
Stage 2: where
| where event_type == "breach"
Stage 3: where
| where action == "domain_breached"
Stage 4: extend
| extend TargetEmail = user_email
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"domain_breached" |
event_type | eq |
| field:"event_type" kind:eq value:"breach" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TargetEmail | extend |
NRT Process executed from binary hidden in Base64 encoded file
#Encoding malicious software is a technique used to obfuscate files from detection. The first CommandLine component is looking for Python decoding base64. The second CommandLine component is looking for Bash/sh command line base64 decoding. The third one is looking for Ruby decoding base64.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Stealth |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 4688: A new process has been created. |
Rule Body
id: 7ad4c32b-d0d2-411c-a0e8-b557afa12fce
name: NRT Process executed from binary hidden in Base64 encoded file
description: |
'Encoding malicious software is a technique used to obfuscate files from detection.
The first CommandLine component is looking for Python decoding base64.
The second CommandLine component is looking for Bash/sh command line base64 decoding.
The third one is looking for Ruby decoding base64.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
tactics:
- Execution
- DefenseEvasion
relevantTechniques:
- T1059
- T1027
- T1140
query: |
SecurityEvent
| where EventID==4688
| where isnotempty(CommandLine)
| project TimeGenerated, Computer, AccountName = SubjectUserName, AccountNTDomain = SubjectDomainName, FileName = Process, CommandLine, ParentProcessName, SubjectAccount
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| where CommandLine contains ".decode('base64')"
or CommandLine contains "base64 --decode"
or CommandLine contains ".decode64("
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SubjectAccount
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountNTDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
version: 1.0.2
kind: NRT
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventID==4688
Stage 3: where
| where isnotempty(CommandLine)
Stage 4: project
| project TimeGenerated, Computer, AccountName = SubjectUserName, AccountNTDomain = SubjectDomainName, FileName = Process, CommandLine, ParentProcessName, SubjectAccount
Stage 5: extend
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
Stage 6: extend
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
HostNameDomain =DomainIndex != -1substring(Computer, (DomainIndex + 1))ComputerStage 7: where
| where CommandLine contains ".decode('base64')"
or CommandLine contains "base64 --decode"
or CommandLine contains ".decode64("
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CommandLine | contains |
| field:"CommandLine" kind:contains |
CommandLine | is_not_null | field:"CommandLine" kind:is_not_null | |
EventID | eq |
| field:"EventID" kind:eq value:"4688" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountNTDomain | project |
AccountName | project |
CommandLine | project |
Computer | project |
FileName | project |
ParentProcessName | project |
SubjectAccount | project |
TimeGenerated | project |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
Potential Kerberos Relaying Activity - MDE
#The below query detects potential Kerberos relaying event chain generated by KrbRelay.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | No specific technique |
References
Telemetry Coverage
Rule Body
// Author : Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
//
// Link to original post:
// https://posts.bluraven.io/detecting-kerberos-relaying-e6be66fa647c
//
// Description: This query detects potential Kerberos relaying event chain generated by KrbRelay(https://github.com/cube0x0/KrbRelay).
//
// Query parameters:
//
union DeviceProcessEvents, DeviceEvents, DeviceNetworkEvents
| where Timestamp > ago(60m)
| extend PipeName_ = tostring(todynamic(AdditionalFields).PipeName)
| extend Action = case(
(ActionType=="ConnectionSuccess" and RemotePort in (389, 636)), 'ConnectToDC',
(ActionType=="ListeningConnectionCreated"), 'ListeningConnectionCreated',
(ActionType=="NamedPipeEvent" and PipeName_ has @"Winsock2\CatalogChangeListener"), 'NamedPipeEvent',
(ActionType=="ProcessCreated" and InitiatingProcessCommandLine == "svchost.exe -k DcomLaunch -p"), 'DcomProcessCreated',
(ActionType=="ConnectionSuccess" and LocalIP==RemoteIP and InitiatingProcessParentFileName=="svchost.exe"), 'DcomProcessConnecToSelf', 'Other'
)
| summarize Actions = make_set_if(Action, Action <> "Other") by DeviceId, DeviceName, bin(Timestamp,20s)
| where array_length(Actions)==5
| project-rename ActivityTimestamp = Timestamp
| join kind=inner (
DeviceProcessEvents
| where Timestamp > ago(60m)
| where ActionType=="ProcessCreated" and InitiatingProcessCommandLine == "svchost.exe -k DcomLaunch -p"
) on DeviceId
| where abs(datetime_diff('second', Timestamp, ActivityTimestamp)) <=20
union DeviceProcessEvents, DeviceEvents, DeviceNetworkEvents
| where Timestamp > ago(60m)
| extend PipeName_ = tostring(todynamic(AdditionalFields).PipeName)
| where
(ActionType=="ConnectionSuccess" and RemotePort in (389, 636)) or
(ActionType=="ListeningConnectionCreated") or
(ActionType=="NamedPipeEvent" and PipeName_ has @"Winsock2\CatalogChangeListener") or
(ActionType=="ProcessCreated" and InitiatingProcessCommandLine == "svchost.exe -k DcomLaunch -p") or
(ActionType=="ConnectionSuccess" and LocalIP==RemoteIP and InitiatingProcessParentFileName=="svchost.exe")
| sort by Timestamp desc
Stages and Predicates
Union (3 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: DeviceProcessEvents, DeviceEvents, DeviceNetworkEvents
Leg 1: DeviceProcessEvents
Leg 2: DeviceEvents
Leg 3: DeviceNetworkEvents
Applied to the Combined Result
| where Timestamp > ago(60m) | extend PipeName_ = tostring(todynamic(AdditionalFields).PipeName) | extend Action = case(
(ActionType=="ConnectionSuccess" and RemotePort in (389, 636)), 'ConnectToDC',
(ActionType=="ListeningConnectionCreated"), 'ListeningConnectionCreated',
(ActionType=="NamedPipeEvent" and PipeName_ has @"Winsock2\CatalogChangeListener"), 'NamedPipeEvent',
(ActionType=="ProcessCreated" and InitiatingProcessCommandLine == "svchost.exe -k DcomLaunch -p"), 'DcomProcessCreated',
(ActionType=="ConnectionSuccess" and LocalIP==RemoteIP and InitiatingProcessParentFileName=="svchost.exe"), 'DcomProcessConnecToSelf', 'Other'
) | summarize Actions = make_set_if(Action, Action <> "Other") by DeviceId, DeviceName, bin(Timestamp,20s) | where array_length(Actions)==5 | project-rename ActivityTimestamp = Timestamp | join kind=inner (
DeviceProcessEvents
| where Timestamp > ago(60m)
| where ActionType=="ProcessCreated" and InitiatingProcessCommandLine == "svchost.exe -k DcomLaunch -p"
) on DeviceId | where abs(datetime_diff('second', Timestamp, ActivityTimestamp)) <=20
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq |
InitiatingProcessCommandLine | eq |
| field:"InitiatingProcessCommandLine" kind:eq value:"svchost.exe -k DcomLaunch -p" |
InitiatingProcessParentFileName | eq |
| field:"InitiatingProcessParentFileName" kind:eq value:"svchost.exe" |
PipeName_ | match |
| field:"PipeName_" kind:match value:"Winsock2\CatalogChangeListener" |
RemotePort | in |
| field:"RemotePort" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Actions | summarize |
DeviceId | summarize |
DeviceName | summarize |
ActivityTimestamp | project-rename |
Server Network Connection Anomalies
#Servers have a specific baseline. This makes it easy to create a baseline and detect anomalies.
Below queries analyze the network connections made by the specified servers and detects the rare/anomalous ones.
You can add process info to the analysis, but it will probably generate more results(different processes for the same IP).
Telemetry Coverage
Rule Body
// Define servers you want to monitor.
let Servers = dynamic(["server1","server2","etc."]);
// Get rare connections by RemoteIP and InitiatingProcessFileName
DeviceNetworkEvents
| where Timestamp > ago(30d)
| where DeviceName in (Servers) and ActionType == "ConnectionSuccess"
| where RemoteIPType !in ( "Private", "Loopback" )
| where RemoteIP !startswith "169.254."
| summarize make_set(RemoteUrl), count() by RemoteIP
| where count_ < 50
// Exclude traffic to known destinations.
| where not ( set_RemoteUrl has_any (".microsoft.com",".windowsupdate.com","login.microsoftonline.com","login.live.com","autodiscover-s.outlook.com","ocsp.digicert.com","ocsp.verisign.com","login.windows.net", "outlook.office365.com","accounts.accesscontrol.windows.net"))
// Get details of the connections that were made in the last 5 days.
// If you are going to check the results everyday, change the threshold to 1d.
| join kind=inner
(
DeviceNetworkEvents
| where Timestamp > ago(5d)
| where DeviceName in (Servers) and ActionType == "ConnectionSuccess"
| where RemoteIPType !in ( "Private", "Loopback" )
| where RemoteIP !startswith "169.254."
) on RemoteIP
// Define servers you want to monitor.
let Servers = dynamic(["server1","server2","etc."]);
// Get rare connections by DestinationIp
let _lookback = 30d;
let _timeframe = 5d;
let PrivateIPregex = @'^127\.|^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.';
// Parse Sysmon network connections and get only the ones towards the internet.
let parse_sysmon_id3 = (T:(TimeGenerated:datetime,EventID:int, Source:string,RenderedDescription:string, EventData:string))
{
T
| where TimeGenerated > ago(_lookback)
| where Source == "Microsoft-Windows-Sysmon" and EventID == 3
| extend RenderedDescription = tostring(split(RenderedDescription, ":")[0])
| extend EventData = parse_xml(EventData).DataItem.EventData.Data
| mv-expand bagexpansion=array EventData
| evaluate bag_unpack(EventData)
| extend Key=tostring(['@Name']), Value=tostring(['#text'])
| evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)
| extend RuleName = column_ifexists("RuleName", ""), TechniqueId = column_ifexists("TechniqueId", ""), TechniqueName = column_ifexists("TechniqueName", "")
| parse RuleName with * 'technique_id=' TechniqueId ',' * 'technique_name=' TechniqueName
// Filter connections towards the internet
| where not (DestinationIp matches regex PrivateIPregex)
};
// Get rare connections by DestinationIp
Event
| where TimeGenerated > ago(_lookback)
| where Computer in (Servers)
| invoke parse_sysmon_id3()
| summarize count() by DestinationIp
| where count_ < 50
// get details of the rare connections for further analysis.
| join kind=inner
(
Event
| where TimeGenerated > ago(_timeframe)
| where Computer in (Servers)
| invoke parse_sysmon_id3()
) on DestinationIp
Stages and Predicates
Parameters
let Servers = dynamic(["server1","server2","etc."]);
Stage 1: source
DeviceNetworkEvents
Stage 2: where
where Timestamp > ago(2592000s)
Stage 3: where
where ActionType == "ConnectionSuccess" and DeviceName in ("etc.", "server1", "server2")
Stage 4: where
where not (RemoteIPType in ("Loopback", "Private"))
Stage 5: where
where not (RemoteIP startswith "169.254.")
Stage 6: summarize
summarize by RemoteIP
Stage 7: where
where count_ < 50
Stage 8: where
where not ((set_RemoteUrl has ".microsoft.com" or set_RemoteUrl has ".windowsupdate.com" or set_RemoteUrl has "login.microsoftonline.com" or set_RemoteUrl has "login.live.com" or set_RemoteUrl has "autodiscover-s.outlook.com" or set_RemoteUrl has "ocsp.digicert.com" or set_RemoteUrl has "ocsp.verisign.com" or set_RemoteUrl has "login.windows.net" or set_RemoteUrl has "outlook.office365.com" or set_RemoteUrl has "accounts.accesscontrol.windows.net"))
Stage 9: join
join kind=inner (DeviceNetworkEvents) on RemoteIP
Stage 10: source
Event
Stage 11: where
where TimeGenerated > ago(2592000s)
Stage 12: where
where Computer in ("etc.", "server1", "server2")
Stage 13: where
where TimeGenerated > ago(2592000s)
Stage 14: where
where EventID == 3 and Source == "Microsoft-Windows-Sysmon"
Stage 15: extend
extend RenderedDescription
Stage 16: extend
extend EventData
Stage 17: mv-expand
mv-expand EventData
Stage 18: evaluate
evaluate
Stage 19: extend
extend Key, Value
Stage 20: evaluate
evaluate
Stage 21: extend
extend RuleName, TechniqueId, TechniqueName
Stage 22: parse
parse
Stage 23: where
where not (DestinationIp matches regex @"^127\.|^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.")
Stage 24: summarize
summarize by DestinationIp
Stage 25: where
where count_ < 50
Stage 26: join
join kind=inner (Event) on DestinationIp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ConnectionSuccess" |
Computer | in |
| field:"Computer" kind:in |
DeviceName | in |
| field:"DeviceName" kind:in |
EventID | eq |
| field:"EventID" kind:eq value:"3" |
Source | eq |
| field:"Source" kind:eq value:"Microsoft-Windows-Sysmon" |
count_ | lt |
| field:"count_" kind:lt value:"50" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
RemoteIPType | in | Loopback, Private | excludes:RemoteIPType field:"RemoteIPType" value:"Loopback" field:"RemoteIPType" value:"Private" |
RemoteIP | starts_with | 169.254. | excludes:RemoteIP field:"RemoteIP" value:"169.254." |
set_RemoteUrl | match | .microsoft.com, .windowsupdate.com, login.microsoftonline.com, login.live.com, autodiscover-s.outlook.com, ocsp.digicert.com, ocsp.verisign.com, login.windows.net, outlook.office365.com, accounts.accesscontrol.windows.net | excludes:set_RemoteUrl |
DestinationIp | regex_match | ^127., ^10., ^172.1[6-9]., ^172.2[0-9]., ^172.3[0-1]., ^192.168. | excludes:DestinationIp |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
RemoteIP | summarize |
Suspicious VM Instance Creation Activity Detected
#This detection identifies high-severity alerts across various Microsoft security products, including Microsoft Defender XDR and Microsoft Entra ID, and correlates them with instances of Google Cloud VM creation. It focuses on instances where VMs were created within a short timeframe of high-severity alerts, potentially indicating suspicious activity.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Discovery |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: 1cc0ba27-c5ca-411a-a779-fbc89e26be83
name: Suspicious VM Instance Creation Activity Detected
description: |
'This detection identifies high-severity alerts across various Microsoft security products, including Microsoft Defender XDR and Microsoft Entra ID, and correlates them with instances of Google Cloud VM creation. It focuses on instances where VMs were created within a short timeframe of high-severity alerts, potentially indicating suspicious activity.'
severity: Medium
requiredDataConnectors:
- connectorId: GCPAuditLogsDefinition
dataTypes:
- GCPAuditLogs
- connectorId: AzureActiveDirectoryIdentityProtection
dataTypes:
- SecurityAlert (IPC)
- connectorId: MicrosoftThreatProtection
dataTypes:
- SecurityAlert
- connectorId: MicrosoftDefenderAdvancedThreatProtection
dataTypes:
- SecurityAlert (MDATP)
- connectorId: MicrosoftCloudAppSecurity
dataTypes:
- SecurityAlert
- connectorId: BehaviorAnalytics
dataTypes:
- IdentityInfo
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
- Discovery
relevantTechniques:
- T1078
- T1106
- T1526
query: |
// Filter alerts from specific Microsoft security products with medium and high severity
SecurityAlert
| where ProductName in ("Microsoft 365 Defender", "Azure Active Directory", "Microsoft Defender Advanced Threat Protection", "Microsoft Cloud App Security", "Azure Active Directory Identity Protection", "Microsoft Defender ATP")
| where AlertSeverity has_any ("Medium", "High")
// Parse JSON entities and extend AlertTimeGenerated
| extend Entities = parse_json(Entities), AlertTimeGenerated=TimeGenerated
// Extract and process IP entities
| mv-apply Entity = Entities on
(
where Entity.Type == 'ip'
| extend EntityIp = tostring(Entity.Address)
)
// Extract and process account entities
| mv-apply Entity = Entities on
(
where Entity.Type == 'account'
| extend AccountObjectId = tostring(Entity.AadUserId)
)
// Filter out records with empty EntityIp
| where isnotempty(EntityIp)
// Summarize data and create sets of entities and system alert IDs
| summarize Entitys=make_set(Entity), SystemAlertIds=make_set(SystemAlertId)
by
AlertName,
ProductName,
AlertSeverity,
EntityIp,
Tactics,
Techniques,
ProviderName,
AlertTime= bin(AlertTimeGenerated, 1d),
AccountObjectId
// Join with GCPAuditLogs for VM instance creation
| join kind=inner (
GCPAuditLogs
| where ServiceName == "compute.googleapis.com" and MethodName endswith "instances.insert"
| extend
GCPUserUPN= tostring(parse_json(AuthenticationInfo).principalEmail),
GCPUserIp = tostring(parse_json(RequestMetadata).callerIp),
GCPUserUA= tostring(parse_json(RequestMetadata).callerSuppliedUserAgent),
VMStatus = tostring(parse_json(Response).status),
VMOperation=tostring(parse_json(Response).operationType),
VMName= tostring(parse_json(Request).name),
VMType = tostring(split(parse_json(Request).machineType, "/")[-1])
| where GCPUserUPN !has "gserviceaccount.com"
| where VMOperation == "insert" and isnotempty(GCPUserIp) and GCPUserIp != "private"
| project
GCPOperationTime=TimeGenerated,
VMName,
VMStatus,
MethodName,
GCPUserUPN,
ProjectId,
GCPUserIp,
GCPUserUA,
VMOperation,
VMType
)
on $left.EntityIp == $right.GCPUserIp
// Join with IdentityInfo to enrich user identity details
| join kind=inner (IdentityInfo
| distinct AccountObjectId, AccountUPN, JobTitle
)
on AccountObjectId
// Calculate the time difference between the alert and VM creation for further analysis
| extend TimeDiff= datetime_diff('day', AlertTime, GCPOperationTime),Name = split(GCPUserUPN, "@")[0], UPNSuffix = split(GCPUserUPN, "@")[1]
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: GCPUserIp
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: GCPUserUPN
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
customDetails:
AlertName: AlertName
AlertProDuctName: ProductName
AlertUserName: AccountUPN
AlertUserObjectId: AccountObjectId
AlertIds: SystemAlertIds
AlertIp: EntityIp
GCPUserAgent: GCPUserUA
GCPVMName: VMName
GCPProjectId: ProjectId
GCPVMType: VMType
CorrelationWith: "GCPAuditLogs"
alertDetailsOverride:
alertDisplayNameFormat: "IP address {{GCPUserIp}} Assocated with {{AlertName}} found in GCP VM creation event by {{GCPUserUPN}}"
alertDescriptionFormat: "This detection correlates '{{ProductName}}' Alert IP addresse Entity found in VM instance creation in GCP {{ProjectId}}. It identifies successful compute instance creation, from suspicious IP addresse. By joining these datasets on network entities and IP addresses, it detects unauthorized Initial access attempts across GCP environments."
alertSeverityColumnName: AlertSeverity
alertDynamicProperties:
- alertProperty: ProviderName
value: "Microsoft Security"
- alertProperty: ProductName
value: "Microsoft Defender"
- alertProperty: ProductComponentName
value: "Microsoft Defender"
kind: Scheduled
version: 1.0.4
Stages and Predicates
Stage 1: source
SecurityAlert
Stage 2: where
| where ProductName in ("Microsoft 365 Defender", "Azure Active Directory", "Microsoft Defender Advanced Threat Protection", "Microsoft Cloud App Security", "Azure Active Directory Identity Protection", "Microsoft Defender ATP")
Stage 3: where
| where AlertSeverity has_any ("Medium", "High")
Stage 4: extend
| extend Entities = parse_json(Entities), AlertTimeGenerated=TimeGenerated
Stage 5: kusto:mv-apply
| mv-apply Entity = Entities on
(
where Entity.Type == 'ip'
| extend EntityIp = tostring(Entity.Address)
)
Stage 6: kusto:mv-apply
| mv-apply Entity = Entities on
(
where Entity.Type == 'account'
| extend AccountObjectId = tostring(Entity.AadUserId)
)
Stage 7: where
| where isnotempty(EntityIp)
Stage 8: summarize
| summarize Entitys=make_set(Entity), SystemAlertIds=make_set(SystemAlertId)
by
AlertName,
ProductName,
AlertSeverity,
EntityIp,
Tactics,
Techniques,
ProviderName,
AlertTime= bin(AlertTimeGenerated, 1d),
AccountObjectId
Stage 9: join
| join kind=inner (
GCPAuditLogs
| where ServiceName == "compute.googleapis.com" and MethodName endswith "instances.insert"
| extend
GCPUserUPN= tostring(parse_json(AuthenticationInfo).principalEmail),
GCPUserIp = tostring(parse_json(RequestMetadata).callerIp),
GCPUserUA= tostring(parse_json(RequestMetadata).callerSuppliedUserAgent),
VMStatus = tostring(parse_json(Response).status),
VMOperation=tostring(parse_json(Response).operationType),
VMName= tostring(parse_json(Request).name),
VMType = tostring(split(parse_json(Request).machineType, "/")[-1])
| where GCPUserUPN !has "gserviceaccount.com"
| where VMOperation == "insert" and isnotempty(GCPUserIp) and GCPUserIp != "private"
| project
GCPOperationTime=TimeGenerated,
VMName,
VMStatus,
MethodName,
GCPUserUPN,
ProjectId,
GCPUserIp,
GCPUserUA,
VMOperation,
VMType
)
on $left.EntityIp == $right.GCPUserIp
Stage 10: join
| join kind=inner (IdentityInfo
| distinct AccountObjectId, AccountUPN, JobTitle
)
on AccountObjectId
Stage 11: extend
extend Name, TimeDiff, UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertSeverity | match |
| field:"AlertSeverity" kind:match |
Entity.Type | eq |
| field:"Entity.Type" kind:eq |
EntityIp | is_not_null | field:"EntityIp" kind:is_not_null | |
GCPUserIp | is_not_null | field:"GCPUserIp" kind:is_not_null | |
GCPUserIp | ne |
| field:"GCPUserIp" kind:ne value:"private" |
MethodName | ends_with |
| field:"MethodName" kind:ends_with value:"instances.insert" |
ProductName | in |
| field:"ProductName" kind:in |
ServiceName | eq |
| field:"ServiceName" kind:eq value:"compute.googleapis.com" |
VMOperation | eq |
| field:"VMOperation" kind:eq value:"insert" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
GCPUserUPN | match | gserviceaccount.com | excludes:GCPUserUPN field:"GCPUserUPN" value:"gserviceaccount.com" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountObjectId | summarize |
AlertName | summarize |
AlertSeverity | summarize |
AlertTime | summarize |
EntityIp | summarize |
Entitys | summarize |
ProductName | summarize |
ProviderName | summarize |
SystemAlertIds | summarize |
Tactics | summarize |
Techniques | summarize |
Name | extend |
TimeDiff | extend |
UPNSuffix | extend |
Tailscale: DNS nameservers modified
#Identifies when the tailnet's global DNS nameserver list is modified. Adding an attacker-controlled resolver as a tailnet-wide nameserver enables broad DNS hijacking for every device using MagicDNS resolution.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment | |
| Credential Access | |
| Command & Control |
Rule Body
id: c5d6e7f8-2345-6789-01ab-cdef12345011
name: "Tailscale: DNS nameservers modified"
description: |
Identifies when the tailnet's global DNS nameserver list is modified. Adding an attacker-controlled resolver as a tailnet-wide nameserver enables broad DNS hijacking for every device using MagicDNS resolution.
severity: High
status: Available
requiredDataConnectors:
- connectorId: TailscaleCCF
dataTypes:
- Tailscale_Audit_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- CommandAndControl
relevantTechniques:
- T1556
- T1568
query: |
Tailscale_Audit_CL
| where Action == "UPDATE"
| where tostring(Target.type) == "TAILNET"
| where tostring(Target.property) == "DNS_NAMESERVERS"
| extend ActorLogin = tostring(Actor.loginName)
| extend OldServers = Old.dns
| extend NewServers = New.dns
| extend Added = set_difference(NewServers, OldServers)
| extend Removed = set_difference(OldServers, NewServers)
| project TimeGenerated, ActorLogin, OldServers, NewServers, Added, Removed, Origin
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorLogin
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P1D
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0
Stages and Predicates
Stage 1: source
Tailscale_Audit_CL
Stage 2: where
| where Action == "UPDATE"
Stage 3: where
| where tostring(Target.type) == "TAILNET"
Stage 4: where
| where tostring(Target.property) == "DNS_NAMESERVERS"
Stage 5: extend (5 consecutive steps)
| extend ActorLogin = tostring(Actor.loginName)
| extend OldServers = Old.dns
| extend NewServers = New.dns
| extend Added = set_difference(NewServers, OldServers)
| extend Removed = set_difference(OldServers, NewServers)
Stage 6: project
| project TimeGenerated, ActorLogin, OldServers, NewServers, Added, Removed, Origin
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"UPDATE" |
Target.property | eq |
| field:"Target.property" kind:eq value:"DNS_NAMESERVERS" |
Target.type | eq |
| field:"Target.type" kind:eq value:"TAILNET" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorLogin | project |
Added | project |
NewServers | project |
OldServers | project |
Origin | project |
Removed | project |
TimeGenerated | project |
Tomcat - Server errors after multiple requests from same IP
#'Detects server errors after multiple requests from same IP address.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Impact |
Rule Body
id: 875da588-4875-11ec-81d3-0242ac130003
name: Tomcat - Server errors after multiple requests from same IP
description: |
'Detects server errors after multiple requests from same IP address.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CustomLogsAma
datatypes:
- Tomcat_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
- InitialAccess
relevantTechniques:
- T1498
- T1190
- T1133
query: |
let multi_errors = TomcatEvent
| where toint(HttpStatusCode) >= 500 and toint(HttpStatusCode) <= 599
| sort by EventStartTime asc
| summarize MultipleServerErrors = count() by SrcIpAddr, bin(EventStartTime, 5m)
| where MultipleServerErrors > 10;
let error_time_table = TomcatEvent
| where toint(HttpStatusCode) >= 500 and toint(HttpStatusCode) <= 599
| summarize error_time=min(EventStartTime) by SrcIpAddr
| join kind=innerunique (multi_errors) on SrcIpAddr;
TomcatEvent
| where toint(HttpStatusCode) >= 100 and toint(HttpStatusCode) <= 399
| summarize success_time=max(EventStartTime) by SrcIpAddr
| join kind=innerunique (error_time_table) on SrcIpAddr
| extend time_between_error_and_success = datetime_diff('second', error_time, success_time)
| where time_between_error_and_success between (1 .. 300)
| project time_between_error_and_success, error_time, success_time, SrcIpAddr
| extend IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Let Binding: multi_errors
let multi_errors = TomcatEvent
| where toint(HttpStatusCode) >= 500 and toint(HttpStatusCode) <= 599
| sort by EventStartTime asc
| summarize MultipleServerErrors = count() by SrcIpAddr, bin(EventStartTime, 5m)
| where MultipleServerErrors > 10;
Let Binding: error_time_table
let error_time_table = TomcatEvent
| where toint(HttpStatusCode) >= 500 and toint(HttpStatusCode) <= 599
| summarize error_time=min(EventStartTime) by SrcIpAddr
| join kind=innerunique (multi_errors) on SrcIpAddr;
Stage 1: source
TomcatEvent
Stage 2: where
| where toint(HttpStatusCode) >= 100 and toint(HttpStatusCode) <= 399
Stage 3: summarize
| summarize success_time=max(EventStartTime) by SrcIpAddr
Stage 4: join
join kind=innerunique (error_time_table) on SrcIpAddr
Stage 5: extend
extend time_between_error_and_success
Stage 6: where
where time_between_error_and_success >= 1 and time_between_error_and_success <= 300
Stage 7: project
project SrcIpAddr, error_time, success_time, time_between_error_and_success
Stage 8: extend
extend IPCustomEntity
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcIpAddr | project |
error_time | project |
success_time | project |
time_between_error_and_success | project |
IPCustomEntity | extend |
Vectra Create Incident Based on Tag for Hosts
#Create an incident when the host entity presents a specific tag. If the tag is present, an incident should be created and marked with highest priority.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: 8b7a1a64-8ef2-4000-b8c9-9bca3b93aace
name: Vectra Create Incident Based on Tag for Hosts
version: 1.1.0
kind: Scheduled
description: Create an incident when the host entity presents a specific tag. If the tag is present, an incident should be created and marked with highest priority.
status: Available
requiredDataConnectors:
- connectorId: VectraXDR
dataTypes:
- Entities_Data_CL
severity: High
queryFrequency: 10m
queryPeriod: 10m
suppressionDuration: PT1H
suppressionEnabled: false
triggerOperator: GreaterThan
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1546
query: |
Entities_Data_CL
| where entity_type == "host"
| extend Tags = todynamic(tags)
| where set_has_element(Tags, "MDR - Customer Escalation")
| summarize arg_max(['last_modified_timestamp'], *) by ['name']
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: name
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: P7D
matchingMethod: AllEntities
groupByEntities: [Host]
groupByAlertDetails: [DisplayName]
customDetails:
entity_id: id
entity_type: entity_type
tags: tags
attack_profile: attack_profile
ip_address: ip
alertDetailsOverride:
alertDisplayNameFormat: Vectra AI Incident- {{name}}
alertDescriptionFormat: An incident has been escalated for Vectra AI entity {{name}} that is presenting an urgency score of {{urgency_score}}
alertDynamicProperties:
- alertProperty: "AlertLink"
value: "url"
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Stage 1: source
Entities_Data_CL
Stage 2: where
| where entity_type == "host"
Stage 3: extend
| extend Tags = todynamic(tags)
Stage 4: where
| where set_has_element(Tags, "MDR - Customer Escalation")
Stage 5: summarize
| summarize arg_max(['last_modified_timestamp'], *) by ['name']
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Tags | in |
| field:"Tags" kind:in value:"MDR - Customer Escalation" |
entity_type | eq |
| field:"entity_type" kind:eq value:"host" |