Detection rules › Kusto

Kusto rules: 2v

1Password - Non-privileged vault user permission change

#
Severity
medium
Time window
5m
Source
github.com/Azure/Azure-Sentinel

This will alert when user permissions have changed within a non-privileged vault which have been implemented by an actor that was not the target user account. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same. Ref: https://1password.com/ Ref: https://github.com/securehats/

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rule Body

id: 327e0579-7c03-4ec7-acf5-a29dcc4a12b6
name: 1Password - Non-privileged vault user permission change
version: 1.0.0
kind: Scheduled
description: |-
  This will alert when user permissions have changed within a non-privileged vault which have been implemented by an actor that was not the target user account. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same.

  Ref: https://1password.com/
  Ref: https://github.com/securehats/
severity: Medium
requiredDataConnectors:
  - connectorId: 1Password
    dataTypes:
      - OnePasswordEventLogs_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1098
query: |-
  let watchlist =
      _GetWatchlist("PV1PW")
      | project SearchKey
  ;
  // Insert the vault UUIDs below when using the dynamic vaults list within the analytics rule itself
  let vaults = dynamic([""]);
  OnePasswordEventLogs_CL
  | where log_source == "auditevents"
  | where action has_any("grant", "revoke", "update")
  | where object_type == "uva"
  | where tostring(actor_details.email) != tostring(aux_details.email)
  // Enable the line below when using the "Privileged Vaults - 1PW" watchlist
  | where object_uuid !in (watchlist)
  // Enable the line below when using the dynamic vaults list within the analytics rule itself
  // | where object_uuid !in (vaults)
  | extend
      TargetUsername = aux_details.email
      , ActorUsername = actor_details.email
      , SrcIpAddr = session.ip
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: ActorUsername
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: TargetUsername
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: SrcIpAddr

Stages and Predicates

Parameters

let vaults = dynamic([""]);

Let Binding: watchlist used in Stage 6

let watchlist = _GetWatchlist("PV1PW")
    | project SearchKey;

Stage 1: source

OnePasswordEventLogs_CL

Stage 2: where

| where log_source == "auditevents"

Stage 3: where

| where action has_any("grant", "revoke", "update")

Stage 4: where

| where object_type == "uva"

Stage 5: where

| where tostring(actor_details.email) != tostring(aux_details.email)

Stage 6: where

| where object_uuid !in (watchlist)

Stage 7: extend

| extend
    TargetUsername = aux_details.email
    , ActorUsername = actor_details.email
    , SrcIpAddr = session.ip

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
actionmatch
  • grant transforms: term corpus 4 (kusto 4)
  • revoke transforms: term corpus 2 (kusto 2)
  • update transforms: term corpus 3 (kusto 3)
field:"action" kind:match
log_sourceeq
  • auditevents corpus 12 (kusto 12)
field:"log_source" kind:eq value:"auditevents"
object_typeeq
  • uva corpus 4 (kusto 4)
field:"object_type" kind:eq value:"uva"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
object_uuideqwatchlistexcludes:object_uuid field:"object_uuid" value:"watchlist"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActorUsernameextend
SrcIpAddrextend
TargetUsernameextend

A host is potentially running PowerShell to send HTTP(S) requests (ASIM Web Session schema)

#
Severity
medium
Time window
15m
Author
Yaron
Source
github.com/Azure/Azure-Sentinel

'This rule identifies a web request with a user agent header known to belong PowerShell. <br>You can add custom Powershell indicating User-Agent headers using a watchlist, for more information refer to the UnusualUserAgents Watchlist.<br><br> 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

Rule Body

id: 42436753-9944-4d70-801c-daaa4d19ddd2
name: A host is potentially running PowerShell to send HTTP(S) requests (ASIM Web Session schema)
description: |
  'This rule identifies a web request with a user agent header known to belong PowerShell. <br>You can add custom Powershell indicating User-Agent headers using a watchlist, for more information refer to the [UnusualUserAgents Watchlist](https://aka.ms/ASimUnusualUserAgentsWatchlist).<br><br>
   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/CiscoUmbrellaPowershellUserAgentDetected.yaml
    version: 1.0.0
  - Schema: ASimWebSession
    SchemaVersion: 0.2.1
requiredDataConnectors:
  - connectorId: SquidProxy
    dataTypes:
      - SquidProxy_CL
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog

queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - DefenseEvasion
  - Execution
relevantTechniques:
  - T1132
  - T1140
  - T1059.001
query: |
    let threatCategory="Powershell";
    let knownUserAgentsIndicators = materialize(externaldata(UserAgent:string, Category:string)
        [ @"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/UnusualUserAgents.csv"]
            with(format="csv", ignoreFirstRecord=True));
    let knownUserAgents=toscalar(knownUserAgentsIndicators | where Category==threatCategory | where isnotempty(UserAgent) | summarize make_list(UserAgent));
    let customUserAgents=toscalar(_GetWatchlist("UnusualUserAgents") | where SearchKey==threatCategory | extend UserAgent=column_ifexists("UserAgent","") | where isnotempty(UserAgent) | summarize make_list(UserAgent));
    let fullUAList = array_concat(knownUserAgents,customUserAgents);
    _Im_WebSession(httpuseragent_has_any=fullUAList)
    | project SrcIpAddr, Url, TimeGenerated, HttpUserAgent, SrcUsername
    | extend AccountName = tostring(split(SrcUsername, "@")[0]), AccountUPNSuffix = tostring(split(SrcUsername, "@")[1])
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: SrcUsername
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix

alertDetailsOverride:
  alertDisplayNameFormat: 'Host {{SrcIpAddr}} is potentially running PowerShell'
  alertDescriptionFormat: 'The host at address {{SrcIpAddr}} sent an HTTP request to the URL {{Url}} with the HTTP user agent header {{HttpUserAgent}}. This user agent is known to be used by PowerShell and indicates suspicious activity on the host.'
customDetails:
  UserAgent: HttpUserAgent

eventGroupingSettings:
  aggregationKind: AlertPerResult
version: 1.1.5
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Yaron
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection" ]

Stages and Predicates

Parameters

let threatCategory = "Powershell";
let fullUAList = array_concat(knownUserAgents,customUserAgents);

Let Binding: knownUserAgentsIndicators

let knownUserAgentsIndicators = materialize(externaldata(UserAgent:string, Category:string)
    [ @"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/UnusualUserAgents.csv"]
        with(format="csv", ignoreFirstRecord=True));

Let Binding: knownUserAgents

let knownUserAgents = toscalar(knownUserAgentsIndicators | where Category==threatCategory | where isnotempty(UserAgent) | summarize make_list(UserAgent));

Let Binding: customUserAgents

let customUserAgents = toscalar(_GetWatchlist("UnusualUserAgents") | where SearchKey==threatCategory | extend UserAgent=column_ifexists("UserAgent","") | where isnotempty(UserAgent) | summarize make_list(UserAgent));

Stage 1: source

_Im_WebSession(httpuseragent_has_any=fullUAList)

Stage 2: project

| project SrcIpAddr, Url, TimeGenerated, HttpUserAgent, SrcUsername

Stage 3: extend

| extend AccountName = tostring(split(SrcUsername, "@")[0]), AccountUPNSuffix = tostring(split(SrcUsername, "@")[1])

