Detection rules › Kusto

Kusto rules: 1q

Alarming number of anomalies generated in NetBackup

#

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
15m
Source
github.com/Azure/Azure-Sentinel

This rule generates an incident when an alarming number of anomalies are generated in the last 15 minutes.

MITRE ATT&CK Coverage

TacticTechniques
Credential AccessNo specific technique
DiscoveryNo specific technique

Rule Body

id: 2e0efcd4-56d2-41df-9098-d6898a58c62b
name: Alarming number of anomalies generated in NetBackup
description: This rule generates an incident when an alarming number of anomalies are generated in the last 15 minutes.
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
    - Discovery
    - CredentialAccess
techniques:
    - T1110
    - T1212
query: "NetBackupAlerts_CL\r\n| where Category contains \"ANOMALY_NEW\"\r\n| extend\
  \ client =  split(Message, \"client '\")[1]\r\n| extend clientName = split(client,\
  \ \"'\")[0]\r\n| summarize Total=count() by tostring(clientName)\r\n| where\
  \ Total >= 10"
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: clientName
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
    reopenClosedIncident: false
suppressionDuration: PT5H
suppressionEnabled: false
eventGroupingSettings:
  aggregationKind: SingleAlert
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

NetBackupAlerts_CL

Stage 2: where

| where Category contains "ANOMALY_NEW"

Stage 3: extend

| extend client =  split(Message, "client '")[1]

Stage 4: extend

| extend clientName = split(client, "'")[0]

Stage 5: summarize

| summarize Total=count() by tostring(clientName)

Stage 6: where

| where Total >= 10

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Totalsummarize

Authentications of Privileged Accounts Outside of Expected Controls

#
Severity
medium
Time window
7d
Group by
AccountUPN
Author
Pete Bryan
Source
github.com/Azure/Azure-Sentinel

Detects when a privileged user account successfully authenticates from a location, device or ASN that another admin has not logged in from in the last 7 days. Privileged accounts are a key target for threat actors, monitoring for logins from these accounts that deviate from normal activity can help identify compromised accounts. Authentication attempts should be investigated to ensure the activity was legitimate and if there is other similar activity. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-successful-unusual-sign-ins

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: af435ca1-fb70-4de1-92c1-7435c48482a9
name: Authentications of Privileged Accounts Outside of Expected Controls
description: |
  'Detects when a privileged user account successfully authenticates from a location, device or ASN that another admin has not logged in from in the last 7 days.
    Privileged accounts are a key target for threat actors, monitoring for logins from these accounts that deviate from normal activity can help identify compromised accounts.
    Authentication attempts should be investigated to ensure the activity was legitimate and if there is other similar activity.
    Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-user-accounts#monitoring-for-successful-unusual-sign-ins'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: BehaviorAnalytics
    dataTypes:
      - BehaviorAnalytics
  - connectorId: BehaviorAnalytics
    dataTypes:
      - IdentityInfo
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
  let admin_users = (IdentityInfo
    | summarize arg_max(TimeGenerated, *) by AccountUPN
    | where AssignedRoles contains "admin"
    | summarize by tolower(AccountUPN));
    let admin_asn = (SigninLogs
    | where TimeGenerated between (ago(7d)..ago(1d))
    | where tolower(UserPrincipalName) in (admin_users)
    | summarize by AutonomousSystemNumber);
    let admin_locations = (SigninLogs
    | where TimeGenerated between (ago(7d)..ago(1d))
    | where tolower(UserPrincipalName) in (admin_users)
    | summarize by Location);
    let admin_devices = (SigninLogs
    | where TimeGenerated between (ago(7d)..ago(1d))
    | where tolower(UserPrincipalName) in (admin_users)
    | extend deviceId = tostring(DeviceDetail.deviceId)
    | where isnotempty(deviceId)
    | summarize by deviceId);
    SigninLogs
    | where TimeGenerated > ago(1d)
    | where ResultType == 0
    | where tolower(UserPrincipalName) in (admin_users)
    | extend deviceId = tostring(DeviceDetail.deviceId)
    | where AutonomousSystemNumber !in (admin_asn) and deviceId !in (admin_devices) and Location !in (admin_locations)
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
version: 1.0.2
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Pete Bryan
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Let Binding: admin_users used in Stages 1, 5

let admin_users = (IdentityInfo
  | summarize arg_max(TimeGenerated, *) by AccountUPN
  | where AssignedRoles contains "admin"
  | summarize by tolower(AccountUPN));

Let Binding: admin_asn used in Stage 7

let admin_asn = (SigninLogs
  | where TimeGenerated between (ago(7d)..ago(1d))
  | where tolower(UserPrincipalName) in (admin_users)
  | summarize by AutonomousSystemNumber);

Let Binding: admin_locations used in Stage 7

let admin_locations = (SigninLogs
  | where TimeGenerated between (ago(7d)..ago(1d))
  | where tolower(UserPrincipalName) in (admin_users)
  | summarize by Location);

Let Binding: admin_devices used in Stage 7

let admin_devices = (SigninLogs
  | where TimeGenerated between (ago(7d)..ago(1d))
  | where tolower(UserPrincipalName) in (admin_users)
  | extend deviceId = tostring(DeviceDetail.deviceId)
  | where isnotempty(deviceId)
  | summarize by deviceId);

Stage 1: source

let admin_users

Stage 2: source

SigninLogs

Stage 3: where

where TimeGenerated > ago(86400s)

Stage 4: where

where ResultType == 0

Stage 5: where

where UserPrincipalName == "admin_users"

Stage 6: extend

extend deviceId

Stage 7: where

where not (AutonomousSystemNumber == "admin_asn") and not (Location == "admin_locations") and not (deviceId == "admin_devices")

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
deviceIdextend

Changes to PIM Settings

#
Severity
high
Time window
1d
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

PIM provides a key mechanism for assigning privileges to accounts, this query detects changes to PIM role settings. Monitor these changes to ensure they are being made legitimately and don't confer more privileges than expected or reduce the security of a PIM elevation. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#changes-to-privileged-accounts

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

PlatformRecord / event type
AzureUpdate role setting in PIM

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 0ed0fe7c-af29-4990-af7f-bb5ccb231198
name: Changes to PIM Settings
description: |
  'PIM provides a key mechanism for assigning privileges to accounts, this query detects changes to PIM role settings.
    Monitor these changes to ensure they are being made legitimately and don't confer more privileges than expected or reduce the security of a PIM elevation.
    Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#changes-to-privileged-accounts'
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
  AuditLogs
    | where Category =~ "RoleManagement"
    | where OperationName =~ "Update role setting in PIM"
    | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
    | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
    | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
    | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
    | project-reorder TimeGenerated, OperationName, ResultReason, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, InitiatingAccountName, InitiatingAccountUPNSuffix
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIPAddress
version: 1.1.1
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where Category =~ "RoleManagement"

Stage 3: where

| where OperationName =~ "Update role setting in PIM"

Stage 4: extend (4 consecutive steps)

| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])

Stage 5: project-reorder

| project-reorder TimeGenerated, OperationName, ResultReason, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIPAddress, InitiatingAccountName, InitiatingAccountUPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Categoryeq
  • RoleManagement corpus 17 (kusto 16, sigma 1)
field:"Category" kind:eq value:"RoleManagement"
OperationNameeq
  • Update role setting in PIM
field:"OperationName" kind:eq value:"Update role setting in PIM"

Output Fields

These fields are emitted when the rule matches.

FieldSource
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIPAddressextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend

Conditional Access - A Conditional Access policy was put into report-only mode

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

A Conditional Access policy was put into report-only mode in Entra ID.

