Detection rules › Kusto

Kusto rules: 32

AWS Security Hub - Detect IAM root user Access Key existence

#
Status
available
Severity
high
Time window
1h
Group by
AwsAccountId, AwsRegion, AwsSecurityFindingDescription, AwsSecurityFindingId, AwsSecurityFindingTitle, ComplianceSecurityControlId, RootUserARN
Source
github.com/Azure/Azure-Sentinel

This query detects AWS Security Hub control IAM.4 findings indicating that an AWS account root user Access Key exists. A root user Access Key presents a high risk of privilege abuse and should be removed.

MITRE ATT&CK Coverage

Rule Body

id: 171cbece-be87-4467-8754-63d82b3d3dfb
name: AWS Security Hub - Detect IAM root user Access Key existence
description: |
  This query detects AWS Security Hub control IAM.4 findings indicating that an AWS account root user Access Key exists.
  A root user Access Key presents a high risk of privilege abuse and should be removed.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AWSSecurityHub
    dataTypes:
      - AWSSecurityHubFindings
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
  - Persistence
relevantTechniques:
  - T1134.001
  - T1134.003
  - T1098.001
tags:
  - PCI DSS v3.2.1
  - NIST 800-53 r5
  - CIS AWS Foundations Benchmark v1.4.0
query: |
  AWSSecurityHubFindings
  | where RecordState == "ACTIVE" and ComplianceStatus == "FAILED"
  | where tostring(AwsSecurityFindingGeneratorId) == "security-control/IAM.4"
    or tostring(ComplianceSecurityControlId) == "IAM.4"
  | extend RootUserARN = tostring(Resources[0].Id)
  | summarize TimeGenerated = max(TimeGenerated) by AwsAccountId, AwsRegion, AwsSecurityFindingTitle, AwsSecurityFindingDescription, AwsSecurityFindingId, ComplianceSecurityControlId, RootUserARN
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AwsAccountId
      - identifier: CloudAppAccountId
        columnName: RootUserARN
customDetails:
  ComplianceControlId: ComplianceSecurityControlId
  Region: AwsRegion
  FindingId: AwsSecurityFindingId
  RootUserARN: RootUserARN
alertDetailsOverride:
  alertDisplayNameFormat: "AWS Account {{AwsAccountId}} root access key exists"
  alertDescriptionFormat: "AWS Account {{AwsAccountId}} has a root user access key (Resource: {{RootUserARN}})."
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

AWSSecurityHubFindings

Stage 2: where

| where RecordState == "ACTIVE" and ComplianceStatus == "FAILED"

Stage 3: where

| where tostring(AwsSecurityFindingGeneratorId) == "security-control/IAM.4"
  or tostring(ComplianceSecurityControlId) == "IAM.4"

Stage 4: extend

| extend RootUserARN = tostring(Resources[0].Id)

Stage 5: summarize

| summarize TimeGenerated = max(TimeGenerated) by AwsAccountId, AwsRegion, AwsSecurityFindingTitle, AwsSecurityFindingDescription, AwsSecurityFindingId, ComplianceSecurityControlId, RootUserARN

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AwsAccountIdsummarize
AwsRegionsummarize
AwsSecurityFindingDescriptionsummarize
AwsSecurityFindingIdsummarize
AwsSecurityFindingTitlesummarize
ComplianceSecurityControlIdsummarize
RootUserARNsummarize
TimeGeneratedsummarize

AWS Security Hub - Detect SQS Queue policy allowing public access

#
Status
available
Severity
high
Time window
1h
Group by
AwsAccountId, AwsRegion, AwsSecurityFindingDescription, AwsSecurityFindingId, AwsSecurityFindingTitle, ComplianceSecurityControlId, QueueArn
Source
github.com/Azure/Azure-Sentinel

This query detects Amazon SQS queues with access policies that allow public (unauthenticated or cross-account unrestricted) access, using AWS Security Hub control SQS.3 findings. Publicly accessible queues can enable data exfiltration, unauthorized message injection, or disruption of workflows.

MITRE ATT&CK Coverage

Rule Body

id: 4f0f3c2a-8d44-43f8-9d9a-5b1e0d5f2c11
name: AWS Security Hub - Detect SQS Queue policy allowing public access
description: |
  This query detects Amazon SQS queues with access policies that allow public (unauthenticated or cross-account unrestricted) access, using AWS Security Hub control SQS.3 findings.
  Publicly accessible queues can enable data exfiltration, unauthorized message injection, or disruption of workflows.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AWSSecurityHub
    dataTypes:
      - AWSSecurityHubFindings
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - Collection
relevantTechniques:
  - T1567
  - T1530
tags:
  - AWS Foundational Security Best Practices v1.0.0
query: |
  AWSSecurityHubFindings
  | where RecordState == "ACTIVE" and ComplianceStatus == "FAILED"
  | where tostring(AwsSecurityFindingGeneratorId) == "security-control/SQS.3"
        or tostring(ComplianceSecurityControlId) == "SQS.3"
  | mv-expand Resource = Resources
  | where tostring(Resource.Type) == "AwsSqsQueue"
  | extend QueueArn = tostring(Resource.Id)
  | summarize TimeGenerated = max(TimeGenerated)
      by AwsAccountId, AwsRegion, AwsSecurityFindingTitle, AwsSecurityFindingDescription,
         AwsSecurityFindingId, ComplianceSecurityControlId, QueueArn
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AwsAccountId
      - identifier: CloudAppAccountId
        columnName: AwsAccountId
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: QueueArn
customDetails:
  ComplianceControlId: ComplianceSecurityControlId
  Region: AwsRegion
  FindingId: AwsSecurityFindingId
  QueueArn: QueueArn
