Detection rules › Kusto

Kusto rules: 1i

A client made a web request to a potentially harmful file (ASIM Web Session schema)

#
Severity
medium
Time window
10m
Group by
SrcHostname, SrcIpAddr, SrcUsername, Url, requestedFileName
Author
Yaron
Source
github.com/Azure/Azure-Sentinel

'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

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EventCountsummarize
EventEndTimesummarize
EventStartTimesummarize
SrcHostnamesummarize
SrcIpAddrsummarize
SrcUsernamesummarize
Urlsummarize
requestedFileNamesummarize
Nameextend
UPNSuffixextend

API - API Scraping

#
Status
available
Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

'42Crunch API protection against API scraping'

MITRE ATT&CK Coverage

TacticTechniques
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.

FieldKindValuesSearch
Statuseq
  • 200
field:"Status" kind:eq value:"200"
UriPathmatch
  • /api/properties?page= transforms: term
field:"UriPath" kind:match value:"/api/properties?page="

Application Gateway WAF - XSS Detection

#
Severity
high
Time window
6h
Group by
SQLI_Score, TotalInboundScore, Uri, XSS_Score, action_s, clientIp_s, transactionId_g
Author
shabaz-github
Source
github.com/Azure/Azure-Sentinel

'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

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Detail_Datasummarize
Detail_Messagesummarize
EndTimesummarize
Messagesummarize
SQLI_Scoresummarize
StartTimesummarize
TotalInboundScoresummarize
Total_TransactionIdsummarize
TransactionIDsummarize
Urisummarize
XSS_Scoresummarize
action_ssummarize
clientIp_ssummarize

Aqua Blizzard AV hits - Feb 2022

#
Status
available
Severity
high
Time window
6h
Source
github.com/Azure/Azure-Sentinel

'Identifies a match in the Security Alert table for MDATP hits related to the Aqua Blizzard actor'

MITRE ATT&CK Coverage

TacticTechniques
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 used in Stage 4

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.

FieldKindValuesSearch
ProviderNameeq
  • MDATP corpus 3 (kusto 3)
field:"ProviderName" kind:eq value:"MDATP"
ThreatName_match
  • AVHits transforms: term
field:"ThreatName_" kind:match value:"AVHits"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
AlertNameproject
AlertSeverityproject
Descriptionproject
Directoryproject
Entitiesproject
ExtendedPropertiesproject
FileNameproject
Hostnameproject
ProviderNameproject
RemediationStepsproject
SHA256project
ThreatName_project
TimeGeneratedproject
FileHashextend
FileHashTypeextend
AccountUPNSuffixextend
Nameextend
DomainIndexextend
Hostextend
HostNameDomainextend

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.

Status
available
Severity
high
Time window
1h
Source
github.com/Azure/Azure-Sentinel

Exploitable Cloud Security Issues are ones that expose cloud resources to the internet and allow initial access to your environment.

MITRE ATT&CK Coverage

TacticTechniques
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.

FieldKindValuesSearch
exploitable_beq
  • true transforms: boolean
field:"exploitable_b" kind:eq value:"true"

AWSCloudTrail - Successful Tampering with AWS CloudTrail Logs

#
Status
available
Severity
high
Time window
1d
Group by
AWSRegion, AccountName, AccountUPNSuffix, EventName, EventSource, EventTypeName, RecipientAccountId, SessionMfaAuthenticated, SourceIpAddress, UserAgent, UserIdentityAccountId, UserIdentityPrincipalid, UserIdentityUserName
Source
github.com/Azure/Azure-Sentinel

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

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

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.

FieldKindValuesSearch
ErrorCodeis_null
  • (no value, null check)
field:"ErrorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"ErrorMessage" kind:is_null
EventNameeq
  • UpdateTrail
field:"EventName" kind:eq value:"UpdateTrail"
EventNamein
  • DeleteEventBus
  • DeleteFlowLogs
  • DeleteLogGroup
  • DeleteTrail
  • StopLogging
  • UpdateTrail
field:"EventName" kind:in
RequestParameters.enableLogFileValidationeq
  • false transforms: kusto_accessor_expr:{"expression":"parse_json(RequestParameters).enableLogFileValidation","field":"RequestParameters.enableLogFileValidation"}, kusto_dynamic_path
field:"RequestParameters.enableLogFileValidation" kind:eq value:"false"
RequestParameters.isMultiRegionTraileq
  • false transforms: kusto_accessor_expr:{"expression":"parse_json(RequestParameters).isMultiRegionTrail","field":"RequestParameters.isMultiRegionTrail"}, kusto_dynamic_path
field:"RequestParameters.isMultiRegionTrail" kind:eq value:"false"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AWSRegionsummarize
AccountNamesummarize
AccountUPNSuffixsummarize
EndTimeUtcsummarize
EventNamesummarize
EventSourcesummarize
EventTypeNamesummarize
RecipientAccountIdsummarize
SessionMfaAuthenticatedsummarize
SourceIpAddresssummarize
StartTimeUtcsummarize
UserAgentsummarize
UserIdentityAccountIdsummarize
UserIdentityPrincipalidsummarize
UserIdentityUserNamesummarize

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.

Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

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.