MITRE ATT&CK Coverage

Telemetry Coverage

PlatformRecord / event type
AzureUpdate conditional access policy

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 5588de32-73b1-40b9-bddc-4d9e74051859
name: Conditional Access - A Conditional Access policy was put into report-only mode
version: 1.0.1
kind: Scheduled
description: A Conditional Access policy was put into report-only mode in Entra ID.
severity: Low
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
  - T1562.007
query: |+
  // A Conditional Access policy was put into report-only mode.
  AuditLogs
  | where OperationName in ("Update conditional access policy")
  | extend stateOld = extractjson("$.state", tostring(TargetResources[0].modifiedProperties[0].oldValue))
  | extend stateNew = extractjson("$.state", tostring(TargetResources[0].modifiedProperties[0].newValue))
  | where stateOld == "enabled" and stateNew == "enabledForReportingButNotEnforced"
  | extend modifiedBy = tostring(InitiatedBy.user.userPrincipalName)
  | extend accountName = tostring(split(modifiedBy, "@")[0])
  | extend upnSuffix = tostring(split(modifiedBy, "@")[1])
  | project
      TimeGenerated,
      OperationName,
      policy = TargetResources[0].displayName,
      modifiedBy,
      accountName,
      upnSuffix,
      result = Result,
      stateOld,
      stateNew
  | order by TimeGenerated desc
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: Name
    columnName: accountName
  - identifier: UPNSuffix
    columnName: upnSuffix
suppressionEnabled: false
suppressionDuration: 5h
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5M
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName in ("Update conditional access policy")

Stage 3: extend

| extend stateOld = extractjson("$.state", tostring(TargetResources[0].modifiedProperties[0].oldValue))

Stage 4: extend

| extend stateNew = extractjson("$.state", tostring(TargetResources[0].modifiedProperties[0].newValue))

Stage 5: where

| where stateOld == "enabled" and stateNew == "enabledForReportingButNotEnforced"

Stage 6: extend (3 consecutive steps)

| extend modifiedBy = tostring(InitiatedBy.user.userPrincipalName)
| extend accountName = tostring(split(modifiedBy, "@")[0])
| extend upnSuffix = tostring(split(modifiedBy, "@")[1])

Stage 7: project

| project
    TimeGenerated,
    OperationName,
    policy = TargetResources[0].displayName,
    modifiedBy,
    accountName,
    upnSuffix,
    result = Result,
    stateOld,
    stateNew

Stage 8: sort

| order by TimeGenerated desc

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
OperationNameproject
TimeGeneratedproject
accountNameproject
modifiedByproject
policyproject
resultproject
stateNewproject
stateOldproject
upnSuffixproject

CybleVision Alerts Telegram Mentions

#

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 mentions of monitored keywords across Telegram channels and groups. Extracts message content, URLs, chat metadata, user identity, and timestamps.'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance
Resource Development
Command & Control

Rule Body

id: 4238f545-8b6f-4f7c-80b5-14cca2cebc99
name: CybleVision Alerts Telegram Mentions
description: |
  'Detects mentions of monitored keywords across Telegram channels and groups. Extracts message content, URLs, chat metadata, user identity, and timestamps.'
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
- InitialAccess
- CommandAndControl
relevantTechniques:
- T1598
- T1585
- T1102
subTechniques: []
query: |
  Alerts_telegram_mentions 
  | where Service == "telegram_mentions" 
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
alertDetailsOverride:
  alertDisplayNameFormat: CybleVision Telegram Mention in {{TG_ChatTitle}}
  alertDescriptionFormat: |
    A Telegram post mentioning monitored keywords was detected. User {{TG_Username2}}. Message {{TG_Message}} URL {{TG_Urls}}\n
  alertDynamicProperties: []
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  ChatID: TG_ChatID
  ChatTitle: TG_ChatTitle
  Message: TG_Message
  User: TG_Username2
  CreatedDate: TG_CreatedDate
  UpdatedDate: TG_UpdatedDate
  URLs: TG_Urls
  Tags: TG_Tags
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: Name
    columnName: TG_Username2
- entityType: URL
  fieldMappings:
  - identifier: Url
    columnName: TG_Urls
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_telegram_mentions

Stage 2: where

| where Service == "telegram_mentions"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

CYFIRMA - Brand Intelligence - Social Media Handle Impersonation Detected Medium Rule

#

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
5m
Source
github.com/Azure/Azure-Sentinel

"Detects high-severity alerts related to impersonation of official social media handles associated with your brand. These spoofed accounts may be used for phishing, disinformation, or fraud campaigns, posing significant reputational and security risks."

MITRE ATT&CK Coverage

Rule Body

id: 710f4755-490d-4fa7-aef0-43b5a66edc7b
name: CYFIRMA - Brand Intelligence - Social Media Handle Impersonation Detected Medium Rule
description: |
  "Detects high-severity alerts related to impersonation of official social media handles associated with your brand. 
  These spoofed accounts may be used for phishing, disinformation, or fraud campaigns, posing significant reputational and security risks."
version: 1.0.1
kind: Scheduled
severity: Medium
requiredDataConnectors:
  - connectorId: CyfirmaBrandIntelligenceAlertsDC
    dataTypes:
      - CyfirmaBISocialHandlersAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - Reconnaissance
  - ResourceDevelopment
  - InitialAccess
relevantTechniques:
  - T1589.003
  - T1591.002
  - T1585.001
  - T1566.002
query: |
  // Medium severity - Social Media Handle Impersonation
  let timeFrame = 5m;
  CyfirmaBISocialHandlersAlerts_CL 
    | where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())
    | extend
        Description=description,
        FirstSeen=first_seen,
        LastSeen=last_seen,
        RiskScore=risk_score,
        AlertUID=alert_uid,
        UID=uid,
        AssetType=asset_type,
        AssetValue=asset_value,
        Impact=impact,
        Recommendation=recommendation,
        SourceSype=source_type,
        ProviderName="CYFIRMA",
        ProductName="DeCYFIR/DeTCT"
    | project 
        TimeGenerated,
        Description,
        RiskScore,
        FirstSeen,
        LastSeen,
        AlertUID,
        UID,
        AssetType,
        AssetValue,
        Impact,
        Recommendation,
        SourceSype,
        ProductName,
        ProviderName
customDetails:
  AssetType: AssetType
  AssetValue: AssetValue
  Impact: Impact
  Recommendation: Recommendation
  SourceSype: SourceSype
  Description: Description
  RiskScore: RiskScore
  TimeGenerated: TimeGenerated
  FirstSeen: FirstSeen
  LastSeen: LastSeen
  AlertUID: AlertUID
  UID: UID
alertDetailsOverride:
  alertDisplayNameFormat: "CYFIRMA - Medium Severity Social Media Handle Impersonation Detected - {{AssetType}}  :  {{AssetValue}} "
  alertDescriptionFormat: "{{Description}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
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

CyfirmaBISocialHandlersAlerts_CL

Stage 2: where

| where severity == 'High' and TimeGenerated between (ago(timeFrame) .. now())

Stage 3: extend

| extend
      Description=description,
      FirstSeen=first_seen,
      LastSeen=last_seen,
      RiskScore=risk_score,
      AlertUID=alert_uid,
      UID=uid,
      AssetType=asset_type,
      AssetValue=asset_value,
      Impact=impact,
      Recommendation=recommendation,
      SourceSype=source_type,
      ProviderName="CYFIRMA",
      ProductName="DeCYFIR/DeTCT"

Stage 4: project

