Detection rules › Kusto

Kusto rules: 2n

1Password - Vault export prior to account suspension or deletion

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

This will alert when a successful vault export has occurred within the last 14 days prior to an account being suspended or deleted from 1Password. Ref: https://1password.com/ Ref: https://github.com/securehats/

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Telemetry Coverage

Rule Body

id: 51617533-cf51-4415-9020-b15bd47d69d2
name: 1Password - Vault export prior to account suspension or deletion
version: 1.0.0
kind: Scheduled
description: |-
  This will alert when a successful vault export has occurred within the last 14 days prior to an account being suspended or deleted from 1Password.

  Ref: https://1password.com/
  Ref: https://github.com/securehats/
severity: Medium
requiredDataConnectors:
  - connectorId: 1Password
    dataTypes:
      - OnePasswordEventLogs_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1555
query: |-
  let ruleFrequency = 1h;
  let lookback = 14d;
  let onePasswordVaultExportCheck =
      OnePasswordEventLogs_CL
      | where TimeGenerated between (ago(lookback) .. ago(now() - ago(ruleFrequency)))
      | where action == "export" and object_type == "vault"
  ;
  OnePasswordEventLogs_CL
  | where (action == "suspend" and object_type == "user") or (action == "delete" and object_type == "user")
  | extend
      userUuid = tostring(object_details.uuid)
      , auditevents = bag_pack("action", action, "object_type", object_type)
  | join (
      onePasswordVaultExportCheck
      | extend userUuid = tostring(actor_details.uuid)
      )
      on $left.userUuid == $right.userUuid
  | extend
      TargetUsername = actor_details1.email
      , SrcIpAddr = session1.ip_address
  | project
      TimeGenerated
      , auditevents
      , actor_details
      , location_details = location
      , session_details = session
      , exported_vault = object_details
      , TargetUsername
      , SrcIpAddr
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: TargetUsername
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: SrcIpAddr
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 1h
    matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
  aggregationKind: SingleAlert

Stages and Predicates

Parameters

let ruleFrequency = 1h;
let lookback = 14d;

Let Binding: onePasswordVaultExportCheck used in Stage 4

let onePasswordVaultExportCheck = OnePasswordEventLogs_CL
    | where TimeGenerated between (ago(lookback) .. ago(now() - ago(ruleFrequency)))
    | where action == "export" and object_type == "vault";

Stage 1: source

OnePasswordEventLogs_CL

Stage 2: where

| where (action == "suspend" and object_type == "user") or (action == "delete" and object_type == "user")

Stage 3: extend

| extend
    userUuid = tostring(object_details.uuid)
    , auditevents = bag_pack("action", action, "object_type", object_type)

Stage 4: join

| join (
    onePasswordVaultExportCheck
    | extend userUuid = tostring(actor_details.uuid)
    )
    on $left.userUuid == $right.userUuid

Stage 5: extend

extend SrcIpAddr, TargetUsername

Stage 6: project

project SrcIpAddr, TargetUsername, TimeGenerated, actor_details, auditevents, exported_vault, location_details, session_details

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
actioneq
  • delete
  • export
  • suspend
field:"action" kind:eq
object_typeeq
  • user corpus 2 (kusto 2)
  • vault
field:"object_type" kind:eq

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcIpAddrproject
TargetUsernameproject
TimeGeneratedproject
actor_detailsproject
auditeventsproject
exported_vaultproject
location_detailsproject
session_detailsproject

AIShield - Image Segmentation AI Model extraction high suspicious vulnerability detection

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

'This alert creates an incident when Image Segmentation AI Model extraction high suspicious, high severity vulnerability detected from the AIShield.'

Rule Body

id: 1ed02dcc-0bc9-465e-94b4-bd8969221602
name: AIShield - Image Segmentation AI Model extraction high suspicious vulnerability detection
description: |
  'This alert creates an incident when Image Segmentation AI Model extraction high suspicious, high severity vulnerability detected from the AIShield.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: BoschAIShield
    dataTypes:
      - AIShield
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  AIShield
  | where Message has 'Image Segmentation AI Model Extraction Attack Identified'
  | where Severity =~ 'High'
  | where SuspiciousLevel =~ 'high suspicious attack'
  | extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceName
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: AIShield - Image Segmentation AI Model extraction high suspicious vulnerability detected.
  alertDescriptionFormat: |
    This query detects Image Segmentation AI Model extraction high suspicious, high severity alert from AIShield generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
  alertTacticsColumnName: null
  alertSeverityColumnName: Severity
version: 1.0.4
kind: Scheduled

Stages and Predicates

Stage 1: source

AIShield

Stage 2: where

| where Message has 'Image Segmentation AI Model Extraction Attack Identified'

Stage 3: where

| where Severity =~ 'High'

Stage 4: where

| where SuspiciousLevel =~ 'high suspicious attack'

Stage 5: extend

| extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostNameextend
NTDomainextend

Application Group Settings Updated

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

Detects when application group settings are updated in Veeam Backup & Replication. This might indicate configuration changes that require review.

Rule Body