FieldKindValuesSearch
Findingeq
  • T0DumpSMSA
field:"Finding" kind:eq value:"T0DumpSMSA"

Cloudflare - Unexpected client request

#
Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'Detects client requests to unusual client request.'

MITRE ATT&CK Coverage

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.

FieldKindValuesSearch
ClientRequestURImatch
  • .htaccess transforms: term
  • /admin transforms: term
  • /admin.php transforms: term
  • /etc/hosts transforms: term
  • /etc/passwd transforms: term
  • /etc/shadow transforms: term
  • /etc/ssh/ transforms: term
  • wp-admin transforms: term
field:"ClientRequestURI" kind:match
DstBytesne
  • 0
field:"DstBytes" kind:ne value:"0"
HttpRequestMethodeq
  • GET
field:"HttpRequestMethod" kind:eq value:"GET"
SrcBytesne
  • 0
field:"SrcBytes" kind:ne value:"0"

Connection to Backup Repository Lost

#
Status
available
Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
User Nameproject

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.

Status
available
Severity
low
Time window
30m
Source
github.com/Azure/Azure-Sentinel

'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

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.

FieldKindValuesSearch
Serviceeq
  • cloud_storage
field:"Service" kind:eq value:"cloud_storage"

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

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.

Severity
high
Time window
5m
Group by
name
Source
github.com/Azure/Azure-Sentinel

"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

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
NetworkIPsproject
ProductNameproject
ProviderNameproject
TimeGeneratedproject
UIDproject
attack_complexityproject
attack_vectorproject
confidence_scoreproject
confidentiality_impactproject
createdproject
cvss_scoreproject
cvss_vectorproject
cvss_versionproject
descriptionproject
exploitability_scoreproject
impact_scoreproject
integrity_impactproject
modifiedproject
nameproject
privileges_requiredproject
productsproject
scopeproject
serversproject
technologiesproject
user_interactionproject
vendorsproject
vulnerability_categoryproject
vulnerability_typeproject

Detect CVE exploits on network for which a device is vulnerable

#
Group by
DeviceName
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

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

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 used in Stages 1, 7

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AdditionalFieldsextend
SamplePacketContentextend
SignatureMatchedContentextend
SignatureNameextend

Excessive Denied Proxy Traffic

#
Status
available
Severity
low
Time window
1h
Group by
c_ip, cs_host
Source
github.com/Azure/Azure-Sentinel

'This alert creates an incident when a client generates an excessive amounts of denied proxy traffic.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EndTimesummarize
StartTimesummarize
c_ipsummarize
cs_hostsummarize

Mimecast Data Leak Prevention - Hold

#
Severity
informational
Time window
15m
Source
github.com/Azure/Azure-Sentinel

Detects threat for data leak when action is hold

MITRE ATT&CK Coverage

TacticTechniques
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.

Process Injection From Untrusted Process

#
Group by
DeviceId, InitiatingProcessFolderPath, InitiatingProcessSHA1
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

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

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 used in Stages 2, 8

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.

FieldKindValuesSearch
ActionTypein
  • CreateRemoteThreadApiCall
  • NtAllocateVirtualMemoryRemoteApiCall
  • NtMapViewOfSectionRemoteApiCall
  • QueueUserApcRemoteApiCall
  • SetThreadContextRemoteApiCall
field:"ActionType" kind:in
GlobalPrevalencelt
  • 200 transforms: coalesce_default:0
  • 500 transforms: coalesce_default:0
field:"GlobalPrevalence" kind:lt
Signeris_null
  • (no value, null check)
field:"Signer" kind:is_null

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
InitiatingProcessFolderPathstarts_withFolderPathexcludes:InitiatingProcessFolderPath field:"InitiatingProcessFolderPath" value:"FolderPath"
InitiatingProcessSHA1is_null(no value, null check)excludes:InitiatingProcessSHA1
ProfileAvailabilityeqErrorexcludes:ProfileAvailability field:"ProfileAvailability" value:"Error"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceIdsummarize
InitiatingProcessFolderPathsummarize
InjectionTargetssummarize
InjectionCommandLineextend
InjectionSourceextend

Rare RDP Connections

#
Severity
medium
Time window
14d
Group by
Account, AccountType, Activity, Computer, IpAddress, LogonTypeName, ProcessName
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Lateral Movement

Telemetry Coverage

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.

FieldKindValuesSearch
EventDatamatch
  • 10 transforms: term
field:"EventData" kind:match value:"10"
EventIDeq
  • 4624 corpus 8 (chronicle 4, kusto 3, elastic 1)
field:"EventID" kind:eq value:"4624"
LogonTypeeq
  • 10 corpus 3 (kusto 3)