| project 
      TimeGenerated,
      Description,
      RiskScore,
      FirstSeen,
      LastSeen,
      AlertUID,
      UID,
      AssetType,
      AssetValue,
      Impact,
      Recommendation,
      SourceSype,
      ProductName,
      ProviderName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertUIDproject
AssetTypeproject
AssetValueproject
Descriptionproject
FirstSeenproject
Impactproject
LastSeenproject
ProductNameproject
ProviderNameproject
Recommendationproject
RiskScoreproject
SourceSypeproject
TimeGeneratedproject
UIDproject

CYFIRMA - Social and Public Exposure - Source Code Exposure on Public Repositories Rule

#

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

"This rule triggers when CYFIRMA detects source code related to internal or enterprise domains exposed on public platforms like GitHub. Such exposure may lead to intellectual property leakage or help adversaries understand internal systems, increasing the risk of targeted attacks."

MITRE ATT&CK Coverage

Rule Body

id: 42e6f16a-7773-44cc-8668-8f648bd1aa4f
name: CYFIRMA - Social and Public Exposure - Source Code Exposure on Public Repositories Rule
description: |
  "This rule triggers when CYFIRMA detects source code related to internal or enterprise domains exposed on public platforms like GitHub. 
  Such exposure may lead to intellectual property leakage or help adversaries understand internal systems, increasing the risk of targeted attacks."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
  - connectorId: CyfirmaDigitalRiskAlertsConnector
    dataTypes:
      - CyfirmaSPESourceCodeAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - ResourceDevelopment
  - CredentialAccess
  - Discovery
relevantTechniques:
  - T1587.001
  - T1606.001
  - T1082
query: |
  // High severity - Social and Public Exposure - Source Code Exposure on Public Repositories
  let timeFrame = 5m;
  CyfirmaSPESourceCodeAlerts_CL
  | where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
  | extend
      Description=description,
      FirstSeen=first_seen,
      LastSeen=last_seen,
      RiskScore=risk_score,
      AlertUID=alert_uid,
      UID=uid,
      AssetType=asset_type,
      AssetValue=signature,
      Source=source,
      Impact=impact,
      Recommendation=recommendation,
      ProviderName='CYFIRMA',
      ProductName='DeCYFIR/DeTCT',
      AlertTitle=Alert_title
  | project
      TimeGenerated,
      Description,
      RiskScore,
      FirstSeen,
      LastSeen,
      AlertUID,
      UID,
      AssetType,
      AssetValue,
      Impact,
      ProductName,
      ProviderName,
      AlertTitle
customDetails:
  AssetType: AssetType
  AssetValue: AssetValue
  RiskScore: RiskScore
  Description: Description
  FirstSeen: FirstSeen
  LastSeen: LastSeen
  TimeGenerated: TimeGenerated
  AlertUID: AlertUID
  UID: UID
  Impact: Impact
alertDetailsOverride:
  alertDisplayNameFormat: "CYFIRMA High Severity Alert - Source Code Exposure on Public Repositories - {{AlertTitle}} "
  alertDescriptionFormat: "{{Description}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
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

CyfirmaSPESourceCodeAlerts_CL

Stage 2: where

| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())

Stage 3: extend

| extend
    Description=description,
    FirstSeen=first_seen,
    LastSeen=last_seen,
    RiskScore=risk_score,
    AlertUID=alert_uid,
    UID=uid,
    AssetType=asset_type,
    AssetValue=signature,
    Source=source,
    Impact=impact,
    Recommendation=recommendation,
    ProviderName='CYFIRMA',
    ProductName='DeCYFIR/DeTCT',
    AlertTitle=Alert_title

Stage 4: project

| project
    TimeGenerated,
    Description,
    RiskScore,
    FirstSeen,
    LastSeen,
    AlertUID,
    UID,
    AssetType,
    AssetValue,
    Impact,
    ProductName,
    ProviderName,
    AlertTitle

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AlertTitleproject
AlertUIDproject
AssetTypeproject
AssetValueproject
Descriptionproject
FirstSeenproject
Impactproject
LastSeenproject
ProductNameproject
ProviderNameproject
RiskScoreproject
TimeGeneratedproject
UIDproject

DCOM Lateral Movement

#
Status
available
Severity
medium
Time window
1h
Group by
$left.DCOMHostPID, $left.DCOMPID, $right.InitiatingProcessId, $right.InitiatingProcessParentId, DCOMCmdLine, DeviceId, DeviceName, InitiatingProcessId, LastBootTime, LocalIP, RemoteIP, TimestampNetwEventExact, TimestampProcEventExact
Source
github.com/Azure/Azure-Sentinel

This detection looks for cases of close-time proximity between incoming network traffic on RPC/TCP, followed by the creation of a DCOM object, followed by the creation of a child process of the DCOM object. The query first identifies incoming network traffic over RPC/TCP, followed by the creation of a DCOM object (process) within 2 seconds, followed by the creation of a child process of this DCOM object.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: d58035ff-0bac-4c61-a7f4-f58939ff9764
name: DCOM Lateral Movement
description: |
  This detection looks for cases of close-time proximity between incoming network traffic on RPC/TCP, followed by the creation of a DCOM object, followed by the creation of a child process of the DCOM object. 
  The query first identifies incoming network traffic over RPC/TCP, followed by the creation of a DCOM object (process) within 2 seconds, followed by the creation of a child process of this DCOM object. 
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
      - DeviceNetworkEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
relevantTechniques:
  - T1021.003
query: |
  let allowListedExecs = dynamic(["TiWorker.exe", "dllhost.exe", "backgroundTaskHost.exe", "mobsync.exe", "WmiPrvSE.exe", "RuntimeBroker.exe", "smartscreen.exe", "SppExtComObj.Exe", "usocoreworker.exe", "browser_broker.exe", "ssoncom.exe"]);
  let rpcNetwEvents = materialize(DeviceNetworkEvents
  | where InitiatingProcessFileName =~ "svchost.exe" and InitiatingProcessCommandLine has "rpcss" and LocalPort == 135
  | where LocalIP !~ RemoteIP and ActionType != "ListeningConnectionCreated"
  | project TimestampNetwEvent=bin(Timestamp, 5s),TimestampNetwEventExact=Timestamp, DeviceId, DeviceName, RPCHostID=InitiatingProcessId, RPCHostFileName=InitiatingProcessFileName, LocalIP, RemoteIP);
  let dcomProcEvents = materialize (DeviceProcessEvents
  | where InitiatingProcessFileName =~ "svchost.exe" and InitiatingProcessCommandLine has "dcomlaunch"
  | project TimestampProcEvent=bin(Timestamp, 5s),TimestampProcEventExact=Timestamp, DeviceId, DeviceName, DCOMHostPID=InitiatingProcessId, DCOMHostFileName=InitiatingProcessFileName, DCOMPID=ProcessId, DCOMFileName=FileName, DCOMCmdLine=ProcessCommandLine);
  let lastBootTime = materialize(DeviceProcessEvents
  | where FileName =~ "services.exe"
  | summarize LastBootTime=max(Timestamp) by DeviceId);
  let RemoteDcomProcs = materialize(rpcNetwEvents
  | join kind=inner dcomProcEvents on DeviceId 
  | join kind=leftouter lastBootTime on DeviceId
  | where TimestampProcEvent > LastBootTime+5m // Ignore first 5 min after boot.
  // Avoiding < 2 since if the time between netw and proc creation is negative, they can't be related. Network event must come first. 
  | where datetime_diff("second", TimestampProcEventExact, TimestampNetwEventExact) between (0 .. 2) 
  // Allow-listing some usual suspects which create lot of noise. This is dangerous though...huge gap for bypass. 
  | where DCOMFileName  !in~ (allowListedExecs));
  RemoteDcomProcs
  | join kind=inner hint.strategy=broadcast (
      DeviceProcessEvents 
      | where InitiatingProcessParentFileName =~ "svchost.exe" and InitiatingProcessFileName in ((RemoteDcomProcs | project DCOMFileName))) 
  on $left.DCOMHostPID == $right.InitiatingProcessParentId, DeviceId, $left.DCOMPID == $right.InitiatingProcessId
  | where InitiatingProcessParentFileName =~ "svchost.exe" and InitiatingProcessFileName =~ DCOMFileName 
  // Allow-listing the magic of Defender.
  | where FileName !in~ ("csc.exe") 
  | summarize make_set(ProcessCommandLine) by TimestampNetwEventExact, TimestampProcEventExact, DeviceId, DeviceName, InitiatingProcessId, LocalIP, RemoteIP, LastBootTime, DCOMCmdLine
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: RemoteIP 
  - entityType: Process
    fieldMappings:
      - identifier: CommandLine
        columnName: DCOMCmdLine
