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.
This rule generates an incident when an alarming number of anomalies are generated in the last 15 minutes.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access | No specific technique |
| Discovery | No 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | contains |
| field:"Category" kind:contains value:"ANOMALY_NEW" |
Total | ge |
| field:"Total" kind:ge value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Total | summarize |
Authentications of Privileged Accounts Outside of Expected Controls
#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
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Cisco - firewall block but success logon to Microsoft Entra ID (Kusto)
- Detect non-admin requesting token for admin applications (Kusto)
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
let admin_users = (IdentityInfo
| summarize arg_max(TimeGenerated, *) by AccountUPN
| where AssignedRoles contains "admin"
| summarize by tolower(AccountUPN));
Let Binding: admin_asn
let admin_asn = (SigninLogs
| where TimeGenerated between (ago(7d)..ago(1d))
| where tolower(UserPrincipalName) in (admin_users)
| summarize by AutonomousSystemNumber);
Let Binding: admin_locations
let admin_locations = (SigninLogs
| where TimeGenerated between (ago(7d)..ago(1d))
| where tolower(UserPrincipalName) in (admin_users)
| summarize by Location);
Let Binding: admin_devices
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ResultType | eq |
| field:"ResultType" kind:eq value:"0" |
UserPrincipalName | in |
| field:"UserPrincipalName" kind:in value:"admin_users" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
AutonomousSystemNumber | eq | admin_asn | excludes:AutonomousSystemNumber field:"AutonomousSystemNumber" value:"admin_asn" |
Location | eq | admin_locations | excludes:Location field:"Location" value:"admin_locations" |
deviceId | eq | admin_devices | excludes:deviceId field:"deviceId" value:"admin_devices" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
deviceId | extend |
Changes to PIM Settings
#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
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Update role setting in PIM |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- [Entra ID] Mass Privileged Role Change Activity Detected (Kusto)
- [Entra ID] Privilege Elevation Request Denied (Kusto)
- [Entra ID] Privileged Role Assigned to a New User (Kusto)
- [Entra ID] Privileged Role Assigned to User (Kusto)
- Account Elevated to New Role (Kusto)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Assigned To Azure RBAC/Microsoft Entra Role (Sigma)
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.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"RoleManagement" |
OperationName | eq |
| field:"OperationName" kind:eq value:"Update role setting in PIM" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InitiatingUserPrincipalName | extend |
InitiatingAadUserId | extend |
InitiatingIPAddress | extend |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
Conditional Access - A Conditional Access policy was put into report-only mode
#A Conditional Access policy was put into report-only mode in Entra ID.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Update conditional access policy |
Rules Detecting the Same Action
These rules filter on the same operation.
- Azure MFA Disabled (Panther)
- Azure Policy Violation Detected (Panther)
- CA Policy Removed by Non Approved Actor (Sigma)
- CA Policy Updated by Non Approved Actor (Sigma)
- Certificate-Based Authentication Enabled (Sigma)
- Changes to Device Registration Policy (Sigma)
- Conditional Access - A Conditional Access app exclusion has changed (Kusto)
- Conditional Access - A Conditional Access Device platforms condition has changed (the Device platforms condition can be spoofed) (Kusto)
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.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | in |
| field:"OperationName" kind:in value:"Update conditional access policy" |
stateNew | eq |
| field:"stateNew" kind:eq value:"enabledForReportingButNotEnforced" |
stateOld | eq |
| field:"stateOld" kind:eq value:"enabled" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
OperationName | project |
TimeGenerated | project |
accountName | project |
modifiedBy | project |
policy | project |
result | project |
stateNew | project |
stateOld | project |
upnSuffix | project |
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.
'Detects mentions of monitored keywords across Telegram channels and groups. Extracts message content, URLs, chat metadata, user identity, and timestamps.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"telegram_mentions" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
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.
"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
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Resource Development | |
| Initial Access |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"High" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertUID | project |
AssetType | project |
AssetValue | project |
Description | project |
FirstSeen | project |
Impact | project |
LastSeen | project |
ProductName | project |
ProviderName | project |
Recommendation | project |
RiskScore | project |
SourceSype | project |
TimeGenerated | project |
UID | project |
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.
"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
| Tactic | Techniques |
|---|---|
| Resource Development | |
| Credential Access | |
| Discovery |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"Critical" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertTitle | project |
AlertUID | project |
AssetType | project |
AssetValue | project |
Description | project |
FirstSeen | project |
Impact | project |
LastSeen | project |
ProductName | project |
ProviderName | project |
RiskScore | project |
TimeGenerated | project |
UID | project |
DCOM Lateral Movement
#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
| Tactic | Techniques |
|---|---|
| Lateral Movement |
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
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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | ne |
| field:"ActionType" kind:ne value:"ListeningConnectionCreated" |
FileName | eq |
| field:"FileName" kind:eq value:"services.exe" |
InitiatingProcessCommandLine | match |
| field:"InitiatingProcessCommandLine" kind:match |
InitiatingProcessFileName | eq |
| field:"InitiatingProcessFileName" kind:eq value:"svchost.exe" |
InitiatingProcessParentFileName | eq |
| field:"InitiatingProcessParentFileName" kind:eq value:"svchost.exe" |
LocalPort | eq |
| field:"LocalPort" kind:eq value:"135" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
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 | excludes:DCOMFileName |
FileName | eq | csc.exe | excludes:FileName field:"FileName" value:"csc.exe" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DCOMCmdLine | summarize |
DeviceId | summarize |
DeviceName | summarize |
InitiatingProcessId | summarize |
LastBootTime | summarize |
LocalIP | summarize |
RemoteIP | summarize |
TimestampNetwEventExact | summarize |
TimestampProcEventExact | summarize |
Exchange SSRF Autodiscover ProxyShell - Detection
#'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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
csUriQuery | contains |
| field:"csUriQuery" kind:contains |
csUriQuery | is_not_null | field:"csUriQuery" kind:is_not_null | |
csUriQuery | match |
| field:"csUriQuery" kind:match |
csUriQuery | regex_match |
| field:"csUriQuery" kind:regex_match |
csUriStem | starts_with |
| field:"csUriStem" kind:starts_with value:"/autodiscover/autodiscover.json" |
scStatus | match |
| field:"scStatus" kind:match |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
cIP | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 | excludes:cIP |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | project |
FileUri | project |
TimeGenerated | project |
_ResourceId | project |
cIP | project |
csUriQuery | project |
csUriStem | project |
csUserName | project |
sIP | project |
sSiteName | project |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
AccountName | extend |
AccountUPNSuffix | extend |
GCP Security Command Center - Detect Resources with Logging Disabled
#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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
FindingCategory | in |
| field:"FindingCategory" kind:in |
Findings.state | eq |
| field:"Findings.state" kind:eq value:"ACTIVE" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Description | project |
ExternalUri | project |
FindingCategory | project |
ProjectName | project |
Resource | project |
ResourceName | project |
ResourceType | project |
Severity | project |
TimeGenerated | project |
Illumio VEN Deactivated Detection Rule
#'Create Microsoft Sentinel Incident When Ven Goes Into Deactivated state'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
event_type | match |
| field:"event_type" kind:match value:"agent.deactivate" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
hostname | extend |
workload_href | extend |
workload_labels | extend |
ipaddress | extend |
ven_href | extend |
M2131_DataConnectorAddedChangedRemoved
#'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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActivityStatusValue | eq |
| field:"ActivityStatusValue" kind:eq value:"Succeeded" |
OperationNameValue | contains |
| field:"azure_ad::operation_name_value" kind:contains value:"Microsoft.SecurityInsights/dataConnectors/" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityStatusValue | project |
ActivitySubstatusValue | project |
Caller | project |
CallerIpAddress | project |
OperationNameValue | project |
Properties | project |
ResourceGroup | project |
ResourceId | project |
TimeGenerated | project |
Account | extend |
Oracle - Request to sensitive files
#'Detects request to sensitive files.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
File | in |
| field:"File" kind:in |
File | is_not_null | field:"File" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
File | extend |
FileCustomEntity | extend |
UrlCustomEntity | extend |
Storage Accounts Alerts From Prancer
#'High severity storage account alerts found by Prancer.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
data_data_result_s | eq |
| field:"data_data_result_s" kind:eq value:"failed" |
data_data_severity_s | eq |
| field:"data_data_severity_s" kind:eq value:"High" |
data_data_snapshots_s[0].type | eq |
| field:"data_data_snapshots_s[0].type" kind:eq value:"Microsoft.Storage/storageAccounts" |
deviceProduct_s | eq |
| field:"deviceProduct_s" kind:eq value:"azure" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
snapshot | extend |
collection | extend |
id | extend |
path | extend |
reference | extend |
region | extend |
resourceTypes | extend |
source | extend |
structure | extend |
type | extend |
TEARDROP memory-only dropper
#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
| Tactic | Techniques |
|---|---|
| Execution | |
| Persistence | |
| Stealth |
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.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | match |
| field:"ActionType" kind:match value:"ExploitGuardNonMicrosoftSignedBlocked" |
FileName | match |
| field:"file_name" kind:match value:"NetSetupSvc.dll" |
InitiatingProcessFileName | match |
| field:"InitiatingProcessFileName" kind:match value:"svchost.exe" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HashAlgorithm | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
Threats detected by ESET
#'Escalates threats detected by ESET.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"Threat_Event" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DvcHostname | extend |
DvcIpAddr | extend |
FileHashAlgo | extend |
FileHashSha1 | extend |
SrcUserName | extend |
TI map Email entity to SecurityAlert
#'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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSenderAddress | is_not_null | field:"EmailSenderAddress" kind:is_not_null | |
EntityEmail | regex_match |
| field:"EntityEmail" kind:regex_match value:"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$" |
Entitytype | eq |
| field:"Entitytype" kind:eq value:"account" |
MSTI | eq |
| field:"MSTI" kind:eq value:"false" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
AlertName | project |
AlertSeverity | project |
AlertType | project |
Alert_TimeGenerated | project |
ConfidenceScore | project |
Description | project |
EmailRecipient | project |
EmailSenderName | project |
EmailSourceDomain | project |
EmailSourceIpAddress | project |
EmailSubject | project |
Entities | project |
EntityEmail | project |
ExpirationDateTime | project |
FileHashType | project |
FileHashValue | project |
IndicatorId | project |
ProviderName | project |
ThreatType | project |
Url | project |
VendorName | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
TI Map IP Entity to CommonSecurityLog
#This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in CommonSecurityLog.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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 =isnotempty(NetworkIP)NetworkIPNetworkDestinationIPStage 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.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSourceIpAddress | is_not_null | field:"EmailSourceIpAddress" kind:is_not_null | |
NetworkDestinationIP | is_not_null | field:"NetworkDestinationIP" kind:is_not_null | |
NetworkIP | is_not_null | field:"NetworkIP" kind:is_not_null | |
NetworkSourceIP | is_not_null | field:"NetworkSourceIP" kind:is_not_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TI_ipEntity | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 | excludes:TI_ipEntity |
TI_ipEntity | starts_with | 127. | excludes:TI_ipEntity field:"TI_ipEntity" value:"127." |
TI_ipEntity | starts_with | :: | excludes:TI_ipEntity field:"TI_ipEntity" value:"::" |
TI_ipEntity | starts_with | fe80 | excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CS_ipEntity | project |
ConfidenceScore | project |
DestinationIP | project |
DeviceAction | project |
DeviceProduct | project |
DeviceVendor | project |
ExpirationDateTime | project |
IndicatorId | project |
LogSeverity | project |
Message | project |
MessageIP | project |
SourceIP | project |
TI_ipEntity | project |
ThreatType | project |
Type | project |
timestamp | project |
User Sign in from different countries
#'This query searches for successful user logins from different countries within 30 mins.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| 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
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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"Login" |
LoginStatus | eq |
| field:"LoginStatus" kind:eq value:"LOGIN_NO_ERROR" |
NumOfCountries | ge |
| field:"NumOfCountries" kind:ge value:"2" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Hour | summarize |
NumOfCountries | summarize |
User | summarize |