Detection rules › Kusto

Kusto rules: 0t

Addition of a Temporary Access Pass to a Privileged Account

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

Detects when a Temporary Access Pass (TAP) is created for a Privileged Account. A Temporary Access Pass is a time-limited passcode issued by an admin that satisfies strong authentication requirements and can be used to onboard other authentication methods, including Passwordless ones such as Microsoft Authenticator or even Windows Hello. A threat actor could use a TAP to register a new authentication method to maintain persistance to an account. Review any TAP creations to ensure they were used legitimately. Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#changes-to-privileged-accounts

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: d7feb859-f03e-4e8d-8b21-617be0213b13
name: Addition of a Temporary Access Pass to a Privileged Account
description: |
  'Detects when a Temporary Access Pass (TAP) is created for a Privileged Account.
    A Temporary Access Pass is a time-limited passcode issued by an admin that satisfies strong authentication requirements and can be used to onboard other authentication methods, including Passwordless ones such as Microsoft Authenticator or even Windows Hello.
    A threat actor could use a TAP to register a new authentication method to maintain persistance to an account.
    Review any TAP creations to ensure they were used legitimately.
    Ref: https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#changes-to-privileged-accounts'
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
  - connectorId: BehaviorAnalytics
    dataTypes:
      - IdentityInfo
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
  let admin_users = (IdentityInfo
    | summarize arg_max(TimeGenerated, *) by AccountUPN
    | where AssignedRoles contains "admin"
    | summarize by tolower(AccountUPN));
    AuditLogs
    | where OperationName =~ "Admin registered security info"
    | where ResultReason =~ "Admin registered temporary access pass method for user"
    | extend TargetUserPrincipalName = tostring(TargetResources[0].userPrincipalName)
    | where tolower(TargetUserPrincipalName) in (admin_users)
    | extend TargetAadUserId = tostring(TargetResources[0].id)
    | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
    | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
    | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
    | extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])
    | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetUserPrincipalName
      - identifier: Name
        columnName: TargetAccountName
      - identifier: UPNSuffix
        columnName: TargetAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: TargetAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIPAddress
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection", "Identity" ]

Stages and Predicates

Let Binding: admin_users used in Stages 1, 6

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

Stage 1: source

let admin_users

Stage 2: source

AuditLogs

Stage 3: where

where OperationName =~ "Admin registered security info"

Stage 4: where

where ResultReason =~ "Admin registered temporary access pass method for user"

Stage 5: extend

extend TargetUserPrincipalName

Stage 6: where

where TargetUserPrincipalName == "admin_users"

Stage 7: extend (6 consecutive steps)

extend InitiatingAadUserId, InitiatingAccountName, InitiatingAccountUPNSuffix, InitiatingIPAddress, InitiatingUserPrincipalName, TargetAadUserId, TargetAccountName, TargetAccountUPNSuffix

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
TargetUserPrincipalNameextend
TargetAadUserIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIPAddressextend
TargetAccountNameextend
TargetAccountUPNSuffixextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend

AWSCloudTrail - Changes to Amazon VPC settings

#
Status
available
Severity
low
Time window
1d
Group by
AWSRegion, AccountName, AccountUPNSuffix, AdditionalEventData, EventName, EventSource, EventTypeName, RecipientAccountId, ResponseElements, SessionMfaAuthenticated, SourceIpAddress, UserAgent, UserIdentityAccountId, UserIdentityPrincipalid, UserIdentityUserName
Source
github.com/Azure/Azure-Sentinel

Identifies changes to Amazon VPC(Virtual Private Cloud) settings that can alter network exposure, routing, or access controls. This rule identifies changes to Amazon VPC (Virtual Private Cloud) settings such as new ACL entries, routes, routetable or Gateways. AWS VPC API Docs: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/OperationList-query-vpc.html'

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 65360bb0-8986-4ade-a89d-af3cf44d28aa
name: AWSCloudTrail - Changes to Amazon VPC settings
description: |
  Identifies changes to Amazon VPC(Virtual Private Cloud) settings that can alter network exposure, routing, or access controls.
  This rule identifies changes to Amazon VPC (Virtual Private Cloud) settings such as new ACL entries, routes, routetable or Gateways. 
  AWS VPC API Docs: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/OperationList-query-vpc.html'
severity: Low
status: Available
requiredDataConnectors:
 - connectorId: AWS
   dataTypes:
    - AWSCloudTrail
 - connectorId: AWSS3
   dataTypes:
    - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - PrivilegeEscalation
  - DefenseEvasion
relevantTechniques:
  - T1078
  - T1562.007
query: |
  let EventNameList = dynamic(["CreateNetworkAclEntry","CreateRoute","CreateRouteTable","CreateInternetGateway","CreateNatGateway"]);
  AWSCloudTrail
  | where EventName in~ (EventNameList)
  | where EventSource != "apigateway.amazonaws.com"
  | extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
  | extend UserName = tostring(split(UserIdentityArn, '/')[-1])
  | extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
  | extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
    AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
  | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by EventName, EventTypeName, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent,
  UserIdentityUserName, SessionMfaAuthenticated, SourceIpAddress, AWSRegion, EventSource, AdditionalEventData, ResponseElements
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  AWSRegion: AWSRegion
  RecipientAccountId: RecipientAccountId
  EventName: EventName
  EventSource: EventSource
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS VPC change by {{AccountName}} in {{AWSRegion}}'
  alertDescriptionFormat: 'Event {{EventName}} executed by {{AccountName}} from {{SourceIpAddress}}.'
version: 1.0.8
kind: Scheduled

Stages and Predicates

Parameters

let EventNameList = dynamic(["CreateNetworkAclEntry","CreateRoute","CreateRouteTable","CreateInternetGateway","CreateNatGateway"]);

Stage 1: source

AWSCloudTrail

Stage 2: where

| where EventName in~ (EventNameList)

Stage 3: where

| where EventSource != "apigateway.amazonaws.com"

Stage 4: extend (4 consecutive steps)

| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
  AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")

Stage 5: summarize

| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by EventName, EventTypeName, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, UserIdentityPrincipalid, UserAgent,
UserIdentityUserName, SessionMfaAuthenticated, SourceIpAddress, AWSRegion, EventSource, AdditionalEventData, ResponseElements

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventNamein
  • CreateInternetGateway
  • CreateNatGateway
  • CreateNetworkAclEntry
  • CreateRoute
  • CreateRouteTable