Output Fields

These fields are emitted when the rule matches.

FieldSource
HttpUserAgentproject
SrcIpAddrproject
SrcUsernameproject
TimeGeneratedproject
Urlproject
AccountNameextend
AccountUPNSuffixextend

BloodHound Attack Path Finding - Large Default Groups With WriteDacl Privilege

#

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 - Large Default Groups With WriteDacl Privilege

Rule Body

id: cfa3b93b-0203-4dd1-b784-c148af74e389
name: BloodHound Attack Path Finding - Large Default Groups With WriteDacl Privilege
description: BloodHound Attack Path Finding - Large Default Groups With WriteDacl
  Privilege
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 == "LargeDefaultGroupsWriteDacl"
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 == "LargeDefaultGroupsWriteDacl"

Indicators

These rows show field, operator, and value matches.

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

Cross-Cloud Suspicious user activity observed in GCP Envourment

#
Severity
medium
Time window
1d
Group by
AlertIPEntity, AlertLink, AlertName, AlertSeverity, AlertUserUPN, Description, GCPAccoutType, GCPUserIdentity, GCPUserIp, ProductName, SystemAlertId, Tactics
Source
github.com/Azure/Azure-Sentinel

'This detection query aims to correlate potentially suspicious user activities logged in Google Cloud Platform (GCP) Audit Logs with security alerts originating from Microsoft Security products. This correlation facilitates the identification of potential cross-cloud security incidents. By summarizing these findings, the query provides valuable insights into cross-cloud identity threats and their associated details, enabling organizations to respond promptly and mitigate potential risks effectively.'

MITRE ATT&CK Coverage

Rule Body

id: 58e306fe-1c49-4b8f-9b0e-15f25e8f0cd7
name: Cross-Cloud Suspicious user activity observed in GCP Envourment
description: |
  'This detection query aims to correlate potentially suspicious user activities logged in Google Cloud Platform (GCP) Audit Logs with security alerts originating from Microsoft Security products. This correlation facilitates the identification of potential cross-cloud security incidents. By summarizing these findings, the query provides valuable insights into cross-cloud identity threats and their associated details, enabling organizations to respond promptly and mitigate potential risks effectively.'
severity: Medium
requiredDataConnectors:
  - connectorId: GCPAuditLogsDefinition
    dataTypes:
      - GCPAuditLogs
  - connectorId: AzureActiveDirectoryIdentityProtection
    dataTypes:
      - SecurityAlert (IPC)
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - SecurityAlert
  - connectorId: MicrosoftDefenderAdvancedThreatProtection
    dataTypes:
      - SecurityAlert (MDATP)
  - connectorId: MicrosoftCloudAppSecurity
    dataTypes:
      - SecurityAlert
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - Execution
  - Persistence
  - PrivilegeEscalation
  - CredentialAccess
  - Discovery
relevantTechniques:
  - T1566
  - T1059
  - T1078
  - T1046
  - T1547
  - T1548
  - T1069
  - T1552
query: |
  // Filter GCP Audit Logs to exclude service accounts
  GCPAuditLogs 
  | where PrincipalEmail !endswith "gserviceaccount.com"
  // Exclude system-related authentication information
  | where AuthenticationInfo !has ("system:")
  // Extract GCP request name and relevant attributes
  | extend GCPRequestName= parse_json(Request).name
  | extend
      GCPAccoutType= tostring(split(GCPRequestName, "/")[2]),
      GCPUserIdentity = iff(isempty(tostring(split(GCPRequestName, "/")[3])), tostring(parse_json(AuthenticationInfo).principalEmail), "na"), 
      GCPUserIp = tostring(parse_json(RequestMetadata).callerIp),
      GCPCallerUA = tostring(parse_json(RequestMetadata).callerSuppliedUserAgent)
  // Filter out empty or service account identities
  | where isnotempty(GCPUserIdentity) and GCPUserIdentity !endswith "gserviceaccount.com"
  // Select relevant attributes for further analysis
  | project
      PrincipalEmail,
      GCPUserIdentity,
      GCPAccoutType,
      GCPRequestName,
      GCPCallerUA,
      Request,
      RequestMetadata,
      GCPUserIp,
      MethodName,
      ServiceName,
      GCPEventTime= TimeGenerated,
      ProjectId
  // Join GCP Audit Logs with SecurityAlert data based on user identity and IP
  | join kind=inner (    
      SecurityAlert 
      // Exclude alerts from Azure Sentinel
      | where ProductName !in ("Azure Sentinel")
      // Extract IP entities from alert data
      | extend AlertIPEntity=  tostring(extract(@"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", 0, Entities))
      | extend
          AlertUserUPN = tostring(extract(@'\b[\w\.\-]+@[\w\.\-]+\b', 0, Entities)),
          AlertTime= TimeGenerated
      // Filter out empty user identities and IP entities
      | where isnotempty(AlertIPEntity) and isnotempty(AlertUserUPN)
      )
      on $left.GCPUserIdentity == $right.AlertUserUPN and $left.GCPUserIp == $right.AlertIPEntity
  // Summarize the data, calculating time differences and aggregating attributes
  | summarize
      FirstAlert=min(AlertTime),
      LastAlert=max(AlertTime),
      TimeDiff=datetime_diff('minute', min(AlertTime), min(GCPEventTime)),
      MethodName=make_set(MethodName),
      ServiceName= make_set(ServiceName),
      GCPProjctId=make_set(ProjectId),
      Request=make_set(Request),
      GCPCallerUA=make_set(GCPCallerUA)
      by
      AlertUserUPN,
      AlertIPEntity,
      GCPUserIp,
      GCPUserIdentity,
      AlertSeverity,
      AlertName,
      AlertLink,
      Description,
      Tactics,
      ProductName,
      SystemAlertId,
      GCPAccoutType
  // Extend the data with additional attributes
  | extend
      Name = tostring(split(GCPUserIdentity, "@")[0]),
      UPNSuffix = tostring(split(GCPUserIdentity, "@")[1])
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: GCPUserIp
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: GCPUserIdentity
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
customDetails:
  AlertName: AlertName
  FirstAlert: FirstAlert
  LastAlert: LastAlert
  TimeDiff: TimeDiff
  MethodName: MethodName
  GCPProjctId: GCPProjctId
  GCPCallerUA: GCPCallerUA
  ServiceName: ServiceName
  AlertUserUPN: AlertUserUPN
  SystemAlertId: SystemAlertId
  Tactics: Tactics
  Request: Request
  CorrelationWith: "GCPAuditLogs"
alertDetailsOverride:
  alertDisplayNameFormat: "A user {{GCPUserUPN}} has been linked to {{AlertName}}, and has potentially suspicious behavior within the GCP environment from, originating from the IP address {{GCPUserIp}}."
  alertDescriptionFormat: " This detection compiles and correlates unauthorized user access alerts originating from {{ProductName}} With Alert Description '{{Description}}' observed activity in GCP environmeny. It focuses on Microsoft Security, specifically targeting user bhaviour and network IP associations tied to activities such as logins from malicious IP addresses or instance credential exfiltration attempts. The detection leverages these common network IP advisories to detect and pinpoint users suspicious activity to access both Azure and GCP resources.  \n\n Microsoft Security ALert Link : '{{AlertLink}}'"
  alertSeverityColumnName: AlertSeverity
  alertDynamicProperties:
    - alertProperty: AlertLink
      value: AlertLink
    - alertProperty: ProviderName
      value: "ProductName"
    - alertProperty: ProductComponentName
      value: "Microsoft Security"