version: 1.0.0
kind: Scheduled

Stages and Predicates

let rpcNetwEvents and let RemoteDcomProcs are inlined into the numbered stages below.

Let Binding: allowListedExecs

let allowListedExecs = dynamic(["TiWorker.exe", "dllhost.exe", "backgroundTaskHost.exe", "mobsync.exe", "WmiPrvSE.exe", "RuntimeBroker.exe", "smartscreen.exe", "SppExtComObj.Exe", "usocoreworker.exe", "browser_broker.exe", "ssoncom.exe"]);

Let Binding: dcomProcEvents used in Stages 2, 9

let dcomProcEvents = materialize (DeviceProcessEvents
| where InitiatingProcessFileName =~ "svchost.exe" and InitiatingProcessCommandLine has "dcomlaunch"
| project TimestampProcEvent=bin(Timestamp, 5s),TimestampProcEventExact=Timestamp, DeviceId, DeviceName, DCOMHostPID=InitiatingProcessId, DCOMHostFileName=InitiatingProcessFileName, DCOMPID=ProcessId, DCOMFileName=FileName, DCOMCmdLine=ProcessCommandLine);

Let Binding: lastBootTime used in Stages 3, 10

let lastBootTime = materialize(DeviceProcessEvents
| where FileName =~ "services.exe"
| summarize LastBootTime=max(Timestamp) by DeviceId);

Stage 1: source

let rpcNetwEvents

Stage 2: source

let dcomProcEvents

Stage 3: source

let lastBootTime

Stage 4: source

let RemoteDcomProcs

Stage 5: source

DeviceNetworkEvents

Stage 6: where

where InitiatingProcessCommandLine has "rpcss" and InitiatingProcessFileName =~ "svchost.exe" and LocalPort == 135

Stage 7: where

where ActionType != "ListeningConnectionCreated" and LocalIP != RemoteIP

Stage 8: project

project DeviceId, DeviceName, LocalIP, RPCHostFileName, RPCHostID, RemoteIP, TimestampNetwEvent, TimestampNetwEventExact

Stage 9: join

join kind=inner (dcomProcEvents) on DeviceId

Stage 10: join

join kind=leftouter (lastBootTime) on DeviceId

Stage 11: where TimestampProcEvent - LastBootTime > 5m

where TimestampProcEvent - LastBootTime > 5m

Stage 12: where TimestampProcEventExact - TimestampNetwEventExact >= 0s AND TimestampProcEventExact - TimestampNetwEventExact <= 2s

where TimestampProcEventExact - TimestampNetwEventExact >= 0s AND TimestampProcEventExact - TimestampNetwEventExact <= 2s

Stage 13: where

where not (DCOMFileName in~ ("RuntimeBroker.exe", "SppExtComObj.Exe", "TiWorker.exe", "WmiPrvSE.exe", "backgroundTaskHost.exe", "browser_broker.exe", "dllhost.exe", "mobsync.exe", "smartscreen.exe", "ssoncom.exe", "usocoreworker.exe"))

Stage 14: join

join kind=inner (DeviceProcessEvents) on $left.DCOMHostPID, $right.InitiatingProcessParentId, DeviceId, $left.DCOMPID, $right.InitiatingProcessId

Stage 15: where

where InitiatingProcessFileName =~ DCOMFileName and InitiatingProcessParentFileName =~ "svchost.exe"

Stage 16: where

where not (FileName =~ "csc.exe")

Stage 17: summarize

summarize by TimestampNetwEventExact, TimestampProcEventExact, DeviceId, DeviceName, InitiatingProcessId, LocalIP, RemoteIP, LastBootTime, DCOMCmdLine

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DCOMFileNameinRuntimeBroker.exe, SppExtComObj.Exe, TiWorker.exe, WmiPrvSE.exe, backgroundTaskHost.exe, browser_broker.exe, dllhost.exe, mobsync.exe, smartscreen.exe, ssoncom.exe, usocoreworker.exeexcludes:DCOMFileName
FileNameeqcsc.exeexcludes:FileName field:"FileName" value:"csc.exe"

Output Fields

These fields are emitted when the rule matches.

FieldSource
DCOMCmdLinesummarize
DeviceIdsummarize
DeviceNamesummarize
InitiatingProcessIdsummarize
LastBootTimesummarize
LocalIPsummarize
RemoteIPsummarize
TimestampNetwEventExactsummarize
TimestampProcEventExactsummarize

Exchange SSRF Autodiscover ProxyShell - Detection

#
Severity
high
Time window
12h
Author
Thomas McElroy
Source
github.com/Azure/Azure-Sentinel

'This query looks for suspicious request patterns to Exchange servers that fit patterns recently blogged about by PeterJson. This exploitation chain utilises an SSRF vulnerability in Exchange which eventually allows the attacker to execute arbitrary Powershell on the server. In the example powershell can be used to write an email to disk with an encoded attachment containing a shell. Reference: https://peterjson.medium.com/reproducing-the-proxyshell-pwn2own-exploit-49743a4ea9a1'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 968358d6-6af8-49bb-aaa4-187b3067fb95
name: Exchange SSRF Autodiscover ProxyShell - Detection
description: |
  'This query looks for suspicious request patterns to Exchange servers that fit patterns recently blogged about by PeterJson. This exploitation chain utilises an SSRF vulnerability in Exchange which eventually allows the attacker to execute arbitrary Powershell on the server.
  In the example powershell can be used to write an email to disk with an encoded attachment containing a shell.
  Reference: https://peterjson.medium.com/reproducing-the-proxyshell-pwn2own-exploit-49743a4ea9a1'
severity: High
requiredDataConnectors:
  - connectorId: AzureMonitor(IIS)
    dataTypes:
      - W3CIISLog
