Detection rules › Kusto
Kusto rules: 1i
A client made a web request to a potentially harmful file (ASIM Web Session schema)
#'This rule identifies a web request to a URL that holds a file type, including .ps1, .bat, .vbs, and .scr that can be harmful if downloaded. This analytic rule uses ASIM and supports any built-in or custom source that supports the ASIM WebSession schema (ASIM WebSession Schema)'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 09c49590-4e9d-4da9-a34d-17222d0c9e7e
name: A client made a web request to a potentially harmful file (ASIM Web Session schema)
description: |
'This rule identifies a web request to a URL that holds a file type, including .ps1, .bat, .vbs, and .scr that can be harmful if downloaded. This analytic rule uses [ASIM](https://aka.ms/AboutASIM) and supports any built-in or custom source that supports the ASIM WebSession schema (ASIM WebSession Schema)'
severity: Medium
tags:
- ParentAlert: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/CiscoUmbrella/CiscoUmbrellaRequestBlocklistedFileType.yaml
version: 1.0.0
- Schema: ASimWebSession
SchemaVersion: 0.2.1
requiredDataConnectors:
- connectorId: SquidProxy
dataTypes:
- SquidProxy_CL
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1189
query: |
let default_file_ext_blocklist = dynamic(['.ps1', '.vbs', '.bat', '.scr']); // Update this list as per your requirement
let custom_file_ext_blocklist=toscalar(_GetWatchlist('RiskyFileTypes')
| extend Extension=column_ifexists("Extension", "")
| where isnotempty(Extension)
| summarize make_set(Extension)); // If you have an extensive list, you can also create a Watchlist that includes the file extensions you want to detect
let file_ext_blocklist = array_concat(default_file_ext_blocklist, custom_file_ext_blocklist);
_Im_WebSession(starttime=ago(10min), url_has_any=file_ext_blocklist, eventresult='Success')
| extend requestedFileName=tostring(split(tostring(parse_url(Url)["Path"]), '/')[-1])
| extend requestedFileExtension=extract(@'(\.\w+)$', 1, requestedFileName, typeof(string))
| where requestedFileExtension in (file_ext_blocklist)
| summarize
EventStartTime=min(TimeGenerated),
EventEndTime=max(TimeGenerated),
EventCount=count()
by SrcIpAddr, SrcUsername, SrcHostname, requestedFileName, Url
| extend
Name = iif(SrcUsername contains "@", tostring(split(SrcUsername, '@', 0)[0]), SrcUsername),
UPNSuffix = iif(SrcUsername contains "@", tostring(split(SrcUsername, '@', 1)[0]), "")
alertDetailsOverride:
alertDisplayNameFormat: Client {{SrcIpAddr}} accessed a URL with potentially harmful extension {{requestedFileExtension}}
alertDescriptionFormat: The client at address {{SrcIpAddr}} accessed the URL {{Url}} that has the extension {{requestedFileExtension}}. Downloading a file with this extension may be harmful and may indicate malicious activity.
customDetails:
requestedFileExt: requestedFileExtension
Username: SrcUsername
SrcHostname: SrcHostname
eventGroupingSettings:
aggregationKind: AlertPerResult
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
- entityType: File
fieldMappings:
- identifier: Name
columnName: requestedFileName
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SrcUsername
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
version: 1.1.3
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Yaron
support:
tier: Community
categories:
domains: [ "Security - Others" ]
Stages and Predicates
Parameters
let default_file_ext_blocklist = dynamic(['.ps1', '.vbs', '.bat', '.scr']);
let file_ext_blocklist = array_concat(default_file_ext_blocklist, custom_file_ext_blocklist);
Let Binding: custom_file_ext_blocklist
let custom_file_ext_blocklist = toscalar(_GetWatchlist('RiskyFileTypes')
| extend Extension=column_ifexists("Extension", "")
| where isnotempty(Extension)
| summarize make_set(Extension));
Stage 1: source
_Im_WebSession(starttime=ago(10min), url_has_any=file_ext_blocklist, eventresult='Success')
Stage 2: extend
| extend requestedFileName=tostring(split(tostring(parse_url(Url)["Path"]), '/')[-1])
Stage 3: extend
| extend requestedFileExtension=extract(@'(\.\w+)$', 1, requestedFileName, typeof(string))
Stage 4: where
| where requestedFileExtension in (file_ext_blocklist)
Stage 5: summarize
| summarize
EventStartTime=min(TimeGenerated),
EventEndTime=max(TimeGenerated),
EventCount=count()
by SrcIpAddr, SrcUsername, SrcHostname, requestedFileName, Url
Stage 6: extend
| extend
Name = iif(SrcUsername contains "@", tostring(split(SrcUsername, '@', 0)[0]), SrcUsername),
UPNSuffix = iif(SrcUsername contains "@", tostring(split(SrcUsername, '@', 1)[0]), "")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
requestedFileExtension | in |
| field:"requestedFileExtension" kind:in value:"file_ext_blocklist" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EventCount | summarize |
EventEndTime | summarize |
EventStartTime | summarize |
SrcHostname | summarize |
SrcIpAddr | summarize |
SrcUsername | summarize |
Url | summarize |
requestedFileName | summarize |
Name | extend |
UPNSuffix | extend |
API - API Scraping
#'42Crunch API protection against API scraping'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Collection |
Rule Body
id: d944d564-b6fa-470d-b5ab-41b341878c5e
name: API - API Scraping
description: |
'42Crunch API protection against API scraping'
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:
- Reconnaissance
- Collection
relevantTechniques:
- T1593
- T1119
query: |
let firstRec = FortyTwoCrunchAPIProtection
| where TimeGenerated >= ago(5m)
| project-away NonBlockingMode, SourcePort, DestinationPort, Query, ApiId, RequestHeader, ResponseHeader, Errors, EventType, Uuid
| where UriPath has "/api/properties?page=" and Status == 200;
let rateLimitCount = (toscalar(firstRec | count));
let recCount = iff((rateLimitCount > 100), 1, 0);
firstRec | top recCount by Timestamp desc;
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
Parameters
let recCount = iff((rateLimitCount > 100), 1, 0);
let firstRec is inlined into the numbered stages below.
Let Binding: rateLimitCount
let rateLimitCount = (toscalar(firstRec | count));
Stages 1 to 4 define let firstRec 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/properties?page=" and Status == 200
Stage 5: top
firstRec
| top recCount by Timestamp desc;
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/properties?page=" |
Application Gateway WAF - XSS Detection
#'Identifies a match for XSS attack in the Application gateway WAF logs. The Threshold value in the query can be changed as per your infrastructure's requirement. References: https://owasp.org/www-project-top-ten/2017/A7_2017-Cross-Site_Scripting_(XSS)'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Execution |
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: d2bc08fa-030a-4eea-931a-762d27c6a042
name: Application Gateway WAF - XSS Detection
description: |
'Identifies a match for XSS attack in the Application gateway WAF logs. The Threshold value in the query can be changed as per your infrastructure's requirement.
References: https://owasp.org/www-project-top-ten/2017/A7_2017-Cross-Site_Scripting_(XSS)'
severity: High
requiredDataConnectors:
- connectorId: WAF
dataTypes:
- AzureDiagnostics
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
relevantTechniques:
- T1189
- T1203
- T0853
tags:
- Cross Site Scripting
query: |
let Threshold = 1;
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Matched"
| project transactionId_g, hostname_s, requestUri_s, TimeGenerated, clientIp_s, Message, details_message_s, details_data_s
| join kind = inner(
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| parse Message with MessageText 'Total Inbound Score: ' TotalInboundScore ' - SQLI=' SQLI_Score ',XSS=' XSS_Score ',RFI=' RFI_Score ',LFI=' LFI_Score ',RCE=' RCE_Score ',PHPI=' PHPI_Score ',HTTP=' HTTP_Score ',SESS=' SESS_Score '): ' Blocked_Reason '; individual paranoia level scores:' Paranoia_Score
| where Blocked_Reason contains "XSS" and toint(TotalInboundScore) >=15 and toint(XSS_Score) >= 10 and toint(SQLI_Score) <= 5) on transactionId_g
| extend Uri = strcat(hostname_s,requestUri_s)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), TransactionID = make_set(transactionId_g), Message = make_set(Message), Detail_Message = make_set(details_message_s), Detail_Data = make_set(details_data_s), Total_TransactionId = dcount(transactionId_g) by clientIp_s, Uri, action_s, SQLI_Score, XSS_Score, TotalInboundScore
| where Total_TransactionId >= Threshold
# The Threshold value above can be changed as per your infrastructure's requirement
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Uri
- entityType: IP
fieldMappings:
- identifier: Address
columnName: clientIp_s
version: 1.0.1
kind: Scheduled
metadata:
source:
kind: Community
author:
name: shabaz-github
support:
tier: Community
categories:
domains: [ "Security - Threat Protection", "Platform" ]
Stages and Predicates
Parameters
let Threshold = 1;
Stage 1: source
AzureDiagnostics
Stage 2: where
| where Category == "ApplicationGatewayFirewallLog"
Stage 3: where
| where action_s == "Matched"
Stage 4: project
| project transactionId_g, hostname_s, requestUri_s, TimeGenerated, clientIp_s, Message, details_message_s, details_data_s
Stage 5: join
| join kind = inner(
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog"
| where action_s == "Blocked"
| parse Message with MessageText 'Total Inbound Score: ' TotalInboundScore ' - SQLI=' SQLI_Score ',XSS=' XSS_Score ',RFI=' RFI_Score ',LFI=' LFI_Score ',RCE=' RCE_Score ',PHPI=' PHPI_Score ',HTTP=' HTTP_Score ',SESS=' SESS_Score '): ' Blocked_Reason '; individual paranoia level scores:' Paranoia_Score
| where Blocked_Reason contains "XSS" and toint(TotalInboundScore) >=15 and toint(XSS_Score) >= 10 and toint(SQLI_Score) <= 5) on transactionId_g
Stage 6: extend
extend Uri
Stage 7: summarize
summarize Detail_Data, Detail_Message, EndTime, Message, StartTime, Total_TransactionId, TransactionID by clientIp_s, Uri, action_s, SQLI_Score, XSS_Score, TotalInboundScore
Stage 8: where
where Total_TransactionId >= 1
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Blocked_Reason | contains |
| field:"Blocked_Reason" kind:contains value:"XSS" |
Category | eq |
| field:"Category" kind:eq value:"ApplicationGatewayFirewallLog" |
SQLI_Score | le |
| field:"SQLI_Score" kind:le value:"5" |
TotalInboundScore | ge |
| field:"TotalInboundScore" kind:ge value:"15" |
Total_TransactionId | ge |
| field:"Total_TransactionId" kind:ge value:"1" |
XSS_Score | ge |
| field:"XSS_Score" kind:ge value:"10" |
action_s | eq |
| field:"action_s" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Detail_Data | summarize |
Detail_Message | summarize |
EndTime | summarize |
Message | summarize |
SQLI_Score | summarize |
StartTime | summarize |
TotalInboundScore | summarize |
Total_TransactionId | summarize |
TransactionID | summarize |
Uri | summarize |
XSS_Score | summarize |
action_s | summarize |
clientIp_s | summarize |
Aqua Blizzard AV hits - Feb 2022
#'Identifies a match in the Security Alert table for MDATP hits related to the Aqua Blizzard actor'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Rule Body
id: 18dbdc22-b69f-4109-9e39-723d9465f45f
name: Aqua Blizzard AV hits - Feb 2022
description: |
'Identifies a match in the Security Alert table for MDATP hits related to the Aqua Blizzard actor'
severity: High
status: Available
requiredDataConnectors:
- connectorId: MicrosoftDefenderAdvancedThreatProtection
dataTypes:
- SecurityAlert (MDATP)
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1137
tags:
- Aqua Blizzard
query: |
let iocs = externaldata(DateAdded:string,IoC:string,Type:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/ActiniumIOC.csv"] with (format="csv", ignoreFirstRecord=True);
let AVHits = (iocs | where Type =~ "AVDetection"| project IoC);
SecurityAlert
| where ProviderName == 'MDATP'
| extend ThreatName_ = tostring(parse_json(ExtendedProperties).ThreatName)
| where ThreatName_ has_any (AVHits)
| extend Directory = tostring(parse_json(Entities)[0].Directory), SHA256 = tostring(parse_json(tostring(parse_json(Entities)[0].FileHashes))[2].Value), FileName = tostring(parse_json(Entities)[0].Name), Hostname = tostring(parse_json(Entities)[6].FQDN)
| extend AccountName = tostring(parse_json(tostring(parse_json(Entities)[6].LoggedOnUsers))[0].AccountName)
| project TimeGenerated, AlertName, ThreatName_, ProviderName, AlertSeverity, Description, RemediationSteps, ExtendedProperties, Entities, FileName,SHA256, Directory, Hostname, AccountName
| extend FileHash = SHA256, FileHashType = "SHA256"
| extend Name = tostring(split(AccountName, "@")[0]), AccountUPNSuffix = tostring(split(AccountName, "@")[1])
| extend Host = tostring(split(Hostname, ".")[0]), DomainIndex = toint(indexof(Hostname, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Hostname, DomainIndex + 1), Hostname)
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Hostname
- identifier: HostName
columnName: Host
- identifier: NTDomain
columnName: HostNameDomain
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: FileHashType
- identifier: Value
columnName: FileHash
version: 1.0.3
kind: Scheduled
Stages and Predicates
Let Binding: iocs
let iocs = externaldata(DateAdded:string,IoC:string,Type:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/ActiniumIOC.csv"] with (format="csv", ignoreFirstRecord=True);
Let Binding: AVHits
let AVHits = (iocs | where Type =~ "AVDetection"| project IoC);
Stage 1: source
SecurityAlert
Stage 2: where
| where ProviderName == 'MDATP'
Stage 3: extend
| extend ThreatName_ = tostring(parse_json(ExtendedProperties).ThreatName)
Stage 4: where
| where ThreatName_ has_any (AVHits)
Stage 5: extend
| extend Directory = tostring(parse_json(Entities)[0].Directory), SHA256 = tostring(parse_json(tostring(parse_json(Entities)[0].FileHashes))[2].Value), FileName = tostring(parse_json(Entities)[0].Name), Hostname = tostring(parse_json(Entities)[6].FQDN)
Stage 6: extend
| extend AccountName = tostring(parse_json(tostring(parse_json(Entities)[6].LoggedOnUsers))[0].AccountName)
Stage 7: project
| project TimeGenerated, AlertName, ThreatName_, ProviderName, AlertSeverity, Description, RemediationSteps, ExtendedProperties, Entities, FileName,SHA256, Directory, Hostname, AccountName
Stage 8: extend (4 consecutive steps)
| extend FileHash = SHA256, FileHashType = "SHA256"
| extend Name = tostring(split(AccountName, "@")[0]), AccountUPNSuffix = tostring(split(AccountName, "@")[1])
| extend Host = tostring(split(Hostname, ".")[0]), DomainIndex = toint(indexof(Hostname, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Hostname, DomainIndex + 1), Hostname)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ProviderName | eq |
| field:"ProviderName" kind:eq value:"MDATP" |
ThreatName_ | match |
| field:"ThreatName_" kind:match value:"AVHits" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
AlertName | project |
AlertSeverity | project |
Description | project |
Directory | project |
Entities | project |
ExtendedProperties | project |
FileName | project |
Hostname | project |
ProviderName | project |
RemediationSteps | project |
SHA256 | project |
ThreatName_ | project |
TimeGenerated | project |
FileHash | extend |
FileHashType | extend |
AccountUPNSuffix | extend |
Name | extend |
DomainIndex | extend |
Host | extend |
HostNameDomain | extend |
ARGOS Cloud Security - Exploitable Cloud Resources
#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.
Exploitable Cloud Security Issues are ones that expose cloud resources to the internet and allow initial access to your environment.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: a9bf1b8c-c761-4840-b9a8-7535ca68ca28
name: ARGOS Cloud Security - Exploitable Cloud Resources
description: Exploitable Cloud Security Issues are ones that expose cloud resources to the internet and allow initial access to your environment.
requiredDataConnectors:
- connectorId: ARGOSCloudSecurity
dataTypes:
- ARGOS_CL
kind: Scheduled
severity: High
status: Available
query: |
ARGOS_CL | where exploitable_b
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: PT5H
suppressionEnabled: false
tactics:
- InitialAccess
relevantTechniques:
- T1190
alertRuleTemplateName:
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: New exploitable cloud resource - {{name_s}} - {{ruleId_s}}
customDetails:
entityMappings:
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: ResourceId
- entityType: URL
fieldMappings:
- identifier: Url
columnName: url_s
version: 1.0.3
Stages and Predicates
Stage 1: source
ARGOS_CL
Stage 2: where
| where exploitable_b
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
exploitable_b | eq |
| field:"exploitable_b" kind:eq value:"true" |
AWSCloudTrail - Successful Tampering with AWS CloudTrail Logs
#Identifies successful attempts to disable, delete, or weaken AWS logging telemetry, including CloudTrail, CloudWatch/EventBridge, and VPC flow logs. This behavior can indicate defense evasion and deliberate reduction of incident visibility by an attacker.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- A CloudTrail Was Created or Updated (Panther)
- Account Security Configuration Changed (Panther)
- ASL AWS Defense Evasion Delete Cloudtrail (Splunk)
- ASL AWS Defense Evasion Delete CloudWatch Log Group (Splunk)
- ASL AWS Defense Evasion Stop Logging Cloudtrail (Splunk)
- ASL AWS Defense Evasion Update Cloudtrail (Splunk)
- AWS Lateral Movement from Kubernetes SA via AssumeRoleWithWebIdentity (Elastic)
- AWS VPC Flow Logs Deleted (Sigma)
Rule Body
id: 633a91df-d031-4b6e-a413-607a61540559
name: AWSCloudTrail - Successful Tampering with AWS CloudTrail Logs
description: |
Identifies successful attempts to disable, delete, or weaken AWS logging telemetry, including CloudTrail,
CloudWatch/EventBridge, and VPC flow logs. This behavior can indicate defense evasion and deliberate reduction
of incident visibility by an attacker.
severity: High
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
- connectorId: AWSS3
dataTypes:
- AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562.008
query: |
let EventNameList = dynamic(["UpdateTrail","DeleteTrail","StopLogging","DeleteFlowLogs","DeleteEventBus","DeleteLogGroup"]);
AWSCloudTrail
| where (EventName in~ (EventNameList) or (EventName == "UpdateTrail" and (parse_json(RequestParameters).enableLogFileValidation) == false) or (EventName == "UpdateTrail" and (parse_json(RequestParameters).isMultiRegionTrail) == false)) and isempty(ErrorMessage) and isempty(ErrorCode)
| 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]), "")
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by EventName, EventTypeName, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent,
UserIdentityUserName, SessionMfaAuthenticated, SourceIpAddress, AWSRegion, EventSource
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
EventTypeName: EventTypeName
EventSource: EventSource
AWSRegion: AWSRegion
UserAgent: UserAgent
alertDetailsOverride:
alertDisplayNameFormat: 'AWS log tampering attempt: {{EventName}} by {{AccountName}}'
alertDescriptionFormat: 'Detected {{EventName}} in {{AWSRegion}} affecting account {{RecipientAccountId}} logging telemetry.'
version: 1.0.6
kind: Scheduled
Stages and Predicates
Parameters
let EventNameList = dynamic(["UpdateTrail","DeleteTrail","StopLogging","DeleteFlowLogs","DeleteEventBus","DeleteLogGroup"]);
Stage 1: source
AWSCloudTrail
Stage 2: where
| where (EventName in~ (EventNameList) or (EventName == "UpdateTrail" and (parse_json(RequestParameters).enableLogFileValidation) == false) or (EventName == "UpdateTrail" and (parse_json(RequestParameters).isMultiRegionTrail) == false)) and isempty(ErrorMessage) and isempty(ErrorCode)
Stage 3: 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 4: summarize
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by EventName, EventTypeName, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent,
UserIdentityUserName, SessionMfaAuthenticated, SourceIpAddress, AWSRegion, EventSource
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ErrorCode | is_null | field:"ErrorCode" kind:is_null | |
ErrorMessage | is_null | field:"ErrorMessage" kind:is_null | |
EventName | eq |
| field:"EventName" kind:eq value:"UpdateTrail" |
EventName | in |
| field:"EventName" kind:in |
RequestParameters.enableLogFileValidation | eq |
| field:"RequestParameters.enableLogFileValidation" kind:eq value:"false" |
RequestParameters.isMultiRegionTrail | eq |
| field:"RequestParameters.isMultiRegionTrail" kind:eq value:"false" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AWSRegion | summarize |
AccountName | summarize |
AccountUPNSuffix | summarize |
EndTimeUtc | summarize |
EventName | summarize |
EventSource | summarize |
EventTypeName | summarize |
RecipientAccountId | summarize |
SessionMfaAuthenticated | summarize |
SourceIpAddress | summarize |
StartTimeUtc | summarize |
UserAgent | summarize |
UserIdentityAccountId | summarize |
UserIdentityPrincipalid | summarize |
UserIdentityUserName | summarize |
BloodHound Attack Path Finding - Tier Zero SMSA Installed on Non-Tier Zero Computer
#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 - Tier Zero SMSA Installed on Non-Tier Zero Computer
Rule Body
id: 221a122d-8e68-42c9-beaf-010fe184a88f
name: BloodHound Attack Path Finding - Tier Zero SMSA Installed on Non-Tier Zero Computer
description: BloodHound Attack Path Finding - Tier Zero SMSA Installed on Non-Tier
Zero Computer
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 == "T0DumpSMSA"
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 == "T0DumpSMSA"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0DumpSMSA" |
Cloudflare - Unexpected client request
#'Detects client requests to unusual client request.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: f32142b1-4bcb-45c0-92e4-2ddc18768522
name: Cloudflare - Unexpected client request
description: |
'Detects client requests to unusual client request.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CloudflareDataConnector
dataTypes:
- Cloudflare
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
- T1133
query: |
Cloudflare
| where HttpRequestMethod =~ 'GET'
| where DstBytes != 0 or SrcBytes != 0
| where ClientRequestURI has_any ('/admin', '/admin.php', 'wp-admin', '.htaccess', '/etc/shadow', '/etc/passwd', '/etc/hosts', '/etc/ssh/')
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
Cloudflare
Stage 2: where
| where HttpRequestMethod =~ 'GET'
Stage 3: where
| where DstBytes != 0 or SrcBytes != 0
Stage 4: where
| where ClientRequestURI has_any ('/admin', '/admin.php', 'wp-admin', '.htaccess', '/etc/shadow', '/etc/passwd', '/etc/hosts', '/etc/ssh/')
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ClientRequestURI | match |
| field:"ClientRequestURI" kind:match |
DstBytes | ne |
| field:"DstBytes" kind:ne value:"0" |
HttpRequestMethod | eq |
| field:"HttpRequestMethod" kind:eq value:"GET" |
SrcBytes | ne |
| field:"SrcBytes" kind:ne value:"0" |
Connection to Backup Repository Lost
#Detects when a backup server fails to connect to a backup repository.
Rule Body
id: 8777f9e1-db58-4078-a381-4e47a3a2ee01
name: Connection to Backup Repository Lost
description: Detects when a backup server fails to connect to a backup repository.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 21224\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n [\"User Name\"] = user,\n MessageDetails = Description,\n\
\ Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 21224
Stage 3: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["User Name"] = user,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"21224" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
User Name | project |
Cyble Vision Alerts Cloud Storage
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
'Detects cloud storage objects/paths discovered in ingestion (uses Alerts_cloud_storage parser). Creates incidents for discovered S3/GCS/Azure blob objects, includes bucket/object/url, size and workflow metadata. Mandatory custom details: MappedSeverity, Status, AlertID, Service.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery | |
| Exfiltration |
Rule Body
id: db417cee-529c-4eac-b7b9-36eb0166800a
name: Cyble Vision Alerts Cloud Storage
description: |
'Detects cloud storage objects/paths discovered in ingestion (uses Alerts_cloud_storage parser). Creates incidents for discovered S3/GCS/Azure blob objects, includes bucket/object/url, size and workflow metadata. Mandatory custom details: MappedSeverity, Status, AlertID, Service.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- Exfiltration
- Discovery
relevantTechniques:
- T1537
- T1083
query: |
Alerts_cloud_storage
| where Service == "cloud_storage"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: Exposed cloud object {{CS_Filename}} in {{CS_Bucket}}.
alertDescriptionFormat: |
A cloud storage object was discovered by ingestion. (ID {{CS_BucketId}}). Object {{CS_FullPath}} (ID {{CS_ObjectId}}). Review and remediate access if required.
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
CS_Bucket: CS_Bucket
CS_BucketId: CS_BucketId
CS_Filename: CS_Filename
CS_FullPath: CS_FullPath
CS_ObjectId: CS_ObjectId
CS_Size: CS_Size
CS_Url: CS_Url
ProcessedByAgenticAI: CS_ProcessedByAgenticAI
CS_AgenticAIStatus: CS_AgenticAIStatus
CS_WorkflowID: CS_WorkflowID
entityMappings:
- entityType: Url
fieldMappings:
- identifier: Url
columnName: CS_Url
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: CS_Bucket
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_cloud_storage
Stage 2: where
| where Service == "cloud_storage"
Stage 3: extend
| extend MappedSeverity = Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"cloud_storage" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
CYFIRMA - High Severity Asset based Vulnerabilities Rule Alert
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This rule detects high severity asset-based vulnerabilities from CYFIRMA's vulnerability intelligence data. It identifies vulnerabilities with a confidence score of 80 or higher, excluding those categorized as 'ATTACK_SURFACE_VULNERABILITY', and generates alerts for assets that may be at risk."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Privilege Escalation | |
| Defense Impairment | |
| Credential Access | |
| Lateral Movement |
Rule Body
id: 123fad02-6d9e-439e-8241-7a2fffa7e0a5
name: CYFIRMA - High Severity Asset based Vulnerabilities Rule Alert
description: |
"This rule detects high severity asset-based vulnerabilities from CYFIRMA's vulnerability intelligence data.
It identifies vulnerabilities with a confidence score of 80 or higher, excluding those categorized as 'ATTACK_SURFACE_VULNERABILITY', and generates alerts for assets that may be at risk."
version: 1.0.1
kind: Scheduled
severity: High
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaVulnerabilitiesIntelDC
dataTypes:
- CyfirmaVulnerabilities_CL
query: |
// High severity - Asset based Vulnerabilities
let timeFrame = 5m;
CyfirmaVulnerabilities_CL
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
| mv-expand extensionKeys
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
attack_complexity = tostring(props.attack_complexity),
cvss_score = toreal(props.cvss_score),
integrity_impact = tostring(props.integrity_impact),
impact_score = tostring(props.impact_score),
attack_vector = tostring(props.attack_vector),
privileges_required = tostring(props.privileges_required),
cvss_version = tostring(props.cvss_version),
user_interaction = tostring(props.user_interaction),
cvss_vector = tostring(props.cvss_vector),
scope = tostring(props.scope),
confidentiality_impact = tostring(props.confidentiality_impact),
exploitability_score = toreal(props.exploitability_score),
products = tostring(props.products),
technologies = tostring(props.technologies),
vendors = tostring(props.vendors),
confidence_score = toint(confidence),
servers = tostring(props.servers),
vulnerability_type = tostring(props.vulnerability_type),
vulnerability_category = tostring(props.vulnerability_category),
NetworkIPs = tostring(props.ips),
ProviderName ='CYFIRMA',
ProductName ='DeCYFIR/DeTCT'
| summarize arg_max(
integrity_impact,
TimeGenerated,
id,
description,
confidence_score,
created,
modified,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
)
by name
| where confidence_score >= 80 and vulnerability_category != 'ATTACK_SURFACE_VULNERABILITY' and TimeGenerated between (ago(timeFrame) .. now())
| project
TimeGenerated,
name,
confidence_score,
integrity_impact,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
UID = id,
description,
created,
modified,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: false
tactics:
- Execution
- LateralMovement
- PrivilegeEscalation
- InitialAccess
- CredentialAccess
- DefenseEvasion
relevantTechniques:
- T1059
- T1203
- T1210
- T1068
- T1190
- T1133
- T1003
- T1553
- T1548.002
- T1021.002
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - High Severity Asset based Vulnerability Identified - {{name}} "
alertDescriptionFormat: "{{description}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
TimeGenerated: TimeGenerated
CVE: name
ConfidenceScore: confidence_score
IntegrityImpact: integrity_impact
AttackComplexity: attack_complexity
CVSSScore: cvss_score
ImpactScore: impact_score
AttackVector: attack_vector
ConfidentialImpact: confidentiality_impact
PrivilegesRequired: privileges_required
CVSSVersion: cvss_version
UserInteraction: user_interaction
CVSSVector: cvss_vector
scope: scope
ExploitabilityScore: exploitability_score
Modified: modified
Products: products
Vendors: vendors
Technologies: technologies
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaVulnerabilities_CL
Stage 2: extend
| extend parsed = parse_json(extensions)
Stage 3: extend
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
attack_complexity = tostring(props.attack_complexity),
cvss_score = toreal(props.cvss_score),
integrity_impact = tostring(props.integrity_impact),
impact_score = tostring(props.impact_score),
attack_vector = tostring(props.attack_vector),
privileges_required = tostring(props.privileges_required),
cvss_version = tostring(props.cvss_version),
user_interaction = tostring(props.user_interaction),
cvss_vector = tostring(props.cvss_vector),
scope = tostring(props.scope),
confidentiality_impact = tostring(props.confidentiality_impact),
exploitability_score = toreal(props.exploitability_score),
products = tostring(props.products),
technologies = tostring(props.technologies),
vendors = tostring(props.vendors),
confidence_score = toint(confidence),
servers = tostring(props.servers),
vulnerability_type = tostring(props.vulnerability_type),
vulnerability_category = tostring(props.vulnerability_category),
NetworkIPs = tostring(props.ips),
ProviderName ='CYFIRMA',
ProductName ='DeCYFIR/DeTCT'
Stage 6: summarize
| summarize arg_max(
integrity_impact,
TimeGenerated,
id,
description,
confidence_score,
created,
modified,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
)
by name
Stage 7: where
| where confidence_score >= 80 and vulnerability_category != 'ATTACK_SURFACE_VULNERABILITY' and TimeGenerated between (ago(timeFrame) .. now())
Stage 8: project
| project
TimeGenerated,
name,
confidence_score,
integrity_impact,
attack_complexity,
cvss_score,
impact_score,
attack_vector,
UID = id,
description,
created,
modified,
privileges_required,
cvss_version,
user_interaction,
cvss_vector,
scope,
confidentiality_impact,
exploitability_score,
products,
technologies,
vendors,
ProviderName,
ProductName,
servers,
NetworkIPs,
vulnerability_type,
vulnerability_category
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
confidence_score | ge |
| field:"confidence_score" kind:ge value:"80" |
vulnerability_category | ne |
| field:"vulnerability_category" kind:ne value:"ATTACK_SURFACE_VULNERABILITY" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
NetworkIPs | project |
ProductName | project |
ProviderName | project |
TimeGenerated | project |
UID | project |
attack_complexity | project |
attack_vector | project |
confidence_score | project |
confidentiality_impact | project |
created | project |
cvss_score | project |
cvss_vector | project |
cvss_version | project |
description | project |
exploitability_score | project |
impact_score | project |
integrity_impact | project |
modified | project |
name | project |
privileges_required | project |
products | project |
scope | project |
servers | project |
technologies | project |
user_interaction | project |
vendors | project |
vulnerability_category | project |
vulnerability_type | project |
Detect CVE exploits on network for which a device is vulnerable
#This detection query can be used to find specific CVE exploits passing on the wire for which the device is vulnerable. This query should have a very high TP rate, and can be considered as a 'High severity' query.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Privilege Escalation | |
| Lateral Movement |
References
Telemetry Coverage
Rule Body
// Get all the TVM data
let tvm_data = DeviceTvmSoftwareVulnerabilities
| distinct DeviceName, SoftwareName, SoftwareVendor, SoftwareVersion, CveId, VulnerabilitySeverityLevel;
// Get CVE signatures on the network
DeviceNetworkEvents
| where ActionType contains "NetworkSignatureInspected"
| extend AdditionalFields = todynamic(AdditionalFields)
| extend SignatureName = tostring(AdditionalFields.SignatureName),
SignatureMatchedContent = tostring(AdditionalFields.SignatureMatchedContent),
SamplePacketContent = tostring(AdditionalFields.SamplePacketContent)
| where SignatureName contains "CVE"
// Join the TVM data of the related device
| join kind=inner tvm_data on DeviceName
// Check if the server is vulnerable to the detected CVE in network traffic
| where SignatureName == CveId
| project-away DeviceName1
Stages and Predicates
Let Binding: tvm_data
let tvm_data = DeviceTvmSoftwareVulnerabilities
| distinct DeviceName, SoftwareName, SoftwareVendor, SoftwareVersion, CveId, VulnerabilitySeverityLevel;
Stage 1: source
let tvm_data
Stage 2: source
DeviceNetworkEvents
Stage 3: where
where ActionType contains "NetworkSignatureInspected"
Stage 4: extend
extend AdditionalFields
Stage 5: extend
extend SamplePacketContent, SignatureMatchedContent, SignatureName
Stage 6: where
where SignatureName contains "CVE"
Stage 7: join
join kind=inner (tvm_data) on DeviceName
Stage 8: where
where SignatureName =~ CveId
Stage 9: project-away
project-away DeviceName1
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | contains |
| field:"ActionType" kind:contains value:"NetworkSignatureInspected" |
SignatureName | contains |
| field:"SignatureName" kind:contains value:"CVE" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AdditionalFields | extend |
SamplePacketContent | extend |
SignatureMatchedContent | extend |
SignatureName | extend |
Excessive Denied Proxy Traffic
#'This alert creates an incident when a client generates an excessive amounts of denied proxy traffic.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment | |
| Command & Control |
Rule Body
id: 7a58b253-0ef2-4248-b4e5-c350f15a8346
name: Excessive Denied Proxy Traffic
description: |
'This alert creates an incident when a client generates an excessive amounts of denied proxy traffic.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- CommandAndControl
relevantTechniques:
- T1090
- T1562
query: |
let threshold = 100;
SymantecProxySG
| where sc_filter_result =~ "DENIED"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by c_ip, cs_host
| where count_ > threshold
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: cs_host
- entityType: IP
fieldMappings:
- identifier: Address
columnName: c_ip
version: 1.0.5
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 100;
Stage 1: source
SymantecProxySG
Stage 2: where
| where sc_filter_result =~ "DENIED"
Stage 3: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by c_ip, cs_host
Stage 4: where
| where count_ > threshold
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
count_ | gt |
| field:"count_" kind:gt value:"100" |
sc_filter_result | eq |
| field:"sc_filter_result" kind:eq value:"DENIED" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTime | summarize |
StartTime | summarize |
c_ip | summarize |
cs_host | summarize |
Mimecast Data Leak Prevention - Hold
#Detects threat for data leak when action is hold
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: 3e12b7b1-75e5-497c-ba01-b6cb30b60d7f
name: Mimecast Data Leak Prevention - Hold
description: Detects threat for data leak when action is hold
severity: Informational
requiredDataConnectors:
- connectorId: MimecastSIEMAPI
dataTypes:
- MimecastDLP_CL
enabled: true
query: MimecastDLP_CL| where action_s == "hold";
queryFrequency: 5m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- Exfiltration
relevantTechniques:
- T1030
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1d
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: MailMessage
fieldMappings:
- identifier: Sender
columnName: senderAddress_s
- identifier: Recipient
columnName: recipientAddress_s
- identifier: DeliveryAction
columnName: action_s
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
MimecastDLP_CL
Stage 2: where
| where action_s == "hold";
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action_s | eq |
| field:"action_s" kind:eq value:"hold" |
Process Injection From Untrusted Process
#This query searches for processes performing remote process injection via multiple API calls related to process injection. It filters out programs that inject into their own process or into a process from the same directory. It then finds suspicious processes based on the global prevalence.
Known False Positives
- Some legitimate software uses process injection, for example, when performing debugging.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Privilege Escalation | |
| Stealth |
References
Telemetry Coverage
Rule Body
let timeframe = 2*1h;
let default_global_prevalence = 0;
let AllProcessInjectionEvents = materialize(
DeviceEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType in~ ("QueueUserApcRemoteApiCall","NtAllocateVirtualMemoryRemoteApiCall", "CreateRemoteThreadApiCall", "SetThreadContextRemoteApiCall", "NtMapViewOfSectionRemoteApiCall") and ProcessId != InitiatingProcessId
| extend InitiatingProcessSHA1=tolower(InitiatingProcessSHA1)
| where not(InitiatingProcessFolderPath startswith FolderPath) // Exclude injection into processes in the same directory.
);
let SuspiciousProcessInjectionEvents = (
AllProcessInjectionEvents
| where not(isempty(InitiatingProcessSHA1)) // Only with a valid SHA1.
| summarize MachineCount=dcount(DeviceId) by InitiatingProcessSHA1
// Take 1000 of the most unique hashes, as files with high prevalence are very likely to be legitimately signed.
| top 1000 by MachineCount asc
| invoke FileProfile(InitiatingProcessSHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where coalesce(GlobalPrevalence,default_global_prevalence) < 200 or ((isempty(Signer) or not(IsCertificateValid)) and coalesce(GlobalPrevalence,default_global_prevalence) < 500)
);
AllProcessInjectionEvents
| lookup kind=inner SuspiciousProcessInjectionEvents on InitiatingProcessSHA1
// Work around the Defender limitation where FolderPath for CreateRemoteThreadApiCall does not contain FileName where it does for other events.
| extend InjectionTarget=strcat(FolderPath,@"\",FileName)
// Begin environment-specific filter.
// End environment-specific filter.
| summarize arg_min(Timestamp, *), InjectionTargets=make_set(InjectionTarget) by DeviceId, InitiatingProcessFolderPath // Show only the first invocation per device.
| extend InjectionSource=InitiatingProcessFolderPath, InjectionCommandLine=InitiatingProcessCommandLine
| project-reorder Timestamp, InjectionSource, InjectionCommandLine, InjectionTargets
Stages and Predicates
Parameters
let timeframe = 2*1h;
let default_global_prevalence = 0;
let AllProcessInjectionEvents is inlined into the numbered stages below.
Let Binding: SuspiciousProcessInjectionEvents
let SuspiciousProcessInjectionEvents = (
AllProcessInjectionEvents
| where not(isempty(InitiatingProcessSHA1))
| summarize MachineCount=dcount(DeviceId) by InitiatingProcessSHA1
| top 1000 by MachineCount asc
| invoke FileProfile(InitiatingProcessSHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where coalesce(GlobalPrevalence,default_global_prevalence) < 200 or ((isempty(Signer) or not(IsCertificateValid)) and coalesce(GlobalPrevalence,default_global_prevalence) < 500)
);
Stage 1: source
let AllProcessInjectionEvents
Stage 2: source
let SuspiciousProcessInjectionEvents
Stage 3: source
DeviceEvents
Stage 4: where
where ...
Stage 5: where
where ActionType in~ ("CreateRemoteThreadApiCall", "NtAllocateVirtualMemoryRemoteApiCall", "NtMapViewOfSectionRemoteApiCall", "QueueUserApcRemoteApiCall", "SetThreadContextRemoteApiCall") and ProcessId != InitiatingProcessId
Stage 6: extend
extend InitiatingProcessSHA1
Stage 7: where
where not (InitiatingProcessFolderPath startswith "FolderPath")
Stage 8: kusto:lookup
lookup kind=inner (SuspiciousProcessInjectionEvents) on InitiatingProcessSHA1
Stage 9: extend
extend InjectionTarget
Stage 10: summarize
summarize InjectionTargets by DeviceId, InitiatingProcessFolderPath
Stage 11: extend
extend InjectionCommandLine, InjectionSource
Stage 12: project-reorder
project-reorder
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | in |
| field:"ActionType" kind:in |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt |
Signer | is_null | field:"Signer" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
InitiatingProcessFolderPath | starts_with | FolderPath | excludes:InitiatingProcessFolderPath field:"InitiatingProcessFolderPath" value:"FolderPath" |
InitiatingProcessSHA1 | is_null | excludes:InitiatingProcessSHA1 | |
ProfileAvailability | eq | Error | excludes:ProfileAvailability field:"ProfileAvailability" value:"Error" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceId | summarize |
InitiatingProcessFolderPath | summarize |
InjectionTargets | summarize |
InjectionCommandLine | extend |
InjectionSource | extend |
Rare RDP Connections
#Identifies when an RDP connection is new or rare related to any logon type by a given account today compared with the previous 14 days. RDP connections are indicated by the EventID 4624 with LogonType = 10
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 4624: An account was successfully logged on. |
Rule Body
id: 45b903c5-6f56-4969-af10-ae62ac709718
name: Rare RDP Connections
description: |
'Identifies when an RDP connection is new or rare related to any logon type by a given account today compared with the previous 14 days.
RDP connections are indicated by the EventID 4624 with LogonType = 10'
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
relevantTechniques:
- T1021
query: |
let starttime = 14d;
let endtime = 1d;
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated >= ago(endtime)
| where EventID == 4624 and LogonType == 10
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), ConnectionCount = count()
by Account = tolower(Account), Computer = toupper(Computer), IpAddress, AccountType, Activity, LogonTypeName, ProcessName
// use left anti to exclude anything from the previous 14 days that is not rare
),
(WindowsEvent
| where TimeGenerated >= ago(endtime)
| where EventID == 4624 and EventData has ("10")
| extend LogonType = tostring(EventData.LogonType)
| where LogonType == 10
| extend Account = strcat(tostring(EventData.TargetDomainName),"\\", tostring(EventData.TargetUserName))
| extend ProcessName = tostring(EventData.ProcessName)
| extend IpAddress = tostring(EventData.IpAddress)
| extend TargetUserSid = tostring(EventData.TargetUserSid)
| extend AccountType=case(Account endswith "$" or TargetUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(TargetUserSid), "", "User")
| extend Activity="4624 - An account was successfully logged on."
| extend LogonTypeName="10 - RemoteInteractive"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), ConnectionCount = count()
by Account = tolower(Account), Computer = toupper(Computer), IpAddress, AccountType, Activity, LogonTypeName, ProcessName
))
| join kind=leftanti (
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated between (ago(starttime) .. ago(endtime))
| where EventID == 4624
| summarize by Computer = toupper(Computer), IpAddress, Account = tolower(Account)
),
( WindowsEvent
| where TimeGenerated between (ago(starttime) .. ago(endtime))
| where EventID == 4624
| extend IpAddress = tostring(EventData.IpAddress)
| extend Account = strcat(tostring(EventData.TargetDomainName),"\\", tostring(EventData.TargetUserName))
| summarize by Computer = toupper(Computer), IpAddress, Account = tolower(Account)
))
) on Account, Computer
| summarize StartTime = min(StartTime), EndTime = max(EndTime), ConnectionCount = sum(ConnectionCount)
by Account, Computer, IpAddress, AccountType, Activity, LogonTypeName, ProcessName
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(Account, @"\")[1]), AccountNTDomain = tostring(split(Account, @"\")[0])
| project-away DomainIndex
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountNTDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: HostNameDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IpAddress
version: 1.2.5
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Threat Protection" ]
Stages and Predicates
Parameters
let starttime = 14d;
let endtime = 1d;
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, WindowsEvent
Leg 1: SecurityEvent
SecurityEvent
| where TimeGenerated >= ago(endtime)
| where EventID == 4624 and LogonType == 10
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), ConnectionCount = count()
by Account = tolower(Account), Computer = toupper(Computer), IpAddress, AccountType, Activity, LogonTypeName, ProcessName
Leg 2: WindowsEvent
WindowsEvent
| where TimeGenerated >= ago(endtime)
| where EventID == 4624 and EventData has ("10")
| extend LogonType = tostring(EventData.LogonType)
| where LogonType == 10
| extend Account = strcat(tostring(EventData.TargetDomainName),"\\", tostring(EventData.TargetUserName))
| extend ProcessName = tostring(EventData.ProcessName)
| extend IpAddress = tostring(EventData.IpAddress)
| extend TargetUserSid = tostring(EventData.TargetUserSid)
| extend AccountType=case(Account endswith "$" or TargetUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(TargetUserSid), "", "User")
| extend Activity="4624 - An account was successfully logged on."
| extend LogonTypeName="10 - RemoteInteractive"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), ConnectionCount = count()
by Account = tolower(Account), Computer = toupper(Computer), IpAddress, AccountType, Activity, LogonTypeName, ProcessName
Applied to the Combined Result
| join kind=leftanti (
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated between (ago(starttime) .. ago(endtime))
| where EventID == 4624
| summarize by Computer = toupper(Computer), IpAddress, Account = tolower(Account)
),
( WindowsEvent
| where TimeGenerated between (ago(starttime) .. ago(endtime))
| where EventID == 4624
| extend IpAddress = tostring(EventData.IpAddress)
| extend Account = strcat(tostring(EventData.TargetDomainName),"\\", tostring(EventData.TargetUserName))
| summarize by Computer = toupper(Computer), IpAddress, Account = tolower(Account)
))
) on Account, Computer
| summarize StartTime = min(StartTime), EndTime = max(EndTime), ConnectionCount = sum(ConnectionCount)
by Account, Computer, IpAddress, AccountType, Activity, LogonTypeName, ProcessName
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(Account, @"\")[1]), AccountNTDomain = tostring(split(Account, @"\")[0])
| project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventData | match |
| field:"EventData" kind:match value:"10" |
EventID | eq |
| field:"EventID" kind:eq value:"4624" |
LogonType | eq |
| field:"LogonType" kind:eq value:"10" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
EventID | eq | 4624 | excludes:EventID field:"EventID" value:"4624" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | summarize |
AccountType | summarize |
Activity | summarize |
Computer | summarize |
ConnectionCount | summarize |
EndTime | summarize |
IpAddress | summarize |
LogonTypeName | summarize |
ProcessName | summarize |
StartTime | summarize |
HostName | extend |
HostNameDomain | extend |
AccountNTDomain | extend |
AccountName | extend |
Sentinel One - New admin created
#'Detects when new admin user is created.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Rule Body
id: e73d293d-966c-47ec-b8e0-95255755f12c
name: Sentinel One - New admin created
description: |
'Detects when new admin user is created.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SentinelOne
dataTypes:
- SentinelOne
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1078
query: |
SentinelOne
| where ActivityType == 23
| extend AccountCustomEntity = SrcUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SentinelOne
Stage 2: where
| where ActivityType == 23
Stage 3: extend
| extend AccountCustomEntity = SrcUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityType | eq |
| field:"ActivityType" kind:eq value:"23" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
SQL Server spawning suspicious child process
#This query looks for potential abuse of the SQL Server stored procedure xp_cmdshell which allows command execution on the OS. Running xp_cmdshell on the system triggers the follow process chain: sqlservr.exe => xp_cmdshell 'whoami' => "cmd.exe /c" whoami => whoami.exe. This rule tries to identify running of suspicious commands as a grandchild of sqlservr.exe. The rule is based on a block-list of executables of LOLBINs and other known recon commands or any executable executed with a low prevalence.
Known False Positives
- The xp_cmdshell functionality is often used by legitimate applications for interfacing with the OS and performing all kinds of maintenance tasks (i.e., back-ups, reporting, etc.).
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rule Body
let timeframe = 2*1h;
let default_global_prevalence = 0;
let lolbins = dynamic(["at.exe", "atbroker.exe", "bash.exe", "bitsadmin.exe", "certreq.exe", "certutil.exe", "cmd.exe", "cmdkey.exe", "cmstp.exe", "control.exe", "csc.exe", "cscript.exe", "desktopimgdownldr.exe", "dfsvc.exe", "diantz.exe", "diskshadow.exe", "dnscmd.exe", "esentutl.exe", "eventvwr.exe", "expand.exe", "extexport.exe", "extrac32.exe", "findstr.exe", "forfiles.exe", "ftp.exe", "gfxdownloadwrapper.exe", "gpscript.exe", "hh.exe", "ie4uinit.exe", "ieexec.exe", "ilasm.exe", "infdefaultinstall.exe", "installutil.exe", "jsc.exe", "makecab.exe", "mavinject.exe", "microsoft.workflow.compiler.exe", "mmc.exe", "mpcmdrun.exe", "msbuild.exe", "msconfig.exe", "msdt.exe", "mshta.exe", "msiexec.exe", "netsh.exe", "odbcconf.exe", "pcalua.exe", "pcwrun.exe", "pktmon.exe", "presentationhost.exe", "print.exe", "psr.exe", "rasautou.exe", "reg.exe", "regasm.exe", "regedit.exe", "regini.exe", "register-cimprovider.exe", "regsvcs.exe", "regsvr32.exe", "replace.exe", "rpcping.exe", "rundll32.exe", "runonce.exe", "runscripthelper.exe", "sc.exe", "schtasks.exe", "scriptrunner.exe", "syncappvpublishingserver.exe", "ttdinject.exe", "tttracer.exe", "vbc.exe", "verclsid.exe", "wab.exe", "wmic.exe", "wscript.exe", "wsreset.exe", "xwizard.exe", "agentexecutor.exe", "appvlp.exe", "bginfo.exe", "cdb.exe", "csi.exe", "devtoolslauncher.exe", "dnx.exe", "dotnet.exe", "dxcap.exe", "excel.exe", "mftrace.exe", "msdeploy.exe", "msxsl.exe", "ntdsutil.exe", "powerpnt.exe", "rcsi.exe", "sqldumper.exe", "sqlps.exe", "sqltoolsps.exe", "squirrel.exe", "te.exe", "tracker.exe", "vsjitdebugger.exe", "winword.exe", "wsl.exe", "powershell.exe", "pwsh.exe"]);
let binaries_of_interest = dynamic(["net.exe", "net1.exe", "whoami.exe", "ipconfig.exe", "tasklist.exe", "quser.exe", "tracert.exe", "route.exe", "runas.exe", "klist.exe", "wevtutil.exe", "wmiprvse.exe", "powershell.exe", "bash.exe", "qwinsta.exe", "rwinsta.exe", "replace.exe", "findstr.exe", "icacls.exe", "cacls.exe", "xcopy.exe", "robocopy.exe", "takeown.exe", "vssadmin.exe", "nltest.exe", "nltestk.exe", "sctasks.exe", "nbtstat.exe", "nbtinfo.exe", "mofcomp.exe", "nltestrk.exe", "dnscmd.exe", "registercimprovider.exe", "registercimprovider2.exe", "procdump", "ru.exe", "pspasswd.exe", "psexec.c", "psexec.exe", "pslist.exe", "regsize", "pskill.exe", "pkill.exe", "wsmprovhost.exe", "fltmc.exe", "sdbinst.exe"]);
// Merge both lists into one reference list.
let original_file_name_set=array_concat(lolbins,binaries_of_interest);
let allGrandChilderen = materialize(
DeviceProcessEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType =~ "ProcessCreated"
| where InitiatingProcessParentFileName =~ "sqlservr.exe"
| where InitiatingProcessCommandLine startswith "\"cmd.exe\" /c"
);
let allSuspiciousHashes = (
// The reason for looking at grand children of SQL Server instead of children is that xp_cmdshell always runs a command
// via cmd.exe /c so the child process will always be cmd.exe. We are interested in the grand child process which is the actual command.
allGrandChilderen
// FileProfile is case-sensitive and works on lower-case hashes.
| where not(isempty(SHA1))
| summarize DeviceCount=dcount(DeviceId) by SHA1
| top 1000 by DeviceCount asc
| invoke FileProfile(SHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where coalesce(GlobalPrevalence,default_global_prevalence) < 250
| where SignatureState != "SignedValid"
);
allGrandChilderen
| where FileName in~ (original_file_name_set) or SHA1 in ((allSuspiciousHashes))
| join kind=leftouter allSuspiciousHashes on SHA1
// Begin environment-specific filter.
// End environment-specific filter.
Stages and Predicates
Parameters
let timeframe = 2*1h;
let default_global_prevalence = 0;
let original_file_name_set = array_concat(lolbins,binaries_of_interest);
let allGrandChilderen is inlined into the numbered stages below.
Let Binding: lolbins
let lolbins = dynamic(["at.exe", "atbroker.exe", "bash.exe", "bitsadmin.exe", "certreq.exe", "certutil.exe", "cmd.exe", "cmdkey.exe", "cmstp.exe", "control.exe", "csc.exe", "cscript.exe", "desktopimgdownldr.exe", "dfsvc.exe", "diantz.exe", "diskshadow.exe", "dnscmd.exe", "esentutl.exe", "eventvwr.exe", "expand.exe", "extexport.exe", "extrac32.exe", "findstr.exe", "forfiles.exe", "ftp.exe", "gfxdownloadwrapper.exe", "gpscript.exe", "hh.exe", "ie4uinit.exe", "ieexec.exe", "ilasm.exe", "infdefaultinstall.exe", "installutil.exe", "jsc.exe", "makecab.exe", "mavinject.exe", "microsoft.workflow.compiler.exe", "mmc.exe", "mpcmdrun.exe", "msbuild.exe", "msconfig.exe", "msdt.exe", "mshta.exe", "msiexec.exe", "netsh.exe", "odbcconf.exe", "pcalua.exe", "pcwrun.exe", "pktmon.exe", "presentationhost.exe", "print.exe", "psr.exe", "rasautou.exe", "reg.exe", "regasm.exe", "regedit.exe", "regini.exe", "register-cimprovider.exe", "regsvcs.exe", "regsvr32.exe", "replace.exe", "rpcping.exe", "rundll32.exe", "runonce.exe", "runscripthelper.exe", "sc.exe", "schtasks.exe", "scriptrunner.exe", "syncappvpublishingserver.exe", "ttdinject.exe", "tttracer.exe", "vbc.exe", "verclsid.exe", "wab.exe", "wmic.exe", "wscript.exe", "wsreset.exe", "xwizard.exe", "agentexecutor.exe", "appvlp.exe", "bginfo.exe", "cdb.exe", "csi.exe", "devtoolslauncher.exe", "dnx.exe", "dotnet.exe", "dxcap.exe", "excel.exe", "mftrace.exe", "msdeploy.exe", "msxsl.exe", "ntdsutil.exe", "powerpnt.exe", "rcsi.exe", "sqldumper.exe", "sqlps.exe", "sqltoolsps.exe", "squirrel.exe", "te.exe", "tracker.exe", "vsjitdebugger.exe", "winword.exe", "wsl.exe", "powershell.exe", "pwsh.exe"]);
Let Binding: binaries_of_interest
let binaries_of_interest = dynamic(["net.exe", "net1.exe", "whoami.exe", "ipconfig.exe", "tasklist.exe", "quser.exe", "tracert.exe", "route.exe", "runas.exe", "klist.exe", "wevtutil.exe", "wmiprvse.exe", "powershell.exe", "bash.exe", "qwinsta.exe", "rwinsta.exe", "replace.exe", "findstr.exe", "icacls.exe", "cacls.exe", "xcopy.exe", "robocopy.exe", "takeown.exe", "vssadmin.exe", "nltest.exe", "nltestk.exe", "sctasks.exe", "nbtstat.exe", "nbtinfo.exe", "mofcomp.exe", "nltestrk.exe", "dnscmd.exe", "registercimprovider.exe", "registercimprovider2.exe", "procdump", "ru.exe", "pspasswd.exe", "psexec.c", "psexec.exe", "pslist.exe", "regsize", "pskill.exe", "pkill.exe", "wsmprovhost.exe", "fltmc.exe", "sdbinst.exe"]);
Let Binding: allSuspiciousHashes
let allSuspiciousHashes = (
allGrandChilderen
| where not(isempty(SHA1))
| summarize DeviceCount=dcount(DeviceId) by SHA1
| top 1000 by DeviceCount asc
| invoke FileProfile(SHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where coalesce(GlobalPrevalence,default_global_prevalence) < 250
| where SignatureState != "SignedValid"
);
Stage 1: source
let allGrandChilderen
Stage 2: source
let allSuspiciousHashes
Stage 3: source
DeviceProcessEvents
Stage 4: where
where ...
Stage 5: where
where ActionType =~ "ProcessCreated"
Stage 6: where
where InitiatingProcessParentFileName =~ "sqlservr.exe"
Stage 7: where
where InitiatingProcessCommandLine startswith "\"cmd.exe\" /c"
Stage 8: where
where (FileName =~ "original_file_name_set")
Stage 9: join
join kind=leftouter (allSuspiciousHashes) on SHA1
Stage 10: summarize aggregation inside the join branch
summarize by SHA1
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ProcessCreated" |
FileName | in |
| field:"FileName" kind:in value:"original_file_name_set" |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt value:"250" |
InitiatingProcessCommandLine | starts_with |
| field:"ParentCommandLine" kind:starts_with |
InitiatingProcessParentFileName | eq |
| field:"InitiatingProcessParentFileName" kind:eq value:"sqlservr.exe" |
SignatureState | ne |
| field:"SignatureState" kind:ne value:"SignedValid" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
ProfileAvailability | eq | Error | excludes:ProfileAvailability field:"ProfileAvailability" value:"Error" |
SHA1 | is_null | excludes:SHA1 |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SHA1 | summarize |
Suspicious malware found in the network (Microsoft Defender for IoT)
#'This alert leverages Defender for IoT to detect IoT/OT malware found on the network indicating possible attempts to compromise production systems.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 6fb1acd5-356d-40f7-9b97-78d993c6a183
name: Suspicious malware found in the network (Microsoft Defender for IoT)
description: |
'This alert leverages Defender for IoT to detect IoT/OT malware found on the network indicating possible attempts to compromise production systems.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: IoT
dataTypes:
- SecurityAlert (ASC for IoT)
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T0882
query: |
let alertList = dynamic(["Malware", "Suspicion of Malicious Activity", "Invalid SMB Message (DoublePulsar Backdoor Implant)", "Connection Attempt to Known Malicious IP", "Malicious Domain Name Request", "Suspicion of Remote Code Execution with PsExec", "Suspicion of Remote Windows Service Management", "Suspicious Executable File Detected on Endpoint", "Suspicious Traffic Detected"]);
SecurityAlert
| where ProviderName == "IoTSecurity"
| where AlertName has_any (alertList)
| extend ExtendedProperties = parse_json(ExtendedProperties)
| where tostring(ExtendedProperties.isNew) == "True"
| extend DeviceId = tostring(ExtendedProperties.DeviceId),
SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress),
DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress),
RemediationSteps = tostring(parse_json(RemediationSteps)[0]),
Protocol = tostring(ExtendedProperties.Protocol),
AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)
| project
TimeGenerated,
DeviceId,
ProductName,
ProductComponentName,
AlertSeverity,
AlertName,
Description,
Protocol,
SourceDeviceAddress,
DestDeviceAddress,
RemediationSteps,
Tactics,
Entities,
VendorOriginalId,
AlertLink,
AlertManagementUri,
Techniques
entityMappings:
sentinelEntitiesMappings:
- columnName: Entities
eventGroupingSettings:
aggregationKind: AlertPerResult
customDetails:
Sensor: DeviceId
Protocol: Protocol
VendorOriginalId: VendorOriginalId
AlertManagementUri: AlertManagementUri
alertDetailsOverride:
alertDisplayNameFormat: (MDIoT) {{AlertName}}
alertDescriptionFormat: (MDIoT) {{Description}}
alertTacticsColumnName: Tactics
alertSeverityColumnName: AlertSeverity
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: RemediationSteps
value: RemediationSteps
- alertProperty: Techniques
value: Techniques
- alertProperty: ProductComponentName
value: ProductComponentName
- alertProperty: AlertLink
value: AlertLink
version: 1.0.3
kind: Scheduled
Stages and Predicates
Let Binding: alertList
let alertList = dynamic(["Malware", "Suspicion of Malicious Activity", "Invalid SMB Message (DoublePulsar Backdoor Implant)", "Connection Attempt to Known Malicious IP", "Malicious Domain Name Request", "Suspicion of Remote Code Execution with PsExec", "Suspicion of Remote Windows Service Management", "Suspicious Executable File Detected on Endpoint", "Suspicious Traffic Detected"]);
Stage 1: source
SecurityAlert
Stage 2: where
| where ProviderName == "IoTSecurity"
Stage 3: where
| where AlertName has_any (alertList)
Stage 4: extend
| extend ExtendedProperties = parse_json(ExtendedProperties)
Stage 5: where
| where tostring(ExtendedProperties.isNew) == "True"
Stage 6: extend
| extend DeviceId = tostring(ExtendedProperties.DeviceId),
SourceDeviceAddress = tostring(ExtendedProperties.SourceDeviceAddress),
DestDeviceAddress = tostring(ExtendedProperties.DestinationDeviceAddress),
RemediationSteps = tostring(parse_json(RemediationSteps)[0]),
Protocol = tostring(ExtendedProperties.Protocol),
AlertManagementUri = tostring(ExtendedProperties.AlertManagementUri)
Stage 7: project
| project
TimeGenerated,
DeviceId,
ProductName,
ProductComponentName,
AlertSeverity,
AlertName,
Description,
Protocol,
SourceDeviceAddress,
DestDeviceAddress,
RemediationSteps,
Tactics,
Entities,
VendorOriginalId,
AlertLink,
AlertManagementUri,
Techniques
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertName | match |
| field:"AlertName" kind:match |
ExtendedProperties.isNew | eq |
| field:"ExtendedProperties.isNew" kind:eq value:"True" |
ProviderName | eq |
| field:"ProviderName" kind:eq value:"IoTSecurity" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertLink | project |
AlertManagementUri | project |
AlertName | project |
AlertSeverity | project |
Description | project |
DestDeviceAddress | project |
DeviceId | project |
Entities | project |
ProductComponentName | project |
ProductName | project |
Protocol | project |
RemediationSteps | project |
SourceDeviceAddress | project |
Tactics | project |
Techniques | project |
TimeGenerated | project |
VendorOriginalId | project |
Suspicious Network Beacons - Microsoft Defender for Endpoint Aggregated Reports
#Below query detects suspicious beaconing activity by analyzing DeviceNetworkEvents Aggregated Reports telemetry. Use it as a starting point and refine further as it may generate too many results.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control | No specific technique |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceNetworkEvents action any: Network activity |
Rule Body
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
// Link to original post: https://academy.bluraven.io/blog/beaconing-detection-using-mde-aggregated-report-telemetry
//
// Query parameters:
let lookback = 3d;
let min_uniform_count = 4 * (lookback / 1d); // (4 uniform distribution per lookback)
DeviceNetworkEvents
| where Timestamp > ago(lookback)
| where ActionType endswith "AggregatedReport"
| where ipv4_is_private(RemoteIP) == false
| extend ConnectionCount = toint(parse_json(AdditionalFields).uniqueEventsAggregated)
| project Timestamp = bin(Timestamp, 1h), DeviceName, InitiatingProcessFileName, RemoteIP, ConnectionCount
| sort by Timestamp asc
| summarize Timestamp = make_list(Timestamp), ConnCounts = make_list(ConnectionCount) by DeviceName, InitiatingProcessFileName, RemoteIP
| extend count_of_hours = array_length(Timestamp)
| extend anomalies_decomposed = series_decompose_anomalies(ConnCounts, 1.5, -1),
series_stats(ConnCounts)
| mv-apply anomaly = anomalies_decomposed to typeof(int) on (
summarize inliner_count = countif(anomaly !in (-1, 1)), outlier_count = countif(anomaly in (-1, 1))
)
| where inliner_count >= min_uniform_count and series_stats_ConnCounts_avg > 45 // avg=45 is roughly 1 min sleep with some jitter
| sort by series_stats_ConnCounts_avg desc
| extend FirstConnection = Timestamp[0], LastConnection=Timestamp[-1], avg_conn_count=toint(series_stats_ConnCounts_avg)
| project-reorder FirstConnection, LastConnection, DeviceName, InitiatingProcessFileName, RemoteIP, count_of_hours, inliner_count, outlier_count, avg_conn_count
Stages and Predicates
Parameters
let lookback = 3d;
let min_uniform_count = 4 * (lookback / 1d);
Stage 1: source
DeviceNetworkEvents
Stage 2: where
| where Timestamp > ago(lookback)
Stage 3: where
| where ActionType endswith "AggregatedReport"
Stage 4: where
| where ipv4_is_private(RemoteIP) == false
Stage 5: extend
| extend ConnectionCount = toint(parse_json(AdditionalFields).uniqueEventsAggregated)
Stage 6: project
| project Timestamp = bin(Timestamp, 1h), DeviceName, InitiatingProcessFileName, RemoteIP, ConnectionCount
Stage 7: sort
| sort by Timestamp asc
Stage 8: summarize
| summarize Timestamp = make_list(Timestamp), ConnCounts = make_list(ConnectionCount) by DeviceName, InitiatingProcessFileName, RemoteIP
Stage 9: extend
| extend count_of_hours = array_length(Timestamp)
The stages below score time-series anomalies (make-series, series_decompose_anomalies).
Stage 10: extend
| extend anomalies_decomposed = series_decompose_anomalies(ConnCounts, 1.5, -1),
series_stats(ConnCounts)
Stage 11: kusto:mv-apply
| mv-apply anomaly = anomalies_decomposed to typeof(int) on (
summarize inliner_count = countif(anomaly !in (-1, 1)), outlier_count = countif(anomaly in (-1, 1))
)
Stage 12: where
| where inliner_count >= min_uniform_count and series_stats_ConnCounts_avg > 45
Stage 13: sort
| sort by series_stats_ConnCounts_avg desc
Stage 14: extend
| extend FirstConnection = Timestamp[0], LastConnection=Timestamp[-1], avg_conn_count=toint(series_stats_ConnCounts_avg)
Stage 15: project-reorder
| project-reorder FirstConnection, LastConnection, DeviceName, InitiatingProcessFileName, RemoteIP, count_of_hours, inliner_count, outlier_count, avg_conn_count
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | ends_with |
| field:"ActionType" kind:ends_with value:"AggregatedReport" |
series_stats_ConnCounts_avg | gt |
| field:"series_stats_ConnCounts_avg" kind:gt value:"45" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
RemoteIP | 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:RemoteIP |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ConnCounts | summarize |
DeviceName | summarize |
InitiatingProcessFileName | summarize |
RemoteIP | summarize |
Timestamp | summarize |
count_of_hours | extend |
anomalies_decomposed | extend |
FirstConnection | extend |
LastConnection | extend |
avg_conn_count | extend |
Suspicious Resource deployment
#Identifies when a rare Resource and ResourceGroup deployment occurs by a previously unseen caller.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
Rule Body
id: 9fb57e58-3ed8-4b89-afcf-c8e786508b1c
name: Suspicious Resource deployment
description: |
'Identifies when a rare Resource and ResourceGroup deployment occurs by a previously unseen caller.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: AzureActivity
dataTypes:
- AzureActivity
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1496
query: |
// Add or remove operation names below as per your requirements. For operations lists, please refer to https://learn.microsoft.com/en-us/Azure/role-based-access-control/resource-provider-operations#all
let szOperationNames = dynamic(["Microsoft.Compute/virtualMachines/write", "Microsoft.Resources/deployments/write", "Microsoft.Resources/subscriptions/resourceGroups/write"]);
let starttime = 14d;
let endtime = 1d;
let RareCaller = AzureActivity
| where TimeGenerated between (ago(starttime) .. ago(endtime))
| where OperationNameValue in~ (szOperationNames)
| summarize count() by CallerIpAddress, Caller, OperationNameValue, bin(TimeGenerated,1d)
// Returns all the records from the right side that don't have matches from the left.
| join kind=rightantisemi (
AzureActivity
| where TimeGenerated > ago(endtime)
| where OperationNameValue in~ (szOperationNames)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ActivityTimeStamp = make_set(TimeGenerated,100), ActivityStatusValue = make_set(ActivityStatusValue,100), CorrelationIds = make_set(CorrelationId,100), ResourceGroups = make_set(ResourceGroup,100), ResourceIds = make_set(_ResourceId,100), ActivityCountByCallerIPAddress = count()
by CallerIpAddress, Caller, OperationNameValue) on CallerIpAddress, Caller, OperationNameValue;
RareCaller
| extend Name = iif(Caller has '@',tostring(split(Caller,'@',0)[0]),"")
| extend UPNSuffix = iif(Caller has '@',tostring(split(Caller,'@',1)[0]),"")
| extend AadUserId = iif(Caller !has '@',Caller,"")
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Caller
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: AadUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CallerIpAddress
version: 2.0.4
kind: Scheduled
Stages and Predicates
Parameters
let starttime = 14d;
let endtime = 1d;
let RareCaller is inlined into the numbered stages below.
Let Binding: szOperationNames
let szOperationNames = dynamic(["Microsoft.Compute/virtualMachines/write", "Microsoft.Resources/deployments/write", "Microsoft.Resources/subscriptions/resourceGroups/write"]);
Stages 1 to 5 define let RareCaller as the rule's main pipeline source. Stage 6 uses it.
Stage 1: source
AzureActivity
Stage 2: where
where TimeGenerated between (ago(1209600s) .. ago(86400s))
Stage 3: where
where OperationNameValue in~ ("Microsoft.Compute/virtualMachines/write", "Microsoft.Resources/deployments/write", "Microsoft.Resources/subscriptions/resourceGroups/write")
Stage 4: summarize
summarize by CallerIpAddress, Caller, OperationNameValue
Stage 5: join
| join kind=rightantisemi (
AzureActivity
| where TimeGenerated > ago(endtime)
| where OperationNameValue in~ (szOperationNames)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ActivityTimeStamp = make_set(TimeGenerated,100), ActivityStatusValue = make_set(ActivityStatusValue,100), CorrelationIds = make_set(CorrelationId,100), ResourceGroups = make_set(ResourceGroup,100), ResourceIds = make_set(_ResourceId,100), ActivityCountByCallerIPAddress = count()
by CallerIpAddress, Caller, OperationNameValue) on CallerIpAddress, Caller, OperationNameValue
Stage 6: extend (3 consecutive steps)
extend AadUserId, Name, UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationNameValue | in |
| field:"azure_ad::operation_name_value" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Caller | summarize |
CallerIpAddress | summarize |
OperationNameValue | summarize |
Name | extend |
UPNSuffix | extend |
AadUserId | extend |
TI map IP entity to Web Session Events (ASIM Web Session schema)
#This rule identifies Web Sessions for which the source IP address is a known IoC. This rule uses the Advanced Security Information Model (ASIM) and supports any web session source that complies with ASIM.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: e2559891-383c-4caf-ae67-55a008b9f89e
name: TI map IP entity to Web Session Events (ASIM Web Session schema)
description: |
This rule identifies Web Sessions for which the source IP address is a known IoC. This rule uses the [Advanced Security Information Model (ASIM)](https://aka.ms/AboutASIM) and supports any web session source that complies with ASIM.
severity: Medium
requiredDataConnectors:
- connectorId: SquidProxy
dataTypes:
- SquidProxy_CL
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog
- 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 HAS_ANY_MAX = 10000;
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let IP_TI = ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack)
// As there is potentially more than 1 indicator type for matching IP, taking NetworkIP first, then others if that is empty.
// Taking the first non-empty value based on potential IOC match availability
| extend TI_ipEntity = coalesce(NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, "NO_IP")
// Picking up only IOC's that contain the entities we want
| where TI_ipEntity != "NO_IP"
// Exclude local addresses, using the ipv4_is_private operator
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now();
let IP_TI_list = toscalar(IP_TI
| summarize NIoCs = dcount(TI_ipEntity), IoCs = make_set(TI_ipEntity)
| project IoCs = iff(NIoCs > HAS_ANY_MAX, dynamic([]), IoCs));
IP_TI
// 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 (
_Im_WebSession (starttime = ago(dt_lookBack), srcipaddr_has_any_prefix = IP_TI_list)
| where isnotempty(SrcIpAddr)
// renaming time column so it is clear the log this came from
| extend imNWS_TimeGenerated = TimeGenerated
)
on $left.TI_ipEntity == $right.SrcIpAddr
| where imNWS_TimeGenerated < ExpirationDateTime
| summarize imNWS_TimeGenerated = arg_max(imNWS_TimeGenerated, *) by IndicatorId, DstIpAddr
| project imNWS_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore,
TI_ipEntity, Dvc, SrcIpAddr, DstIpAddr, Url, Type
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DstIpAddr
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
customDetails:
EventTime: imNWS_TimeGenerated
IoCDescription: Description
ActivityGroupNames: ActivityGroupNames
IndicatorId: IndicatorId
ThreatType: ThreatType
IoCExpirationTime: ExpirationDateTime
IoCConfidenceScore: ConfidenceScore
alertDetailsOverride:
alertDisplayNameFormat: The IP {{SrcIpAddr}} of the web request matches an IP IoC
alertDescriptionFormat: The source address {{SrcIpAddr}} of the web request for the URL {{Url}} matches a known indicator of compromise of {{ThreatType}}. Consult the threat intelligence feed for more information about the indicator.
version: 1.2.6
kind: Scheduled
Stages and Predicates
Parameters
let HAS_ANY_MAX = 10000;
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let IP_TI is inlined into the numbered stages below.
Let Binding: IP_TI_list
let IP_TI_list = toscalar(IP_TI
| summarize NIoCs = dcount(TI_ipEntity), IoCs = make_set(TI_ipEntity)
| project IoCs = iff(NIoCs > HAS_ANY_MAX, dynamic([]), IoCs));
Stages 1 to 7 define let IP_TI as the rule's main pipeline source. Stages 8 to 11 use it.
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 3: extend
| extend TI_ipEntity = coalesce(NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, "NO_IP")
Stage 4: where
| where TI_ipEntity != "NO_IP"
Stage 5: where
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith "fe80" and TI_ipEntity !startswith "::" and TI_ipEntity !startswith "127."
Stage 6: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 7: where
| where Active == true and ExpirationDateTime > now()
Stage 8: join
IP_TI
| join kind = innerunique (
_Im_WebSession (starttime = ago(dt_lookBack), srcipaddr_has_any_prefix = IP_TI_list)
| where isnotempty(SrcIpAddr)
| extend imNWS_TimeGenerated = TimeGenerated
)
on $left.TI_ipEntity == $right.SrcIpAddr
Stage 9: where
where imNWS_TimeGenerated < ExpirationDateTime
Stage 10: summarize
summarize imNWS_TimeGenerated by IndicatorId, DstIpAddr
Stage 11: project
project ActivityGroupNames, ConfidenceScore, Description, DstIpAddr, Dvc, ExpirationDateTime, IndicatorId, SrcIpAddr, TI_ipEntity, ThreatType, Type, Url, imNWS_TimeGenerated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
SrcIpAddr | is_not_null | field:"SrcIpAddr" kind:is_not_null | |
TI_ipEntity | ne |
| field:"TI_ipEntity" kind:ne value:"NO_IP" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TI_ipEntity | 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:TI_ipEntity |
TI_ipEntity | starts_with | 127. | excludes:TI_ipEntity field:"TI_ipEntity" value:"127." |
TI_ipEntity | starts_with | :: | excludes:TI_ipEntity field:"TI_ipEntity" value:"::" |
TI_ipEntity | starts_with | fe80 | excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
ConfidenceScore | project |
Description | project |
DstIpAddr | project |
Dvc | project |
ExpirationDateTime | project |
IndicatorId | project |
SrcIpAddr | project |
TI_ipEntity | project |
ThreatType | project |
Type | project |
Url | project |
imNWS_TimeGenerated | project |
Veeam ONE VM with No Backup
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
Detects Veeam ONE VMs with no backup.
Rule Body
id: 0e76e420-fa55-4718-adc6-40a1a76411af
name: Veeam ONE VM with No Backup
description: Detects Veeam ONE VMs with no backup.
severity: High
status: Available
requiredDataConnectors:
- connectorId: VeeamCustomTablesDataConnector
dataTypes:
- VeeamOneTriggeredAlarms_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: VeeamOneTriggeredAlarms_CL | where PredefinedAlarmId == 314
version: 1.0.1
kind: Scheduled
customDetails:
PredefinedAlarmId: PredefinedAlarmId
TriggeredAlarmId: TriggeredAlarmId
Description: Description
Name: Name
VoneHostName: VoneHostName
TriggeredTime: TriggeredTime
Status: Status
Comment: Comment
ObjectId: ObjectId
ObjectName: ObjectName
ObjectType: ObjectType
Stages and Predicates
Stage 1: source
VeeamOneTriggeredAlarms_CL
Stage 2: where
| where PredefinedAlarmId == 314
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PredefinedAlarmId | eq |
| field:"PredefinedAlarmId" kind:eq value:"314" |