alertDetailsOverride:
  alertDisplayNameFormat: "Public access detected for SQS queue {{QueueArn}}"
  alertDescriptionFormat: |-
    AWS Account {{AwsAccountId}} has an SQS queue ({{QueueArn}}) with a policy permitting public access. Review and restrict the queue access policy.
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

AWSSecurityHubFindings

Stage 2: where

| where RecordState == "ACTIVE" and ComplianceStatus == "FAILED"

Stage 3: where

| where tostring(AwsSecurityFindingGeneratorId) == "security-control/SQS.3"
      or tostring(ComplianceSecurityControlId) == "SQS.3"

Stage 4: mv-expand

| mv-expand Resource = Resources

Stage 5: where

| where tostring(Resource.Type) == "AwsSqsQueue"

Stage 6: extend

| extend QueueArn = tostring(Resource.Id)

Stage 7: summarize

| summarize TimeGenerated = max(TimeGenerated)
    by AwsAccountId, AwsRegion, AwsSecurityFindingTitle, AwsSecurityFindingDescription,
       AwsSecurityFindingId, ComplianceSecurityControlId, QueueArn

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AwsAccountIdsummarize
AwsRegionsummarize
AwsSecurityFindingDescriptionsummarize
AwsSecurityFindingIdsummarize
AwsSecurityFindingTitlesummarize
ComplianceSecurityControlIdsummarize
QueueArnsummarize
TimeGeneratedsummarize

BloodHound Attack Path Finding - GenericAll Privileges on Tier Zero Objects

#

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 - GenericAll Privileges on Tier Zero Objects

Rule Body

id: 5ae4f7e1-b14c-45fe-8d38-f409e148c1da
name: BloodHound Attack Path Finding - GenericAll Privileges on Tier Zero Objects
description: BloodHound Attack Path Finding - GenericAll Privileges on Tier Zero Objects
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BloodHoundEnterprise
    dataTypes:
      - BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: "BHEAttackPathsData_CL \n| where Finding == \"T0GenericAll\""
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 == "T0GenericAll"

Indicators

These rows show field, operator, and value matches.

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

Cognni Incidents for Medium Sensitivity Governance Information

#

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

'Display incidents in which medium sensitivity governance information was placed at risk by user sharing.'

MITRE ATT&CK Coverage

TacticTechniques
Collection

Rule Body

id: c1d4a005-e220-4d06-9e53-7326a22b8fe4
name: Cognni Incidents for Medium Sensitivity Governance Information
description: |
  'Display incidents in which medium sensitivity governance information was placed at risk by user sharing.'
severity: Medium
requiredDataConnectors:
  - connectorId: CognniSentinelDataConnector
    dataTypes:
      - CognniIncidents_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - Collection
relevantTechniques:
  - T1530
query: |
  let mediumRisk = 2;
  let goverence = 'Goverence Information';
  CognniIncidents_CL 
  | where Severity == mediumRisk
  | where informationType_s == goverence
  | where TimeGenerated >= ago(5h)
  | extend AccountCustomEntity = userId_s
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let mediumRisk = 2;
let goverence = 'Goverence Information';

Stage 1: source

CognniIncidents_CL

Stage 2: where

| where Severity == mediumRisk

Stage 3: where

| where informationType_s == goverence

Stage 4: where

| where TimeGenerated >= ago(5h)

Stage 5: extend

| extend AccountCustomEntity = userId_s

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

F&O - Reverted bank account number modifications

#
Status
available
Severity
low
Time window
1d
Group by
$left.OldAccountNum, $right.UpdatedAccount
Source
github.com/Azure/Azure-Sentinel

Identifies changes to bank account numbers in Finance & Operations, whereby a bank account number is modified but then subsequently reverted a short time later.

MITRE ATT&CK Coverage

Rule Body

id: 44b1021c-d517-4b7a-9ba6-a91eab94e632
kind: Scheduled
name: F&O - Reverted bank account number modifications
description: Identifies changes to bank account numbers in Finance & Operations, whereby
  a bank account number is modified but then subsequently reverted a short time later.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: Dynamics365Finance
    dataTypes:
      - FinanceOperationsActivity_CL
queryFrequency: 15m
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1565
  - T1496
  - T0828
  - T0831
query: |
  let detection_window = 24h;
  let query_frequency = 15m;
  let bank_changes = FinanceOperationsActivity_CL
      | where LogType == "Update" and TableName == "BankAccountTable"
      | extend AccountId = tostring(parse_json(tostring(FormattedData.AccountID)).NewData)
      | extend AccountNum = parse_json(tostring(FormattedData.AccountNum))
      | extend
          CurrentAccountNum = tostring(AccountNum.NewData),
          OldAccountNum = tostring(AccountNum.OldData)
      | where CurrentAccountNum != OldAccountNum;
  bank_changes
  | join kind=inner (bank_changes
      | where TimeGenerated >= ago(query_frequency)
      | project-rename UpdatedTime = LogCreatedDateTime, UpdatedAccount = CurrentAccountNum)
      on $left.OldAccountNum == $right.UpdatedAccount
  | where UpdatedTime between (LogCreatedDateTime .. (LogCreatedDateTime + detection_window))
  | extend FinOpsAppId = 32780
  | project
      TimeGenerated,
      LogCreatedDateTime,
      LogType,
      TableName,
      Username,
      AccountId,
      CurrentAccountNum,
      OldAccountNum,
      FinOpsAppId
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Username
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: FinOpsAppId
alertDetailsOverride:
  alertDisplayNameFormat: F&O - Suspicious bank account number changes
  alertDescriptionFormat: A suspicous bank account change was made in F&O, the bank
    account number was updated and then changed back to the orginal number a short
    time later. {{AccountId}} was changed by {{Username}}