kind: Scheduled
version: 1.0.2

Stages and Predicates

Stage 1: source

GCPAuditLogs

Stage 2: where

| where PrincipalEmail !endswith "gserviceaccount.com"

Stage 3: where

| where AuthenticationInfo !has ("system:")

Stage 4: extend

| extend GCPRequestName= parse_json(Request).name

Stage 5: extend

| extend
    GCPAccoutType= tostring(split(GCPRequestName, "/")[2]),
    GCPUserIdentity = iff(isempty(tostring(split(GCPRequestName, "/")[3])), tostring(parse_json(AuthenticationInfo).principalEmail), "na"), 
    GCPUserIp = tostring(parse_json(RequestMetadata).callerIp),
    GCPCallerUA = tostring(parse_json(RequestMetadata).callerSuppliedUserAgent)
GCPUserIdentity =
if/* macro: isempty(tostring(split(GCPRequestName, "/")[3])) */tostring(parse_json(AuthenticationInfo).principalEmail)
else"na"

Stage 6: where

| where isnotempty(GCPUserIdentity) and GCPUserIdentity !endswith "gserviceaccount.com"

Stage 7: project

| project
    PrincipalEmail,
    GCPUserIdentity,
    GCPAccoutType,
    GCPRequestName,
    GCPCallerUA,
    Request,
    RequestMetadata,
    GCPUserIp,
    MethodName,
    ServiceName,
    GCPEventTime= TimeGenerated,
    ProjectId

Stage 8: join

| join kind=inner (    
    SecurityAlert 
    | where ProductName !in ("Azure Sentinel")
    | extend AlertIPEntity=  tostring(extract(@"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", 0, Entities))
    | extend
        AlertUserUPN = tostring(extract(@'\b[\w\.\-]+@[\w\.\-]+\b', 0, Entities)),
        AlertTime= TimeGenerated
    | where isnotempty(AlertIPEntity) and isnotempty(AlertUserUPN)
    )
    on $left.GCPUserIdentity == $right.AlertUserUPN and $left.GCPUserIp == $right.AlertIPEntity

Stage 9: summarize

summarize FirstAlert, GCPCallerUA, GCPProjctId, LastAlert, MethodName, Request, ServiceName, TimeDiff by AlertUserUPN, AlertIPEntity, GCPUserIp, GCPUserIdentity, AlertSeverity, AlertName, AlertLink, Description, Tactics, ProductName, SystemAlertId, GCPAccoutType

Stage 10: extend

extend Name, UPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AlertIPEntityis_not_null
  • (no value, null check)
field:"AlertIPEntity" kind:is_not_null
AlertUserUPNis_not_null
  • (no value, null check)
field:"AlertUserUPN" kind:is_not_null
GCPUserIdentityis_not_null
  • (no value, null check)
field:"GCPUserIdentity" kind:is_not_null

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertIPEntitysummarize
AlertLinksummarize
AlertNamesummarize
AlertSeveritysummarize
AlertUserUPNsummarize
Descriptionsummarize
FirstAlertsummarize
GCPAccoutTypesummarize
GCPCallerUAsummarize
GCPProjctIdsummarize
GCPUserIdentitysummarize
GCPUserIpsummarize
LastAlertsummarize
MethodNamesummarize
ProductNamesummarize
Requestsummarize
ServiceNamesummarize
SystemAlertIdsummarize
Tacticssummarize
TimeDiffsummarize
Nameextend
UPNSuffixextend

Cyble Vision Alerts Compromised Files

#

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 compromised files containing credential or logon data (stealer logs) related to monitored entities. Uses Alerts_compromised_files parser to expose file paths, log objects, and extracted email identifiers.'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access
Exfiltration

Rule Body

id: 0f6a8287-09ee-4f82-b8c3-e35c4ac6212e
name: Cyble Vision Alerts Compromised Files
description: |
  'Detects compromised files containing credential or logon data (stealer logs) related to monitored entities. Uses Alerts_compromised_files parser to expose file paths, log objects, and extracted email identifiers.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
  dataTypes:
  - CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
- CredentialAccess
- Exfiltration
relevantTechniques:
- T1552
- T1041
query: |
  Alerts_compromised_files  
  | where Service == "compromised_files" 
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
alertDetailsOverride:
  alertDisplayNameFormat: Compromised Credentials File {{CF_Filename}}
  alertDescriptionFormat: |
    A compromised file containing potential credential or logon data was discovered. Relative path {{CF_RelativePath}}. Log object path {{CF_LogObjPath}}. Primary email (if found) {{CF_PrimaryEmail}}.
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  CF_Filename: CF_Filename
  CF_FileObjPath: CF_FileObjPath
  CF_LogObjPath: CF_LogObjPath
  CF_PrimaryEmail: CF_PrimaryEmail
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: CF_PrimaryEmail
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_compromised_files

Stage 2: where

| where Service == "compromised_files"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

CybleVision Alerts Cyber Crime Forum Alerts

#

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 discussions, marketplace posts, threat actor activity, and intelligence mentions from cybercrime forums. Extracts discussion metadata, topic details, URLs, emails, phone numbers, and content using Alerts_CyberCrimeForums parser.'

MITRE ATT&CK Coverage

Rule Body

id: b78c4641-cc16-48e0-9d05-c9b36a55d214
name: CybleVision Alerts Cyber Crime Forum Alerts
description: |
  'Detects discussions, marketplace posts, threat actor activity, and intelligence mentions from cybercrime forums. Extracts discussion metadata, topic details, URLs, emails, phone numbers, and content using Alerts_CyberCrimeForums parser.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
  dataTypes:
  - CybleVisionAlerts_CL
enabled: true
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: PT5H
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
- Reconnaissance
- ResourceDevelopment
- Exfiltration
relevantTechniques:
- T1598
- T1585
- T1589
- T1646
subTechniques: []
query: |
  Alerts_cyber_crime_forums 
  | where Service == "cyber_crime_forums" 
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
alertDetailsOverride:
  alertDisplayNameFormat: CybleVision Forum Mention {{CF_TopicName}}
  alertDescriptionFormat: |
    A mention was detected on a cybercrime forum. Source {{CF_SourceName}} By {{CF_DiscussionBy}} Content {{CF_Content}}
  alertDynamicProperties: []
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  TopicName: CF_TopicName
  DiscussionBy: CF_DiscussionBy
  DiscussionDate: CF_DiscussionDate
  Content: CF_Content
  Phones: CF_Phones
  TopicURL: CF_TopicURL
  TopicID: CF_TopicID
  TopicCreatedBy: CF_TopicCreatedBy
  Reputation: CF_Reputation
  Likes: CF_Likes
  Tags: CF_Tags
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: Name
    columnName: CF_Emails
- entityType: URL
  fieldMappings:
  - identifier: Url
    columnName: CF_Urls