queryFrequency: 12h
queryPeriod: 12h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
query: |
  let successCodes = dynamic([200, 302, 401]);
  W3CIISLog
  | where scStatus has_any (successCodes)
  | where ipv4_is_private(cIP) == False
  | where csUriStem hasprefix "/autodiscover/autodiscover.json"
  | project TimeGenerated, cIP, sIP, sSiteName, csUriStem, csUriQuery, Computer, csUserName, _ResourceId, FileUri
  | where (csUriQuery !has "Protocol" and isnotempty(csUriQuery))
  or (csUriQuery has_any("/mapi/", "powershell"))
  or (csUriQuery contains "@" and csUriQuery matches regex @"\.[a-zA-Z]{2,4}?(?:[a-zA-Z]{2,4}\/)")
  or (csUriQuery contains ":" and csUriQuery matches regex @"\:[0-9]{2,4}\/")
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend AccountName = tostring(split(csUserName, "@")[0]), AccountUPNSuffix = tostring(split(csUserName, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: csUserName
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix  
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: cIP
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: _ResourceId
version: 1.0.3
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Thomas McElroy
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let successCodes = dynamic([200, 302, 401]);

Stage 1: source

W3CIISLog

Stage 2: where

| where scStatus has_any (successCodes)

Stage 3: where

| where ipv4_is_private(cIP) == False

Stage 4: where

| where csUriStem hasprefix "/autodiscover/autodiscover.json"

Stage 5: project

| project TimeGenerated, cIP, sIP, sSiteName, csUriStem, csUriQuery, Computer, csUserName, _ResourceId, FileUri

Stage 6: where

| where (csUriQuery !has "Protocol" and isnotempty(csUriQuery))
or (csUriQuery has_any("/mapi/", "powershell"))
or (csUriQuery contains "@" and csUriQuery matches regex @"\.[a-zA-Z]{2,4}?(?:[a-zA-Z]{2,4}\/)")
or (csUriQuery contains ":" and csUriQuery matches regex @"\:[0-9]{2,4}\/")

Stage 7: extend (3 consecutive steps)

| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(csUserName, "@")[0]), AccountUPNSuffix = tostring(split(csUserName, "@")[1])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
csUriQuerycontains
  • :
  • @
field:"csUriQuery" kind:contains
csUriQueryis_not_null
  • (no value, null check)
field:"csUriQuery" kind:is_not_null
csUriQuerymatch
  • /mapi/ transforms: term
  • powershell transforms: term
field:"csUriQuery" kind:match
csUriQueryregex_match
  • .[a-zA-Z]{2,4}?(?:[a-zA-Z]{2,4}\/)
  • \:[0-9]{2,4}\/
field:"csUriQuery" kind:regex_match
csUriStemstarts_with
  • /autodiscover/autodiscover.json
field:"csUriStem" kind:starts_with value:"/autodiscover/autodiscover.json"
scStatusmatch
  • 200 transforms: term
  • 302 transforms: term
  • 401 transforms: term
field:"scStatus" kind:match

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
cIPcidr_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:cIP

Output Fields

These fields are emitted when the rule matches.

FieldSource
Computerproject
FileUriproject
TimeGeneratedproject
_ResourceIdproject
cIPproject
csUriQueryproject
csUriStemproject
csUserNameproject
sIPproject
sSiteNameproject
DomainIndexextend
HostNameextend
HostNameDomainextend
AccountNameextend
AccountUPNSuffixextend

GCP Security Command Center - Detect Resources with Logging Disabled

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

Detects Google Cloud resources where logging is disabled for services like (Cloud Storage buckets, Firewall rules, Cloud DNS networks) based on Google Cloud Security Command Center findings.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Rule Body

id: d1fe8d30-4852-463a-b6ee-3b459788b75d
name: GCP Security Command Center - Detect Resources with Logging Disabled
description: |
  Detects Google Cloud resources where logging is disabled for services like (Cloud Storage buckets, Firewall rules, Cloud DNS networks) based on Google Cloud Security Command Center findings.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: GoogleSCCDefinition
    dataTypes:
      - GoogleCloudSCC
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
tags:
  - Logging
  - GCP
query: |
  GoogleCloudSCC
  | where tostring(Findings.state) == "ACTIVE"
  | extend FindingCategory = tostring(Findings.category)
  | where FindingCategory in ("BUCKET_LOGGING_DISABLED", "FIREWALL_RULE_LOGGING_DISABLED", "DNS_LOGGING_DISABLED")
  | extend FindingsJson = parse_json(Findings)
  | extend Resource = tostring(FindingsJson.resourceName),
           ExternalUri = tostring(FindingsJson.externalUri),
           Description = tostring(FindingsJson.description),
           Severity = tostring(FindingsJson.severity),
           ProjectName = extract(@"projects/([^/]+)", 1, tostring(FindingsJson.resourceName)),
           ResourceType = extract(@"//([a-z0-9_.-]+)/", 1, tostring(FindingsJson.resourceName)),
           SourceProps = parse_json(tostring(FindingsJson.sourceProperties))
  // Normalize display-friendly resource id
  | extend ResourceName = case(
      Resource contains "firewalls/", extract(@"firewalls/([^/]+)", 1, Resource),
      Resource contains "storage.googleapis.com/", extract(@'storage.googleapis.com/([^/\"]+)', 1, Resource),
      Resource contains "networks/", extract(@"networks/([^/]+)", 1, Resource),
      Resource
    )
  | project TimeGenerated, ProjectName, ResourceType, ResourceName, Resource, FindingCategory, Severity, ExternalUri, Description
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: ResourceName
customDetails:
  ProjectName: ProjectName
  ResourceType: ResourceType
  FindingCategory: FindingCategory
  ExternalUri: ExternalUri
  Description: Description
  Severity: Severity
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: "GCP resources with logging disabled: {{ProjectName}} ({{ResourceType}})"
  alertDescriptionFormat: |-
    Resource {{ResourceName}} in project {{ProjectName}} (type: {{ResourceType}}) has logging disabled.
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GoogleCloudSCC

Stage 2: where

| where tostring(Findings.state) == "ACTIVE"

Stage 3: extend

| extend FindingCategory = tostring(Findings.category)

Stage 4: where

| where FindingCategory in ("BUCKET_LOGGING_DISABLED", "FIREWALL_RULE_LOGGING_DISABLED", "DNS_LOGGING_DISABLED")

Stage 5: extend (3 consecutive steps)

| extend FindingsJson = parse_json(Findings)
| extend Resource = tostring(FindingsJson.resourceName),
         ExternalUri = tostring(FindingsJson.externalUri),
         Description = tostring(FindingsJson.description),
         Severity = tostring(FindingsJson.severity),
         ProjectName = extract(@"projects/([^/]+)", 1, tostring(FindingsJson.resourceName)),
         ResourceType = extract(@"//([a-z0-9_.-]+)/", 1, tostring(FindingsJson.resourceName)),
         SourceProps = parse_json(tostring(FindingsJson.sourceProperties))
| extend ResourceName = case(
    Resource contains "firewalls/", extract(@"firewalls/([^/]+)", 1, Resource),
    Resource contains "storage.googleapis.com/", extract(@'storage.googleapis.com/([^/\"]+)', 1, Resource),
    Resource contains "networks/", extract(@"networks/([^/]+)", 1, Resource),
    Resource
  )

Stage 6: project

| project TimeGenerated, ProjectName, ResourceType, ResourceName, Resource, FindingCategory, Severity, ExternalUri, Description

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
FindingCategoryin
  • BUCKET_LOGGING_DISABLED
  • DNS_LOGGING_DISABLED
  • FIREWALL_RULE_LOGGING_DISABLED
field:"FindingCategory" kind:in
Findings.stateeq
  • ACTIVE transforms: tostring, kusto_dynamic_path
field:"Findings.state" kind:eq value:"ACTIVE"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Descriptionproject
ExternalUriproject
FindingCategoryproject
ProjectNameproject
Resourceproject
ResourceNameproject
ResourceTypeproject
Severityproject
TimeGeneratedproject

Illumio VEN Deactivated Detection Rule

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

'Create Microsoft Sentinel Incident When Ven Goes Into Deactivated state'

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Rule Body

id: c18bd8c2-50f0-4aa2-8122-d449243627d7
name: Illumio VEN Deactivated Detection Rule
description: |
  'Create Microsoft Sentinel Incident When Ven Goes Into Deactivated state'
severity: High
status: Available
requiredDataConnectors: 
  - connectorId: IllumioSaaSDataConnector
    dataTypes:
      - Illumio_Auditable_Events_CL
  - connectorId: SyslogAma
    datatypes:
      - Syslog      
queryFrequency: 60m
queryPeriod: 60m
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
  Illumio_Auditable_Events_CL
  | union IllumioSyslogAuditEvents  
  | where event_type has 'agent.deactivate'
  | mv-expand resource_changes
  | extend hostname = resource_changes['resource']['workload']['hostname'],
      workload_href = resource_changes['resource']['workload']['href'],
      workload_labels = resource_changes['resource']['workload']['labels']
  | extend ipaddress = action.src_ip,       
        ven_href = created_by.ven.href
  | project-away resource_changes, action, version 
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: hostname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ipaddress            
alertDetailsOverride:
  alertDisplayNameFormat: | 
    Illumio VEN Deactivated Incident
  alertDescriptionFormat: | 
    Illumio VEN Deactivated Incident generated at {{TimeGenerated}}
version: 1.0.6
kind: Scheduled

Stages and Predicates

Stage 1: source

Illumio_Auditable_Events_CL

Stage 2: union

union of 1 branches

Stage 3: source

IllumioSyslogAuditEvents

Stage 4: where

where event_type has "agent.deactivate"

Stage 5: mv-expand

mv-expand resource_changes

Stage 6: extend

extend hostname, workload_href, workload_labels

Stage 7: extend

extend ipaddress, ven_href

Stage 8: project-away

project-away action, resource_changes, version

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
event_typematch
  • agent.deactivate transforms: term
field:"event_type" kind:match value:"agent.deactivate"

Output Fields

These fields are emitted when the rule matches.

FieldSource
hostnameextend
workload_hrefextend
workload_labelsextend
ipaddressextend
ven_hrefextend

M2131_DataConnectorAddedChangedRemoved

#
Status
available
Severity
medium
Time window
14d
Source
github.com/Azure/Azure-Sentinel

'This alert is designed to monitor data connector configurations. This alert is triggered when a data connector is added, updated, or deleted.'

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: eeb11b6b-e626-4228-b74d-3e730dca8999
name: M2131_DataConnectorAddedChangedRemoved
description: |
  'This alert is designed to monitor data connector configurations. This alert is triggered when a data connector is added, updated, or deleted.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1082
query: |
  AzureActivity
  | where OperationNameValue contains "Microsoft.SecurityInsights/dataConnectors/"
  | where ActivityStatusValue == "Succeeded"
  | project OperationNameValue, Caller, CallerIpAddress, ActivityStatusValue, ActivitySubstatusValue, ResourceGroup, Properties, ResourceId, TimeGenerated
  | sort by TimeGenerated desc
  | extend Account = Caller
entityMappings:
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: ResourceId
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

AzureActivity

Stage 2: where

| where OperationNameValue contains "Microsoft.SecurityInsights/dataConnectors/"

Stage 3: where

| where ActivityStatusValue == "Succeeded"

Stage 4: project

| project OperationNameValue, Caller, CallerIpAddress, ActivityStatusValue, ActivitySubstatusValue, ResourceGroup, Properties, ResourceId, TimeGenerated

Stage 5: sort

| sort by TimeGenerated desc

Stage 6: extend

| extend Account = Caller

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityStatusValueproject
ActivitySubstatusValueproject
Callerproject
CallerIpAddressproject
OperationNameValueproject
Propertiesproject
ResourceGroupproject
ResourceIdproject
TimeGeneratedproject
Accountextend

Oracle - Request to sensitive files

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

'Detects request to sensitive files.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 9cc9ed36-573f-11ec-bf63-0242ac130002
name: Oracle - Request to sensitive files
description: |
  'Detects request to sensitive files.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    dataTypes:
      - OracleWebLogicServer_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1189
query: |
  let forbidden_files = dynamic(['shadow', 'passwd', 'hosts', 'id_rsa']);
  OracleWebLogicServerEvent    
  | extend File = extract(@'(.*\/)?(.*)', 2, tostring(UrlOriginal))
  | where isnotempty(File)
  | where File in~ (forbidden_files)
  | extend FileCustomEntity = File, UrlCustomEntity = UrlOriginal
entityMappings:
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: FileCustomEntity
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: UrlCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let forbidden_files = dynamic(['shadow', 'passwd', 'hosts', 'id_rsa']);

Stage 1: source

OracleWebLogicServerEvent

Stage 2: extend

| extend File = extract(@'(.*\/)?(.*)', 2, tostring(UrlOriginal))

Stage 3: where

| where isnotempty(File)

Stage 4: where

| where File in~ (forbidden_files)

Stage 5: extend

| extend FileCustomEntity = File, UrlCustomEntity = UrlOriginal

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Filein
  • hosts
  • id_rsa
  • passwd
  • shadow
field:"File" kind:in
Fileis_not_null
  • (no value, null check)
field:"File" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
Fileextend
FileCustomEntityextend
UrlCustomEntityextend

Storage Accounts Alerts From Prancer

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

'High severity storage account alerts found by Prancer.'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance

Rule Body

id: 4adf2b5d-6b88-4b96-8cc2-a3c7fbbee10b
name: Storage Accounts Alerts From Prancer
description: |
  'High severity storage account alerts found by Prancer.'
severity: High
requiredDataConnectors:
- connectorId: PrancerLogData
  dataTypes:
    - prancer_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: SingleAlert
status: Available
tactics:
  - Reconnaissance
relevantTechniques:
  - T1595
query: |
  union prancer_CL
  | where deviceProduct_s == 'azure'
  | where parse_json(data_data_snapshots_s)[0].type == 'Microsoft.Storage/storageAccounts'
  | where data_data_severity_s == 'High' and data_data_result_s == 'failed'
  | extend snapshot = parse_json(data_data_snapshots_s)
  | mv-expand snapshot 
  | extend
      id = tostring(snapshot.id),
      structure = tostring(snapshot.structure),
      reference = tostring(snapshot.reference),
      source = tostring(snapshot.source),
      collection = tostring(snapshot.collection),
      type = tostring(snapshot.type),
      region = tostring(snapshot.region),
      resourceTypes = tostring(snapshot.resourceTypes),
      path = tostring(snapshot.path)

customDetails:
entityMappings:
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: path
alertDetailsOverride:
  alertDisplayNameFormat: "{{data_data_message_s}}"  
  alertDescriptionFormat: "{{data_data_description_s}}"
  alertSeverityColumnName: "{{data_data_severity_s}}"
  alertDynamicProperties:
    - alertProperty: RemediationSteps
      value: data_data_remediation_description_s
version: 1.0.2
kind: Scheduled


Stages and Predicates

Stage 1: union

union of 1 branches

Stage 2: source

prancer_CL

Stage 3: where

where deviceProduct_s == "azure"

Stage 4: where

where parse_json(data_data_snapshots_s)[0].type == "Microsoft.Storage/storageAccounts"

Stage 5: where

where data_data_result_s == "failed" and data_data_severity_s == "High"

Stage 6: extend

extend snapshot

Stage 7: mv-expand

mv-expand snapshot

Stage 8: extend

extend collection, id, path, reference, region, resourceTypes, source, structure, type

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
data_data_result_seq
  • failed
field:"data_data_result_s" kind:eq value:"failed"
data_data_severity_seq
  • High
field:"data_data_severity_s" kind:eq value:"High"
data_data_snapshots_s[0].typeeq
  • Microsoft.Storage/storageAccounts transforms: kusto_accessor_expr:{"expression":"parse_json(data_data_snapshots_s)[0].type","field":"data_data_snapshots_s[0].type"}, kusto_dynamic_path
field:"data_data_snapshots_s[0].type" kind:eq value:"Microsoft.Storage/storageAccounts"
deviceProduct_seq
  • azure
field:"deviceProduct_s" kind:eq value:"azure"

Output Fields

These fields are emitted when the rule matches.

FieldSource
snapshotextend
collectionextend
idextend
pathextend
referenceextend
regionextend
resourceTypesextend
sourceextend
structureextend
typeextend

TEARDROP memory-only dropper

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

Identifies SolarWinds TEARDROP memory-only dropper IOCs in Window's defender Exploit Guard activity References: - https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html - https://gist.github.com/olafhartong/71ffdd4cab4b6acd5cbcd1a0691ff82f

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 738702fd-0a66-42c7-8586-e30f0583f8fe
name: TEARDROP memory-only dropper
description: |
  Identifies SolarWinds TEARDROP memory-only dropper IOCs in Window's defender Exploit Guard activity
  References:
  - https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html
  - https://gist.github.com/olafhartong/71ffdd4cab4b6acd5cbcd1a0691ff82f
severity: High
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - Persistence
  - DefenseEvasion
relevantTechniques:
  - T1543
  - T1059
  - T1027
tags:
  - Sunburst
  - Solorigate
  - NOBELIUM
query:  |
  DeviceEvents
  | where ActionType has "ExploitGuardNonMicrosoftSignedBlocked"
  | where InitiatingProcessFileName has "svchost.exe" and FileName has "NetSetupSvc.dll"
  | extend HashAlgorithm = "SHA1"
  | extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName 
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: Account
    fieldMappings:
      - identifier: FullName 
        columnName: InitiatingProcessAccountUpn
      - identifier: Name
        columnName: InitiatingProcessAccountName
      - identifier: UPNSuffix
        columnName: InitiatingProcessAccountDomain
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: HashAlgorithm
      - identifier: Value
        columnName: InitiatingProcessSHA1
version: 1.0.6
kind: Scheduled

Stages and Predicates

Stage 1: source

DeviceEvents

Stage 2: where

| where ActionType has "ExploitGuardNonMicrosoftSignedBlocked"

Stage 3: where

| where InitiatingProcessFileName has "svchost.exe" and FileName has "NetSetupSvc.dll"

Stage 4: extend (3 consecutive steps)

| extend HashAlgorithm = "SHA1"
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
HashAlgorithmextend
DomainIndexextend
HostNameextend
HostNameDomainextend

Threats detected by ESET

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

'Escalates threats detected by ESET.'

MITRE ATT&CK Coverage

TacticTechniques
Execution

Rule Body

id: 64badfab-1dd8-4491-927b-3ca206fa9a17
name: Threats detected by ESET
description: |
  'Escalates threats detected by ESET.'
severity: Low
requiredDataConnectors:
  - connectorId: ESETPROTECT
    dataTypes:
      - ESETPROTECT
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
relevantTechniques:
  - T1204
query: |
  ESETPROTECT
  | where EventType == "Threat_Event"
  | extend DvcHostname, SrcUserName, DvcIpAddr, FileHashSha1, FileHashAlgo = "SHA1"
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: SrcUserName
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DvcHostname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: DvcIpAddr
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: FileHashAlgo
      - identifier: Value
        columnName: FileHashSha1
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

ESETPROTECT

Stage 2: where

| where EventType == "Threat_Event"

Stage 3: extend

| extend DvcHostname, SrcUserName, DvcIpAddr, FileHashSha1, FileHashAlgo = "SHA1"

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DvcHostnameextend
DvcIpAddrextend
FileHashAlgoextend
FileHashSha1extend
SrcUserNameextend

TI map Email entity to SecurityAlert

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

'Identifies a match in SecurityAlert table from any Email IOC from TI which will extend coverage to datatypes such as MCAS, StorageThreatProtection and many others'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: a2e36ce0-da4d-4b6e-88c6-4e40161c5bfc
name: TI map Email entity to SecurityAlert
description: |
  'Identifies a match in SecurityAlert table from any Email IOC from TI which will extend coverage to datatypes such as MCAS, StorageThreatProtection and many others'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureSecurityCenter
    dataTypes:
      - SecurityAlert
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1566
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
  ThreatIntelligenceIndicator
  //Filtering the table for Email related IOCs
  | where isnotempty(EmailSenderAddress)
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now()
  // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  | join kind=innerunique (
      SecurityAlert
      | where TimeGenerated >= ago(dt_lookBack)
      | extend MSTI = case(AlertName has "TI map" and VendorName == "Microsoft" and ProductName == 'Azure Sentinel', true, false)
      | where MSTI == false
      // Converting Entities into dynamic data type and use mv-expand to unpack the array
      | extend EntitiesDynamicArray = parse_json(Entities) | mv-expand EntitiesDynamicArray
      // Parsing relevant entity column to filter type account and creating new column by combining account and UPNSuffix
      | extend Entitytype = tostring(parse_json(EntitiesDynamicArray).Type), EntityName = tostring(parse_json(EntitiesDynamicArray).Name),
      EntityUPNSuffix = tostring(parse_json(EntitiesDynamicArray).UPNSuffix)
      | where Entitytype =~ "account"
      | extend EntityEmail = tolower(strcat(EntityName, "@", EntityUPNSuffix))
      | where EntityEmail matches regex emailregex
      | extend Alert_TimeGenerated = TimeGenerated
  )
  on $left.EmailSenderAddress == $right.EntityEmail
  | where Alert_TimeGenerated < ExpirationDateTime
  | summarize Alert_TimeGenerated = arg_max(Alert_TimeGenerated, *) by IndicatorId, AlertName
  | project Alert_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore,
  EmailSenderName, EmailRecipient, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, FileHashValue, FileHashType, EntityEmail, AlertName, AlertType,
  AlertSeverity, Entities, ProviderName, VendorName
  | extend Name = tostring(split(EntityEmail, '@', 0)[0]), UPNSuffix = tostring(split(EntityEmail, '@', 1)[0])
  | extend timestamp = Alert_TimeGenerated
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: EntityEmail
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix 
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.2.8
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where isnotempty(EmailSenderAddress)