version: 3.2.0

Stages and Predicates

Parameters

let detection_window = 24h;
let query_frequency = 15m;

let bank_changes is inlined into the numbered stages below.

Stages 1 to 4 define let bank_changes as the rule's main pipeline source. Stages 5 to 8 use it.

Stage 1: source

FinanceOperationsActivity_CL

Stage 2: where

| where LogType == "Update" and TableName == "BankAccountTable"

Stage 3: extend (3 consecutive steps)

| extend AccountId = tostring(parse_json(tostring(FormattedData.AccountID)).NewData)
| extend AccountNum = parse_json(tostring(FormattedData.AccountNum))
| extend
        CurrentAccountNum = tostring(AccountNum.NewData),
        OldAccountNum = tostring(AccountNum.OldData)

Stage 4: where

| where CurrentAccountNum != OldAccountNum

Stage 5: join

bank_changes
| join kind=inner (bank_changes
    | where TimeGenerated >= ago(query_frequency)
    | project-rename UpdatedTime = LogCreatedDateTime, UpdatedAccount = CurrentAccountNum)
    on $left.OldAccountNum == $right.UpdatedAccount

Stage 6: where UpdatedTime - LogCreatedDateTime >= 0s AND UpdatedTime - LogCreatedDateTime <= 1d

where UpdatedTime - LogCreatedDateTime >= 0s AND UpdatedTime - LogCreatedDateTime <= 1d

Stage 7: extend

extend FinOpsAppId

Stage 8: project

project AccountId, CurrentAccountNum, FinOpsAppId, LogCreatedDateTime, LogType, OldAccountNum, TableName, TimeGenerated, Username

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountIdproject
CurrentAccountNumproject
FinOpsAppIdproject
LogCreatedDateTimeproject
LogTypeproject
OldAccountNumproject
TableNameproject
TimeGeneratedproject
Usernameproject

Flare leaked credentials results

#

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

'This query searches for leaked credential events.'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 9cb7c337-f170-4af6-b0e8-b6b7552d762d
name: Flare leaked credentials results
description: |
    'This query searches for leaked credential events.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Flare
    dataTypes:
      - FireworkV2_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  FireworkV2_CL
  | where notempty(uid) and RiskScore >= 3
  | extend index_name = split(uid, "/")[0]
  | where index_name == "leaked_credential"
version: 3.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

FireworkV2_CL

Stage 2: where

| where notempty(uid) and RiskScore >= 3

Stage 3: extend

| extend index_name = split(uid, "/")[0]

Stage 4: where

| where index_name == "leaked_credential"

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
RiskScorege
  • 3 transforms: number
field:"RiskScore" kind:ge value:"3"
index_nameeq
  • leaked_credential
field:"index_name" kind:eq value:"leaked_credential"
uidis_not_null
  • (no value, null check)
field:"uid" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
index_nameextend

Gambit Security - Critical Policy Issue Promotion

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

'Creates an incident when Gambit Security reports an Active, High-severity policy issue in Microsoft Sentinel.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 8d2b6f4c-1a37-4e9b-b5c8-7f0e3a2d9c14
name: Gambit Security - Critical Policy Issue Promotion
description: |
  'Creates an incident when Gambit Security reports an Active, High-severity policy issue in Microsoft Sentinel.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: GambitSecurityPush
    dataTypes:
      - GambitPoliciesIssues_CL
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1490
query: |
  GambitPoliciesIssues
  | where State == "Active" and Severity == "High"
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult
customDetails:
  PolicyName: PolicyName
  PolicyCategories: PolicyCategories
  IssueStatus: IssueStatus
  AffectedSystems: AffectedSystems
  Environment: Environment
  RemediationSteps: RemediationSteps
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: ResourceDisplayId
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: ResourceDisplayId
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GambitPoliciesIssues

Stage 2: where

| where State == "Active" and Severity == "High"

Indicators

These rows show field, operator, and value matches.

GCP Security Command Center - Detect projects with API Keys present

#
Status
available
Severity
medium
Time window
1h
Group by
ProjectId, ProjectName, ResourceName, Severity
Source
github.com/Azure/Azure-Sentinel

Detects Google Cloud projects that have API Keys present using Security Command Center API_KEY_EXISTS findings. Projects with API Keys may expose credentials that enable unauthorized access if keys are leaked.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 395f3ced-3923-4b83-b05d-8d077fd48c1e
name: GCP Security Command Center - Detect projects with API Keys present
description: |
  Detects Google Cloud projects that have API Keys present using Security Command Center API_KEY_EXISTS findings.
  Projects with API Keys may expose credentials that enable unauthorized access if keys are leaked.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: GoogleSCCDefinition
    dataTypes:
      - GoogleCloudSCC
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1552
tags:
  - CIS GCP Foundation 3.0 1.12
  - NIST 800-53 R5 PL-8, SA-8
  - PCI-DSS v4.0 2.2.2, 6.2.1
  - ISO-27001 v2022 A.8.27
  - Cloud Controls Matrix 4 DSP-07
  - NIST Cybersecurity Framework 1.0 PR-IP-2
  - CIS Controls 8.0 16.10