field:"LogonType" kind:eq value:"10"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
EventIDeq4624excludes:EventID field:"EventID" value:"4624"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountsummarize
AccountTypesummarize
Activitysummarize
Computersummarize
ConnectionCountsummarize
EndTimesummarize
IpAddresssummarize
LogonTypeNamesummarize
ProcessNamesummarize
StartTimesummarize
HostNameextend
HostNameDomainextend
AccountNTDomainextend
AccountNameextend

Sentinel One - New admin created

#
Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

'Detects when new admin user is created.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

SQL Server spawning suspicious child process

#
Group by
SHA1
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

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

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 used in Stages 2, 9

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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
ProfileAvailabilityeqErrorexcludes:ProfileAvailability field:"ProfileAvailability" value:"Error"
SHA1is_null(no value, null check)excludes:SHA1

Output Fields

These fields are emitted when the rule matches.

FieldSource
SHA1summarize

Suspicious malware found in the network (Microsoft Defender for IoT)

#
Status
available
Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

'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

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 used in Stage 3

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.

FieldKindValuesSearch
AlertNamematch
  • Connection Attempt to Known Malicious IP transforms: term
  • Invalid SMB Message (DoublePulsar Backdoor Implant) transforms: term
  • Malicious Domain Name Request transforms: term
  • Malware transforms: term
  • Suspicion of Malicious Activity transforms: term
  • Suspicion of Remote Code Execution with PsExec transforms: term
  • Suspicion of Remote Windows Service Management transforms: term
  • Suspicious Executable File Detected on Endpoint transforms: term
  • Suspicious Traffic Detected transforms: term
field:"AlertName" kind:match
ExtendedProperties.isNeweq
  • True transforms: tostring, kusto_dynamic_path corpus 15 (kusto 15)
field:"ExtendedProperties.isNew" kind:eq value:"True"
ProviderNameeq
  • IoTSecurity corpus 15 (kusto 15)
field:"ProviderName" kind:eq value:"IoTSecurity"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertLinkproject
AlertManagementUriproject
AlertNameproject
AlertSeverityproject
Descriptionproject
DestDeviceAddressproject
DeviceIdproject
Entitiesproject
ProductComponentNameproject
ProductNameproject
Protocolproject
RemediationStepsproject
SourceDeviceAddressproject
Tacticsproject
Techniquesproject
TimeGeneratedproject
VendorOriginalIdproject

Suspicious Network Beacons - Microsoft Defender for Endpoint Aggregated Reports

#
Group by
DeviceName, InitiatingProcessFileName, RemoteIP
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

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

TacticTechniques
Command & ControlNo specific technique

Telemetry Coverage

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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
RemoteIPcidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:RemoteIP

Output Fields

These fields are emitted when the rule matches.

FieldSource
ConnCountssummarize
DeviceNamesummarize
InitiatingProcessFileNamesummarize
RemoteIPsummarize
Timestampsummarize
count_of_hoursextend
anomalies_decomposedextend
FirstConnectionextend
LastConnectionextend
avg_conn_countextend

Suspicious Resource deployment

#
Status
available
Severity
low
Time window
1d
Group by
Caller, CallerIpAddress, OperationNameValue
Source
github.com/Azure/Azure-Sentinel

Identifies when a rare Resource and ResourceGroup deployment occurs by a previously unseen caller.

MITRE ATT&CK Coverage

TacticTechniques
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 used in Stage 5

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.

FieldKindValuesSearch
OperationNameValuein
  • Microsoft.Compute/virtualMachines/write corpus 4 (kusto 4)
  • Microsoft.Compute/virtualMachines/write
  • Microsoft.Resources/deployments/write corpus 4 (kusto 4)
  • Microsoft.Resources/deployments/write
  • Microsoft.Resources/subscriptions/resourceGroups/write
  • Microsoft.Resources/subscriptions/resourceGroups/write
field:"azure_ad::operation_name_value" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
Callersummarize
CallerIpAddresssummarize
OperationNameValuesummarize
Nameextend
UPNSuffixextend
AadUserIdextend

TI map IP entity to Web Session Events (ASIM Web Session schema)

#
Severity
medium
Time window
14d
Group by
$left.TI_ipEntity, $right.SrcIpAddr, DstIpAddr, IndicatorId
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
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 used in Stage 8

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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
TI_ipEntitycidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:TI_ipEntity
TI_ipEntitystarts_with127.excludes:TI_ipEntity field:"TI_ipEntity" value:"127."
TI_ipEntitystarts_with::excludes:TI_ipEntity field:"TI_ipEntity" value:"::"
TI_ipEntitystarts_withfe80excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ConfidenceScoreproject
Descriptionproject
DstIpAddrproject
Dvcproject
ExpirationDateTimeproject
IndicatorIdproject
SrcIpAddrproject
TI_ipEntityproject
ThreatTypeproject
Typeproject
Urlproject
imNWS_TimeGeneratedproject

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.

Status
available
Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

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.

Back to all Kusto rules