Stage 3: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 4: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId

Stage 5: where

| where Active == true and ExpirationDateTime > now()

Stage 6: join

| join kind=innerunique (
    SecurityAlert
    | where TimeGenerated >= ago(dt_lookBack)
    | extend MSTI = case(AlertName has "TI map" and VendorName == "Microsoft" and ProductName == 'Azure Sentinel', true, false)
    | where MSTI == false
    | extend EntitiesDynamicArray = parse_json(Entities) | mv-expand EntitiesDynamicArray
    | extend Entitytype = tostring(parse_json(EntitiesDynamicArray).Type), EntityName = tostring(parse_json(EntitiesDynamicArray).Name),
    EntityUPNSuffix = tostring(parse_json(EntitiesDynamicArray).UPNSuffix)
    | where Entitytype =~ "account"
    | extend EntityEmail = tolower(strcat(EntityName, "@", EntityUPNSuffix))
    | where EntityEmail matches regex emailregex
    | extend Alert_TimeGenerated = TimeGenerated
)
on $left.EmailSenderAddress == $right.EntityEmail

Stage 7: where

where Alert_TimeGenerated < ExpirationDateTime

Stage 8: summarize

summarize Alert_TimeGenerated by IndicatorId, AlertName

Stage 9: project

project ActivityGroupNames, AlertName, AlertSeverity, AlertType, Alert_TimeGenerated, ConfidenceScore, Description, EmailRecipient, EmailSenderName, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, Entities, EntityEmail, ExpirationDateTime, FileHashType, FileHashValue, IndicatorId, ProviderName, ThreatType, Url, VendorName