query: |
 GoogleCloudSCC
  | where tostring(Findings.state) == "ACTIVE"
  | extend FindingCategory = tostring(Findings.category)
  | where FindingCategory == "API_KEY_EXISTS"
  | extend FindingsJson = parse_json(Findings), FindingsResourceJson = parse_json(FindingsResource)
  | extend ResourceName = tostring(FindingsJson.resourceName)
  | extend ProjectId = extract(@"projects/([^/]+)", 1, ResourceName)
  | extend ProjectName = tostring(FindingsResourceJson.displayName)
  | extend Severity = tostring(FindingsJson.severity),
           FindingName = tostring(FindingsJson.name),
           ExternalUri = tostring(FindingsJson.externalUri),
           Description = tostring(FindingsJson.description)
  // Produce one row per project with an API key finding
  | summarize TimeGenerated = max(TimeGenerated),
              FindingsCount = count(),
              ExternalUri = any(ExternalUri),
              Description = any(Description)
    by ProjectId, ProjectName, Severity, ResourceName
  | project TimeGenerated, ProjectId, ProjectName, ResourceName, FindingsCount, Severity, ExternalUri, Description
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: ProjectName
      - identifier: AppId
        columnName: ProjectId
customDetails:
  ProjectId: ProjectId
  ProjectName: ProjectName
  FindingsCount: FindingsCount
  SampleExternalUri: ExternalUri
  SampleDescription: Description
alertDetailsOverride:
  alertDisplayNameFormat: "GCP project {{ProjectName}} has API key(s) present"
  alertDescriptionFormat: |-
    Project {{ProjectName}} ({{ProjectId}}) has {{FindingsCount}} API key finding(s). Review API keys and consider rotating or removing keys and using IAM-based authentication.
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

GoogleCloudSCC

Stage 2: where

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

Stage 3: extend

| extend FindingCategory = tostring(Findings.category)

Stage 4: where

| where FindingCategory == "API_KEY_EXISTS"

Stage 5: extend (5 consecutive steps)

| extend FindingsJson = parse_json(Findings), FindingsResourceJson = parse_json(FindingsResource)
| extend ResourceName = tostring(FindingsJson.resourceName)
| extend ProjectId = extract(@"projects/([^/]+)", 1, ResourceName)
| extend ProjectName = tostring(FindingsResourceJson.displayName)
| extend Severity = tostring(FindingsJson.severity),
          FindingName = tostring(FindingsJson.name),
          ExternalUri = tostring(FindingsJson.externalUri),
          Description = tostring(FindingsJson.description)

Stage 6: summarize

| summarize TimeGenerated = max(TimeGenerated),
             FindingsCount = count(),
             ExternalUri = any(ExternalUri),
             Description = any(Description)
   by ProjectId, ProjectName, Severity, ResourceName

Stage 7: project

| project TimeGenerated, ProjectId, ProjectName, ResourceName, FindingsCount, Severity, ExternalUri, Description

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
FindingCategoryeq
  • API_KEY_EXISTS
field:"FindingCategory" kind:eq value:"API_KEY_EXISTS"
Findings.stateeq
  • ACTIVE transforms: tostring, kusto_dynamic_path
field:"Findings.state" kind:eq value:"ACTIVE"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Descriptionproject
ExternalUriproject
FindingsCountproject
ProjectIdproject
ProjectNameproject
ResourceNameproject
Severityproject
TimeGeneratedproject

Job Deleted

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

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

Rule Body