field:"aws::eventName" kind:in
EventSourcene
  • apigateway.amazonaws.com
field:"aws::eventSource" kind:ne value:"apigateway.amazonaws.com"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AWSRegionsummarize
AccountNamesummarize
AccountUPNSuffixsummarize
AdditionalEventDatasummarize
EndTimeUtcsummarize
EventNamesummarize
EventSourcesummarize
EventTypeNamesummarize
RecipientAccountIdsummarize
ResponseElementssummarize
SessionMfaAuthenticatedsummarize
SourceIpAddresssummarize
StartTimeUtcsummarize
UserAgentsummarize
UserIdentityAccountIdsummarize
UserIdentityPrincipalidsummarize
UserIdentityUserNamesummarize

Box - Executable file in folder

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

'Detects executable files in folders.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: b91ec98d-5747-45c8-b2f6-a07bf47068f0
name: Box - Executable file in folder
description: |
  'Detects executable files in folders.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: BoxDataConnector
    dataTypes:
      - BoxEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1189
query: |
  BoxEvents
  | where SourceFileName hassuffix '.exe' or SourceItemName hassuffix '.exe'
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

BoxEvents

Stage 2: where

| where SourceFileName hassuffix '.exe' or SourceItemName hassuffix '.exe'

Stage 3: extend

| extend IPCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
IPCustomEntityextend

Cisco SEG - Multiple large emails sent to external recipient

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

'Detects possible data exfiltration.'

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Rule Body

id: 1399a9a5-6200-411e-8c34-ca5658754cf7
name: Cisco SEG - Multiple large emails sent to external recipient
description: |
  'Detects possible data exfiltration.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1030
query: |
  let e_theshold = 3;
  let s_threshold = 10000000;
  CiscoSEGEvent
  | where AdditionalFields[15]['ESAMsgSize'] > s_threshold
  | where NetworkDirection =~ 'Outgoing'
  | extend rec_domain = extract(@'@(.*)', 1, DstUserName)
  | extend s_domain = extract(@'@(.*)', 1, SrcUserName)
  | where s_domain != rec_domain
  | summarize count() by SrcUserName
  | where count_ >= e_theshold
  | extend AccountCustomEntity = SrcUserName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let e_theshold = 3;
let s_threshold = 10000000;

Stage 1: source

CiscoSEGEvent

Stage 2: where

| where AdditionalFields[15]['ESAMsgSize'] > s_threshold

Stage 3: where

| where NetworkDirection =~ 'Outgoing'

Stage 4: extend

| extend rec_domain = extract(@'@(.*)', 1, DstUserName)

Stage 5: extend

| extend s_domain = extract(@'@(.*)', 1, SrcUserName)

Stage 6: where

| where s_domain != rec_domain

Stage 7: summarize

| summarize count() by SrcUserName

Stage 8: where

| where count_ >= e_theshold

Stage 9: extend

| extend AccountCustomEntity = SrcUserName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
AdditionalFields[15]["ESAMsgSize"]gt
  • 10000000 transforms: kusto_dynamic_path
NetworkDirectioneq
  • Outgoing
field:"NetworkDirection" kind:eq value:"Outgoing"
count_ge
  • 3 transforms: number
field:"count_" kind:ge value:"3"

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcUserNamesummarize
AccountCustomEntityextend

Conditional Access - Dynamic Group Exclusion Changes

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

// Detects changes to Dynamic Membership Rules for specified groups (often used in CA exclusions)

MITRE ATT&CK Coverage

TacticTechniques
Privilege Escalation

Telemetry Coverage

PlatformRecord / event type
AzureUpdate group

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: c385944b-17b9-4b2b-921e-0e8d0341a675
name: Conditional Access - Dynamic Group Exclusion Changes
version: 1.0.1
kind: Scheduled
description: // Detects changes to Dynamic Membership Rules for specified groups (often used in CA exclusions)
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1484
query: |-
  // Detects changes to Dynamic Membership Rules for specified groups (often used in CA exclusions)
  let monitoredGroups = dynamic(["Group1", "Group2"]);  // <-- Customize this list
  AuditLogs
  | where OperationName == "Update group"
  | where AdditionalDetails[0].value == "DynamicMembership"
  | extend DynamicGroupName = tostring(TargetResources[0].displayName)
  | where DynamicGroupName in (monitoredGroups)
  | extend modifiedBy = tostring(InitiatedBy.user.userPrincipalName)
  | extend accountName = tostring(split(modifiedBy, "@")[0])
  | extend upnSuffix = tostring(split(modifiedBy, "@")[1])
  | extend oldRule = tostring(TargetResources[0].modifiedProperties[0].oldValue)
  | extend newRule = tostring(TargetResources[0].modifiedProperties[0].newValue)
  | where oldRule != newRule
  | project
      TimeGenerated,
      OperationName,
      DynamicGroupName,
      modifiedBy,
      accountName,
      upnSuffix,
      result = Result,
      oldRule,
      newRule
  | order by TimeGenerated desc
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: Name
    columnName: accountName
  - identifier: UPNSuffix
    columnName: upnSuffix
suppressionEnabled: false
suppressionDuration: 5h
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []

Stages and Predicates

Parameters

let monitoredGroups = dynamic(["Group1", "Group2"]);

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName == "Update group"

Stage 3: where

| where AdditionalDetails[0].value == "DynamicMembership"

Stage 4: extend

| extend DynamicGroupName = tostring(TargetResources[0].displayName)

Stage 5: where

| where DynamicGroupName in (monitoredGroups)

Stage 6: extend (5 consecutive steps)

| extend modifiedBy = tostring(InitiatedBy.user.userPrincipalName)
| extend accountName = tostring(split(modifiedBy, "@")[0])
| extend upnSuffix = tostring(split(modifiedBy, "@")[1])
| extend oldRule = tostring(TargetResources[0].modifiedProperties[0].oldValue)
| extend newRule = tostring(TargetResources[0].modifiedProperties[0].newValue)

Stage 7: where

| where oldRule != newRule

Stage 8: project

| project
    TimeGenerated,
    OperationName,
    DynamicGroupName,
    modifiedBy,
    accountName,
    upnSuffix,
    result = Result,
    oldRule,
    newRule