Stage 10: extend

extend Name, UPNSuffix

Stage 11: extend

extend timestamp

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
AlertNameproject
AlertSeverityproject
AlertTypeproject
Alert_TimeGeneratedproject
ConfidenceScoreproject
Descriptionproject
EmailRecipientproject
EmailSenderNameproject
EmailSourceDomainproject
EmailSourceIpAddressproject
EmailSubjectproject
Entitiesproject
EntityEmailproject
ExpirationDateTimeproject
FileHashTypeproject
FileHashValueproject
IndicatorIdproject
ProviderNameproject
ThreatTypeproject
Urlproject
VendorNameproject
Nameextend
UPNSuffixextend
timestampextend

TI Map IP Entity to CommonSecurityLog

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

This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in CommonSecurityLog.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 66c81ae2-1f89-4433-be00-2fbbd9ba5ebe
name: TI Map IP Entity to CommonSecurityLog
description: |
  This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in CommonSecurityLog.
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: CEF
    dataTypes:
      - CommonSecurityLog
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let IPRegex = '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}';
  let dt_lookBack = 1h; // Look back 1 hour for CommonSecurityLog events
  let ioc_lookBack = 14d; // Look back 14 days for threat intelligence indicators
  // Fetch threat intelligence indicators related to IP addresses
  let IP_Indicators = ThreatIntelligenceIndicator
    | where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
    | where TimeGenerated >= ago(ioc_lookBack)
    | 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)
    | 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 CommonSecurityLog events
  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 (
        CommonSecurityLog
        | where TimeGenerated >= ago(dt_lookBack)
        | extend MessageIP = extract(IPRegex, 0, Message)
        | extend CS_ipEntity = iff((not(ipv4_is_private(SourceIP)) and isnotempty(SourceIP)), SourceIP, DestinationIP)
        | extend CS_ipEntity = iff(isempty(CS_ipEntity) and isnotempty(MessageIP), MessageIP, CS_ipEntity)
        | extend CommonSecurityLog_TimeGenerated = TimeGenerated
    )
    on $left.TI_ipEntity == $right.CS_ipEntity
    // Filter out logs that occurred after the expiration of the corresponding indicator
    | where CommonSecurityLog_TimeGenerated < ExpirationDateTime
    // Group the results by IndicatorId and CS_ipEntity, and keep the log entry with the latest timestamp
    | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, CS_ipEntity
    // Select the desired output fields
    | project timestamp = CommonSecurityLog_TimeGenerated, SourceIP, DestinationIP, MessageIP, Message, DeviceVendor, DeviceProduct, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, TI_ipEntity, CS_ipEntity, LogSeverity, DeviceAction, Type
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: CS_ipEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP       
version: 1.2.4
kind: Scheduled