id: b4ea5da0-77a8-400e-ae2b-3ca8fdb779ef
name: Job Deleted
description: Detects when a job is deleted from Veeam Backup & Replication. This might
  indicate unauthorized removal of critical components.
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 == 23090\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    UserName = user,\n    [\"Job Name\"] = \"\",\n \
  \  MessageDetails = Description,\n   Severity = SeverityDescription\n| project Date,\
  \ DataSource, EventId, UserName, [\"Job Name\"],MessageDetails,Severity"
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 == 23090

Stage 3: project

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

Stage 4: project

| project Date, DataSource, EventId, UserName, ["Job Name"],MessageDetails,Severity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
Job Nameproject
MessageDetailsproject
Severityproject
UserNameproject

NDMP Server Deleted

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

Detects when an NDMP server is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.

Rule Body

id: e92ba6bb-53ea-4347-b1a6-2e015298bbdc
name: NDMP Server Deleted
description: Detects when an NDMP server is deleted from Veeam Backup & Replication.
  This might indicate unauthorized removal of critical components.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
  dataTypes:
  - Syslog
- connectorId: SyslogAma
  dataTypes:
  - Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 28850\n| project\n    Date\
  \ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n\
  \    EventId = instanceId,\n    UserName = user,\n MessageDetails = Description,\n\
  \   Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 28850

Stage 3: project

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

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
EventIdproject
MessageDetailsproject
Severityproject
UserNameproject

NRT Creation of expensive computes in Azure

#
Status
available
Severity
medium
Source
github.com/Azure/Azure-Sentinel

Identifies the creation of large size or expensive VMs (with GPUs or with a large number of virtual CPUs) in Azure. An adversary may create new or update existing virtual machines to evade defenses or use them for cryptomining purposes. For Windows/Linux Vm Sizes, see https://docs.microsoft.com/azure/virtual-machines/windows/sizes Azure VM Naming Conventions, see https://docs.microsoft.com/azure/virtual-machines/vm-naming-conventions

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Telemetry Coverage

Rule Body

id: 56fe0db0-6779-46fa-b3c5-006082a53064
name: NRT Creation of expensive computes in Azure
description: |
  'Identifies the creation of large size or expensive VMs (with GPUs or with a large number of virtual CPUs) in Azure.
  An adversary may create new or update existing virtual machines to evade defenses or use them for cryptomining purposes.
  For Windows/Linux Vm Sizes, see https://docs.microsoft.com/azure/virtual-machines/windows/sizes 
  Azure VM Naming Conventions, see https://docs.microsoft.com/azure/virtual-machines/vm-naming-conventions'
severity: Medium
status: Available 
requiredDataConnectors:
  - connectorId: AzureActivity
    dataTypes:
      - AzureActivity
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1578
query: |
  let tokens = dynamic(["416","208","192","128","120","96","80","72","64","48","44","40","nc12","nc24","nv24"]);
  let operationList = dynamic(["microsoft.compute/virtualmachines/write", "microsoft.resources/deployments/write"]);
  AzureActivity
  | where OperationNameValue in~ (operationList)
  | where ActivityStatusValue startswith "Accept"
  | where Properties has 'vmSize'
  | extend parsed_property= parse_json(tostring((parse_json(Properties).responseBody))).properties
  | extend vmSize = tostring((parsed_property.hardwareProfile).vmSize)
  | mv-apply token=tokens to typeof(string) on (where vmSize contains token)
  | extend ComputerName = tostring((parsed_property.osProfile).computerName)
  | project TimeGenerated, OperationNameValue, ActivityStatusValue, Caller, CallerIpAddress, ComputerName, vmSize
  | extend Name = tostring(split(Caller,'@',0)[0]), UPNSuffix = tostring(split(Caller,'@',1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Caller
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: ComputerName  
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: CallerIpAddress
version: 2.0.3
kind: NRT

Stages and Predicates

Parameters

let tokens = dynamic(["416","208","192","128","120","96","80","72","64","48","44","40","nc12","nc24","nv24"]);
let operationList = dynamic(["microsoft.compute/virtualmachines/write", "microsoft.resources/deployments/write"]);

Stage 1: source

AzureActivity

Stage 2: where

| where OperationNameValue in~ (operationList)

Stage 3: where

| where ActivityStatusValue startswith "Accept"

Stage 4: where

| where Properties has 'vmSize'

Stage 5: extend

| extend parsed_property= parse_json(tostring((parse_json(Properties).responseBody))).properties

Stage 6: extend

| extend vmSize = tostring((parsed_property.hardwareProfile).vmSize)

Stage 7: kusto:mv-apply

| mv-apply token=tokens to typeof(string) on (where vmSize contains token)

Stage 8: extend

| extend ComputerName = tostring((parsed_property.osProfile).computerName)

Stage 9: project

| project TimeGenerated, OperationNameValue, ActivityStatusValue, Caller, CallerIpAddress, ComputerName, vmSize

Stage 10: extend

| extend Name = tostring(split(Caller,'@',0)[0]), UPNSuffix = tostring(split(Caller,'@',1)[0])

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActivityStatusValuestarts_with
  • Accept corpus 2 (kusto 2)
field:"ActivityStatusValue" kind:starts_with value:"Accept"
OperationNameValuein
  • microsoft.compute/virtualmachines/write corpus 4 (kusto 4)
  • microsoft.resources/deployments/write corpus 4 (kusto 4)
field:"azure_ad::operation_name_value" kind:in
Propertiesmatch
  • vmSize transforms: term corpus 2 (kusto 2)
field:"Properties" kind:match value:"vmSize"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityStatusValueproject
Callerproject
CallerIpAddressproject
ComputerNameproject
OperationNameValueproject
TimeGeneratedproject
vmSizeproject
Nameextend
UPNSuffixextend

Pathlock TDnR - DDIC Table Utility Changes (SE14)

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

Detects changes made using the DDIC table utility tool SE14 in SAP, forwarded by Pathlock Threat Detection and Response. SE14 operations can delete or restructure database tables, making this one of the highest-risk actions an SAP user can perform - often used to destroy audit trails or manipulate historical data.

MITRE ATT&CK Coverage

TacticTechniques
Defense Impairment

Rule Body

id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c69
name: Pathlock TDnR - DDIC Table Utility Changes (SE14)
kind: Scheduled
description: >-
  Detects changes made using the DDIC table utility tool SE14 in SAP, forwarded by Pathlock
  Threat Detection and Response. SE14 operations can delete or restructure database tables,
  making this one of the highest-risk actions an SAP user can perform - often used to destroy
  audit trails or manipulate historical data.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: Pathlock_TDnR
    dataTypes:
      - Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1562
query: |
  Pathlock_TDnR_CL
  | where DataSource == "TABLE_UTILITY"
  | 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 == "TABLE_UTILITY"

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

SAP BTP - Mass user deletion in a sub account

#
Status
available
Severity
medium
Time window
1h
Group by
SpaceId, Tenant, UserName
Source
github.com/Azure/Azure-Sentinel

Identifies user account deletion activity where the amount of deleted users exceeds a predefined threshold.

MITRE ATT&CK Coverage

Rule Body

id: 6f1e58bd-cd95-4dfb-8883-94207f30929a
kind: Scheduled
name: SAP BTP - Mass user deletion in a sub account
description: Identifies user account deletion activity where the amount of deleted
  users exceeds a predefined threshold.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SAPBTPAuditEvents
    dataTypes:
      - SAPBTPAuditLog_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1531
  - T1485
  - T1489
  - T0813
  - T0826
  - T0827
query: |
  let bulk_delete_threshold = 10;
  SAPBTPAuditLog_CL
  | where isnotnull(Message.object)
  | where Message.object has "scim user" and Message.object has "DELETE"
  | mv-expand Attributes = Message.attributes
  | where isnotempty(Attributes.old)
  | extend DeletedUserName = tostring(parse_json(tostring(Attributes.old)).userName)
  | where isnotempty(DeletedUserName)
  | summarize
      Start = min(UpdatedOn),
      End = max(UpdatedOn),
      DeletedUsers = make_set(DeletedUserName, 100)
      by UserName, Tenant, SpaceId
  | where array_length(DeletedUsers) > bulk_delete_threshold
  | project Start, End, UserName, DeletedUsers, Tenant, SpaceId, CloudApp = "SAP BTP"
  | extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: CloudApp
version: 3.0.6

Stages and Predicates

Parameters

let bulk_delete_threshold = 10;

Stage 1: source

SAPBTPAuditLog_CL

Stage 2: where

| where isnotnull(Message.object)

Stage 3: where

| where Message.object has "scim user" and Message.object has "DELETE"

Stage 4: mv-expand

| mv-expand Attributes = Message.attributes

Stage 5: where

| where isnotempty(Attributes.old)

Stage 6: extend

| extend DeletedUserName = tostring(parse_json(tostring(Attributes.old)).userName)

Stage 7: where

| where isnotempty(DeletedUserName)

Stage 8: summarize

| summarize
    Start = min(UpdatedOn),
    End = max(UpdatedOn),
    DeletedUsers = make_set(DeletedUserName, 100)
    by UserName, Tenant, SpaceId

Stage 9: where

| where array_length(DeletedUsers) > bulk_delete_threshold

Stage 10: project

| project Start, End, UserName, DeletedUsers, Tenant, SpaceId, CloudApp = "SAP BTP"

Stage 11: extend

| extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Attributes.oldis_not_null
  • (no value, null check)
field:"Attributes.old" kind:is_not_null
DeletedUserNameis_not_null
  • (no value, null check)
field:"DeletedUserName" kind:is_not_null
Message.objectis_not_null
  • (no value, null check)
field:"Message.object" kind:is_not_null
Message.objectmatch
  • DELETE transforms: term, kusto_dynamic_path
  • scim user transforms: term, kusto_dynamic_path
field:"Message.object" kind:match

Output Fields

These fields are emitted when the rule matches.

FieldSource
CloudAppproject
DeletedUsersproject
Endproject
SpaceIdproject
Startproject
Tenantproject
UserNameproject
AccountNameextend
UPNSuffixextend

Stale AWS policy attachment to identity

#

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

The policy detects 'AWS policies' attached to IAM users or roles that have not used it during the last X days. It is recommended to remove unused policies from identities to reduce risk.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 766a3b1b-0d5b-4a8d-b0d6-7dd379e73567
name: Stale AWS policy attachment to identity
kind: Scheduled
description: |2-
  The policy detects 'AWS policies' attached to IAM users or roles that have not used it during the last X days. It is recommended to remove unused policies from identities to reduce risk.
severity: Low
status: Available
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |-
  Authomize_v2_CL
  | where ingestion_time() >= ago(30m)
  | extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s
  | where Policy has "Stale AWS policy attachment to identity"
  | project  EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URL
requiredDataConnectors:
- connectorId: Authomize
  dataTypes: [ "Authomize_v2_CL" ]
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AnyAlert
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []
eventGroupingSettings:
  aggregationKind: SingleAlert
alertDetailsOverride:
  alertnameFormat: Alert from Authomize - Stale AWS policy attachment to identity
  alertDescriptionFormat: Stale AWS policy attachment to identity. The policy detects 'AWS policies' attached to IAM users or roles that have not used it during the last X days. It is recommended to remove unused policies from identities to reduce risk.
  alertSeverity: Severity
  alertTactics: Tactics
  alertDynamicProperties:
  - alertProperty: AlertLink
    value: URL
customDetails:
  AuthomizeEventID: EventID
  EventName: Policy
  EventDescription: Description
  EventRecommendation: Recommendation
  ReferencedURL: URL
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.2

Stages and Predicates

Stage 1: source

Authomize_v2_CL

Stage 2: where

| where ingestion_time() >= ago(30m)

Stage 3: extend

| extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s

Stage 4: where

| where Policy has "Stale AWS policy attachment to identity"

Stage 5: project

| project  EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Policymatch
  • Stale AWS policy attachment to identity transforms: term
field:"policy_name_s" kind:match value:"Stale AWS policy attachment to identity"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Categoryproject
Descriptionproject
EventIDproject
Policyproject
Recommendationproject
Severityproject
Tacticsproject
URLproject

SUPERNOVA webshell

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

'Identifies SUPERNOVA webshell based on W3CIISLog data. References: - https://unit42.paloaltonetworks.com/solarstorm-supernova/'

MITRE ATT&CK Coverage

TacticTechniques
Persistence
Command & Control

Rule Body

id: 2acc91c3-17c2-4388-938e-4eac2d5894e8
name: SUPERNOVA webshell
description: |
  'Identifies SUPERNOVA webshell based on W3CIISLog data.
   References:
   - https://unit42.paloaltonetworks.com/solarstorm-supernova/'
severity: High
requiredDataConnectors:
  - connectorId: AzureMonitor(IIS)
    dataTypes:
      - W3CIISLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - CommandAndControl
relevantTechniques:
  - T1505
  - T1071
query: |
  W3CIISLog
  | where csMethod == 'GET'
  | where isnotempty(csUriStem) and isnotempty(csUriQuery)
  | where csUriStem contains "logoimagehandler.ashx"
  | where csUriQuery contains "codes" and csUriQuery contains "clazz" and csUriQuery contains "method" and csUriQuery contains "args"
  | extend timestamp = TimeGenerated
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
  | extend AccountName = tostring(split(csUserName, "@")[0]), AccountUPNSuffix = tostring(split(csUserName, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: csUserName
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: cIP
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Scheduled
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Stage 1: source

W3CIISLog

Stage 2: where

| where csMethod == 'GET'

Stage 3: where

| where isnotempty(csUriStem) and isnotempty(csUriQuery)

Stage 4: where

| where csUriStem contains "logoimagehandler.ashx"

Stage 5: where

| where csUriQuery contains "codes" and csUriQuery contains "clazz" and csUriQuery contains "method" and csUriQuery contains "args"

Stage 6: extend (4 consecutive steps)

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

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
timestampextend
DomainIndexextend
HostNameextend
HostNameDomainextend
AccountNameextend
AccountUPNSuffixextend

Tailscale: Policy file (ACL) modified

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

Identifies when the tailnet ACL/policy file is modified. Review the diff - incorrect ACLs can silently expand blast radius across the tailnet.

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Rule Body

id: 1e7249c2-1a9d-05fd-45cb-c859eef5b8ae
name: "Tailscale: Policy file (ACL) modified"
description: |
  Identifies when the tailnet ACL/policy file is modified. Review the diff - incorrect ACLs can silently expand blast radius across the tailnet.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: TailscaleCCF
    dataTypes:
      - Tailscale_Audit_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - Persistence
relevantTechniques:
  - T1556
query: |
  Tailscale_Audit_CL
  | where Action == "UPDATE"
  | where tostring(Target.type) == "TAILNET"
  | where tostring(Target.property) == "ACL"
  | extend ActorLogin = tostring(Actor.loginName)
  | project TimeGenerated, ActorLogin, Action, Target, Origin, Old, New
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ActorLogin
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0

Stages and Predicates

Stage 1: source

Tailscale_Audit_CL

Stage 2: where

| where Action == "UPDATE"

Stage 3: where

| where tostring(Target.type) == "TAILNET"

Stage 4: where

| where tostring(Target.property) == "ACL"

Stage 5: extend

| extend ActorLogin = tostring(Actor.loginName)

Stage 6: project

| project TimeGenerated, ActorLogin, Action, Target, Origin, Old, New

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • UPDATE
field:"Action" kind:eq value:"UPDATE"
Target.propertyeq
  • ACL transforms: tostring, kusto_dynamic_path
field:"Target.property" kind:eq value:"ACL"
Target.typeeq
  • TAILNET transforms: tostring, kusto_dynamic_path
field:"Target.type" kind:eq value:"TAILNET"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionproject
ActorLoginproject
Newproject
Oldproject
Originproject
Targetproject
TimeGeneratedproject

TI map IP entity to DNS Events (ASIM DNS schema)

#
Severity
medium
Time window
14d
Group by
IndicatorId, IoC
Source
github.com/Azure/Azure-Sentinel

This rule identifies DNS requests for which response IP address is a known IoC. This analytic rule uses ASIM and supports any built-in or custom source that supports the ASIM DNS schema.

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

ProviderRecord / event type
SysmonEvent ID 22: DNSEvent (DNS query)

Rule Body

id: 67775878-7f8b-4380-ac54-115e1e828901
name: TI map IP entity to DNS Events (ASIM DNS schema)
description: |
  'This rule identifies DNS requests for which response IP address is a known IoC. This analytic rule uses [ASIM](https://aka.ms/AboutASIM) and supports any built-in or custom source that supports the ASIM DNS schema.'
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: DNS
    dataTypes:
      - DnsEvents
  - connectorId: AzureFirewall
    dataTypes: 
      - AzureDiagnostics
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog
  - connectorId: InfobloxNIOS
    dataTypes: 
      - Syslog
  - connectorId: GCPDNSDataConnector
    dataTypes: 
      - GCP_DNS_CL
  - connectorId: NXLogDnsLogs
    dataTypes: 
      - NXLog_DNS_Server_CL
  - connectorId: CiscoUmbrellaDataConnector
    dataTypes: 
      - Cisco_Umbrella_dns_CL
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: Corelight
    dataTypes: 
      - Corelight_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
tags:
  - ParentAlert: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/ThreatIntelligenceIndicator/IPEntity_DnsEvents.yaml
    version: 1.0.0
  - Schema: ASIMDns
    SchemaVersion: 0.1.1
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let IP_TI = 
  ThreatIntelligenceIndicator
  | where TimeGenerated >= ago(ioc_lookBack)
  | extend IoC = coalesce(NetworkIP, NetworkDestinationIP, NetworkSourceIP,EmailSourceIpAddress,"NO_IP")
  | where IoC != "NO_IP"
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
  | where Active == true and ExpirationDateTime > now();
  IP_TI
  | join kind=innerunique // 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
  (
  _Im_Dns(starttime=ago(dt_lookBack))
  | where isnotempty(DnsResponseName)
  | summarize imDns_mintime=min(TimeGenerated), imDns_maxtime=max(TimeGenerated) by SrcIpAddr, DnsQuery, DnsResponseName, Dvc, EventProduct, EventVendor
  | extend addresses = extract_all (@'(\d+\.\d+\.\d+\.\d+)', DnsResponseName)
  | mv-expand IoC = addresses to typeof(string)
  )
  on IoC
  | where imDns_mintime < ExpirationDateTime
  | project imDns_mintime, imDns_maxtime, Description, ActivityGroupNames, IndicatorId, ThreatType, LatestIndicatorTime, ExpirationDateTime, ConfidenceScore, SrcIpAddr, IoC, Dvc, EventVendor, EventProduct, DnsQuery, DnsResponseName
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Dvc
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IoC
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
customDetails:
  LatestIndicatorTime: LatestIndicatorTime
  Description: Description
  ActivityGroupNames: ActivityGroupNames
  IndicatorId: IndicatorId
  ThreatType: ThreatType
  ExpirationDateTime: ExpirationDateTime
  ConfidenceScore: ConfidenceScore
  DNSRequestTime: imDns_mintime
  SourceIPAddress: SrcIpAddr
  DnsQuery: DnsQuery
alertDetailsOverride:
  alertDisplayNameFormat: The response {{IoC}} to DNS query matched an IoC 
  alertDescriptionFormat: The response address {{IoC}} to a DNS query matched a known indicator of compromise of {{ThreatType}}. Consult the threat intelligence blade for more information on the indicator.
version: 1.2.4
kind: Scheduled

Stages and Predicates

Parameters

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

let IP_TI is inlined into the numbered stages below.

Stages 1 to 6 define let IP_TI as the rule's main pipeline source. Stages 7 to 9 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: extend

| extend IoC = coalesce(NetworkIP, NetworkDestinationIP, NetworkSourceIP,EmailSourceIpAddress,"NO_IP")

Stage 4: where

| where IoC != "NO_IP"

Stage 5: summarize

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

Stage 6: where

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

Stage 7: join

IP_TI
| join kind=innerunique
(
_Im_Dns(starttime=ago(dt_lookBack))
| where isnotempty(DnsResponseName)
| summarize imDns_mintime=min(TimeGenerated), imDns_maxtime=max(TimeGenerated) by SrcIpAddr, DnsQuery, DnsResponseName, Dvc, EventProduct, EventVendor
| extend addresses = extract_all (@'(\d+\.\d+\.\d+\.\d+)', DnsResponseName)
| mv-expand IoC = addresses to typeof(string)
)
on IoC

Stage 8: where

where imDns_mintime < ExpirationDateTime

Stage 9: project

project ActivityGroupNames, ConfidenceScore, Description, DnsQuery, DnsResponseName, Dvc, EventProduct, EventVendor, ExpirationDateTime, IndicatorId, IoC, LatestIndicatorTime, SrcIpAddr, ThreatType, imDns_maxtime, imDns_mintime

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Activeeq
  • true corpus 60 (kusto 60)
field:"Active" kind:eq value:"true"
DnsResponseNameis_not_null
  • (no value, null check)
field:"DnsResponseName" kind:is_not_null
IoCne
  • NO_IP
field:"IoC" kind:ne value:"NO_IP"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
ConfidenceScoreproject
Descriptionproject
DnsQueryproject
DnsResponseNameproject
Dvcproject
EventProductproject
EventVendorproject
ExpirationDateTimeproject
IndicatorIdproject
IoCproject
LatestIndicatorTimeproject
SrcIpAddrproject
ThreatTypeproject
imDns_maxtimeproject
imDns_mintimeproject

Ubiquiti - Unusual DNS connection

#
Status
available
Severity
medium
Time window
1h
Group by
DstIpAddr, SrcIpAddr
Source
github.com/Azure/Azure-Sentinel

'Detects unusual remote to local (R2L) DNS connections.'

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Rule Body

id: 14a23ded-7fb9-48ee-ba39-859517a49b51
name: Ubiquiti - Unusual DNS connection
description: |
  'Detects unusual remote to local (R2L) DNS connections.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CustomLogsAma
    dataTypes:
      - Ubiquiti_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1090
  - T1572
query: |
  let threshold = 50;
  UbiquitiAuditEvent
  | where EventCategory =~ 'firewall'
  | where ipv4_is_private(SrcIpAddr) == 'False'
  | where ipv4_is_private(DstIpAddr)
  | where DstPortNumber == '53'
  | summarize count() by SrcIpAddr, DstIpAddr
  | where count_ > threshold
  | extend IPCustomEntity = DstIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 50;

Stage 1: source

UbiquitiAuditEvent

Stage 2: where

| where EventCategory =~ 'firewall'

Stage 3: where

| where ipv4_is_private(SrcIpAddr) == 'False'

Stage 4: where

| where ipv4_is_private(DstIpAddr)

Stage 5: where

| where DstPortNumber == '53'

Stage 6: summarize

| summarize count() by SrcIpAddr, DstIpAddr

Stage 7: where

| where count_ > threshold

Stage 8: extend

| extend IPCustomEntity = DstIpAddr

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
DstIpAddrcidr_match
  • 10.0.0.0/8
  • 127.0.0.0/8
  • 169.254.0.0/16
  • 172.16.0.0/12
  • 192.168.0.0/16
field:"dest_ip" kind:cidr_match
DstPortNumbereq
  • 53
field:"DstPortNumber" kind:eq value:"53"
EventCategoryeq
  • firewall
field:"EventCategory" kind:eq value:"firewall"
count_gt
  • 50 transforms: number
field:"count_" kind:gt value:"50"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
SrcIpAddrcidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:SrcIpAddr

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstIpAddrsummarize
SrcIpAddrsummarize
IPCustomEntityextend

Back to all Kusto rules