Stage 9: sort

| order by TimeGenerated desc

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DynamicGroupNameproject
OperationNameproject
TimeGeneratedproject
accountNameproject
modifiedByproject
newRuleproject
oldRuleproject
resultproject
upnSuffixproject

CTE - Device License Capacity Critical (99%)

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

Device license capacity has reached 99% - new and existing unmanaged devices may go unprotected. Emitted once when usage newly crosses this threshold by the daily tenant health-check job (api/src/functions/tenantHealthChecks.js); resets and can re-fire if usage later drops back below 95% and crosses again.

Rule Body

id: 7f4af730-5c89-4f7a-aa7a-933b6e9309b5
name: CTE - Device License Capacity Critical (99%)
description: |
  Device license capacity has reached 99% - new and existing unmanaged devices may go unprotected. Emitted once when usage newly crosses this threshold by the daily tenant health-check job (api/src/functions/tenantHealthChecks.js); resets and can re-fire if usage later drops back below 95% and crosses again.
severity: High
requiredDataConnectors:
  - connectorId: cybereinforce_cte
    dataTypes:
      - CybereinforceCTE_CL
queryFrequency: 24h
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
tactics:
  []
relevantTechniques: []
query: |
  CybereinforceCTE_CL
  | where EventType == "license.capacity_critical"
  | extend d = parse_json(Details)
  | extend AppName = "Cybereinforce", ActiveDevices = toint(d.activeDevices), LicensedDevices = toint(d.licensedDevices), UsedPct = todouble(d.usedPct)
  | project TimeGenerated, TenantId, AppName, ActiveDevices, LicensedDevices, UsedPct, Severity, Category, Source
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: AppName
      - identifier: InstanceName
        columnName: TenantId
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

CybereinforceCTE_CL

Stage 2: where

| where EventType == "license.capacity_critical"

Stage 3: extend

| extend d = parse_json(Details)

Stage 4: extend

| extend AppName = "Cybereinforce", ActiveDevices = toint(d.activeDevices), LicensedDevices = toint(d.licensedDevices), UsedPct = todouble(d.usedPct)

Stage 5: project

| project TimeGenerated, TenantId, AppName, ActiveDevices, LicensedDevices, UsedPct, Severity, Category, Source

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventTypeeq
  • license.capacity_critical
field:"EventType" kind:eq value:"license.capacity_critical"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActiveDevicesproject
AppNameproject
Categoryproject
LicensedDevicesproject
Severityproject
Sourceproject
TenantIdproject
TimeGeneratedproject
UsedPctproject

Cyble Vision Alerts News Feed Alert

#

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

'Triggers when monitored keywords are found in external news feeds. Helps analysts track relevant cybersecurity news, advisories, or threat intelligence updates tied to monitored entities.'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance

Rule Body

id: d205a93f-b2e3-4708-a359-5e0c88ee3e59
name: Cyble Vision Alerts News Feed Alert
description: |
  'Triggers when monitored keywords are found in external news feeds. Helps analysts track relevant cybersecurity news, advisories, or threat intelligence updates tied to monitored entities.'
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
relevantTechniques:
- T1598
query: |
  Alerts_news_feed  
  | where Service == "news_feed" 
  | extend MappedSeverity = Severity
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
  alertDetailsOverride:
  alertDisplayNameFormat: Relevant Cybersecurity News {{NF_PostTitle}}
  alertDescriptionFormat: |
    A news article referencing the monitored keyword {{KeywordName}} was identified. Source {{NF_PostSourceName}}. URL {{NF_PostURL}}.
customDetails:
  MappedSeverity: Severity
  Status: Status
  AlertID: AlertID
  Service: Service
  NF_PostTitle: NF_PostTitle
  NF_PostSource: NF_PostSourceName
  NF_PostURL: NF_PostURL
  NF_AI_Summary: NF_AI_Summary
entityMappings:
- entityType: Url
  fieldMappings:
  - identifier: Url
    columnName: NF_PostURL
- entityType: Host
  fieldMappings:
  - identifier: HostName
    columnName: KeywordName
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

Alerts_news_feed

Stage 2: where

| where Service == "news_feed"

Stage 3: extend

| extend MappedSeverity = Severity

Indicators

These rows show field, operator, and value matches.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
MappedSeverityextend

Egress Defend - Dangerous Attachment Detected

#
Status
available
Severity
medium
Time window
30m
Group by
Account_0_FullName, From, SenderIP, Subject, TimeGenerated, timesClicked
Source
github.com/Azure/Azure-Sentinel

'Defend has detected a user has a suspicious file type from a suspicious sender in their mailbox.'

MITRE ATT&CK Coverage

Rule Body

id: a0e55dd4-8454-4396-91e6-f28fec3d2cab
name: Egress Defend - Dangerous Attachment Detected
description: |
  'Defend has detected a user has a suspicious file type from a suspicious sender in their mailbox.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: EgressDefend
    dataTypes:
      - EgressDefend_CL
      - EgressDefend_v4_CL
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - InitialAccess
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1204
  - T0853
  - T0863
  - T1566
  - T1546
  - T1546
tags:
  - Defend
query: |
  DefendAuditData_v4
  | where ThreatLevel == "suspicious" or ThreatLevel == "dangerous"
  | mv-expand todynamic(Attachments)
  | where Attachments.name matches regex @"(?i)^.*\.(doc|docx|docm|pdf|xls|xlsx|xlsm|html|zip)$(?-i)"
  | summarize attachmentCount=count() by TimeGenerated, tostring(Attachments.name), Subject, From, Account_0_FullName = trim(@"[^@.\w]+",Recipients), timesClicked = LinksClicked, SenderIP
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account_0_FullName
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: Attachments_name
  - entityType: Mailbox
    fieldMappings:
      - identifier: MailboxPrimaryAddress
        columnName: Account_0_FullName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SenderIP
alertDetailsOverride:
  alertDisplayNameFormat: Alert - {{Account_0_FullName}} has suspicious attachment.
version: 1.1.0
kind: Scheduled

Stages and Predicates

Stage 1: source

DefendAuditData_v4

Stage 2: where

| where ThreatLevel == "suspicious" or ThreatLevel == "dangerous"

Stage 3: mv-expand

| mv-expand todynamic(Attachments)

Stage 4: where