- entityType: DNS
  fieldMappings:
  - identifier: DomainName
    columnName: CF_SourceName
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_cyber_crime_forums

Stage 2: where

| where Service == "cyber_crime_forums"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

Digital Guardian - Unexpected protocol

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

'Detects RDP protocol usage for data transfer which is not common.'

MITRE ATT&CK Coverage

Rule Body

id: a14f2f95-bbd2-4036-ad59-e3aff132b296
name: Digital Guardian - Unexpected protocol
description: |
  'Detects RDP protocol usage for data transfer which is not common.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1048
query: |
  DigitalGuardianDLPEvent
  | where DstPortNumber == 3389
  | extend AccountCustomEntity = SrcUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

DigitalGuardianDLPEvent

Stage 2: where

| where DstPortNumber == 3389

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

Jira - User's password changed multiple times

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

'Detects when user's password was changed multiple times from different IP addresses.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Rule Body

id: 943176e8-b979-45c0-8ad3-58ba6cfd41f0
name: Jira - User's password changed multiple times
description: |
  'Detects when user's password was changed multiple times from different IP addresses.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: JiraAuditAPI
    dataTypes:
      - JiraAudit
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1078
query: |
  JiraAudit
  | where EventMessage =~ "User's password changed"
  | extend user = todynamic(AssociatedItems)[0]['name']
  | summarize ip_list = makeset(SrcIpAddr) by tostring(user), bin(TimeGenerated, 30m)
  | where array_length(ip_list) > 1
  | extend AccountCustomEntity = user, IPCustomEntity = ip_list
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

JiraAudit

Stage 2: where

| where EventMessage =~ "User's password changed"

Stage 3: extend

| extend user = todynamic(AssociatedItems)[0]['name']

Stage 4: summarize

| summarize ip_list = makeset(SrcIpAddr) by tostring(user), bin(TimeGenerated, 30m)

Stage 5: where

| where array_length(ip_list) > 1

Stage 6: extend

| extend AccountCustomEntity = user, IPCustomEntity = ip_list

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ip_listsummarize
AccountCustomEntityextend
IPCustomEntityextend

Malware attachment delivered

#

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
Group by
Recipient, Sender, SenderIPAddress, Subject, TimeGenerated, classification, threatType
Source
github.com/Azure/Azure-Sentinel

'This query identifies a message containing a malware attachment that was delivered.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 0558155e-4556-447e-9a22-828f2a7de06b
name: Malware attachment delivered
description: |
  'This query identifies a message containing a malware attachment that was delivered.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: ProofpointTAPv2
    dataTypes:
      - ProofPointTAPMessagesDeliveredV2_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1566.001