Stages and Predicates

Parameters

let IPRegex = '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}';
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 11 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 (
      CommonSecurityLog
      | where TimeGenerated >= ago(dt_lookBack)
      | extend MessageIP = extract(IPRegex, 0, Message)
      | extend CS_ipEntity = iff((not(ipv4_is_private(SourceIP)) and isnotempty(SourceIP)), SourceIP, DestinationIP)
      | extend CS_ipEntity = iff(isempty(CS_ipEntity) and isnotempty(MessageIP), MessageIP, CS_ipEntity)
      | extend CommonSecurityLog_TimeGenerated = TimeGenerated
  )
  on $left.TI_ipEntity == $right.CS_ipEntity

Stage 9: where

where CommonSecurityLog_TimeGenerated < ExpirationDateTime

Stage 10: summarize

summarize CommonSecurityLog_TimeGenerated by IndicatorId, CS_ipEntity

Stage 11: project

project CS_ipEntity, ConfidenceScore, DestinationIP, DeviceAction, DeviceProduct, DeviceVendor, ExpirationDateTime, IndicatorId, LogSeverity, Message, MessageIP, SourceIP, TI_ipEntity, ThreatType, Type, timestamp

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
CS_ipEntityproject
ConfidenceScoreproject
DestinationIPproject
DeviceActionproject
DeviceProductproject
DeviceVendorproject
ExpirationDateTimeproject
IndicatorIdproject
LogSeverityproject
Messageproject
MessageIPproject
SourceIPproject
TI_ipEntityproject
ThreatTypeproject
Typeproject
timestampproject

User Sign in from different countries

#
Status
available
Severity
medium
Time window
1h
Group by
ClientIp, Dummy, Hour, User
Source
github.com/Azure/Azure-Sentinel

'This query searches for successful user logins from different countries within 30 mins.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 3094e036-e5ae-4d6e-8626-b0f86ebc71f2
name: User Sign in from different countries
description: |
  'This query searches for successful user logins from different countries within 30 mins.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SalesforceServiceCloudCCPDefinition
    dataTypes:
      - SalesforceServiceCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let threshold = 2;
  let Countrydb = externaldata(Network:string, geoname_id:string, continent_code:string, continent_name:string, country_iso_code:string, country_name:string)
  [@"https://raw.githubusercontent.com/datasets/geoip2-ipv4/master/data/geoip2-ipv4.csv"];
  let UsersLocation = SalesforceServiceCloud
  | where EventType =~ 'Login' and LoginStatus=~'LOGIN_NO_ERROR'
  | project TimeGenerated, TimestampDerived, ClientIp, UserId, User, UserType ;
  UsersLocation
  | extend Dummy=1
  | extend TimestampDerived = todatetime(TimestampDerived)
  | summarize count() by Hour=bin(TimestampDerived,30m), ClientIp,User, Dummy
  | partition by Hour(
                  lookup (Countrydb|extend Dummy=1) on Dummy
                | where ipv4_is_match(ClientIp, Network)
                )
  | summarize NumOfCountries = dcount(country_name) by User, Hour
  | where NumOfCountries >= threshold
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: User
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 2;

let UsersLocation is inlined into the numbered stages below.

Let Binding: Countrydb used in Stage 7

let Countrydb = externaldata(Network:string, geoname_id:string, continent_code:string, continent_name:string, country_iso_code:string, country_name:string)
[@"https://raw.githubusercontent.com/datasets/geoip2-ipv4/master/data/geoip2-ipv4.csv"];

Stages 1 to 3 define let UsersLocation as the rule's main pipeline source. Stages 4 to 9 use it.

Stage 1: source

SalesforceServiceCloud

Stage 2: where

| where EventType =~ 'Login' and LoginStatus=~'LOGIN_NO_ERROR'

Stage 3: project

| project TimeGenerated, TimestampDerived, ClientIp, UserId, User, UserType

Stage 4: extend

UsersLocation
| extend Dummy=1

Stage 5: extend

| extend TimestampDerived = todatetime(TimestampDerived)

Stage 6: summarize

| summarize count() by Hour=bin(TimestampDerived,30m), ClientIp,User, Dummy

Stage 7: partition

| partition by Hour(
                lookup (Countrydb|extend Dummy=1) on Dummy
              | where ipv4_is_match(ClientIp, Network)
              )

Stage 8: summarize

| summarize NumOfCountries = dcount(country_name) by User, Hour

Stage 9: where

| where NumOfCountries >= threshold

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Hoursummarize
NumOfCountriessummarize
Usersummarize

Back to all Kusto rules