| where Attachments.name matches regex @"(?i)^.*\.(doc|docx|docm|pdf|xls|xlsx|xlsm|html|zip)$(?-i)"

Stage 5: summarize

| summarize attachmentCount=count() by TimeGenerated, tostring(Attachments.name), Subject, From, Account_0_FullName = trim(@"[^@.\w]+",Recipients), timesClicked = LinksClicked, SenderIP

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Attachments.nameregex_match
  • (?i)^.*.(doc|docx|docm|pdf|xls|xlsx|xlsm|html|zip)$(?-i) transforms: kusto_dynamic_path
field:"Attachments.name" kind:regex_match value:"(?i)^.*.(doc|docx|docm|pdf|xls|xlsx|xlsm|html|zip)$(?-i)"
ThreatLeveleq
  • dangerous
  • suspicious
field:"ThreatLevel" kind:eq

Output Fields

These fields are emitted when the rule matches.

FieldSource
Account_0_FullNamesummarize
Fromsummarize
SenderIPsummarize
Subjectsummarize
TimeGeneratedsummarize
attachmentCountsummarize
timesClickedsummarize

GitHub - Repository was destroyed

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

Detect activities when a repository was destroyed. This query runs every day and its severity is Medium.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 0b85a077-8ba5-4cb5-90f7-1e882afe10c3
name: GitHub - Repository was destroyed
description: |
  'Detect activities when a repository was destroyed. This query runs every day and its severity is Medium.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
    GitHubAuditData
    | where Action == "repo.destroy"
    | extend AccountName = tostring(split(Actor, "@")[0]), AccountUPNSuffix = tostring(split(Actor, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Actor
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

GitHubAuditData

Stage 2: where

| where Action == "repo.destroy"

Stage 3: extend

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

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • repo.destroy
field:"Action" kind:eq value:"repo.destroy"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameextend
AccountUPNSuffixextend

GWorkspace - Admin permissions granted

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

Triggers on admin permissions granted.

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rule Body

id: 03f25156-6172-11ec-90d6-0242ac120003
name: GWorkspace - Admin permissions granted
description: |
  'Triggers on admin permissions granted.'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: GoogleWorkspaceReportsAPI
    dataTypes:
      - GWorkspaceActivityReports
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1098
query: |
  GWorkspaceActivityReports 
  | where EventMessage =~ 'grant_admin_privilege'
  | extend AccountCustomEntity = ActorEmail
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled

Stages and Predicates

Stage 1: source

GWorkspaceActivityReports

Stage 2: where

| where EventMessage =~ 'grant_admin_privilege'

Stage 3: extend

| extend AccountCustomEntity = ActorEmail

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountCustomEntityextend

HoneyLabs TI Map IP Entity to Network Session (ASIM)

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

Identifies network sessions whose source IP addresses HoneyLabs honeypot telemetry has observed running exploit or loader commands. Uses the ASIM NetworkSession parser, so it works across any normalised network source (firewalls, VPN, cloud NSG flow logs) without requiring CEF. Only the structured source IP is matched; destination IPs are intentionally excluded. Indicator Confidence reflects how much evidence HoneyLabs holds (90 = 100+ observed attacks, 60 = a single sighting); raise the threshold in the query to trade coverage for precision.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 5d3a1c77-2b40-4e6c-9f2d-71c0a8e4b912
name: HoneyLabs TI Map IP Entity to Network Session (ASIM)
version: 1.0.0
kind: Scheduled
description: |
  'Identifies network sessions whose source IP addresses HoneyLabs honeypot telemetry has observed running exploit or loader commands. Uses the ASIM NetworkSession parser, so it works across any normalised network source (firewalls, VPN, cloud NSG flow logs) without requiring CEF. Only the structured source IP is matched; destination IPs are intentionally excluded. Indicator Confidence reflects how much evidence HoneyLabs holds (90 = 100+ observed attacks, 60 = a single sighting); raise the threshold in the query to trade coverage for precision.'
severity: Medium
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - CommandAndControl
relevantTechniques:
  - T1190
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let HoneyLabs_IPs = ThreatIntelIndicators
    | where TimeGenerated >= ago(ioc_lookBack)
    | where IsActive == true and ValidUntil > now()
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
    | where SourceSystem startswith 'HoneyLabs'
    | where ObservableKey == 'ipv4-addr:value'
    | extend TI_ipEntity = tostring(ObservableValue),
             HoneyLabsConfidence = Confidence,
             IndicatorDescription = tostring(Data.description),
             HoneyLabsReport = strcat('https://honeylabs.net/lookup/', tostring(ObservableValue));
  HoneyLabs_IPs
  | join kind=innerunique (
      _Im_NetworkSession(starttime=ago(dt_lookBack))
      | where isnotempty(SrcIpAddr)
      | extend NS_ipEntity = SrcIpAddr
      | extend NetworkSession_TimeGenerated = TimeGenerated
  ) on $left.TI_ipEntity == $right.NS_ipEntity
  | where NetworkSession_TimeGenerated < ValidUntil
  | summarize NetworkSession_TimeGenerated = arg_max(NetworkSession_TimeGenerated, *) by Id, NS_ipEntity
  | project NetworkSession_TimeGenerated, IndicatorDescription, Id, ValidUntil, HoneyLabsConfidence,
    HoneyLabsReport, TI_ipEntity, NS_ipEntity, SrcIpAddr, DstIpAddr, DstPortNumber, Dvc, EventResult
  | extend timestamp = NetworkSession_TimeGenerated
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: NS_ipEntity

Stages and Predicates

Parameters

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

let HoneyLabs_IPs is inlined into the numbered stages below.

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

Stage 1: source

ThreatIntelIndicators

Stage 2: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 3: where

| where IsActive == true and ValidUntil > now()

Stage 4: summarize

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

Stage 5: where

| where SourceSystem startswith 'HoneyLabs'

Stage 6: where

| where ObservableKey == 'ipv4-addr:value'

Stage 7: extend

| extend TI_ipEntity = tostring(ObservableValue),
           HoneyLabsConfidence = Confidence,
           IndicatorDescription = tostring(Data.description),
           HoneyLabsReport = strcat('https://honeylabs.net/lookup/', tostring(ObservableValue))

Stage 8: join

HoneyLabs_IPs
| join kind=innerunique (
    _Im_NetworkSession(starttime=ago(dt_lookBack))
    | where isnotempty(SrcIpAddr)
    | extend NS_ipEntity = SrcIpAddr
    | extend NetworkSession_TimeGenerated = TimeGenerated
) on $left.TI_ipEntity == $right.NS_ipEntity

Stage 9: where

where NetworkSession_TimeGenerated < ValidUntil

Stage 10: summarize

summarize NetworkSession_TimeGenerated by Id, NS_ipEntity

Stage 11: project

project DstIpAddr, DstPortNumber, Dvc, EventResult, HoneyLabsConfidence, HoneyLabsReport, Id, IndicatorDescription, NS_ipEntity, NetworkSession_TimeGenerated, SrcIpAddr, TI_ipEntity, ValidUntil

Stage 12: extend

extend timestamp

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
IsActiveeq
  • true corpus 12 (kusto 12)
field:"IsActive" kind:eq value:"true"
ObservableKeyeq
  • ipv4-addr:value corpus 10 (kusto 10)
field:"ObservableKey" kind:eq value:"ipv4-addr:value"
SourceSystemstarts_with
  • HoneyLabs corpus 4 (kusto 4)
field:"SourceSystem" kind:starts_with value:"HoneyLabs"
SrcIpAddris_not_null
  • (no value, null check)
field:"SrcIpAddr" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstIpAddrproject
DstPortNumberproject
Dvcproject
EventResultproject
HoneyLabsConfidenceproject
HoneyLabsReportproject
Idproject
IndicatorDescriptionproject
NS_ipEntityproject
NetworkSession_TimeGeneratedproject
SrcIpAddrproject
TI_ipEntityproject
ValidUntilproject
timestampextend

Malware in the recycle bin

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

The query detects Windows binaries that can be used for executing malware and have been hidden in the recycle bin. The list of these binaries is sourced from https://lolbas-project.github.io/ References: https://azure.microsoft.com/blog/how-azure-security-center-helps-reveal-a-cyberattack/.

MITRE ATT&CK Coverage

TacticTechniques
Stealth

Telemetry Coverage

Rule Body

id: 75bf9902-0789-47c1-a5d8-f57046aa72df
name: Malware in the recycle bin
description: |
  'The query detects Windows binaries that can be used for executing malware and have been hidden in the recycle bin.
  The list of these binaries is sourced from https://lolbas-project.github.io/
  References: https://azure.microsoft.com/blog/how-azure-security-center-helps-reveal-a-cyberattack/.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes: 
      - WindowsEvent 
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1564
query: |
  let procList = externaldata(Process:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Microsoft_Lolbas_Execution_Binaries.csv"] with (format="csv", ignoreFirstRecord=True);
  let recycle_bin_paths = dynamic([@":\RECYCLER", @":\$RECYCLE.BIN"]);
  let ProcessCreationEvents=(union isfuzzy=true
  (SecurityEvent
  | where EventID==4688
  | where isnotempty(CommandLine)
  | project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, NewProcessName,
  FileName = Process, CommandLine,  ParentProcessName
  ),
  (WindowsEvent
  | where EventID==4688 and EventData has_any (procList) and EventData has_any (recycle_bin_paths)
  | extend CommandLine = tostring(EventData.CommandLine)
  | where isnotempty(CommandLine)
  | extend SubjectUserName = tostring(EventData.SubjectUserName)
  | extend SubjectDomainName = tostring(EventData.SubjectDomainName) 
  | extend NewProcessName = tostring(EventData.NewProcessName)  
  | extend ParentProcessName = tostring(EventData.ParentProcessName)
  | extend Process=tostring(split(NewProcessName, '\\')[-1])
  | project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, NewProcessName,
  FileName = Process, CommandLine,  ParentProcessName
  ));
  ProcessCreationEvents 
  | where FileName in~ (procList)
  | where CommandLine has_any (recycle_bin_paths)
  | project StartTimeUtc = TimeGenerated, Computer, Account, NewProcessName, FileName, CommandLine, ParentProcessName
  | extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Account
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
version: 1.1.6
kind: Scheduled

Stages and Predicates

Parameters

let recycle_bin_paths = dynamic([@":\RECYCLER", @":\$RECYCLE.BIN"]);

Let Binding: procList

let procList = externaldata(Process:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Microsoft_Lolbas_Execution_Binaries.csv"] with (format="csv", ignoreFirstRecord=True);

Union isfuzzy=true (2 Sources)

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

Leg 1: SecurityEvent

SecurityEvent
| where EventID==4688
| where isnotempty(CommandLine)
| project TimeGenerated, Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName, NewProcessName,
FileName = Process, CommandLine,  ParentProcessName

Leg 2: WindowsEvent

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

Applied to the Combined Result

| where FileName in~ (procList) | where CommandLine has_any (recycle_bin_paths) | project StartTimeUtc = TimeGenerated, Computer, Account, NewProcessName, FileName, CommandLine, ParentProcessName | extend HostName = iif(Computer has '.',substring(Computer,0,indexof(Computer,'.')),Computer) , DnsDomain = iif(Computer has '.',substring(Computer,indexof(Computer,'.')+1),'')

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
CommandLineis_not_null
  • (no value, null check)
field:"CommandLine" kind:is_not_null
CommandLinematch
  • :\$RECYCLE.BIN transforms: term
  • :\RECYCLER transforms: term
field:"CommandLine" kind:match
EventDatamatch
  • :\$RECYCLE.BIN transforms: term
  • :\RECYCLER transforms: term
  • procList transforms: term
field:"EventData" kind:match
EventIDeq
  • 4688
field:"EventID" kind:eq value:"4688"
FileNamein
  • procList
field:"FileName" kind:in value:"procList"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountproject
CommandLineproject
Computerproject
FileNameproject
NewProcessNameproject
ParentProcessNameproject
StartTimeUtcproject
DnsDomainextend
HostNameextend

Office365 Sharepoint File transfer Folders above threshold

#
Severity
medium
Time window
15m
Group by
ClientIP, UserAgent, UserId
Source
github.com/Azure/Azure-Sentinel

Identifies Office365 Sharepoint File Transfers with distinct folder count above certain threshold in a 15min time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.

MITRE ATT&CK Coverage

TacticTechniques
Exfiltration

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: 8a547285-801c-4290-aa2e-5e7e20ca157d
name: Office365 Sharepoint File transfer Folders above threshold
description: |
  'Identifies Office365 Sharepoint File Transfers with distinct folder count above certain threshold in a 15min time period.
   Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.'
severity: Medium
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity (SharePoint)
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1020
query: |
  let threshold = 500;
  OfficeActivity
  | where EventSource == "SharePoint" and OfficeWorkload has_any("SharePoint", "OneDrive") and Operation has_any ("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded")
  | summarize count_distinct_SourceRelativeUrl=dcount(SourceRelativeUrl), dirlist=make_set(SourceRelativeUrl, 10000) by UserId,ClientIP,UserAgent,bin(TimeGenerated, 15m)
  | where count_distinct_SourceRelativeUrl >= threshold
  | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField","_", tostring(hash(tostring(dirlist)))))
  | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserId
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: DirSample
customDetails:
  TransferCount: count_distinct_SourceRelativeUrl
  FilesList: dirlist
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: Selected
    groupByEntities:
    - Account
    groupByAlertDetails: []
    groupByCustomDetails: []
version: 1.0.6
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 500;

Stage 1: source

OfficeActivity

Stage 2: where

| where EventSource == "SharePoint" and OfficeWorkload has_any("SharePoint", "OneDrive") and Operation has_any ("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded")

Stage 3: summarize

| summarize count_distinct_SourceRelativeUrl=dcount(SourceRelativeUrl), dirlist=make_set(SourceRelativeUrl, 10000) by UserId,ClientIP,UserAgent,bin(TimeGenerated, 15m)

Stage 4: where

| where count_distinct_SourceRelativeUrl >= threshold

Stage 5: extend

| extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField","_", tostring(hash(tostring(dirlist)))))
DirSample =
ifdirlist == 1tostring(dirlist[0])
elsestrcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))

Stage 6: extend

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

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventSourceeq
  • SharePoint corpus 2 (kusto 2)
field:"aws::eventSource" kind:eq value:"SharePoint"
OfficeWorkloadmatch
  • OneDrive transforms: term corpus 2 (kusto 2)
  • SharePoint transforms: term corpus 2 (kusto 2)
field:"m365::Workload" kind:match
Operationmatch
  • FileDownloaded transforms: term corpus 2 (kusto 2)
  • FileSyncDownloadedFull transforms: term corpus 2 (kusto 2)
  • FileSyncUploadedFull transforms: term corpus 2 (kusto 2)
  • FileUploaded transforms: term corpus 2 (kusto 2)
field:"Operation" kind:match
count_distinct_SourceRelativeUrlge
  • 500 transforms: number
field:"count_distinct_SourceRelativeUrl" kind:ge value:"500"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientIPsummarize
UserAgentsummarize
UserIdsummarize
count_distinct_SourceRelativeUrlsummarize
dirlistsummarize
DirSampleextend
AccountNameextend
AccountUPNSuffixextend

Ping Federate - OAuth old version

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

'Detects requests using not the latest version of OAuth protocol.'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 85f70197-4865-4635-a4b2-a9c57e8fea1b
name: Ping Federate - OAuth old version
description: |
  'Detects requests using not the latest version of OAuth protocol.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
query: |
  PingFederateEvent
  | where isnotempty(DeviceCustomString3)
  | extend proto = extract(@'(OAuth)', 1, DeviceCustomString3)
  | extend ver = extract(@'(\d+)', 1, DeviceCustomString3)
  | where proto =~ 'OAuth'
  | where ver !~ '20'
  | extend AccountCustomEntity = DstUserName
  | extend IpCustomEntity = SrcIpAddr
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpCustomEntity
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

PingFederateEvent

Stage 2: where

| where isnotempty(DeviceCustomString3)

Stage 3: extend

| extend proto = extract(@'(OAuth)', 1, DeviceCustomString3)

Stage 4: extend

| extend ver = extract(@'(\d+)', 1, DeviceCustomString3)

Stage 5: where

| where proto =~ 'OAuth'

Stage 6: where

| where ver !~ '20'

Stage 7: extend

| extend AccountCustomEntity = DstUserName

Stage 8: extend

| extend IpCustomEntity = SrcIpAddr

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
protoextend
verextend
AccountCustomEntityextend
IpCustomEntityextend

Silk Typhoon 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. 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. Reference: https://www.microsoft.com/security/blog/2021/03/02/hafnium-targeting-exchange-servers/'

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 03e04c97-8cae-48b3-9d2f-4ab262e4ffff
name: Silk Typhoon Suspicious File Downloads.
description: |
  'This query looks for messages related to file downloads of suspicious file types. 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. 
  Reference: https://www.microsoft.com/security/blog/2021/03/02/hafnium-targeting-exchange-servers/'
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.3
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - 0-day Vulnerability" ]

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

Theom - Healthcare data exposed

#

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

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

"Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0015 (Theom has observed healthcare data in a data store that is publicly exposed. As per this requirement, use this information to apply data access control lists or access permissions to secure your data)"

MITRE ATT&CK Coverage

Rule Body

id: 078b5614-54c7-41a6-8289-5b5870e4c0f9
name: Theom - Healthcare data exposed
description: |
   "Creates Sentinel incidents for critical/high Theom risks, associated with ruleId TRIS0015 (Theom has observed healthcare data in a data store that is publicly exposed. As per this requirement, use this information to apply data access control lists or access permissions to secure your data)"
severity: High
status : Available
requiredDataConnectors: 
  - connectorId: Theom
    dataTypes:
      - TheomAlerts_CL
queryPeriod: 5m
queryFrequency: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
relevantTechniques:
  - T1213
  - T1530
query: |
   TheomAlerts_CL
     | where customProps_RuleId_s == "TRIS0015" and (priority_s == "P1" or priority_s == "P2")
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: "Theom Alert ID: {{id_s}} "
  alertDescriptionFormat: "\nSummary: {{summary_s}}  \nAdditional info: {{details_s}}\nPlease investigate further on Theom UI at {{deepLink_s}}\n"
entityMappings:
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: customProps_AssetName_s
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: deepLink_s
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

TheomAlerts_CL

Stage 2: where

| where customProps_RuleId_s == "TRIS0015" and (priority_s == "P1" or priority_s == "P2")

Indicators

These rows show field, operator, and value matches.

Vectra AI Detect - Suspected Compromised Account

#
Status
available
Severity
informational
Time window
5m
Group by
saccount
Source
github.com/Azure/Azure-Sentinel

'Create an incident when an Account is suspected to be compromised. The higher the severity level is, the more immediate attention it requires as Vectra AI engine is more confident that this is a real threat. Level of severity are: Low, Medium, High, Critical). Recommended configuration is to trigger an alert for at least High and Critical.'

MITRE ATT&CK Coverage

Rule Body

id: 321f9dbd-64b7-4541-81dc-08cf7732ccb0
name: Vectra AI Detect - Suspected Compromised Account
description: |
  'Create an incident when an Account is suspected to be compromised. 
  The higher the severity level is, the more immediate attention it requires as Vectra AI engine is more confident that this is a real threat. 
  Level of severity are: Low, Medium, High, Critical). Recommended configuration is to trigger an alert for at least High and Critical.'
severity: Informational
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: AlertPerResult
tactics:
  - CredentialAccess
  - Discovery
  - LateralMovement
  - Collection
  - CommandAndControl
  - Exfiltration
  - Impact
relevantTechniques:
  - T1003
  - T1087
  - T1021
  - T1119
  - T1071
  - T1041
  - T1499
query: |
  // Edit this variable to only keep the Severity level where an incident needs to be created.
  //Level of severity are: Low, Medium, High, Critical). Recommended configuration is to trigger an alert for at least High and Critical.'
  let configured_level = dynamic(["High", "Critical"]);
  let upn_has_prefix = ":";
  CommonSecurityLog
  | where DeviceVendor == "Vectra Networks"
  | where DeviceProduct == "X Series"
  | where DeviceEventClassID == "asc"
  | extend saccount = extract("saccount=(.+?);", 1, AdditionalExtensions)
  | extend type = iff(saccount matches regex upn_has_prefix, tostring(split(saccount,":")[0]) ,"network" ) 
  | extend upn = iff(saccount matches regex upn_has_prefix, tostring(split(saccount,":")[1]) , saccount )
  | extend name = tostring(split(upn,"@")[0])
  | extend upn_suffix = tostring(split(upn,"@")[1])
  | project-rename threat_score = FlexNumber1
  | project-rename certainty_score = FlexNumber2
  | project-rename vectra_URL = DeviceCustomString4
  | project-rename detection_name = DeviceEventClassID
  | project-rename score_decreases = DeviceCustomString3
  | extend level = case( threat_score <  50 and certainty_score < 50, "Low",
                        threat_score < 50 and certainty_score >= 50 , "Medium", 
                        threat_score >= 50 and certainty_score <= 50, "High", 
                        threat_score >= 50 and certainty_score >= 50, "Critical",
                        "UNKNOWN")
  | extend Severity = case(level == "Info", "Informational",level == "Critical", "High", level)
  | where level in (configured_level) 
  //keep only the event with the highest threat score per Host
  | summarize arg_max(threat_score, *) by saccount
  | sort by TimeGenerated
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: name
      - identifier: UPNSuffix
        columnName: upn_suffix
alertDetailsOverride:
  alertDisplayNameFormat: Vectra AI Detect - Account {{saccount}} reaches {{level}} severity
  alertDescriptionFormat: |
    The account {{saccount}} has a threat score of {{threat_score}} and a
    certainty of {{certainty_score}}
  alertSeverityColumnName: Severity
  alertDynamicProperties:
    - alertProperty: AlertLink
      value: vectra_URL
    - alertProperty: ProductName
      value: DeviceProduct
    - alertProperty: ProviderName
      value: DeviceVendor
    - alertProperty: ConfidenceScore
      value: certainty_score
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: true
    lookbackDuration: 7d
    matchingMethod: AllEntities
customDetails:
  ScoreDecrease: score_decreases
version: 1.0.9
kind: Scheduled

Stages and Predicates

Parameters

let configured_level = dynamic(["High", "Critical"]);
let upn_has_prefix = ":";

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor == "Vectra Networks"

Stage 3: where

| where DeviceProduct == "X Series"

Stage 4: where

| where DeviceEventClassID == "asc"

Stage 5: extend (5 consecutive steps)

| extend saccount = extract("saccount=(.+?);", 1, AdditionalExtensions)
| extend type = iff(saccount matches regex upn_has_prefix, tostring(split(saccount,":")[0]) ,"network" )
| extend upn = iff(saccount matches regex upn_has_prefix, tostring(split(saccount,":")[1]) , saccount )
| extend name = tostring(split(upn,"@")[0])
| extend upn_suffix = tostring(split(upn,"@")[1])

Stage 6: project-rename

| project-rename threat_score = FlexNumber1

Stage 7: project-rename

| project-rename certainty_score = FlexNumber2

Stage 8: project-rename

| project-rename vectra_URL = DeviceCustomString4

Stage 9: project-rename

| project-rename detection_name = DeviceEventClassID

Stage 10: project-rename

| project-rename score_decreases = DeviceCustomString3

Stage 11: extend

| extend level = case( threat_score <  50 and certainty_score < 50, "Low",
                      threat_score < 50 and certainty_score >= 50 , "Medium", 
                      threat_score >= 50 and certainty_score <= 50, "High", 
                      threat_score >= 50 and certainty_score >= 50, "Critical",
                      "UNKNOWN")
level =
ifthreat_score < 50 and certainty_score < 50"Low"
elifthreat_score < 50 and certainty_score >= 50"Medium"
elifthreat_score >= 50 and certainty_score <= 50"High"
elifthreat_score >= 50 and certainty_score >= 50"Critical"
else"UNKNOWN"

Stage 12: extend

| extend Severity = case(level == "Info", "Informational",level == "Critical", "High", level)
Severity =
iflevel == "Info""Informational"
eliflevel == "Critical""High"
elselevel

Stage 13: where

| where level in (configured_level)

Stage 14: summarize

| summarize arg_max(threat_score, *) by saccount

Stage 15: sort

| sort by TimeGenerated

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
saccountsummarize

Virtual Machines Alerts for Prancer

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

'High severity virtual machine alerts found by Prancer.'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance

Rule Body

id: c13b025c-ea31-4e4b-8e08-955b8fa91fa0
name: Virtual Machines Alerts for Prancer
description: |
  'High severity virtual machine alerts found by Prancer.'
severity: High
requiredDataConnectors:
- connectorId: PrancerLogData
  dataTypes:
    - prancer_CL
queryFrequency: 5h
queryPeriod: 5h
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
  aggregationKind: SingleAlert
status: Available
tactics:
  - Reconnaissance
relevantTechniques:
  - T1595
query: |
  union prancer_CL
  | where deviceProduct_s == 'azure'
  | where parse_json(data_data_snapshots_s)[0].type == 'Microsoft.Compute/virtualMachines'
  | where data_data_severity_s == 'High' and data_data_result_s == 'failed'
  | extend snapshot = parse_json(data_data_snapshots_s)
  | mv-expand snapshot 
  | extend
      id = tostring(snapshot.id),
      structure = tostring(snapshot.structure),
      reference = tostring(snapshot.reference),
      source = tostring(snapshot.source),
      collection = tostring(snapshot.collection),
      type = tostring(snapshot.type),
      region = tostring(snapshot.region),
      resourceTypes = tostring(snapshot.resourceTypes),
      path = tostring(snapshot.path)

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


Stages and Predicates

Stage 1: union

union of 1 branches

Stage 2: source

prancer_CL

Stage 3: where

where deviceProduct_s == "azure"

Stage 4: where

where parse_json(data_data_snapshots_s)[0].type == "Microsoft.Compute/virtualMachines"

Stage 5: where

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

Stage 6: extend

extend snapshot

Stage 7: mv-expand

mv-expand snapshot

Stage 8: extend

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

Indicators

These rows show field, operator, and value matches.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
snapshotextend
collectionextend
idextend
pathextend
referenceextend
regionextend
resourceTypesextend
sourceextend
structureextend
typeextend

Web Application attack detected

#
Status
available
Severity
high
Time window
1d
Group by
SourceIp, ThreatCategory
Source
github.com/Azure/Azure-Sentinel

Identifies Web application attack in Azure Firewall IDPS logs.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Rule Body

id: 8159a8d2-13a5-49af-847b-e062c45ab92b
name: Web Application attack detected
description: |
  Identifies Web application attack in Azure Firewall IDPS logs.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: AzureFirewall
    dataTypes:
      - AZFWIdpsSignature
queryFrequency: 1h
queryPeriod: 24h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
query: |
  let TimeWindow   = 90d;    
  let HitThreshold = 10;     
  let MinSeverity  = 2;
  let EnableCategoryFilter    = true;
  let EnableDescriptionFilter = false;
  let EnableActionFilter      = false;
  let CategoriesOfInterest = dynamic([
      "access to a potentially vulnerable web application",
      "Web Application Attack"
  ]);
  let DescriptionsOfInterest = dynamic([
      "web-application-activity",
      "web-application-attack"
  ]);
  let MatchActions = dynamic(["Deny", "alert"]);
  AZFWIdpsSignature
  | where TimeGenerated >= ago(TimeWindow)
  | where Severity >= MinSeverity
  | where (EnableCategoryFilter == false) or (Category has_any (CategoriesOfInterest))
  | where (EnableDescriptionFilter == false) or (Description has_any (DescriptionsOfInterest))
  | where (EnableActionFilter == false) or (Action in~ (MatchActions))
  | summarize
      StartTime   = min(TimeGenerated),
      EndTime     = max(TimeGenerated),
      TotalHits   = count(),
      MaxSeverity = max(Severity),
      Actions     = make_set(Action, 5),
      Signatures  = make_set(SignatureId, 20),
      Description = make_set(substring(tostring(Description), 0, 120), 3)
      by SourceIp, ThreatCategory = Category
  | where TotalHits >= HitThreshold
  | project
      StartTime,
      EndTime,
      SourceIp,
      ThreatCategory,
      TotalHits,
      MaxSeverity,
      Actions,
      Signatures,
      Description
  | order by MaxSeverity desc, TotalHits desc
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIp
version: 1.0.1
kind: Scheduled

Stages and Predicates

Parameters

let TimeWindow = 90d;
let HitThreshold = 10;
let MinSeverity = 2;
let EnableCategoryFilter = true;
let EnableDescriptionFilter = false;
let EnableActionFilter = false;
let MatchActions = dynamic(["Deny", "alert"]);

Let Binding: CategoriesOfInterest used in Stage 4

let CategoriesOfInterest = dynamic([
    "access to a potentially vulnerable web application",
    "Web Application Attack"
]);

Let Binding: DescriptionsOfInterest used in Stage 5

let DescriptionsOfInterest = dynamic([
    "web-application-activity",
    "web-application-attack"
]);

Stage 1: source

AZFWIdpsSignature

Stage 2: where

| where TimeGenerated >= ago(TimeWindow)

Stage 3: where

| where Severity >= MinSeverity

Stage 4: where

| where (EnableCategoryFilter == false) or (Category has_any (CategoriesOfInterest))

Stage 5: where

| where (EnableDescriptionFilter == false) or (Description has_any (DescriptionsOfInterest))

Stage 6: where

| where (EnableActionFilter == false) or (Action in~ (MatchActions))

Stage 7: summarize

| summarize
    StartTime   = min(TimeGenerated),
    EndTime     = max(TimeGenerated),
    TotalHits   = count(),
    MaxSeverity = max(Severity),
    Actions     = make_set(Action, 5),
    Signatures  = make_set(SignatureId, 20),
    Description = make_set(substring(tostring(Description), 0, 120), 3)
    by SourceIp, ThreatCategory = Category

Stage 8: where

| where TotalHits >= HitThreshold

Stage 9: project

| project
    StartTime,
    EndTime,
    SourceIp,
    ThreatCategory,
    TotalHits,
    MaxSeverity,
    Actions,
    Signatures,
    Description

Stage 10: sort

| order by MaxSeverity desc, TotalHits desc

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Categorymatch
  • Web Application Attack transforms: term
  • access to a potentially vulnerable web application transforms: term
field:"Category" kind:match
Severityge
  • 2 transforms: number
field:"Severity" kind:ge value:"2"
TotalHitsge
  • 10 transforms: number
field:"TotalHits" kind:ge value:"10"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionsproject
Descriptionproject
EndTimeproject
MaxSeverityproject
Signaturesproject
SourceIpproject
StartTimeproject
ThreatCategoryproject
TotalHitsproject

Back to all Kusto rules