query: |
  ProofPointTAPMessagesDeliveredV2_CL
  | mv-expand todynamic(threatsInfoMap)
  | mv-expand todynamic(messageParts)
  | extend threatType = tostring(threatsInfoMap.threatType), classification = tostring(threatsInfoMap.classification)
  | extend filename = tostring(messageParts.filename)
  | where threatType =~ "attachment" and classification =~ "malware"
  | summarize filenames = make_set(filename), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by TimeGenerated, Sender = sender, SenderIPAddress = senderIP, Recipient = tostring(recipient), threatType, classification, Subject = subject
  | mv-expand todynamic(Recipient)
  | extend RecipientName = tostring(split(Recipient, "@")[0]), RecipientUPNSuffix = tostring(split(Recipient, "@")[1])
  | extend SenderName = tostring(split(Sender, "@")[0]), SenderUPNSuffix = tostring(split(Sender, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Recipient
      - identifier: Name
        columnName: RecipientName
      - identifier: UPNSuffix
        columnName: RecipientUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Sender
      - identifier: Name
        columnName: SenderName
      - identifier: UPNSuffix
        columnName: SenderUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SenderIPAddress
version: 1.0.6
kind: Scheduled

Stages and Predicates

Stage 1: source

ProofPointTAPMessagesDeliveredV2_CL

Stage 2: mv-expand

| mv-expand todynamic(threatsInfoMap)

Stage 3: mv-expand

| mv-expand todynamic(messageParts)

Stage 4: extend

| extend threatType = tostring(threatsInfoMap.threatType), classification = tostring(threatsInfoMap.classification)

Stage 5: extend

| extend filename = tostring(messageParts.filename)

Stage 6: where

| where threatType =~ "attachment" and classification =~ "malware"

Stage 7: summarize

| summarize filenames = make_set(filename), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by TimeGenerated, Sender = sender, SenderIPAddress = senderIP, Recipient = tostring(recipient), threatType, classification, Subject = subject

Stage 8: mv-expand

| mv-expand todynamic(Recipient)

Stage 9: extend

| extend RecipientName = tostring(split(Recipient, "@")[0]), RecipientUPNSuffix = tostring(split(Recipient, "@")[1])

Stage 10: extend

| extend SenderName = tostring(split(Sender, "@")[0]), SenderUPNSuffix = tostring(split(Sender, "@")[1])

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EndTimesummarize
Recipientsummarize
Sendersummarize
SenderIPAddresssummarize
StartTimesummarize
Subjectsummarize
TimeGeneratedsummarize
classificationsummarize
filenamessummarize
threatTypesummarize
RecipientNameextend
RecipientUPNSuffixextend
SenderNameextend
SenderUPNSuffixextend

Mimecast Secure Email Gateway - Impersonation Protect

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

'Detects threats from impersonation mail under targeted threat protection.'

MITRE ATT&CK Coverage

TacticTechniques
Collection

Rule Body

id: 2ef77cef-439f-4d94-848f-3eca67510d2f
name: Mimecast Secure Email Gateway - Impersonation Protect
description: |
  'Detects threats from impersonation mail under targeted threat protection.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: MimecastSEGAPI
    dataTypes:
      - MimecastCG
enabled: true
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
- LateralMovement
- Collection
relevantTechniques:
- T1114
query: |
  MimecastCG
  | where Type == "email_ttp_impersonation"
  | extend  SenderEnvelope = ['Sender Envelope'] , SenderIp = ['Sender IP']
suppressionDuration: 5h
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P7D
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
- entityType: MailMessage
  fieldMappings:
  - identifier: Sender
    columnName: SenderEnvelope
  - identifier: SenderIP
    columnName: SenderIp
  - identifier: Recipient
    columnName: Recipients
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

MimecastCG

Stage 2: where

| where Type == "email_ttp_impersonation"

Stage 3: extend

| extend  SenderEnvelope = ['Sender Envelope'] , SenderIp = ['Sender IP']

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Typeeq
  • email_ttp_impersonation
field:"Type" kind:eq value:"email_ttp_impersonation"

Output Fields

These fields are emitted when the rule matches.

FieldSource
SenderEnvelopeextend
SenderIpextend

Mimecast Secure Email Gateway - Impersonation Protect

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

Detects threats from impersonation mail under targeted threat protection

MITRE ATT&CK Coverage

TacticTechniques
Collection

Rule Body

id: 7034abc9-6b66-4533-9bf3-056672fd9d9e
name: Mimecast Secure Email Gateway - Impersonation Protect
description: Detects threats from impersonation mail under targeted threat protection
severity: High
requiredDataConnectors:
  - connectorId: MimecastSIEMAPI
    dataTypes:
      - MimecastSIEM_CL
enabled: true
query: MimecastSIEM_CL| where mimecastEventId_s == "mail_ttp_impersonation"
queryFrequency: 5m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics:
- Discovery
- LateralMovement
- Collection
relevantTechniques:
- T1114
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1d
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: SingleAlert
customDetails:
  Subject: Subject_s
  MsgId_s: MsgId_s
  Route: Route_s
  CustomThreatDict: CustomThreatDictionary_s
  Action: Action_s
  Hits: Hits_s
  SimilarCustExtDomain: SimilarCustomExternalDomain_s
  TaggedExternal: TaggedExternal_s
  SimilarIntDomain: SimilarInternalDomain_s
  Definition: Definition_s
  NewDomain: NewDomain_s
  InternalName: InternalName_s
  ThreatDictionary: ThreatDictionary_s
  SimilarMCExtDomain: SimilarMimecastExternalDomain_s
  CustomName: CustomName_s
  TaggedMalicious: TaggedMalicious_s
  ReplyMismatch: ReplyMismatch_s
entityMappings:
- entityType: MailMessage
  fieldMappings:
  - identifier: Sender
    columnName: Sender_s
  - identifier: SenderIP
    columnName: IP_s
  - identifier: Recipient
    columnName: Recipient_s
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

MimecastSIEM_CL

Stage 2: where

| where mimecastEventId_s == "mail_ttp_impersonation"

Indicators

These rows show field, operator, and value matches.

Multiple Teams deleted by a single user

#
Status
available
Severity
low
Time window
1d
Group by
UserId
Source
github.com/Azure/Azure-Sentinel

This detection flags the occurrences of deleting multiple teams within an hour. This data is a part of Office 365 Connector in Microsoft Sentinel.

MITRE ATT&CK Coverage

Telemetry Coverage

PlatformRecord / event type
Microsoft 365TeamDeleted

Rule Body

id: 173f8699-6af5-484a-8b06-8c47ba89b380
name: Multiple Teams deleted by a single user
description: |
  'This detection flags the occurrences of deleting multiple teams within an hour.
  This data is a part of Office 365 Connector in Microsoft Sentinel.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity (Teams)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1485
  - T1489
query: |
  // Adjust this value to change how many Teams should be deleted before including
  let max_delete_count = 3;
  // Adjust this value to change the timewindow the query runs over
    OfficeActivity
  | where OfficeWorkload =~ "MicrosoftTeams"
  | where Operation =~ "TeamDeleted"
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(TeamName, 1000) by UserId
  | where array_length(DeletedTeams) > max_delete_count
  | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserId
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
version: 2.0.4
kind: Scheduled

Stages and Predicates

Parameters

let max_delete_count = 3;

Stage 1: source

OfficeActivity

Stage 2: where

| where OfficeWorkload =~ "MicrosoftTeams"

Stage 3: where

| where Operation =~ "TeamDeleted"

Stage 4: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(TeamName, 1000) by UserId

Stage 5: where

| where array_length(DeletedTeams) > max_delete_count

Stage 6: extend

| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeletedTeamssummarize
EndTimesummarize
StartTimesummarize
UserIdsummarize
AccountNameextend
AccountUPNSuffixextend

NRT Squid proxy events related to mining pools

#
Severity
low
Source
github.com/Azure/Azure-Sentinel

'Checks for Squid proxy events in Syslog associated with common mining pools .This query presumes the default Squid log format is being used. http://www.squid-cache.org/Doc/config/access_log/'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: dd03057e-4347-4853-bf1e-2b2d21eb4e59
name: NRT Squid proxy events related to mining pools
description: |
  'Checks for Squid proxy events in Syslog associated with common mining pools .This query presumes the default Squid log format is being used.
   http://www.squid-cache.org/Doc/config/access_log/'
severity: Low
requiredDataConnectors:
  - connectorId: Syslog
    dataTypes:
      - Syslog
  - connectorId: SyslogAma
    dataTypes: 
      - Syslog
tactics:
  - CommandAndControl
relevantTechniques:
  - T1102
query: |
  let DomainList = dynamic(["monerohash.com", "do-dear.com", "xmrminerpro.com", "secumine.net", "xmrpool.com", "minexmr.org", "hashanywhere.com", "xmrget.com",
  "mininglottery.eu", "minergate.com", "moriaxmr.com", "multipooler.com", "moneropools.com", "xmrpool.eu", "coolmining.club", "supportxmr.com",
  "minexmr.com", "hashvault.pro", "xmrpool.net", "crypto-pool.fr", "xmr.pt", "miner.rocks", "walpool.com", "herominers.com", "gntl.co.uk", "semipool.com",
  "coinfoundry.org", "cryptoknight.cc", "fairhash.org", "baikalmine.com", "tubepool.xyz", "fairpool.xyz", "asiapool.io", "coinpoolit.webhop.me", "nanopool.org",
  "moneropool.com", "miner.center", "prohash.net", "poolto.be", "cryptoescrow.eu", "monerominers.net", "cryptonotepool.org", "extrmepool.org", "webcoin.me",
  "kippo.eu", "hashinvest.ws", "monero.farm", "supportxmr.com", "xmrpool.eu", "linux-repository-updates.com", "1gh.com", "dwarfpool.com", "hash-to-coins.com",
  "hashvault.pro", "pool-proxy.com", "hashfor.cash", "fairpool.cloud", "litecoinpool.org", "mineshaft.ml", "abcxyz.stream", "moneropool.ru", "cryptonotepool.org.uk",
  "extremepool.org", "extremehash.com", "hashinvest.net", "unipool.pro", "crypto-pools.org", "monero.net", "backup-pool.com", "mooo.com", "freeyy.me", "cryptonight.net",
  "shscrypto.net"]);
  Syslog
  | where ProcessName contains "squid"
  | extend URL = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :]*)",3,SyslogMessage),
          SourceIP = extract("([0-9]+ )(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3}))",2,SyslogMessage),
          Status = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))",1,SyslogMessage),
          HTTP_Status_Code = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))/([0-9]{3})",8,SyslogMessage),
          User = extract("(CONNECT |GET )([^ ]* )([^ ]+)",3,SyslogMessage),
          RemotePort = extract("(CONNECT |GET )([^ ]*)(:)([0-9]*)",4,SyslogMessage),
          Domain = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :\\/]*)",3,SyslogMessage),
          Bytes = toint(extract("([A-Z]+\\/[0-9]{3} )([0-9]+)",2,SyslogMessage)),
          contentType = extract("([a-z/]+$)",1,SyslogMessage)
  | extend TLD = extract("\\.[a-z]*$",0,Domain)
  | where HTTP_Status_Code == '200'
  | where Domain contains "."
  | where Domain has_any (DomainList)
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URL
version: 1.0.1
kind: NRT

Stages and Predicates

Let Binding: DomainList used in Stage 7