id: ff3ceb7d-bed0-4ed2-8dbf-1feb9047810f
name: Application Group Settings Updated
description: Detects when application group settings are updated in Veeam Backup &
  Replication. This might indicate configuration changes that require review.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 30400\n| extend Name =  extract(\"\
  Name=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n    Date = format_datetime(TimeGenerated,\
  \ 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n    EventId = instanceId,\n\
  \    UserName = user,\n    [\"Application Group Name\"] = Name,\n    MessageDetails\
  \ = Description,\n    Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 30400

Stage 3: extend

| extend Name =  extract("Name=\"([^\"]*)\"", 1, SyslogMessage)

Stage 4: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    UserName = user,
    ["Application Group Name"] = Name,
    MessageDetails = Description,
    Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Application Group Nameproject
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
UserNameproject

BloodHound Attack Path Finding - Non-Tier Zero Computer Hosting EnterpriseCA Trusted for NT Authentication

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

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

BloodHound Attack Path Finding - Non-Tier Zero Computer Hosting EnterpriseCA Trusted for NT Authentication

Rule Body

id: 64ba60b8-67d4-4356-8b88-0dfbf98e454a
name: BloodHound Attack Path Finding - Non-Tier Zero Computer Hosting EnterpriseCA
  Trusted for NT Authentication
description: BloodHound Attack Path Finding - Non-Tier Zero Computer Hosting EnterpriseCA
  Trusted for NT Authentication
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "T0GoldenCert"
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: domain_name
customDetails:
  Description: ShortDescription
  Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

BHEAttackPathsData_CL

Stage 2: where

| where Finding == "T0GoldenCert"

Indicators

These rows show field, operator, and value matches.

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

Cortex XDR Incident - High

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

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

'A new incident was created in the Cortex XDR portal with a severity "High". Click on the events for incident details. '

Rule Body

id: f96728eb-9802-4522-b715-47fb66c2ecf5
name: Cortex XDR Incident - High
description: |
  'A new incident was created in the Cortex XDR portal with a severity "High". Click on the events for incident details. '
severity: High
requiredDataConnectors:
  - connectorId: CortexXDR
    dataTypes:
      - CortexXDR_Incidents_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics: []
relevantTechniques: []
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride: null
customDetails: null
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: users_s
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: xdr_url_s
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: hosts_s
sentinelEntitiesMappings: null
query: |
  CortexXDR_Incidents_CL
  | where severity_s contains "high"
  | where status_s !contains "resolved_auto"
  // the below line filters for incidents that has at least one user or one host in it. Comment the below line if you want to generated incidents that have 0 user or 0 host as well
  | where host_count_d > 0 or user_count_d > 0
  | project TimeGenerated, incident_id_s, status_s, severity_s, incident_sources_s, description_s, xdr_url_s, hosts_s, users_s, alert_categories_s, host_count_d, user_count_d, alert_count_d, creation_time_d, modification_time_d
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CortexXDR_Incidents_CL

Stage 2: where

| where severity_s contains "high"

Stage 3: where

| where status_s !contains "resolved_auto"

Stage 4: where

| where host_count_d > 0 or user_count_d > 0

Stage 5: project

| project TimeGenerated, incident_id_s, status_s, severity_s, incident_sources_s, description_s, xdr_url_s, hosts_s, users_s, alert_categories_s, host_count_d, user_count_d, alert_count_d, creation_time_d, modification_time_d

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
status_scontainsresolved_autoexcludes:status_s field:"status_s" value:"resolved_auto"

Output Fields

These fields are emitted when the rule matches.

FieldSource
TimeGeneratedproject
alert_categories_sproject
alert_count_dproject
creation_time_dproject
description_sproject
host_count_dproject
hosts_sproject
incident_id_sproject
incident_sources_sproject
modification_time_dproject
severity_sproject
status_sproject
user_count_dproject
users_sproject
xdr_url_sproject

Credential added after admin consented to Application

#
Status
available
Severity
medium
Time window
2d
Group by
targetResourceID, targetResourceName
Source
github.com/Azure/Azure-Sentinel

This query will identify instances where Service Principal credentials were added to an application by one user after the application was granted admin consent rights by another user. If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential. Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow. For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Privilege Escalation
Credential Access

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 707494a5-8e44-486b-90f8-155d1797a8eb
name: Credential added after admin consented to Application
description: |
  'This query will identify instances where Service Principal credentials were added to an application by one user after the application was granted admin consent rights by another user.
   If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential.
   Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow.
   For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 1d
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - CredentialAccess
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1555
  - T1098
tags:
  - Solorigate
  - NOBELIUM
query: |
  let auditLookbackStart = 2d;
  let auditLookbackEnd = 1d;
  AuditLogs
  | where TimeGenerated >= ago(auditLookbackStart)
  | where OperationName =~ "Consent to application" 
  | where Result =~ "success"
  | mv-apply TargetResource = TargetResources on 
    (
        where TargetResource.type =~ "ServicePrincipal"
        | extend targetResourceName = tostring(TargetResource.displayName),
                 targetResourceID = tostring(TargetResource.id),
                 targetResourceType = tostring(TargetResource.type),
                 targetModifiedProp = TargetResource.modifiedProperties
    )
  | mv-apply Property = targetModifiedProp on 
    (
        where Property.displayName =~ "ConsentContext.IsAdminConsent"
        | extend isAdminConsent = trim(@'"',tostring(Property.newValue))
    )
  | mv-apply Property = targetModifiedProp on 
    (
        where Property.displayName =~ "ConsentAction.Permissions"
        | extend Consent_TargetPermissions = trim(@'"',tostring(Property.newValue))
    )
  | mv-apply Property = targetModifiedProp on 
    (
        where Property.displayName =~ "TargetId.ServicePrincipalNames"
        | extend Consent_TargetServicePrincipalNames = tostring(extract_all(@"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",trim(@'"',tostring(Property.newValue)))[0])
    )
  | extend Consent_InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
  | extend Consent_InitiatingAppName = tostring(InitiatedBy.app.displayName)
  | extend Consent_InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
  | extend Consent_InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
  | extend Consent_InitiatingAadUserId = tostring(InitiatedBy.user.id)
  | extend Consent_InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
  | join kind=inner ( 
  AuditLogs
  | where TimeGenerated  >= ago(auditLookbackEnd)
  | where OperationName =~ "Add service principal credentials"
  | where Result =~ "success"
  | mv-apply TargetResource = TargetResources on 
    (
        where TargetResource.type =~ "ServicePrincipal"
        | extend targetResourceName = tostring(TargetResource.displayName),
                 targetResourceID = tostring(TargetResource.id),
                 targetModifiedProp = TargetResource.modifiedProperties
    )
  | mv-apply Property = targetModifiedProp on 
    (
        where Property.displayName =~ "KeyDescription"
        | extend Credential_TargetKeyDescription = trim(@'"',tostring(Property.newValue))
    )
  | mv-apply Property = targetModifiedProp on 
    (
        where Property.displayName =~ "Included Updated Properties"
        | extend UpdatedProperties = trim(@'"',tostring(Property.newValue))
    )
  | mv-apply Property = targetModifiedProp on 
    (
        where Property.displayName =~ "TargetId.ServicePrincipalNames"
        | extend Credential_TargetServicePrincipalNames = tostring(extract_all(@"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",trim(@'"',tostring(Property.newValue)))[0])
    )
  | extend Credential_InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
  | extend Credential_InitiatingAppName = tostring(InitiatedBy.app.displayName)
  | extend Credential_InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
  | extend Credential_InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
  | extend Credential_InitiatingAadUserId = tostring(InitiatedBy.user.id)
  | extend Credential_InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
  ) on targetResourceName, targetResourceID
  | extend TimeConsent = TimeGenerated, TimeCred = TimeGenerated1
  | where TimeConsent < TimeCred 
  | project TimeConsent, TimeCred, targetResourceName, targetResourceType, isAdminConsent, 
  Consent_InitiatingUserOrApp, Consent_TargetServicePrincipalNames, Consent_TargetPermissions,
  Consent_InitiatingAppName, Consent_InitiatingAppServicePrincipalId, Consent_InitiatingUserPrincipalName, Consent_InitiatingAadUserId, Consent_InitiatingIpAddress,
  Credential_InitiatingUserOrApp, Credential_TargetServicePrincipalNames, Credential_TargetKeyDescription,
  Credential_InitiatingAppName, Credential_InitiatingAppServicePrincipalId, Credential_InitiatingUserPrincipalName, Credential_InitiatingAadUserId, Credential_InitiatingIpAddress
  | extend Consent_AccountName = tostring(split(Consent_InitiatingUserPrincipalName, "@")[0]), Consent_UPNSuffix = tostring(split(Consent_InitiatingUserPrincipalName, "@")[1])
  | extend Credential_AccountName = tostring(split(Credential_InitiatingUserPrincipalName, "@")[0]), Credential_UPNSuffix = tostring(split(Credential_InitiatingUserPrincipalName, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Consent_InitiatingAppName
      - identifier: AadUserId
        columnName: Consent_InitiatingAppServicePrincipalId
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Credential_InitiatingAppName
      - identifier: AadUserId
        columnName: Credential_InitiatingAppServicePrincipalId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Consent_InitiatingUserPrincipalName
      - identifier: Name
        columnName: Consent_AccountName
      - identifier: UPNSuffix
        columnName: Consent_UPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: Consent_InitiatingAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Credential_InitiatingUserPrincipalName
      - identifier: Name
        columnName: Credential_AccountName
      - identifier: UPNSuffix
        columnName: Credential_UPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: Credential_InitiatingAadUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: Consent_InitiatingIpAddress
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: Credential_InitiatingIpAddress
version: 1.1.1
kind: Scheduled

Stages and Predicates

Parameters

let auditLookbackStart = 2d;
let auditLookbackEnd = 1d;

Stage 1: source

AuditLogs

Stage 2: where

| where TimeGenerated >= ago(auditLookbackStart)

Stage 3: where

| where OperationName =~ "Consent to application"

Stage 4: where

| where Result =~ "success"

Stage 5: kusto:mv-apply

| mv-apply TargetResource = TargetResources on 
  (
      where TargetResource.type =~ "ServicePrincipal"
      | extend targetResourceName = tostring(TargetResource.displayName),
               targetResourceID = tostring(TargetResource.id),
               targetResourceType = tostring(TargetResource.type),
               targetModifiedProp = TargetResource.modifiedProperties
  )

Stage 6: kusto:mv-apply

| mv-apply Property = targetModifiedProp on 
  (
      where Property.displayName =~ "ConsentContext.IsAdminConsent"
      | extend isAdminConsent = trim(@'"',tostring(Property.newValue))
  )

Stage 7: kusto:mv-apply

| mv-apply Property = targetModifiedProp on 
  (
      where Property.displayName =~ "ConsentAction.Permissions"
      | extend Consent_TargetPermissions = trim(@'"',tostring(Property.newValue))
  )

Stage 8: kusto:mv-apply

| mv-apply Property = targetModifiedProp on 
  (
      where Property.displayName =~ "TargetId.ServicePrincipalNames"
      | extend Consent_TargetServicePrincipalNames = tostring(extract_all(@"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",trim(@'"',tostring(Property.newValue)))[0])
  )

Stage 9: extend (6 consecutive steps)

| extend Consent_InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| extend Consent_InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend Consent_InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend Consent_InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend Consent_InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend Consent_InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
Consent_InitiatingUserOrApp =
ifisnotempty(InitiatedBy.user.userPrincipalName)tostring(InitiatedBy.user.userPrincipalName)
elsetostring(InitiatedBy.app.displayName)

Stage 10: join

| join kind=inner ( 
AuditLogs
| where TimeGenerated  >= ago(auditLookbackEnd)
| where OperationName =~ "Add service principal credentials"
| where Result =~ "success"
| mv-apply TargetResource = TargetResources on 
  (
      where TargetResource.type =~ "ServicePrincipal"
      | extend targetResourceName = tostring(TargetResource.displayName),
               targetResourceID = tostring(TargetResource.id),
               targetModifiedProp = TargetResource.modifiedProperties
  )
| mv-apply Property = targetModifiedProp on 
  (
      where Property.displayName =~ "KeyDescription"
      | extend Credential_TargetKeyDescription = trim(@'"',tostring(Property.newValue))
  )
| mv-apply Property = targetModifiedProp on 
  (
      where Property.displayName =~ "Included Updated Properties"
      | extend UpdatedProperties = trim(@'"',tostring(Property.newValue))
  )
| mv-apply Property = targetModifiedProp on 
  (
      where Property.displayName =~ "TargetId.ServicePrincipalNames"
      | extend Credential_TargetServicePrincipalNames = tostring(extract_all(@"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",trim(@'"',tostring(Property.newValue)))[0])
  )
| extend Credential_InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| extend Credential_InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend Credential_InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend Credential_InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend Credential_InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend Credential_InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
) on targetResourceName, targetResourceID

Stage 11: extend

extend TimeConsent, TimeCred

Stage 12: where

where TimeConsent < TimeCred

Stage 13: project

project Consent_InitiatingAadUserId, Consent_InitiatingAppName, Consent_InitiatingAppServicePrincipalId, Consent_InitiatingIpAddress, Consent_InitiatingUserOrApp, Consent_InitiatingUserPrincipalName, Consent_TargetPermissions, Consent_TargetServicePrincipalNames, Credential_InitiatingAadUserId, Credential_InitiatingAppName, Credential_InitiatingAppServicePrincipalId, Credential_InitiatingIpAddress, Credential_InitiatingUserOrApp, Credential_InitiatingUserPrincipalName, Credential_TargetKeyDescription, Credential_TargetServicePrincipalNames, TimeConsent, TimeCred, isAdminConsent, targetResourceName, targetResourceType

Stage 14: extend

extend Consent_AccountName, Consent_UPNSuffix

Stage 15: extend

extend Credential_AccountName, Credential_UPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
OperationNameeq
  • Add service principal credentials
  • Consent to application corpus 5 (kusto 5)
field:"OperationName" kind:eq
Property.displayNameeq
  • ConsentAction.Permissions transforms: kusto_dynamic_path
  • ConsentContext.IsAdminConsent transforms: kusto_dynamic_path
  • Included Updated Properties transforms: kusto_dynamic_path
  • KeyDescription transforms: kusto_dynamic_path
  • TargetId.ServicePrincipalNames transforms: kusto_dynamic_path
field:"Property.displayName" kind:eq
Resulteq
  • success corpus 23 (kusto 23)
field:"Result" kind:eq value:"success"
TargetResource.typeeq
  • ServicePrincipal transforms: kusto_dynamic_path
field:"TargetResource.type" kind:eq value:"ServicePrincipal"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Consent_InitiatingAadUserIdproject
Consent_InitiatingAppNameproject
Consent_InitiatingAppServicePrincipalIdproject
Consent_InitiatingIpAddressproject
Consent_InitiatingUserOrAppproject
Consent_InitiatingUserPrincipalNameproject
Consent_TargetPermissionsproject
Consent_TargetServicePrincipalNamesproject
Credential_InitiatingAadUserIdproject
Credential_InitiatingAppNameproject
Credential_InitiatingAppServicePrincipalIdproject
Credential_InitiatingIpAddressproject
Credential_InitiatingUserOrAppproject
Credential_InitiatingUserPrincipalNameproject
Credential_TargetKeyDescriptionproject
Credential_TargetServicePrincipalNamesproject
TimeConsentproject
TimeCredproject
isAdminConsentproject
targetResourceNameproject
targetResourceTypeproject
Consent_AccountNameextend
Consent_UPNSuffixextend
Credential_AccountNameextend
Credential_UPNSuffixextend

Cyble Vision Alerts Postman API Exposure Detection

#

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 exposed Postman requests, collections or endpoints referencing monitored entities. Alerts analysts to possible API enumeration, leaked endpoints, or unintended exposure.'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance
Credential Access
Exfiltration

Rule Body

id: 99ca8956-5aad-4542-9fbc-8254182b424d
name: Cyble Vision Alerts Postman API Exposure Detection
description: |
  'Detects exposed Postman requests, collections or endpoints referencing monitored entities. Alerts analysts to possible API enumeration, leaked endpoints, or unintended exposure.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
  dataTypes:
  - CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
- Reconnaissance
- CredentialAccess
- Exfiltration
relevantTechniques:
- T1595
- T1552
- T1041
query: |
  Alerts_postman  
  | where Service == "postman"
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
  alertDetailsOverride:
  alertDisplayNameFormat: Exposed Postman API Request {{PM_Title}}
  alertDescriptionFormat: |
    A Postman API request referencing monitored entity {{KeywordName}} was discovered. Public Workspace {{PM_IsPublic}}. Developer {{PM_DevelopedBy}}. This may indicate exposed API endpoints, leaked request parameters or sensitive testing data.
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  PM_Title: PM_Title
  PM_DevelopedBy: PM_DevelopedBy
  PM_DevelopedByUrl: PM_DevelopedByUrl
  PM_Category: PM_Category
  PM_PostmanKey: PM_PostmanKey
  PM_Url: PM_Url
entityMappings:
- entityType: Url
  fieldMappings:
  - identifier: Url
    columnName: PM_Url
- entityType: Host
  fieldMappings:
  - identifier: HostName
    columnName: KeywordName
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_postman

Stage 2: where

| where Service == "postman"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

CYFIRMA - Medium severity File Hash Indicators with Block Action and Malware

#

This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.

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

"This KQL query retrieves file hash indicators (MD5, SHA1, SHA256) from the CyfirmaIndicators_CL table within the last 5 minutes. It filters records with a confidence score of 80 or higher, containing file hash patterns, a recommended action of 'Block', and roles marked as 'Malware'. Extracted hashes and key threat intelligence details are projected for Blocking and investigation."

MITRE ATT&CK Coverage

Rule Body

id: 24dcff02-123c-4e10-a531-2a22a609120a
name: CYFIRMA - Medium severity File Hash Indicators with Block Action and Malware
description: |
  "This KQL query retrieves file hash indicators (MD5, SHA1, SHA256) from the CyfirmaIndicators_CL table within the last 5 minutes. 
  It filters records with a confidence score of 80 or higher, containing file hash patterns, a recommended action of 'Block', and roles marked as 'Malware'. 
  Extracted hashes and key threat intelligence details are projected for Blocking and investigation."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  // File Hash Indicators with Block Action and Malware
  let timeFrame = 5m;
  CyfirmaIndicators_CL 
  | where  (ConfidenceScore < 80 and ConfidenceScore >= 50)
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (Roles contains "Malware")
  | extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
  | extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
  | extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
  | extend
      Algo_MD5='md5',
      Algo_SHA1= 'SHA1',
      Algo_SHA256='SHA256',
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project  
      MD5,
      Algo_MD5,
      SHA1,
      Algo_SHA1,
      SHA256,
      Algo_SHA256,
      ThreatActors,
      Sources,
      RecommendedActions,
      Roles,
      Country,
      name,
      Description,
      ConfidenceScore,
      SecurityVendors,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - InitialAccess
  - Execution
  - Persistence
  - PrivilegeEscalation
  - DefenseEvasion
  - CredentialAccess
  - Discovery
  - LateralMovement
  - Collection
  - Impact
relevantTechniques:
  - T1566
  - T1203
  - T1059
  - T1204
  - T1547
  - T1053
  - T1055
  - T1027
  - T1562
  - T1036
  - T1003
  - T1555
  - T1082
  - T1057
  - T1021
  - T1113
  - T1486
  - T1566.001
  - T1059.001
  - T1059.003
  - T1547.001
  - T1053.005
  - T1562.001
  - T1003.001
  - T1555.003
  - T1021.002
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence File Hash Indicators with Block Action and Malware - {{name}} "
  alertDescriptionFormat: "{{Description}} - {{name}} "
  alertDynamicProperties:
    - alertProperty: ProviderName
      value: ProviderName
    - alertProperty: ProductName
      value: ProductName
customDetails:
  ThreatActors: ThreatActors
  Sources: Sources
  RecommendedActions: RecommendedActions
  Roles: Roles
  Country: Country
  Description: Description
  ConfidenceScore: ConfidenceScore
  SecurityVendors: SecurityVendors
  IndicatorID: IndicatorID
  created: created
  modified: modified
  valid_from: valid_from
  Tags: Tags
  ThreatType: ThreatType
  TimeGenerated: TimeGenerated
entityMappings:
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_MD5
      - identifier: Value
        columnName: MD5
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_SHA1
      - identifier: Value
        columnName: SHA1
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_SHA256
      - identifier: Value
        columnName: SHA256
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

CyfirmaIndicators_CL

Stage 2: where

| where  (ConfidenceScore < 80 and ConfidenceScore >= 50)
    and TimeGenerated between (ago(timeFrame) .. now())
    and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (Roles contains "Malware")

Stage 3: extend (4 consecutive steps)

| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
    Algo_MD5='md5',
    Algo_SHA1= 'SHA1',
    Algo_SHA256='SHA256',
    ProviderName = 'CYFIRMA',
    ProductName = 'DeCYFIR/DeTCT'

Stage 4: project

| project  
    MD5,
    Algo_MD5,
    SHA1,
    Algo_SHA1,
    SHA256,
    Algo_SHA256,
    ThreatActors,
    Sources,
    RecommendedActions,
    Roles,
    Country,
    name,
    Description,
    ConfidenceScore,
    SecurityVendors,
    IndicatorID,
    created,
    modified,
    valid_from,
    Tags,
    ThreatType,
    TimeGenerated,
    ProductName,
    ProviderName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Algo_MD5project
Algo_SHA1project
Algo_SHA256project
ConfidenceScoreproject
Countryproject
Descriptionproject
IndicatorIDproject
MD5project
ProductNameproject
ProviderNameproject
RecommendedActionsproject
Rolesproject
SHA1project
SHA256project
SecurityVendorsproject
Sourcesproject
Tagsproject
ThreatActorsproject
ThreatTypeproject
TimeGeneratedproject
createdproject
modifiedproject
nameproject
valid_fromproject

CYFIRMA - Medium severity Trojan File Hash Indicators with Monitor Action 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.

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

"This KQL query extracts file hash indicators associated with Trojan activity from the CyfirmaIndicators_CL table. It specifically targets indicators containing file hashes linked to Trojan behavior and retrieves MD5, SHA1, and SHA256 values. The query also includes contextual threat intelligence such as threat actors, tags, sources, and geolocation information."

MITRE ATT&CK Coverage

Rule Body

id: b89c893e-650f-4569-afc3-c487efee2472
name: CYFIRMA - Medium severity Trojan File Hash Indicators with Monitor Action Rule
description: |
  "This KQL query extracts file hash indicators associated with Trojan activity from the CyfirmaIndicators_CL table. 
  It specifically targets indicators containing file hashes linked to Trojan behavior and retrieves MD5, SHA1, and SHA256 values. 
  The query also includes contextual threat intelligence such as threat actors, tags, sources, and geolocation information."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  //Trojan File Hash Indicators with Monitor Action
  let timeFrame = 5m;
  CyfirmaIndicators_CL 
  | where (ConfidenceScore < 80 and ConfidenceScore >= 50)
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern contains 'file:hashes' and RecommendedActions has 'Monitor' and (Roles contains 'Trojan')
  | extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
  | extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
  | extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
  | extend
      Algo_MD5='md5',
      Algo_SHA1= 'SHA1',
      Algo_SHA256='SHA256',
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project  
      MD5,
      Algo_MD5,
      SHA1,
      Algo_SHA1,
      SHA256,
      Algo_SHA256,
      ThreatActors,
      Sources,
      RecommendedActions,
      Roles,
      Country,
      name,
      Description,
      ConfidenceScore,
      SecurityVendors,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - InitialAccess
  - Execution
  - Persistence
  - DefenseEvasion
  - CommandAndControl
  - CredentialAccess
relevantTechniques:
  - T1566
  - T1204
  - T1547
  - T1027
  - T1071
  - T1003
  - T1566.001
  - T1547.001
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence Trojan File Hash Indicators with Monitor Action - {{name}} "
  alertDescriptionFormat: "{{Description}} - {{name}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
customDetails:
  ThreatActors: ThreatActors
  Sources: Sources
  RecommendedActions: RecommendedActions
  Roles: Roles
  Country: Country
  Description: Description
  ConfidenceScore: ConfidenceScore
  SecurityVendors: SecurityVendors
  IndicatorID: IndicatorID
  created: created
  modified: modified
  ValidFrom: valid_from
  Tags: Tags
  ThreatType: ThreatType
  TimeGenerated: TimeGenerated
entityMappings:
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_MD5
      - identifier: Value
        columnName: MD5
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_SHA1
      - identifier: Value
        columnName: SHA1
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: Algo_SHA256
      - identifier: Value
        columnName: SHA256
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

CyfirmaIndicators_CL

Stage 2: where

| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
    and TimeGenerated between (ago(timeFrame) .. now())
    and pattern contains 'file:hashes' and RecommendedActions has 'Monitor' and (Roles contains 'Trojan')

Stage 3: extend (4 consecutive steps)

| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
    Algo_MD5='md5',
    Algo_SHA1= 'SHA1',
    Algo_SHA256='SHA256',
    ProviderName = 'CYFIRMA',
    ProductName = 'DeCYFIR/DeTCT'

Stage 4: project

| project  
    MD5,
    Algo_MD5,
    SHA1,
    Algo_SHA1,
    SHA256,
    Algo_SHA256,
    ThreatActors,
    Sources,
    RecommendedActions,
    Roles,
    Country,
    name,
    Description,
    ConfidenceScore,
    SecurityVendors,
    IndicatorID,
    created,
    modified,
    valid_from,
    Tags,
    ThreatType,
    TimeGenerated,
    ProductName,
    ProviderName

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Algo_MD5project
Algo_SHA1project
Algo_SHA256project
ConfidenceScoreproject
Countryproject
Descriptionproject
IndicatorIDproject
MD5project
ProductNameproject
ProviderNameproject
RecommendedActionsproject
Rolesproject
SHA1project
SHA256project
SecurityVendorsproject
Sourcesproject
Tagsproject
ThreatActorsproject
ThreatTypeproject
TimeGeneratedproject
createdproject
modifiedproject
nameproject
valid_fromproject

Detect .NET runtime being loaded in JScript for code execution

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

This query detects .NET being loaded from wscript or cscript to run .NET code, such as cactustorch and sharpshooter. All based on the DotNetToJScript by James Foreshaw documented here https://github.com/tyranid/DotNetToJScript.

MITRE ATT&CK Coverage

TacticTechniques
Execution

Telemetry Coverage

Rule Body

id: 9f921513-65f3-48a2-ae7d-326c5901c55e
name: Detect .NET runtime being loaded in JScript for code execution
description: |
  This query detects .NET being loaded from wscript or cscript to run .NET code, such as cactustorch and sharpshooter.
  All based on the DotNetToJScript by James Foreshaw documented here https://github.com/tyranid/DotNetToJScript. 
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceImageLoadEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
relevantTechniques:
  - T1204
query: |
  DeviceImageLoadEvents 
  | where FileName in~ ("mscoree.dll", "mscorlib.dll", "mscorlib.ni.dll") 
  | where tolower(InitiatingProcessFileName) in ("wscript.exe", "cscript.exe", "mshta.exe")
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Sid
        columnName: InitiatingProcessAccountSid 
      - identifier: Name
        columnName: InitiatingProcessAccountName
      - identifier: NTDomain
        columnName: InitiatingProcessAccountDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: Process
    fieldMappings:
      - identifier: CommandLine
        columnName: InitiatingProcessCommandLine
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

DeviceImageLoadEvents

Stage 2: where

| where FileName in~ ("mscoree.dll", "mscorlib.dll", "mscorlib.ni.dll")

Stage 3: where

| where tolower(InitiatingProcessFileName) in ("wscript.exe", "cscript.exe", "mshta.exe")

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
FileNamein
  • mscoree.dll
  • mscorlib.dll
  • mscorlib.ni.dll
field:"image_loaded_basename" kind:in
InitiatingProcessFileNamein
  • cscript.exe transforms: tolower
  • mshta.exe transforms: tolower
  • wscript.exe transforms: tolower
field:"process_basename" kind:in

Detect PIM elevation with user risk

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

When an account with eligible roles in Entra ID is compromised, the attacker will probably escalate their privileges via Microsoft PIM. With this rule you can detect when a risky user is elevating their privileges with PIM.

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

References

Telemetry Coverage

Rule Body

AuditLogs
| where TimeGenerated > ago(1h)
| where OperationName contains "PIM activation" and OperationName contains "completed"
| extend UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| join kind=inner (AADUserRiskEvents | where TimeGenerated > ago(1d)) on UserPrincipalName

CloudAppEvents
| where TimeGenerated > ago(1h)
| where ActionType == "Add member to role."
| extend UserPrincipalName = tostring(RawEventData.ObjectId)
| join kind=inner (AADUserRiskEvents | where TimeGenerated > ago(1d)) on UserPrincipalName

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where OperationName contains "PIM activation" and OperationName contains "completed"

Stage 4: extend

| extend UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)

Stage 5: join

| join kind=inner (AADUserRiskEvents | where TimeGenerated > ago(1d)) on UserPrincipalName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
OperationNamecontains
  • PIM activation
  • completed
field:"OperationName" kind:contains

Output Fields

These fields are emitted when the rule matches.

FieldSource
UserPrincipalNameextend

Exchange Server Suspicious File Downloads.

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

'This query looks for messages related to file downloads of suspicious file types on an Exchange Server. This could indicate attempted deployment of webshells. This query uses the Exchange HttpProxy AOBGeneratorLog, you will need to onboard this log as a custom log under the table http_proxy_oab_CL before using this query. This log is commonly found at C:\Program Files\Microsoft\Exchange Server\V15\Logging\OABGeneratorLog on the Exchange server. Details on collecting custom logs into Sentinel can be found here: https://learn.microsoft.com/en-us/azure/sentinel/connect-custom-logs-ama'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 8955c0fb-3408-47b0-a3b9-a1faec41e427
name: Exchange Server Suspicious File Downloads.
description: |
  'This query looks for messages related to file downloads of suspicious file types on an Exchange Server. This could indicate attempted deployment of webshells. 
  This query uses the Exchange HttpProxy AOBGeneratorLog, you will need to onboard this log as a custom log under the table http_proxy_oab_CL before using this query. 
  This log is commonly found at C:\Program Files\Microsoft\Exchange Server\V15\Logging\OABGeneratorLog on the Exchange server. Details on collecting custom logs into Sentinel
  can be found here: https://learn.microsoft.com/en-us/azure/sentinel/connect-custom-logs-ama'
severity: Medium
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
query: |
  let scriptExtensions = dynamic([".php", ".jsp", ".js", ".aspx", ".asmx", ".asax", ".cfm", ".shtml"]);
  http_proxy_oab_CL
  | where RawData contains "Download failed and temporary file"
  | extend File = extract("([^\\\\]*)(\\\\[^']*)",2,RawData)
  | extend Extension = strcat(".",split(File, ".")[-1])
  | extend InteractiveFile = iif(Extension in (scriptExtensions), "Yes", "No")
  // Uncomment the following line to alert only on interactive file download type
  //| where InteractiveFile =~ "Yes"
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
version: 1.0.5
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Application" ]

Stages and Predicates

Parameters

let scriptExtensions = dynamic([".php", ".jsp", ".js", ".aspx", ".asmx", ".asax", ".cfm", ".shtml"]);

Stage 1: source

http_proxy_oab_CL

Stage 2: where

| where RawData contains "Download failed and temporary file"

Stage 3: extend (5 consecutive steps)

| extend File = extract("([^\\\\]*)(\\\\[^']*)",2,RawData)
| extend Extension = strcat(".",split(File, ".")[-1])
| extend InteractiveFile = iif(Extension in (scriptExtensions), "Yes", "No")
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
RawDatacontains
  • Download failed and temporary file
field:"RawData" kind:contains value:"Download failed and temporary file"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Fileextend
Extensionextend
InteractiveFileextend
DomainIndexextend
HostNameextend
HostNameDomainextend

Four-Eyes Authorization Disabled

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

Detects when four-eyes authorization is disabled.

Rule Body

id: 57a8ff3a-d4aa-456f-9c13-186026bae52e
name: Four-Eyes Authorization Disabled
description: Detects when four-eyes authorization is disabled.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 42401\n| extend Operation =\
  \ extract(\"Operation=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    [\"Operation\"] = Operation,\n    MessageDetails\
  \ = Description,\n    Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 42401

Stage 3: extend

| extend Operation = extract("Operation=\"([^\"]*)\"", 1, SyslogMessage)

Stage 4: project

| project
    Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
    DataSource = original_host,
    EventId = instanceId,
    ["Operation"] = Operation,
    MessageDetails = Description,
    Severity = SeverityDescription

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Operationproject
Severityproject

Guardian- Malicious URL Policy Violation Detection

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

'This alert creates an incident when Malicious URL Policy Violation detected from the Guardian.'

Rule Body

id: 8644d9e0-b810-4b55-889b-b8a9b3795b5d
name: Guardian- Malicious URL Policy Violation Detection
description: |
  'This alert creates an incident when Malicious URL Policy Violation detected from the Guardian.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BoschAIShield
    dataTypes:
      - Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  Guardian
  | where PolicyViolatedControlFeature =~ 'Malicious URL'
  | where Severity =~ 'Medium'
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertDisplayNameFormat: Guardian- Malicious URL Policy Violation detection
  alertDescriptionFormat: |
    This query detects Malicious URL Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
  alertTacticsColumnName: null
  alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Guardian

Stage 2: where

| where PolicyViolatedControlFeature =~ 'Malicious URL'

Stage 3: where

| where Severity =~ 'Medium'

Indicators

These rows show field, operator, and value matches.

High risk Office operation conducted by IP Address that recently attempted to log into a disabled account

#
Severity
medium
Time window
8d
Group by
AppId, IPAddress, JoinedOnIp
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

It is possible that a disabled user account is compromised and another account on the same IP is used to perform operations that are not typical for that user. The query filters the SigninLogs for entries where ResultType is indicates a disabled account and the TimeGenerated is within a defined time range. It then summarizes these entries by IPAddress and AppId, calculating various statistics such as number of login attempts, distinct UPNs, App IDs etc and joins these results with another set of results from SigninLogs, filtering for entries with less than normal number of successful sign-ins. It then filters out entries where there were no successful sign-ins or where successful sign-ins did not occur within the same lookback period as the failed sign-ins, later projecting relevant fields by the count of login attempts, and expands the set of successful sign-ins into individual events. Finally, it joins these results with entries from OfficeActivity where certain operations deemed rare and high risk have been performed, ensuring their occurrance within a certain time range of the successful sign-ins.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 9adbd1c3-a4be-44ef-ac2f-503fd25692ee
name: High risk Office operation conducted by IP Address that recently attempted to log into a disabled account
description: |
 'It is possible that a disabled user account is compromised and another account on the same IP is used to perform operations that are not typical for that user.
  The query filters the SigninLogs for entries where ResultType is indicates a disabled account and the TimeGenerated is within a defined time range.
  It then summarizes these entries by IPAddress and AppId, calculating various statistics such as number of login attempts, distinct UPNs, App IDs etc and joins these results with another set of results from SigninLogs, filtering for entries with less than normal number of successful sign-ins.
  It then filters out entries where there were no successful sign-ins or where successful sign-ins did not occur within the same lookback period as the failed sign-ins, later projecting relevant fields by the count of login attempts, and expands the set of successful sign-ins into individual events.
  Finally, it joins these results with entries from OfficeActivity where certain operations deemed rare and high risk have been performed, ensuring their occurrance within a certain time range of the successful sign-ins.'
severity: Medium 
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: Office365
    dataTypes:
      - OfficeActivity
queryFrequency: 1d 
queryPeriod: 8d 
triggerOperator: gt 
triggerThreshold: 0 
tactics:
  - InitialAccess
  - Persistence
  - Collection
relevantTechniques:
  - T1078
  - T1098
  - T1114
query: |
  let threshold = 100;
  let timeRange = ago(7d);
  let timeBuffer = 1;
  SigninLogs 
  | where TimeGenerated > timeRange
  | where ResultType == "50057" 
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), disabledAccountLoginAttempts = count(), 
  disabledAccountsTargeted = dcount(UserPrincipalName), applicationsTargeted = dcount(AppDisplayName), disabledAccountSet = make_set(UserPrincipalName), 
  applicationSet = make_set(AppDisplayName) by IPAddress, AppId
  | order by disabledAccountLoginAttempts desc
  | join kind=inner (
      // IPs are considered suspicious - and any related successful sign-ins are detected
      SigninLogs
      | where TimeGenerated > timeRange
      | where ResultType == 0
      | summarize successSigninStart = min(TimeGenerated), successSigninEnd = max(TimeGenerated), successfulAccountSigninCount = dcount(UserPrincipalName), successfulAccountSigninSet = make_set(UserPrincipalName, 15) by IPAddress
      // Assume IPs associated with sign-ins from 100+ distinct user accounts are safe
      | where successfulAccountSigninCount < threshold
  ) on IPAddress  
  // IPs where attempts to authenticate as disabled user accounts originated, and had a non-zero success rate for some other account
  | where successfulAccountSigninCount != 0
  // Successful Account Signins occur within the same lookback period as the failed 
  | extend SuccessBeforeFailure = iff(successSigninStart >= StartTime and successSigninEnd <= EndTime, true, false)  
  | project StartTime, EndTime, IPAddress, disabledAccountLoginAttempts, disabledAccountsTargeted, disabledAccountSet, applicationSet, 
  successfulAccountSigninCount, successfulAccountSigninSet, successSigninStart, successSigninEnd, AppId
  | order by disabledAccountLoginAttempts
  // Break up the string of Succesfully signed into accounts into individual events
  | mvexpand successfulAccountSigninSet
  | extend JoinedOnIp = IPAddress
  | join kind = inner (
        OfficeActivity
      | where TimeGenerated > timeRange
      | where Operation in~ ( "Add-MailboxPermission", "Add-MailboxFolderPermission", "Set-Mailbox", "New-ManagementRoleAssignment", "New-InboxRule", "Set-InboxRule", "Set-TransportRule") and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount'))
      // Remove port from the end of the IP and/or square brackets around IP, if they exist 
      | extend JoinedOnIp = case(
        ClientIP matches regex @'\[((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\]-\d{1,5}', tostring(extract('\\[([0-9]+\\.[0-9]+\\.[0-9]+)\\]-[0-9]+', 1, ClientIP)),
        ClientIP matches regex @'\[((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\]', tostring(extract('\\[([0-9]+\\.[0-9]+\\.[0-9]+)\\]', 1, ClientIP)),  
        ClientIP matches regex @'(((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?))-\d{1,5}', tostring(extract('([0-9]+\\.[0-9]+\\.[0-9]+)-[0-9]+', 1, ClientIP)),
        ClientIP matches regex @'((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)', ClientIP,       
        ClientIP matches regex @'\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})\]-\d{1,5}', tostring(extract('\\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})\\]-[0-9]+', 1, ClientIP)),
        ClientIP matches regex @'\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})\]', tostring(extract('\\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})\\]', 1, ClientIP)),  
        ClientIP matches regex @'((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})-\d{1,5}', tostring(extract('((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})-[0-9]+', 1, ClientIP)),
        ClientIP matches regex @'((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})', ClientIP,
        "")
      | where isnotempty(JoinedOnIp)
      | extend OfficeTimeStamp = ElevationTime, UserPrincipalName = UserId
  ) on JoinedOnIp
  // Rare and risky operations only happen within a certain time range of the successful sign-in
  | where OfficeTimeStamp >= successSigninStart and datetime_diff('day', OfficeTimeStamp, successSigninEnd) <= timeBuffer
  | extend AccountName = tostring(split(UserPrincipalName, "@")[0]), AccountUPNSuffix = tostring(split(UserPrincipalName, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: JoinedOnIp
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: ApplicationId
version: 1.0.1
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Intelligence", "Security - Others", "Identity" ]

Stages and Predicates

Parameters

let threshold = 100;
let timeRange = ago(7d);
let timeBuffer = 1;

Stage 1: source

SigninLogs

Stage 2: where

| where TimeGenerated > timeRange

Stage 3: where

| where ResultType == "50057"

Stage 4: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), disabledAccountLoginAttempts = count(), 
disabledAccountsTargeted = dcount(UserPrincipalName), applicationsTargeted = dcount(AppDisplayName), disabledAccountSet = make_set(UserPrincipalName), 
applicationSet = make_set(AppDisplayName) by IPAddress, AppId

Stage 5: sort

| order by disabledAccountLoginAttempts desc

Stage 6: join

| join kind=inner (
    SigninLogs
    | where TimeGenerated > timeRange
    | where ResultType == 0
    | summarize successSigninStart = min(TimeGenerated), successSigninEnd = max(TimeGenerated), successfulAccountSigninCount = dcount(UserPrincipalName), successfulAccountSigninSet = make_set(UserPrincipalName, 15) by IPAddress
    | where successfulAccountSigninCount < threshold
) on IPAddress

Stage 7: where

where successfulAccountSigninCount != 0

Stage 8: extend

extend SuccessBeforeFailure
SuccessBeforeFailure =
ifsuccessSigninStart >= StartTime and successSigninEnd <= EndTimetrue
elsefalse

Stage 9: project

project AppId, EndTime, IPAddress, StartTime, applicationSet, disabledAccountLoginAttempts, disabledAccountSet, disabledAccountsTargeted, successSigninEnd, successSigninStart, successfulAccountSigninCount, successfulAccountSigninSet

Stage 10: sort

sort by disabledAccountLoginAttempts

Stage 11: mv-expand

mv-expand successfulAccountSigninSet

Stage 12: extend

extend JoinedOnIp

Stage 13: join

| join kind = inner (
      OfficeActivity
    | where TimeGenerated > timeRange
    | where Operation in~ ( "Add-MailboxPermission", "Add-MailboxFolderPermission", "Set-Mailbox", "New-ManagementRoleAssignment", "New-InboxRule", "Set-InboxRule", "Set-TransportRule") and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount'))
    | extend JoinedOnIp = case(
      ClientIP matches regex @'\[((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\]-\d{1,5}', tostring(extract('\\[([0-9]+\\.[0-9]+\\.[0-9]+)\\]-[0-9]+', 1, ClientIP)),
      ClientIP matches regex @'\[((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\]', tostring(extract('\\[([0-9]+\\.[0-9]+\\.[0-9]+)\\]', 1, ClientIP)),  
      ClientIP matches regex @'(((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?))-\d{1,5}', tostring(extract('([0-9]+\\.[0-9]+\\.[0-9]+)-[0-9]+', 1, ClientIP)),
      ClientIP matches regex @'((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)', ClientIP,       
      ClientIP matches regex @'\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})\]-\d{1,5}', tostring(extract('\\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})\\]-[0-9]+', 1, ClientIP)),
      ClientIP matches regex @'\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})\]', tostring(extract('\\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})\\]', 1, ClientIP)),  
      ClientIP matches regex @'((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})-\d{1,5}', tostring(extract('((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})-[0-9]+', 1, ClientIP)),
      ClientIP matches regex @'((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})', ClientIP,
      "")
    | where isnotempty(JoinedOnIp)
    | extend OfficeTimeStamp = ElevationTime, UserPrincipalName = UserId
) on JoinedOnIp

Stage 14: where OfficeTimeStamp - successSigninEnd <= 1d

where OfficeTimeStamp >= successSigninStart

Stage 15: extend

extend AccountName, AccountUPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
JoinedOnIpis_not_null
  • (no value, null check)
field:"JoinedOnIp" kind:is_not_null
Operationin
  • Add-MailboxFolderPermission
  • Add-MailboxPermission
  • New-InboxRule
  • New-ManagementRoleAssignment
  • Set-InboxRule
  • Set-Mailbox
  • Set-TransportRule
field:"Operation" kind:in
ResultTypeeq
  • 0
  • 50057
field:"ResultType" kind:eq
successfulAccountSigninCountlt
  • 100 transforms: number
field:"successfulAccountSigninCount" kind:lt value:"100"
successfulAccountSigninCountne
  • 0
field:"successfulAccountSigninCount" kind:ne value:"0"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
UserIdmatchNT AUTHORITY\SYSTEM (Microsoft.Exchange.ServiceHost), NT AUTHORITY\SYSTEM (w3wp), devilfish-applicationaccountexcludes:UserId field:"UserId" value:"NT AUTHORITY\SYSTEM (Microsoft.Exchange.ServiceHost)" field:"UserId" value:"NT AUTHORITY\SYSTEM (w3wp)" field:"UserId" value:"devilfish-applicationaccount"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AppIdproject
EndTimeproject
IPAddressproject
StartTimeproject
applicationSetproject
disabledAccountLoginAttemptsproject
disabledAccountSetproject
disabledAccountsTargetedproject
successSigninEndproject
successSigninStartproject
successfulAccountSigninCountproject
successfulAccountSigninSetproject
JoinedOnIpextend
AccountNameextend
AccountUPNSuffixextend

New UserAgent observed in last 24 hours

#
Status
available
Severity
low
Time window
14d
Group by
Account, EventName, EventSource, NormalizedUserAgent, Operation, RecordType, SourceIP, Type, UserAgent, csMethod, csUriStem, sSiteName
Source
github.com/Azure/Azure-Sentinel

'Identifies new UserAgents observed in the last 24 hours versus the previous 14 days. This detection extracts words from user agents to build the baseline and determine rareity rather than perform a direct comparison. This avoids FPs caused by version numbers and other high entropy user agent components. These new UserAgents could be benign. However, in normally stable environments, these new UserAgents could provide a starting point for investigating malicious activity. Note: W3CIISLog can be noisy depending on the environment, however OfficeActivity and AWSCloudTrail are usually stable with low numbers of detections.'

MITRE ATT&CK Coverage

Rule Body

id: b725d62c-eb77-42ff-96f6-bdc6745fc6e0
name: New UserAgent observed in last 24 hours
description: |
  'Identifies new UserAgents observed in the last 24 hours versus the previous 14 days. This detection extracts words from user agents to build the baseline and determine rareity rather than perform a direct comparison. This avoids FPs caused by version numbers and other high entropy user agent components.
  These new UserAgents could be benign. However, in normally stable environments, these new UserAgents could provide a starting point for investigating malicious activity.
  Note: W3CIISLog can be noisy depending on the environment, however OfficeActivity and AWSCloudTrail are usually stable with low numbers of detections.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
  - connectorId: Office365
    dataTypes:
      - OfficeActivity
  - connectorId: AzureMonitor(IIS)
    dataTypes:
      - W3CIISLog
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - CommandAndControl
  - Execution
relevantTechniques:
  - T1189
  - T1071
  - T1203
query: |
  let starttime = 14d;
  let endtime = 1d;
  let UserAgentAll =
  (union isfuzzy=true
  (OfficeActivity
  | where TimeGenerated >= ago(starttime)
  | where isnotempty(UserAgent)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP, Account = UserId, Type, RecordType, Operation
  ),
  (
  W3CIISLog
  | where TimeGenerated >= ago(starttime)
  | where isnotempty(csUserAgent)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = csUserAgent, SourceIP = cIP, Account = csUserName, Type, sSiteName, csMethod, csUriStem
  ),
  (
  AWSCloudTrail
  | where TimeGenerated >= ago(starttime)
  | where isnotempty(UserAgent)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = SourceIpAddress, Account = UserIdentityUserName, Type, EventSource, EventName
  ))
  // remove wordSize blocks of non-numeric hex characters prior to word extraction
  | extend UserAgentNoHexAlphas = replace("([A-Fa-f]{4,})", "x", UserAgent)
  // once blocks of hex chars are removed, extract wordSize blocks of a-z
  | extend Tokens = extract_all("([A-Za-z]{4,})", UserAgentNoHexAlphas)
  // concatenate extracted words to create a summarized user agent for baseline and comparison
  | extend NormalizedUserAgent = strcat_array(Tokens, "|")
  | project-away UserAgentNoHexAlphas, Tokens;
  UserAgentAll
  | where StartTime >= ago(endtime)
  | summarize StartTime = min(StartTime), EndTime = max(EndTime), count() by UserAgent, NormalizedUserAgent, SourceIP, Account, Type, RecordType, Operation, EventSource, EventName, sSiteName, csMethod, csUriStem
  | join kind=leftanti
  (
  UserAgentAll
  | where StartTime < ago(endtime)
  | summarize by NormalizedUserAgent, SourceIP, Account, Type, RecordType, Operation, EventSource, EventName, sSiteName, csMethod, csUriStem
  )
  on NormalizedUserAgent
  | extend timestamp = StartTime
  | extend Name = tostring(split(Account, '@', 0)[0]), UPNSuffix = tostring(split(Account, '@', 1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
version: 1.0.5
kind: Scheduled

Stages and Predicates

Parameters

let starttime = 14d;
let endtime = 1d;

Union isfuzzy=true (3 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: OfficeActivity, W3CIISLog, AWSCloudTrail

Leg 1: OfficeActivity

OfficeActivity
| where TimeGenerated >= ago(starttime)
| where isnotempty(UserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP, Account = UserId, Type, RecordType, Operation

Leg 2: W3CIISLog

W3CIISLog
| where TimeGenerated >= ago(starttime)
| where isnotempty(csUserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = csUserAgent, SourceIP = cIP, Account = csUserName, Type, sSiteName, csMethod, csUriStem

Leg 3: AWSCloudTrail

AWSCloudTrail
| where TimeGenerated >= ago(starttime)
| where isnotempty(UserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = SourceIpAddress, Account = UserIdentityUserName, Type, EventSource, EventName

Applied to the Combined Result

| extend UserAgentNoHexAlphas = replace("([A-Fa-f]{4,})", "x", UserAgent)
| extend Tokens = extract_all("([A-Za-z]{4,})", UserAgentNoHexAlphas)
| extend NormalizedUserAgent = strcat_array(Tokens, "|")
| project-away UserAgentNoHexAlphas, Tokens | where StartTime >= ago(endtime) | summarize StartTime = min(StartTime), EndTime = max(EndTime), count() by UserAgent, NormalizedUserAgent, SourceIP, Account, Type, RecordType, Operation, EventSource, EventName, sSiteName, csMethod, csUriStem | join kind=leftanti
(
UserAgentAll
| where StartTime < ago(endtime)
| summarize by NormalizedUserAgent, SourceIP, Account, Type, RecordType, Operation, EventSource, EventName, sSiteName, csMethod, csUriStem
)
on NormalizedUserAgent | extend timestamp = StartTime | extend Name = tostring(split(Account, '@', 0)[0]), UPNSuffix = tostring(split(Account, '@', 1)[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
UserAgentis_not_null
  • (no value, null check)
field:"UserAgent" kind:is_not_null
csUserAgentis_not_null
  • (no value, null check)
field:"csUserAgent" kind:is_not_null

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
UserAgentis_not_null(no value, null check)excludes:UserAgent
csUserAgentis_not_null(no value, null check)excludes:csUserAgent

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountsummarize
EndTimesummarize
EventNamesummarize
EventSourcesummarize
NormalizedUserAgentsummarize
Operationsummarize
RecordTypesummarize
SourceIPsummarize
StartTimesummarize
Typesummarize
UserAgentsummarize
csMethodsummarize
csUriStemsummarize
sSiteNamesummarize
timestampextend
Nameextend
UPNSuffixextend

NordPass - User data detected in breach

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

This will alert you when Data Breach Scanner discovers data related to a member of your organization on the dark web.

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Rule Body

id: c4d2eb42-a4ab-4db6-a270-3d2ed7e057a0
name: NordPass - User data detected in breach
kind: Scheduled
version: 1.0.0
displayName: User data detected in breach
description: |
  This will alert you when Data Breach Scanner discovers data related to a member of your organization on the dark web.
severity: High
query: |
  NordPassEventLogs_CL
  | where event_type == "breach"
  | where action == "email_breached"
  | extend TargetEmail = user_email
requiredDataConnectors:
  - connectorId: NordPass
    dataTypes:
      - NordPassEventLogs_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1020
incidentConfiguration:
  createIncident: false
entityMappings:
- entityType: Mailbox
  fieldMappings:
  - identifier: MailboxPrimaryAddress
    columnName: TargetEmail

Stages and Predicates

Stage 1: source

NordPassEventLogs_CL

Stage 2: where

| where event_type == "breach"

Stage 3: where

| where action == "email_breached"

Stage 4: extend

| extend TargetEmail = user_email

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
TargetEmailextend

Pathlock TDnR - Missing SAP Security Notes

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

Detects alerts for missing SAP security notes (OSS Notes) in the environment, forwarded by Pathlock Threat Detection and Response. Unpatched SAP systems with known CVEs represent a significant attack surface and may be actively exploited by threat actors targeting SAP vulnerabilities.

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c37
name: Pathlock TDnR - Missing SAP Security Notes
kind: Scheduled
description: >-
  Detects alerts for missing SAP security notes (OSS Notes) in the environment, forwarded by Pathlock
  Threat Detection and Response. Unpatched SAP systems with known CVEs represent a significant
  attack surface and may be actively exploited by threat actors targeting SAP vulnerabilities.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1082
query: |
  Pathlock_TDnR_CL
  | where DataSource == "MISSING_OSS_NOTES"
  | project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
            Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
            MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Bname
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIp
eventGroupingSettings:
  aggregationKind: SingleAlert
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0

Stages and Predicates

Stage 1: source

Pathlock_TDnR_CL

Stage 2: where

| where DataSource == "MISSING_OSS_NOTES"

Stage 3: project

| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
          Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
          MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AffectedUserproject
Areaproject
Bnameproject
CentralTsproject
DataSourceproject
DestIpproject
Eventidproject
Hostnameproject
Instanceproject
LogLineproject
MessageV1project
MessageV2project
MessageV3project
MessageV4project
MsgIdproject
MsgNoproject
MsgTypeproject
Reportproject
SrcIpproject
Subidproject
Sysidproject
Tcodeproject
TimeGeneratedproject

ProofpointPOD - Possible data exfiltration to private email

#
Status
available
Severity
medium
Time window
10m
Source
github.com/Azure/Azure-Sentinel

'Detects when sender sent email to the non-corporate domain and recipient's username is the same as sender's username.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: aedc5b33-2d7c-42cb-a692-f25ef637cbb1
name: ProofpointPOD - Possible data exfiltration to private email
description: |
  'Detects when sender sent email to the non-corporate domain and recipient's username is the same as sender's username.'
severity: Medium
status: Available 
requiredDataConnectors:
  - connectorId: ProofpointPOD
    dataTypes:
      - ProofpointPOD_message_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let lbtime = 10m;
  ProofpointPOD
  | where TimeGenerated > ago(lbtime)
  | where EventType == 'message'
  | where NetworkDirection == 'outbound'
  | where array_length(todynamic(DstUserUpn)) == 1
  | extend sender = extract(@'\A(.*?)@', 1, SrcUserUpn)
  | extend sender_domain = extract(@'@(.*)$', 1, SrcUserUpn)
  | extend recipient = extract(@'\A(.*?)@', 1, tostring(todynamic(DstUserUpn)[0]))
  | extend recipient_domain = extract(@'@(.*)$', 1, tostring(todynamic(DstUserUpn)[0]))
  | where sender =~ recipient
  | where sender_domain != recipient_domain
  | project SrcUserUpn, DstUserUpn
  | extend AccountCustomEntity = SrcUserUpn
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let lbtime = 10m;

Stage 1: source

ProofpointPOD

Stage 2: where

| where TimeGenerated > ago(lbtime)

Stage 3: where

| where EventType == 'message'

Stage 4: where

| where NetworkDirection == 'outbound'

Stage 5: where

| where array_length(todynamic(DstUserUpn)) == 1

Stage 6: extend (4 consecutive steps)

| extend sender = extract(@'\A(.*?)@', 1, SrcUserUpn)
| extend sender_domain = extract(@'@(.*)$', 1, SrcUserUpn)
| extend recipient = extract(@'\A(.*?)@', 1, tostring(todynamic(DstUserUpn)[0]))
| extend recipient_domain = extract(@'@(.*)$', 1, tostring(todynamic(DstUserUpn)[0]))

Stage 7: where

| where sender =~ recipient

Stage 8: where

| where sender_domain != recipient_domain

Stage 9: project

| project SrcUserUpn, DstUserUpn

Stage 10: extend

| extend AccountCustomEntity = SrcUserUpn

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstUserUpnproject
SrcUserUpnproject
AccountCustomEntityextend

SAP LogServ - HANA DB - User Admin actions

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

Identifies user administration actions. Souirce Action: Create/Update/Delete a DB User. Data Sources: SAP LogServ - HANA DB (Syslog)

MITRE ATT&CK Coverage

TacticTechniques
Privilege EscalationNo specific technique

Rule Body

id: a9e4b02a-5a8c-4c59-9836-a204d1028632
kind: Scheduled
name: SAP LogServ - HANA DB - User Admin actions
description: |
  Identifies user administration actions.
  
  Souirce Action: Create/Update/Delete a DB User.
  
  *Data Sources: SAP LogServ - HANA DB (Syslog)*
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SAPLogServ
    dataTypes:
      - Syslog
queryFrequency: 10m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
relevantTechniques: []
query: |
  let AuditTimeAgo = 60m;
  Syslog
  | where TimeGenerated >= ago(AuditTimeAgo)
  | where SourceSystem == "HANADB" and CollectorHostName == "SAPLogServ"
  | extend raw_split = split(SyslogMessage, ";")
  | extend
    event_timestamp__col_0 = tostring(raw_split[0]), 
    service_name__col_1 = tostring(raw_split[1]), 
    hostname__col_2 = tostring(raw_split[2]), 
    sid__col_3 = tostring(raw_split[3]), 
    instance_number__col_4 = tostring(raw_split[4]), 
    port_number__col_5 = tostring(raw_split[5]), 
    database_name__col_6 = tostring(raw_split[6]), 
    client_ip_address__col_7 = tostring(raw_split[7]), 
    client_name__col_8 = tostring(raw_split[8]), 
    client_process_id__col_9 = tostring(raw_split[9]), 
    client_port_number__col_10 = tostring(raw_split[10]), 
    policy_name__col_11 = tostring(raw_split[11]), 
    audit_level__col_12 = tostring(raw_split[12]), 
    audit_action__col_13 = tostring(raw_split[13]), 
    session_user__col_14 = tostring(raw_split[14]), 
    target_schema__col_15 = tostring(raw_split[15]), 
    target_object__col_16 = tostring(raw_split[16]), 
    privilege_name__col_17 = tostring(raw_split[17]), 
    grantable__col_18 = tostring(raw_split[18]), 
    role_name__col_19 = tostring(raw_split[19]), 
    target_principal__col_20 = tostring(raw_split[20]), 
    action_status__col_21 = tostring(raw_split[21]), 
    component__col_22 = tostring(raw_split[22]), 
    section__col_23 = tostring(raw_split[23]), 
    parameter__col_24 = tostring(raw_split[24]), 
    old_value__col_25 = tostring(raw_split[25]), 
    new_value__col_26 = tostring(raw_split[26]), 
    comment__col_27 = tostring(raw_split[27]), 
    executed_statement__col_28 = tostring(raw_split[28]), 
    session_id__col_29 = tostring(raw_split[29]), 
    application_user_name__col_30 = tostring(raw_split[30]), 
    role_schema_name__col_31 = tostring(raw_split[31]), 
    grantee_schema_name__col_32 = tostring(raw_split[32]), 
    origin_database_name__col_33 = tostring(raw_split[33]), 
    origin_user_name__col_34 = tostring(raw_split[34]), 
    xs_application_user_name__col_35 = tostring(raw_split[35]), 
    application_name__col_36 = tostring(raw_split[36]), 
    statement_user_name__col_37 = tostring(raw_split[37]), 
    create_time__col_38 = tostring(raw_split[38]), 
    xsa_message_ip__col_39 = tostring(raw_split[39]), 
    xsa_tenant__col_40 = tostring(raw_split[40]), 
    xsa_uuid__col_41 = tostring(raw_split[41]), 
    xsa_channel__col_42 = tostring(raw_split[42]), 
    xsa_attachment_id__col_43 = tostring(raw_split[43]), 
    xsa_attachment_name__col_44 = tostring(raw_split[44]), 
    xsa_organization_id__col_45 = tostring(raw_split[45]), 
    xsa_space_id__col_46 = tostring(raw_split[46]), 
    xsa_instance_id__col_47 = tostring(raw_split[47]), 
    xsa_binding_id__col_48 = tostring(raw_split[48]), 
    xsa_object__col_49 = tostring(raw_split[49]), 
    xsa_data_subject__col_50 = tostring(raw_split[50])
  | where 
    audit_action__col_13 =~ "CREATE USER" or
    audit_action__col_13 =~ 'ALTER USER' or
    audit_action__col_13 =~ 'DROP USER' or
    audit_action__col_13 =~ 'DROP SCHEMA'
  | extend AlertRuleUniqueName = 'hanadb-useradminactions-logserv'
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: sid__col_3
      - identifier: InstanceName
        columnName: database_name__col_6
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: hostname__col_2
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: client_ip_address__col_7
alertDetailsOverride:
  alertDisplayNameFormat: 'SAP LogServ - HANA DB - User Admin actions'
  alertDescriptionFormat: |
    {{comment__col_27}}
customDetails:
  SAP_User: session_user__col_14
version: 1.0.1

Stages and Predicates

Parameters

let AuditTimeAgo = 60m;

Stage 1: source

Syslog

Stage 2: where

| where TimeGenerated >= ago(AuditTimeAgo)

Stage 3: where

| where SourceSystem == "HANADB" and CollectorHostName == "SAPLogServ"

Stage 4: extend

| extend raw_split = split(SyslogMessage, ";")

Stage 5: extend

| extend
  event_timestamp__col_0 = tostring(raw_split[0]), 
  service_name__col_1 = tostring(raw_split[1]), 
  hostname__col_2 = tostring(raw_split[2]), 
  sid__col_3 = tostring(raw_split[3]), 
  instance_number__col_4 = tostring(raw_split[4]), 
  port_number__col_5 = tostring(raw_split[5]), 
  database_name__col_6 = tostring(raw_split[6]), 
  client_ip_address__col_7 = tostring(raw_split[7]), 
  client_name__col_8 = tostring(raw_split[8]), 
  client_process_id__col_9 = tostring(raw_split[9]), 
  client_port_number__col_10 = tostring(raw_split[10]), 
  policy_name__col_11 = tostring(raw_split[11]), 
  audit_level__col_12 = tostring(raw_split[12]), 
  audit_action__col_13 = tostring(raw_split[13]), 
  session_user__col_14 = tostring(raw_split[14]), 
  target_schema__col_15 = tostring(raw_split[15]), 
  target_object__col_16 = tostring(raw_split[16]), 
  privilege_name__col_17 = tostring(raw_split[17]), 
  grantable__col_18 = tostring(raw_split[18]), 
  role_name__col_19 = tostring(raw_split[19]), 
  target_principal__col_20 = tostring(raw_split[20]), 
  action_status__col_21 = tostring(raw_split[21]), 
  component__col_22 = tostring(raw_split[22]), 
  section__col_23 = tostring(raw_split[23]), 
  parameter__col_24 = tostring(raw_split[24]), 
  old_value__col_25 = tostring(raw_split[25]), 
  new_value__col_26 = tostring(raw_split[26]), 
  comment__col_27 = tostring(raw_split[27]), 
  executed_statement__col_28 = tostring(raw_split[28]), 
  session_id__col_29 = tostring(raw_split[29]), 
  application_user_name__col_30 = tostring(raw_split[30]), 
  role_schema_name__col_31 = tostring(raw_split[31]), 
  grantee_schema_name__col_32 = tostring(raw_split[32]), 
  origin_database_name__col_33 = tostring(raw_split[33]), 
  origin_user_name__col_34 = tostring(raw_split[34]), 
  xs_application_user_name__col_35 = tostring(raw_split[35]), 
  application_name__col_36 = tostring(raw_split[36]), 
  statement_user_name__col_37 = tostring(raw_split[37]), 
  create_time__col_38 = tostring(raw_split[38]), 
  xsa_message_ip__col_39 = tostring(raw_split[39]), 
  xsa_tenant__col_40 = tostring(raw_split[40]), 
  xsa_uuid__col_41 = tostring(raw_split[41]), 
  xsa_channel__col_42 = tostring(raw_split[42]), 
  xsa_attachment_id__col_43 = tostring(raw_split[43]), 
  xsa_attachment_name__col_44 = tostring(raw_split[44]), 
  xsa_organization_id__col_45 = tostring(raw_split[45]), 
  xsa_space_id__col_46 = tostring(raw_split[46]), 
  xsa_instance_id__col_47 = tostring(raw_split[47]), 
  xsa_binding_id__col_48 = tostring(raw_split[48]), 
  xsa_object__col_49 = tostring(raw_split[49]), 
  xsa_data_subject__col_50 = tostring(raw_split[50])

Stage 6: where

| where 
  audit_action__col_13 =~ "CREATE USER" or
  audit_action__col_13 =~ 'ALTER USER' or
  audit_action__col_13 =~ 'DROP USER' or
  audit_action__col_13 =~ 'DROP SCHEMA'

Stage 7: extend

| extend AlertRuleUniqueName = 'hanadb-useradminactions-logserv'

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
raw_splitextend
action_status__col_21extend
application_name__col_36extend
application_user_name__col_30extend
audit_action__col_13extend
audit_level__col_12extend
client_ip_address__col_7extend
client_name__col_8extend
client_port_number__col_10extend
client_process_id__col_9extend
comment__col_27extend
component__col_22extend
create_time__col_38extend
database_name__col_6extend
event_timestamp__col_0extend
executed_statement__col_28extend
grantable__col_18extend
grantee_schema_name__col_32extend
hostname__col_2extend
instance_number__col_4extend
new_value__col_26extend
old_value__col_25extend
origin_database_name__col_33extend
origin_user_name__col_34extend
parameter__col_24extend
policy_name__col_11extend
port_number__col_5extend
privilege_name__col_17extend
role_name__col_19extend
role_schema_name__col_31extend
section__col_23extend
service_name__col_1extend
session_id__col_29extend
session_user__col_14extend
sid__col_3extend
statement_user_name__col_37extend
target_object__col_16extend
target_principal__col_20extend
target_schema__col_15extend
xs_application_user_name__col_35extend
xsa_attachment_id__col_43extend
xsa_attachment_name__col_44extend
xsa_binding_id__col_48extend
xsa_channel__col_42extend
xsa_data_subject__col_50extend
xsa_instance_id__col_47extend
xsa_message_ip__col_39extend
xsa_object__col_49extend
xsa_organization_id__col_45extend
xsa_space_id__col_46extend
xsa_tenant__col_40extend
xsa_uuid__col_41extend
AlertRuleUniqueNameextend

Squid proxy events related to mining pools

#
Severity
low
Time window
1d
Source
github.com/Azure/Azure-Sentinel

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

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

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

Stages and Predicates

Let Binding: DomainList used in Stage 7

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

Stage 1: source

Syslog

Stage 2: where

| where ProcessName contains "squid"

Stage 3: extend

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

Stage 4: extend

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

Stage 5: where

| where HTTP_Status_Code == '200'

Stage 6: where

| where Domain contains "."

Stage 7: where

| where Domain has_any (DomainList)

Stage 8: extend

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

Indicators

These rows show field, operator, and value matches.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
Bytesextend
Domainextend
HTTP_Status_Codeextend
RemotePortextend
SourceIPextend
Statusextend
URLextend
Userextend
contentTypeextend
TLDextend
AccountNameextend
AccountUPNSuffixextend

TI map Email entity to SigninLogs

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

Identifies a match in SigninLogs table from any Email IOC from TI

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

id: 30fa312c-31eb-43d8-b0cc-bcbdfb360822
name: TI map Email entity to SigninLogs
description: |
  'Identifies a match in SigninLogs table from any Email IOC from TI'
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
  - 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-.]+$';
  let Signins = materialize(union isfuzzy=true
  ( SigninLogs | where TimeGenerated >= ago(dt_lookBack)),
  ( AADNonInteractiveUserSignInLogs | where TimeGenerated >= ago(dt_lookBack)
      | extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails))
  | where isnotempty(UserPrincipalName) and UserPrincipalName matches regex emailregex
  | extend UserPrincipalName = tolower(UserPrincipalName)
  | extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)
  | extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
  | extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
  | extend SigninLogs_TimeGenerated = TimeGenerated);
  let SigninUPNs = Signins | distinct UserPrincipalName | summarize make_list(UserPrincipalName);
  ThreatIntelligenceIndicator
  //Filtering the table for Email related IOCs
  | where isnotempty(EmailSenderAddress)
  | where TimeGenerated >= ago(ioc_lookBack)
  | where EmailSenderAddress in (SigninUPNs)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now()
  | where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
  | join kind=innerunique (Signins) on $left.EmailSenderAddress == $right.UserPrincipalName
  | where SigninLogs_TimeGenerated < ExpirationDateTime
  | summarize SigninLogs_TimeGenerated = arg_max(SigninLogs_TimeGenerated, *) by IndicatorId, UserPrincipalName
  | project SigninLogs_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, EmailSenderName, EmailRecipient, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, FileHashValue, FileHashType, IPAddress, UserPrincipalName, AppDisplayName, StatusCode, StatusDetails, NetworkIP, NetworkDestinationIP, NetworkSourceIP, Type
  | extend Name = tostring(split(UserPrincipalName, '@', 0)[0]), UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
  | extend timestamp = SigninLogs_TimeGenerated
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix 
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.2.7
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-.]+$';

Let Binding: Signins used in Stage 8

let Signins = materialize(union isfuzzy=true
( SigninLogs | where TimeGenerated >= ago(dt_lookBack)),
( AADNonInteractiveUserSignInLogs | where TimeGenerated >= ago(dt_lookBack)
    | extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails))
| where isnotempty(UserPrincipalName) and UserPrincipalName matches regex emailregex
| extend UserPrincipalName = tolower(UserPrincipalName)
| extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
| extend SigninLogs_TimeGenerated = TimeGenerated);

Let Binding: SigninUPNs used in Stage 4

let SigninUPNs = Signins | distinct UserPrincipalName | summarize make_list(UserPrincipalName);

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where isnotempty(EmailSenderAddress)

Stage 3: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 4: where

| where EmailSenderAddress in (SigninUPNs)

Stage 5: summarize

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

Stage 6: where

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

Stage 7: where

| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"

Stage 8: join

join kind=innerunique (Signins) on $left.EmailSenderAddress, $right.UserPrincipalName

Stage 9: where

where SigninLogs_TimeGenerated < ExpirationDateTime

Stage 10: summarize

summarize SigninLogs_TimeGenerated by IndicatorId, UserPrincipalName

Stage 11: project

project ActivityGroupNames, AppDisplayName, ConfidenceScore, Description, EmailRecipient, EmailSenderName, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, ExpirationDateTime, FileHashType, FileHashValue, IPAddress, IndicatorId, NetworkDestinationIP, NetworkIP, NetworkSourceIP, SigninLogs_TimeGenerated, StatusCode, StatusDetails, ThreatType, Type, Url, UserPrincipalName

Stage 12: extend

extend Name, UPNSuffix

Stage 13: extend

extend timestamp

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DescriptioncontainsState: falsepos;excludes:Description field:"Description" value:"State: falsepos;"
DescriptioncontainsState: inactive;excludes:Description field:"Description" value:"State: inactive;"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
AppDisplayNameproject
ConfidenceScoreproject
Descriptionproject
EmailRecipientproject
EmailSenderNameproject
EmailSourceDomainproject
EmailSourceIpAddressproject
EmailSubjectproject
ExpirationDateTimeproject
FileHashTypeproject
FileHashValueproject
IPAddressproject
IndicatorIdproject
NetworkDestinationIPproject
NetworkIPproject
NetworkSourceIPproject
SigninLogs_TimeGeneratedproject
StatusCodeproject
StatusDetailsproject
ThreatTypeproject
Typeproject
Urlproject
UserPrincipalNameproject
Nameextend
UPNSuffixextend
timestampextend

TI Map URL Entity to EmailUrlInfo

#
Severity
medium
Time window
14d
Group by
$left.Url, $right.UrlDomain, Id, IndicatorId, NetworkMessageId, ObservableValue, Url
Source
github.com/Azure/Azure-Sentinel

This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in EmailUrlInfo.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

Rule Body

id: 9e32e545-e60c-47de-9941-f9ca1ada0a42
name: TI Map URL Entity to EmailUrlInfo
description: |
  'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in EmailUrlInfo.'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - EmailUrlInfo
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let EmailUrlInfo_ = materialize(EmailUrlInfo
      | where TimeGenerated >= ago(dt_lookBack)
      | extend EmailUrlInfo_TimeGenerated = TimeGenerated);
  let EmailUrls = EmailUrlInfo_ | distinct Url | summarize make_list(Url);
  let EmailUrlDomains = EmailUrlInfo_ | distinct UrlDomain | summarize make_list(UrlDomain);
  let EmailEvents_ = materialize(EmailEvents
      | where TimeGenerated >= ago(dt_lookBack));
  let TI = materialize(ThreatIntelIndicators
     //extract key part of kv pair
  | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
  | where IndicatorType == "url"
  | extend Url = ObservableValue
  | extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
  | extend IndicatorId = tostring(split(Id, "--")[2])
      | where TimeGenerated >= ago(ioc_lookBack)
      | where tolower(Url) in (EmailUrls) or tolower(Url) in (EmailUrlDomains)
      | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
      | where IsActive and (ValidUntil > now() or isempty(ValidUntil)));
  (union
      (TI | join kind=innerunique (EmailUrlInfo_) on Url),
      (TI | join kind=innerunique (EmailUrlInfo_) on $left.Url == $right.UrlDomain))
  | where EmailUrlInfo_TimeGenerated < ValidUntil
  | summarize EmailUrlInfo_TimeGenerated = arg_max(EmailUrlInfo_TimeGenerated, *) by IndicatorId, Url
  | extend Description = tostring(parse_json(Data).description)
  | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
  | project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, IndicatorId, Type, ValidUntil, Confidence, Url, UrlDomain, UrlLocation, NetworkMessageId
  | extend timestamp = EmailUrlInfo_TimeGenerated
  | join kind=inner (EmailEvents_) on NetworkMessageId
  | where DeliveryAction !has "Blocked"
  | extend Name = tostring(split(RecipientEmailAddress, '@', 0)[0]), UPNSuffix = tostring(split(RecipientEmailAddress, '@', 1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: RecipientEmailAddress
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.0.5
kind: Scheduled

Stages and Predicates

Parameters

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

Let Binding: EmailUrlInfo_

let EmailUrlInfo_ = materialize(EmailUrlInfo
    | where TimeGenerated >= ago(dt_lookBack)
    | extend EmailUrlInfo_TimeGenerated = TimeGenerated);

Let Binding: EmailUrls

let EmailUrls = EmailUrlInfo_ | distinct Url | summarize make_list(Url);

Let Binding: EmailUrlDomains

let EmailUrlDomains = EmailUrlInfo_ | distinct UrlDomain | summarize make_list(UrlDomain);

Let Binding: EmailEvents_

let EmailEvents_ = materialize(EmailEvents
    | where TimeGenerated >= ago(dt_lookBack));

Union (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: TI, TI

Leg 1: TI

TI | join kind=innerunique (EmailUrlInfo_) on Url

Leg 2: TI

TI | join kind=innerunique (EmailUrlInfo_) on $left.Url == $right.UrlDomain

Applied to the Combined Result

| where EmailUrlInfo_TimeGenerated < ValidUntil
| summarize EmailUrlInfo_TimeGenerated = arg_max(EmailUrlInfo_TimeGenerated, *) by IndicatorId, Url
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, IndicatorId, Type, ValidUntil, Confidence, Url, UrlDomain, UrlLocation, NetworkMessageId
| extend timestamp = EmailUrlInfo_TimeGenerated
| join kind=inner (EmailEvents_) on NetworkMessageId
| where DeliveryAction !has "Blocked"
| extend Name = tostring(split(RecipientEmailAddress, '@', 0)[0]), UPNSuffix = tostring(split(RecipientEmailAddress, '@', 1)[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
IndicatorTypeeq
  • url
field:"IndicatorType" kind:eq value:"url"
IsActiveeq
  • true transforms: boolean
field:"IsActive" kind:eq value:"true"
Urlin
  • EmailUrlDomains transforms: tolower
  • EmailUrls transforms: tolower
field:"Url" kind:in
ValidUntilis_null
  • (no value, null check)
field:"ValidUntil" kind:is_null

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DeliveryActionmatchBlockedexcludes:DeliveryAction field:"DeliveryAction" value:"Blocked"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
Confidenceproject
Descriptionproject
EmailUrlInfo_TimeGeneratedproject
IndicatorIdproject
NetworkMessageIdproject
Typeproject
Urlproject
UrlDomainproject
UrlLocationproject
ValidUntilproject
timestampextend
Nameextend
UPNSuffixextend

TI Map URL Entity to SecurityAlert Data

#
Severity
medium
Time window
14d
Group by
AlertName, Id, ObservableValue, Url
Source
github.com/Azure/Azure-Sentinel

'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in SecurityAlert data.'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 3b6bdb38-93c5-452f-ab3a-97a3d1320d16
name: TI Map URL Entity to SecurityAlert Data
description: |
  'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in SecurityAlert data.'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftCloudAppSecurity
    dataTypes:
      - SecurityAlert
  - connectorId: AzureSecurityCenter
    dataTypes:
      - SecurityAlert
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelIndicators
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let URLRegex = "((https?|ftp|ldap|wss?|file):\\/\\/(([\\:\\%\\w\\_\\-]+(\\.|@))*((xn--)?[a-zA-Z0-9\\-]+\\.)+(xn--[a-z0-9]+|[A-Za-z]+)|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{0,3})[.,:\\w@?^=%&\\/~+#-]*[\\w@?^=%&\\/~+#-])";
  let SecurityEvents = materialize( 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
    | mv-expand Entity = parse_json(Entities)
    | extend RawUrl = coalesce(Entity.Url, Entity.Urls)
    | mv-expand RawUrl
    | extend Url = tostring(RawUrl)
    | extend Compromised_Host = tostring(parse_json(ExtendedProperties).["Compromised Host"])
    | extend Alert_TimeGenerated = TimeGenerated);
  let EventUrls = materialize(SecurityEvents | distinct Url | summarize make_list(Url));
  ThreatIntelIndicators
  //extract key part of kv pair
  | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
  | where IndicatorType == "url"
  | extend Url = ObservableValue
  | extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
  | where TimeGenerated >= ago(ioc_lookBack)
  | where tolower(Url) in (EventUrls)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
  | where IsActive and (ValidUntil > now() or isempty(ValidUntil))
  | extend Description = tostring(parse_json(Data).description)
  | where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;" 
   | project-reorder *, Tags, TrafficLightProtocolLevel, Url, Type
  // 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 (SecurityEvents) on Url
  | where Alert_TimeGenerated < ValidUntil
  | extend Description = tostring(parse_json(Data).description)
  | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
  | summarize Alert_TimeGenerated = arg_max(Alert_TimeGenerated, *) by Id, AlertName
  | project timestamp = Alert_TimeGenerated, ActivityGroupNames, Id, Type, ValidUntil, Confidence, AlertName, AlertSeverity, Description, Url, Compromised_Host
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Compromised_Host
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.2.11
kind: Scheduled

Stages and Predicates

Parameters

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

Let Binding: URLRegex

let URLRegex = "((https?|ftp|ldap|wss?|file):\\/\\/(([\\:\\%\\w\\_\\-]+(\\.|@))*((xn--)?[a-zA-Z0-9\\-]+\\.)+(xn--[a-z0-9]+|[A-Za-z]+)|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{0,3})[.,:\\w@?^=%&\\/~+#-]*[\\w@?^=%&\\/~+#-])";

Let Binding: SecurityEvents used in Stage 13

let SecurityEvents = materialize( 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
  | mv-expand Entity = parse_json(Entities)
  | extend RawUrl = coalesce(Entity.Url, Entity.Urls)
  | mv-expand RawUrl
  | extend Url = tostring(RawUrl)
  | extend Compromised_Host = tostring(parse_json(ExtendedProperties).["Compromised Host"])
  | extend Alert_TimeGenerated = TimeGenerated);

Let Binding: EventUrls used in Stage 7

let EventUrls = materialize(SecurityEvents | distinct Url | summarize make_list(Url));

Stage 1: source

ThreatIntelIndicators

Stage 2: extend

| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))

Stage 3: where

| where IndicatorType == "url"

Stage 4: extend

| extend Url = ObservableValue

Stage 5: extend

| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)

Stage 6: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 7: where

| where tolower(Url) in (EventUrls)

Stage 8: summarize

| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue

Stage 9: where

| where IsActive and (ValidUntil > now() or isempty(ValidUntil))

Stage 10: extend

| extend Description = tostring(parse_json(Data).description)

Stage 11: where

| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"

Stage 12: project-reorder

| project-reorder *, Tags, TrafficLightProtocolLevel, Url, Type

Stage 13: join

| join kind=innerunique (SecurityEvents) on Url

Stage 14: where

where Alert_TimeGenerated < ValidUntil

Stage 15: extend

extend Description

Stage 16: extend

extend ActivityGroupNames

Stage 17: summarize

summarize Alert_TimeGenerated by Id, AlertName

Stage 18: project

project ActivityGroupNames, AlertName, AlertSeverity, Compromised_Host, Confidence, Description, Id, Type, Url, ValidUntil, timestamp

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
DescriptioncontainsState: falsepos;excludes:Description field:"Description" value:"State: falsepos;"
DescriptioncontainsState: inactive;excludes:Description field:"Description" value:"State: inactive;"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
AlertNameproject
AlertSeverityproject
Compromised_Hostproject
Confidenceproject
Descriptionproject
Idproject
Typeproject
Urlproject
ValidUntilproject
timestampproject

User account created and deleted within 10 mins

#
Severity
medium
Time window
25h
Group by
Computer, TargetAccount
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Identifies when a user account is created and then deleted within 10 minutes. This can be an indication of compromise and an adversary attempting to hide in the noise.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 4b93c5af-d20b-4236-b696-a28b8c51407f
name: User account created and deleted within 10 mins
description: |
  'Identifies when a user account is created and then deleted within 10 minutes. This can be an indication of compromise and an adversary attempting to hide in the noise.'
severity: Medium
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 1d
queryPeriod: 25h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1098
  - T1078
query: |
  let timeframe = 1d;
  let spanoftime = 10m;
  let threshold = 0;
  (union isfuzzy=true
      (SecurityEvent
      | where TimeGenerated > ago(timeframe+spanoftime)
      // A user account was created
      | where EventID == 4720
      | where AccountType =~ "User"
      | project creationTime = TimeGenerated, CreateEventID = EventID, CreateActivity = Activity, Computer = toupper(Computer), 
      TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
      AccountUsedToCreate = SubjectAccount, CreatedBySubjectUserName = SubjectUserName, CreatedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToCreate = SubjectUserSid, UserPrincipalName
      ),
      (
      WindowsEvent
      | where TimeGenerated > ago(timeframe+spanoftime)
      // A user account was created
      | where EventID == 4720
      | extend SubjectUserSid = tostring(EventData.SubjectUserSid)
      | extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
      | where AccountType =~ "User"
      | extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
      | extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
      | extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
      | extend TargetSid = tostring(EventData.TargetSid)
      | extend UserPrincipalName = tostring(EventData.UserPrincipalName)
      | extend Activity = "4720 - A user account was created."
      | extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName) 
      | project creationTime = TimeGenerated, CreateEventID = EventID, CreateActivity = Activity, Computer = toupper(Computer), 
      TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
      AccountUsedToCreate = SubjectAccount, CreatedBySubjectUserName = SubjectUserName, CreatedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToCreate = SubjectUserSid, UserPrincipalName
      )
    )
  | join kind = inner 
  (
    (union isfuzzy=true
      (SecurityEvent
      | where TimeGenerated > ago(timeframe)
      // A user account was deleted
      | where EventID == 4726
      | where AccountType == "User"
      | project deletionTime = TimeGenerated, DeleteEventID = EventID, DeleteActivity = Activity, Computer = toupper(Computer), 
      TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
      AccountUsedToDelete = SubjectAccount, DeletedBySubjectUserName = SubjectUserName, DeletedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDelete = SubjectUserSid, UserPrincipalName
      ),
      (WindowsEvent
      | where TimeGenerated > ago(timeframe)
      // A user account was deleted
      | where EventID == 4726
      | extend SubjectUserSid = tostring(EventData.SubjectUserSid)
      | extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
      | extend AccountType=case(SubjectAccount endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
      | where AccountType == "User"
      | extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
      | extend TargetSid = tostring(EventData.TargetSid)
      | extend UserPrincipalName = tostring(EventData.UserPrincipalName)
      | extend Activity = "4726 - A user account was deleted."
      | extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName) 
      | extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
      | project deletionTime = TimeGenerated, DeleteEventID = EventID, DeleteActivity = Activity, Computer = toupper(Computer), 
      TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
      AccountUsedToDelete = SubjectAccount, DeletedBySubjectUserName = SubjectUserName, DeletedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDelete = SubjectUserSid, UserPrincipalName
      )
    )
  ) on Computer, TargetAccount
  | where deletionTime - creationTime < spanoftime
  | extend TimeDelta = deletionTime - creationTime
  | where tolong(TimeDelta) >= threshold
  | project TimeDelta, creationTime, CreateEventID, CreateActivity, Computer, TargetAccount, TargetSid, UserPrincipalName, AccountUsedToCreate, SIDofAccountUsedToCreate,
  deletionTime, DeleteEventID, DeleteActivity, AccountUsedToDelete, SIDofAccountUsedToDelete, TargetUserName, TargetDomainName, 
  CreatedBySubjectUserName, CreatedBySubjectDomainName, DeletedBySubjectUserName, DeletedBySubjectDomainName
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountUsedToCreate
      - identifier: Name
        columnName: CreatedBySubjectUserName
      - identifier: NTDomain
        columnName: CreatedBySubjectDomainName
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountUsedToDelete
      - identifier: Name
        columnName: DeletedBySubjectUserName
      - identifier: NTDomain
        columnName: DeletedBySubjectDomainName
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetAccount
      - identifier: Name
        columnName: TargetUserName
      - identifier: NTDomain
        columnName: TargetDomainName
  - entityType: Account
    fieldMappings:
      - identifier: Sid
        columnName: TargetSid
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
version: 1.2.2
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Parameters

let timeframe = 1d;
let spanoftime = 10m;
let threshold = 0;

Union isfuzzy=true (2 Sources)

Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, WindowsEvent

Leg 1: SecurityEvent

SecurityEvent
    | where TimeGenerated > ago(timeframe+spanoftime)
    | where EventID == 4720
    | where AccountType =~ "User"
    | project creationTime = TimeGenerated, CreateEventID = EventID, CreateActivity = Activity, Computer = toupper(Computer), 
    TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
    AccountUsedToCreate = SubjectAccount, CreatedBySubjectUserName = SubjectUserName, CreatedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToCreate = SubjectUserSid, UserPrincipalName

Leg 2: WindowsEvent

WindowsEvent
    | where TimeGenerated > ago(timeframe+spanoftime)
    | where EventID == 4720
    | extend SubjectUserSid = tostring(EventData.SubjectUserSid)
    | extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
    | where AccountType =~ "User"
    | extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
    | extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
    | extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
    | extend TargetSid = tostring(EventData.TargetSid)
    | extend UserPrincipalName = tostring(EventData.UserPrincipalName)
    | extend Activity = "4720 - A user account was created."
    | extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName) 
    | project creationTime = TimeGenerated, CreateEventID = EventID, CreateActivity = Activity, Computer = toupper(Computer), 
    TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
    AccountUsedToCreate = SubjectAccount, CreatedBySubjectUserName = SubjectUserName, CreatedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToCreate = SubjectUserSid, UserPrincipalName

Applied to the Combined Result

| join kind = inner 
(
  (union isfuzzy=true
    (SecurityEvent
    | where TimeGenerated > ago(timeframe)
    | where EventID == 4726
    | where AccountType == "User"
    | project deletionTime = TimeGenerated, DeleteEventID = EventID, DeleteActivity = Activity, Computer = toupper(Computer), 
    TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
    AccountUsedToDelete = SubjectAccount, DeletedBySubjectUserName = SubjectUserName, DeletedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDelete = SubjectUserSid, UserPrincipalName
    ),
    (WindowsEvent
    | where TimeGenerated > ago(timeframe)
    | where EventID == 4726
    | extend SubjectUserSid = tostring(EventData.SubjectUserSid)
    | extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
    | extend AccountType=case(SubjectAccount endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
    | where AccountType == "User"
    | extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
    | extend TargetSid = tostring(EventData.TargetSid)
    | extend UserPrincipalName = tostring(EventData.UserPrincipalName)
    | extend Activity = "4726 - A user account was deleted."
    | extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName) 
    | extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
    | project deletionTime = TimeGenerated, DeleteEventID = EventID, DeleteActivity = Activity, Computer = toupper(Computer), 
    TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid, 
    AccountUsedToDelete = SubjectAccount, DeletedBySubjectUserName = SubjectUserName, DeletedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDelete = SubjectUserSid, UserPrincipalName
    )
  )
) on Computer, TargetAccount
| where deletionTime - creationTime < spanoftime
| extend TimeDelta = deletionTime - creationTime
| where tolong(TimeDelta) >= threshold
| project TimeDelta, creationTime, CreateEventID, CreateActivity, Computer, TargetAccount, TargetSid, UserPrincipalName, AccountUsedToCreate, SIDofAccountUsedToCreate,
deletionTime, DeleteEventID, DeleteActivity, AccountUsedToDelete, SIDofAccountUsedToDelete, TargetUserName, TargetDomainName, 
CreatedBySubjectUserName, CreatedBySubjectDomainName, DeletedBySubjectUserName, DeletedBySubjectDomainName
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AccountTypeeq
  • User corpus 4 (kusto 4)
  • User
field:"AccountType" kind:eq
EventIDeq
  • 4720 corpus 3 (kusto 3)
  • 4726
field:"EventID" kind:eq
TimeDeltage
  • 0 transforms: tolong
field:"TimeDelta" kind:ge value:"0"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountUsedToCreateproject
AccountUsedToDeleteproject
Computerproject
CreateActivityproject
CreateEventIDproject
CreatedBySubjectDomainNameproject
CreatedBySubjectUserNameproject
DeleteActivityproject
DeleteEventIDproject
DeletedBySubjectDomainNameproject
DeletedBySubjectUserNameproject
SIDofAccountUsedToCreateproject
SIDofAccountUsedToDeleteproject
TargetAccountproject
TargetDomainNameproject
TargetSidproject
TargetUserNameproject
TimeDeltaproject
UserPrincipalNameproject
creationTimeproject
deletionTimeproject
HostNameextend
HostNameDomainextend

Back to all Kusto rules