let DomainList = dynamic(["monerohash.com", "do-dear.com", "xmrminerpro.com", "secumine.net", "xmrpool.com", "minexmr.org", "hashanywhere.com", "xmrget.com",
"mininglottery.eu", "minergate.com", "moriaxmr.com", "multipooler.com", "moneropools.com", "xmrpool.eu", "coolmining.club", "supportxmr.com",
"minexmr.com", "hashvault.pro", "xmrpool.net", "crypto-pool.fr", "xmr.pt", "miner.rocks", "walpool.com", "herominers.com", "gntl.co.uk", "semipool.com",
"coinfoundry.org", "cryptoknight.cc", "fairhash.org", "baikalmine.com", "tubepool.xyz", "fairpool.xyz", "asiapool.io", "coinpoolit.webhop.me", "nanopool.org",
"moneropool.com", "miner.center", "prohash.net", "poolto.be", "cryptoescrow.eu", "monerominers.net", "cryptonotepool.org", "extrmepool.org", "webcoin.me",
"kippo.eu", "hashinvest.ws", "monero.farm", "supportxmr.com", "xmrpool.eu", "linux-repository-updates.com", "1gh.com", "dwarfpool.com", "hash-to-coins.com",
"hashvault.pro", "pool-proxy.com", "hashfor.cash", "fairpool.cloud", "litecoinpool.org", "mineshaft.ml", "abcxyz.stream", "moneropool.ru", "cryptonotepool.org.uk",
"extremepool.org", "extremehash.com", "hashinvest.net", "unipool.pro", "crypto-pools.org", "monero.net", "backup-pool.com", "mooo.com", "freeyy.me", "cryptonight.net",
"shscrypto.net"]);

Stage 1: source

Syslog

Stage 2: where

| where ProcessName contains "squid"

Stage 3: extend

| extend URL = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :]*)",3,SyslogMessage),
        SourceIP = extract("([0-9]+ )(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3}))",2,SyslogMessage),
        Status = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))",1,SyslogMessage),
        HTTP_Status_Code = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))/([0-9]{3})",8,SyslogMessage),
        User = extract("(CONNECT |GET )([^ ]* )([^ ]+)",3,SyslogMessage),
        RemotePort = extract("(CONNECT |GET )([^ ]*)(:)([0-9]*)",4,SyslogMessage),
        Domain = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :\\/]*)",3,SyslogMessage),
        Bytes = toint(extract("([A-Z]+\\/[0-9]{3} )([0-9]+)",2,SyslogMessage)),
        contentType = extract("([a-z/]+$)",1,SyslogMessage)

Stage 4: extend

| extend TLD = extract("\\.[a-z]*$",0,Domain)

Stage 5: where

| where HTTP_Status_Code == '200'

Stage 6: where

| where Domain contains "."

Stage 7: where

| where Domain has_any (DomainList)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Domaincontains
  • .
field:"Domain" kind:contains value:"."
Domainmatch
  • 1gh.com transforms: term
  • abcxyz.stream transforms: term
  • asiapool.io transforms: term
  • backup-pool.com transforms: term
  • baikalmine.com transforms: term
  • coinfoundry.org transforms: term
  • coinpoolit.webhop.me transforms: term
  • coolmining.club transforms: term
  • crypto-pool.fr transforms: term
  • crypto-pools.org transforms: term
  • cryptoescrow.eu transforms: term
  • cryptoknight.cc transforms: term
  • cryptonight.net transforms: term
  • cryptonotepool.org transforms: term
  • cryptonotepool.org.uk transforms: term
  • do-dear.com transforms: term
  • dwarfpool.com transforms: term
  • extremehash.com transforms: term
  • extremepool.org transforms: term
  • extrmepool.org transforms: term
  • fairhash.org transforms: term
  • fairpool.cloud transforms: term
  • fairpool.xyz transforms: term
  • freeyy.me transforms: term
  • gntl.co.uk transforms: term
  • hash-to-coins.com transforms: term
  • hashanywhere.com transforms: term
  • hashfor.cash transforms: term
  • hashinvest.net transforms: term
  • hashinvest.ws transforms: term
  • hashvault.pro transforms: term
  • herominers.com transforms: term
  • kippo.eu transforms: term
  • linux-repository-updates.com transforms: term
  • litecoinpool.org transforms: term
  • miner.center transforms: term
  • miner.rocks transforms: term
  • minergate.com transforms: term
  • mineshaft.ml transforms: term
  • minexmr.com transforms: term
  • +30 more values (see full rule source)
field:"Domain" kind:match
HTTP_Status_Codeeq
  • 200
field:"HTTP_Status_Code" kind:eq value:"200"
ProcessNamecontains
  • squid
field:"process_name" kind:contains value:"squid"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Bytesextend
Domainextend
HTTP_Status_Codeextend
RemotePortextend
SourceIPextend
Statusextend
URLextend
Userextend
contentTypeextend
TLDextend

Process executed from binary hidden in Base64 encoded file

#
Status
available
Severity
medium
Time window
1d
Group by
Account, AccountDomain, CommandLine, Computer, FileName, ParentProcessName
Source
github.com/Azure/Azure-Sentinel

Encoding malicious software is a technique used to obfuscate files from detection. The first CommandLine component is looking for Python decoding base64. The second CommandLine component is looking for Bash/sh command line base64 decoding. The third one is looking for Ruby decoding base64.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: d6190dde-8fd2-456a-ac5b-0a32400b0464
name: Process executed from binary hidden in Base64 encoded file
description: |
  'Encoding malicious software is a technique used to obfuscate files from detection. 
  The first CommandLine component is looking for Python decoding base64. 
  The second CommandLine component is looking for Bash/sh command line base64 decoding.
  The third one is looking for Ruby decoding base64.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes: 
      - WindowsEvent 
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - DefenseEvasion
relevantTechniques:
  - T1059
  - T1027
  - T1140
query: |
  let ProcessCreationEvents=(union isfuzzy=true
  (SecurityEvent
  | where EventID==4688
  | where isnotempty(CommandLine)
  | project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, FileName = Process, CommandLine, ParentProcessName
  ),
  (WindowsEvent
  | where EventID==4688
  | where EventData has_any (".decode('base64')", "base64 --decode", ".decode64(" )
  | extend CommandLine = tostring(EventData.CommandLine)
  | where isnotempty(CommandLine)
  | extend SubjectUserName = tostring(EventData.SubjectUserName)
  | extend SubjectDomainName = tostring(EventData.SubjectDomainName)
  | extend NewProcessName = tostring(EventData.NewProcessName)
  | extend FileName=tostring(split(NewProcessName, '\\')[-1])
  | extend ParentProcessName = tostring(EventData.ParentProcessName)
  | project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, CommandLine, ParentProcessName
  ));
  ProcessCreationEvents 
  | where CommandLine contains ".decode('base64')"
          or CommandLine contains "base64 --decode"
          or CommandLine contains ".decode64(" 
  | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), CountToday = count() by Computer, Account, AccountDomain, FileName, CommandLine, ParentProcessName
  | extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Account
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.1.4
kind: Scheduled

Stages and Predicates

Stage 0: let

let ProcessCreationEvents = (union <inlined as stages below>;

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 EventID==4688
| where isnotempty(CommandLine)
| project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, FileName = Process, CommandLine, ParentProcessName

Leg 2: WindowsEvent

WindowsEvent
| where EventID==4688
| where EventData has_any (".decode('base64')", "base64 --decode", ".decode64(" )
| extend CommandLine = tostring(EventData.CommandLine)
| where isnotempty(CommandLine)
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend FileName=tostring(split(NewProcessName, '\\')[-1])
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, CommandLine, ParentProcessName

Applied to the Combined Result

| where CommandLine contains ".decode('base64')"
        or CommandLine contains "base64 --decode"
        or CommandLine contains ".decode64(" | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), CountToday = count() by Computer, Account, AccountDomain, FileName, CommandLine, ParentProcessName | extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CommandLinecontains
  • .decode('base64')
  • .decode64(
  • base64 --decode
field:"CommandLine" kind:contains
CommandLineis_not_null
  • (no value, null check)
field:"CommandLine" kind:is_not_null
EventDatamatch
  • .decode('base64') transforms: term
  • .decode64( transforms: term
  • base64 --decode transforms: term
field:"EventData" kind:match
EventIDeq
  • 4688
field:"EventID" kind:eq value:"4688"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountsummarize
AccountDomainsummarize
CommandLinesummarize
Computersummarize
CountTodaysummarize
EndTimeUtcsummarize
FileNamesummarize
ParentProcessNamesummarize
StartTimeUtcsummarize
DnsDomainextend
HostNameextend

Tape Media Vault Deleted

#
Status
available
Severity
informational
Time window
1d
Source
github.com/Azure/Azure-Sentinel

Detects when a tape media vault is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.

Rule Body

id: 19ed623e-1b1c-45e0-ac74-c1e629fbf117
name: Tape Media Vault Deleted
description: Detects when a tape media vault is deleted from Veeam Backup & Replication.
  This might indicate unauthorized removal of critical components.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 23631\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 == 23631

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

Tenable.ad Password Guessing

#
Severity
high
Time window
2h
Source
github.com/Azure/Azure-Sentinel

'Searches for bruteforce Password Guessing attacks.'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 44d74560-0cd1-4e73-a8f5-d16eeeba219e
name: Tenable.ad Password Guessing
description: |
    'Searches for bruteforce Password Guessing attacks.'
severity: High
requiredDataConnectors:
  - connectorId: Tenable.ad
    dataTypes:
      - Tenable_ad_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  // For the query to work properly, make sure you have imported the afad_parser.kql parser into the workspace
  // Retrieve the parser here: https://raw.githubusercontent.com/tenable/Azure-Sentinel/Tenable.ad-connector/Solutions/TenableAD/Parsers/afad_parser.kql
  // Then, create the Kusto Function with alias afad_parser
  afad_parser
    | where MessageType == 2 and Codename == "Password Guessing"
    | extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

afad_parser

Stage 2: where

| where MessageType == 2 and Codename == "Password Guessing"

Stage 3: extend

| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Codenameeq
  • Password Guessing corpus 3 (kusto 3)
field:"Codename" kind:eq value:"Password Guessing"
MessageTypeeq
  • 2 corpus 21 (kusto 21)
field:"MessageType" kind:eq value:"2"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DnsDomainextend
HostNameextend

TI map IP entity to AzureFirewall

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

Identifies a match in AzureFirewall (NetworkRule & ApplicationRule Logs) from any IP IOC from TI

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 0b904747-1336-4363-8d84-df2710bfe5e7
name: TI map IP entity to AzureFirewall
description: |
  Identifies a match in AzureFirewall (NetworkRule & ApplicationRule Logs) from any IP IOC from TI
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: AzureFirewall
    dataTypes: 
      - AzureDiagnostics
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h; // Look back 1 hour for AzureDiagnostics logs
  let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
  // Fetch threat intelligence indicators related to IP addresses
  let IP_Indicators = ThreatIntelligenceIndicator
    // Filter out indicators without relevant IP address fields
    | where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
    | where TimeGenerated >= ago(ioc_lookBack)
    // Select the IP entity based on availability of different IP fields
    | extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
    | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
    | extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
    // Exclude local addresses using the ipv4_is_private operator and filtering out specific address prefixes
    | 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();
  // Perform a join between IP indicators and AzureDiagnostics logs to identify potential malicious activity
  IP_Indicators
    // Use innerunique to keep performance fast and result set low, as we only need one match to indicate potential malicious activity that needs investigation
    | join kind=innerunique (
        AzureDiagnostics
        | where TimeGenerated >= ago(dt_lookBack)
        | where OperationName in ("AzureFirewallApplicationRuleLog", "AzureFirewallNetworkRuleLog")
        | parse kind=regex flags=U msg_s with Protocol 'request from ' SourceHost 'to ' DestinationHost @'\.? Action: ' Firewall_Action @'\.' Rest_msg
        | extend SourceAddress = extract(@'([\.0-9]+)(:[\.0-9]+)?', 1, SourceHost)
        | extend DestinationAddress = extract(@'([\.0-9]+)(:[\.0-9]+)?', 1, DestinationHost)
        | extend RemoteIP = case(not(ipv4_is_private(DestinationAddress)), DestinationAddress, not(ipv4_is_private(SourceAddress)), SourceAddress, "")
        | where isnotempty(RemoteIP) // Filter out traffic involving public addresses only
        | project-rename AzureFirewall_TimeGenerated = TimeGenerated
    )
    on $left.TI_ipEntity == $right.RemoteIP
    // Filter out logs that occurred after the expiration of the corresponding indicator
    | where AzureFirewall_TimeGenerated < ExpirationDateTime
    // Group the results by IndicatorId and RemoteIP, and keep the log entry with the latest timestamp
    | summarize AzureFirewall_TimeGenerated = arg_max(AzureFirewall_TimeGenerated, *) by IndicatorId, RemoteIP
    // Select the desired output fields
    | project LatestIndicatorTime, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, DomainName, ExpirationDateTime, ConfidenceScore,
      AzureFirewall_TimeGenerated, TI_ipEntity, Resource, Category, msg_s, SourceAddress, DestinationAddress, Firewall_Action, Protocol,
      NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, Type
    // Rename the timestamp field
    | extend timestamp = AzureFirewall_TimeGenerated
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: TI_ipEntity
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.3.2
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;

let IP_Indicators is inlined into the numbered stages below.

Stages 1 to 7 define let IP_Indicators as the rule's main pipeline source. Stages 8 to 12 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)

Stage 3: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 4: extend (3 consecutive steps)

| extend TI_ipEntity = iff(isnotempty(NetworkIP), NetworkIP, NetworkDestinationIP)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(NetworkSourceIP), NetworkSourceIP, TI_ipEntity)
| extend TI_ipEntity = iff(isempty(TI_ipEntity) and isnotempty(EmailSourceIpAddress), EmailSourceIpAddress, TI_ipEntity)
TI_ipEntity =
ifisnotempty(NetworkIP)NetworkIP
elseNetworkDestinationIP

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_Indicators
| join kind=innerunique (
      AzureDiagnostics
      | where TimeGenerated >= ago(dt_lookBack)
      | where OperationName in ("AzureFirewallApplicationRuleLog", "AzureFirewallNetworkRuleLog")
      | parse kind=regex flags=U msg_s with Protocol 'request from ' SourceHost 'to ' DestinationHost @'\.? Action: ' Firewall_Action @'\.' Rest_msg
      | extend SourceAddress = extract(@'([\.0-9]+)(:[\.0-9]+)?', 1, SourceHost)
      | extend DestinationAddress = extract(@'([\.0-9]+)(:[\.0-9]+)?', 1, DestinationHost)
      | extend RemoteIP = case(not(ipv4_is_private(DestinationAddress)), DestinationAddress, not(ipv4_is_private(SourceAddress)), SourceAddress, "")
      | where isnotempty(RemoteIP)
      | project-rename AzureFirewall_TimeGenerated = TimeGenerated
  )
  on $left.TI_ipEntity == $right.RemoteIP

Stage 9: where

where AzureFirewall_TimeGenerated < ExpirationDateTime

Stage 10: summarize

summarize AzureFirewall_TimeGenerated by IndicatorId, RemoteIP

Stage 11: project

project ActivityGroupNames, AzureFirewall_TimeGenerated, Category, ConfidenceScore, Description, DestinationAddress, DomainName, EmailSourceIpAddress, ExpirationDateTime, Firewall_Action, IndicatorId, LatestIndicatorTime, NetworkDestinationIP, NetworkIP, NetworkSourceIP, Protocol, Resource, SourceAddress, TI_ipEntity, ThreatType, Type, Url, msg_s

Stage 12: extend

extend timestamp

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Activeeq
  • true
field:"Active" kind:eq value:"true"
EmailSourceIpAddressis_not_null
  • (no value, null check)
field:"EmailSourceIpAddress" kind:is_not_null
NetworkDestinationIPis_not_null
  • (no value, null check)
field:"NetworkDestinationIP" kind:is_not_null
NetworkIPis_not_null
  • (no value, null check)
field:"NetworkIP" kind:is_not_null
NetworkSourceIPis_not_null
  • (no value, null check)
field:"NetworkSourceIP" kind:is_not_null
OperationNamein
  • AzureFirewallApplicationRuleLog
  • AzureFirewallNetworkRuleLog
field:"OperationName" kind:in
RemoteIPis_not_null
  • (no value, null check)
field:"RemoteIP" kind:is_not_null

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
AzureFirewall_TimeGeneratedproject
Categoryproject
ConfidenceScoreproject
Descriptionproject
DestinationAddressproject
DomainNameproject
EmailSourceIpAddressproject
ExpirationDateTimeproject
Firewall_Actionproject
IndicatorIdproject
LatestIndicatorTimeproject
NetworkDestinationIPproject
NetworkIPproject
NetworkSourceIPproject
Protocolproject
Resourceproject
SourceAddressproject
TI_ipEntityproject
ThreatTypeproject
Typeproject
Urlproject
msg_sproject
timestampextend

Whisper Security - Co-Hosted Malware Cluster Detection

#
Status
available
Severity
high
Time window
1d
Group by
cohostedCount, countries, indicator, parsedIp, registrar
Source
github.com/Azure/Azure-Sentinel

Identifies IP addresses that host multiple domains associated with malware. When an IP has more than 3 co-hosted domains and at least 3 of those domains are flagged as malware by Whisper threat intelligence, it strongly indicates compromised or adversary-controlled infrastructure.

MITRE ATT&CK Coverage

TacticTechniques
Resource Development

Rule Body

id: 374a77f4-23ed-55dc-8441-8e47a1e079e9
name: Whisper Security - Co-Hosted Malware Cluster Detection
description: |
  Identifies IP addresses that host multiple domains associated with malware. When an IP has more than 3 co-hosted domains and at least 3 of those domains are flagged as malware by Whisper threat intelligence, it strongly indicates compromised or adversary-controlled infrastructure.
severity: High
status: Available
requiredDataConnectors:
- connectorId: WhisperSecurityConnector
  dataTypes:
  - WhisperInfraContext_CL
  - WhisperThreatIntel_CL
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- ResourceDevelopment
relevantTechniques:
- T1584
query: |
  // MITRE ATT&CK: T1584.001 - Compromise Infrastructure: Domains
  // Tactic: Resource Development
  // Detects clusters of malware-hosting domains sharing the same IP infrastructure
  let minCohostedCount = 3;
  let minMalwareCoHosts = 3;
  let cohostedInfra = WhisperInfraContext_CL
      | where TimeGenerated > ago(1d)
      | where cohostedCount > minCohostedCount
      | extend parsedIp = tostring(split(ipAddresses, ",")[0])
      | project indicator, parsedIp, cohostedCount, countries, registrar;
  let malwareIndicators = WhisperThreatIntel_CL
      | where TimeGenerated > ago(1d)
      | where isMalware == true
      | project indicator, threatScore, threatLevel, feedNames;
  cohostedInfra
      | join kind=inner (malwareIndicators) on indicator
      | summarize malwareCoHostCount = dcount(indicator), DnsDomains = make_set(indicator, 10), avgThreatScore = avg(threatScore), maxThreatScore = max(threatScore) by parsedIp, cohostedCount, countries, registrar
      | where malwareCoHostCount >= minMalwareCoHosts
      | extend DnsDomain = tostring(DnsDomains[0]), IPAddress = parsedIp
      | project TimeGenerated = now(), DnsDomain, IPAddress, malwareCoHostCount, cohostedCount, avgThreatScore, maxThreatScore, countries, registrar, DnsDomains
entityMappings:
- entityType: DNS
  fieldMappings:
  - identifier: DomainName
    columnName: DnsDomain
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: IPAddress
customDetails:
  MalwareCoHostCount: malwareCoHostCount
  CoHostedCount: cohostedCount
  MaxThreatScore: maxThreatScore
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let minCohostedCount = 3;
let minMalwareCoHosts = 3;

let cohostedInfra is inlined into the numbered stages below.

Let Binding: malwareIndicators used in Stage 6

let malwareIndicators = WhisperThreatIntel_CL
    | where TimeGenerated > ago(1d)
    | where isMalware == true
    | project indicator, threatScore, threatLevel, feedNames;

Stages 1 to 5 define let cohostedInfra as the rule's main pipeline source. Stages 6 to 10 use it.

Stage 1: source

WhisperInfraContext_CL

Stage 2: where

| where TimeGenerated > ago(1d)

Stage 3: where

| where cohostedCount > minCohostedCount

Stage 4: extend

| extend parsedIp = tostring(split(ipAddresses, ",")[0])

Stage 5: project

| project indicator, parsedIp, cohostedCount, countries, registrar

Stage 6: join

cohostedInfra
| join kind=inner (malwareIndicators) on indicator

Stage 7: summarize

summarize DnsDomains, avgThreatScore, malwareCoHostCount, maxThreatScore by parsedIp, cohostedCount, countries, registrar

Stage 8: where

where malwareCoHostCount >= 3

Stage 9: extend

extend DnsDomain, IPAddress

Stage 10: project

project DnsDomain, DnsDomains, IPAddress, TimeGenerated, avgThreatScore, cohostedCount, countries, malwareCoHostCount, maxThreatScore, registrar

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DnsDomainproject
DnsDomainsproject
IPAddressproject
TimeGeneratedproject
avgThreatScoreproject
cohostedCountproject
countriesproject
malwareCoHostCountproject
maxThreatScoreproject
registrarproject

Back to all Kusto rules