Detection rules › Kusto

Kusto rules: 0p

ADWS Connection from Process Injection Target

#
Group by
$left.InitiatingProcessFileName, $left.InitiatingProcessId, $right.FileName, $right.ProcessId, DeviceId, DeviceName, InitiatingProcessFileName, InitiatingProcessId
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

The query first collects all network connections to the Active Directory Web Services (ADWS) service. It then searches for processes that inject into a process that makes a connection to ADWS. This can be used to detect process injection into a process that is used to query Active Directory.

Known False Positives

  • None expected.

MITRE ATT&CK Coverage

References

Telemetry Coverage

Rule Body

let timeframe = 2*1h;
let ADWSConnections=(
    DeviceNetworkEvents
    | where ingestion_time() >= ago(timeframe)
    | where ActionType == "ConnectionSuccess"
    | where RemotePort == 9389
    | extend InitiatingProcessFileName=tolower(InitiatingProcessFileName)
);
let ADWSFileNames=materialize(
    ADWSConnections
    | distinct InitiatingProcessFileName
);
// Look for processes that inject into a process that makes an ADWS connection.
let InjectorProcesses=materialize(
    DeviceEvents
    | where ingestion_time() >= ago(timeframe)
    | where not(isempty(FileName))
    // Look for actions associated with process injection.
    | where ActionType in~ ("CreateRemoteThreadApiCall", "MemoryRemoteProtect","NtAllocateVirtualMemoryApiCall", "NtAllocateVirtualMemoryRemoteApiCall", "NtMapViewOfSectionRemoteApiCall", "NtProtectVirtualMemoryApiCall", "SetThreadContextRemoteApiCall", "QueueUserApcRemoteApiCall")
    | where FileName in~ (ADWSFileNames)
    | where not(InitiatingProcessFolderPath startswith @"c:\program files\vmware\vmware tools" and InitiatingProcessFileName =~ "vmtoolsd.exe")
    | where not(InitiatingProcessFolderPath =~ @"C:\Windows\System32" and InitiatingProcessFileName =~ "csrss.exe")
    | where not(InitiatingProcessFolderPath =~ @"C:\Windows\System32\csrss.exe")
    | where not(InitiatingProcessFolderPath startswith @"c:\program files\microsoft azure ad sync\" and InitiatingProcessFileName =~ "miiserver.exe")
    | extend FileName=tolower(FileName)
    | lookup kind=inner ADWSConnections on DeviceId, $left.ProcessId == $right.InitiatingProcessId, $left.FileName == $right.InitiatingProcessFileName
    // Begin environment-specific filter.
    // End environment-specific filter.
    | project DeviceId, ProcessId, FileName, InjectorProcessId=InitiatingProcessId, InjectorFileName=InitiatingProcessFileName, InjectorActionType=ActionType
);
// Find ADWS connections from processes that were injected into by another process.
ADWSConnections
| lookup kind=inner InjectorProcesses on DeviceId, $left.InitiatingProcessId == $right.ProcessId, $left.InitiatingProcessFileName == $right.FileName
| summarize arg_min(Timestamp,*), InjectionMethods=make_set(InjectorActionType) by DeviceId, DeviceName, InitiatingProcessId, InitiatingProcessFileName
| extend HostName=tostring(split(DeviceName,".")[0]),DnsDomain=iif(DeviceName contains ".", substring(DeviceName, indexof(DeviceName, ".") + 1, strlen(DeviceName)),"")
// Begin environment-specific filter.
// End environment-specific filter.

Stages and Predicates

Parameters

let timeframe = 2*1h;

let ADWSConnections is inlined into the numbered stages below.

Let Binding: ADWSFileNames used in Stage 2

let ADWSFileNames = materialize(
    ADWSConnections
    | distinct InitiatingProcessFileName
);

Let Binding: InjectorProcesses used in Stages 3, 9

let InjectorProcesses = materialize(
    DeviceEvents
    | where ingestion_time() >= ago(timeframe)
    | where not(isempty(FileName))
    | where ActionType in~ ("CreateRemoteThreadApiCall", "MemoryRemoteProtect","NtAllocateVirtualMemoryApiCall", "NtAllocateVirtualMemoryRemoteApiCall", "NtMapViewOfSectionRemoteApiCall", "NtProtectVirtualMemoryApiCall", "SetThreadContextRemoteApiCall", "QueueUserApcRemoteApiCall")
    | where FileName in~ (ADWSFileNames)
    | where not(InitiatingProcessFolderPath startswith @"c:\program files\vmware\vmware tools" and InitiatingProcessFileName =~ "vmtoolsd.exe")
    | where not(InitiatingProcessFolderPath =~ @"C:\Windows\System32" and InitiatingProcessFileName =~ "csrss.exe")
    | where not(InitiatingProcessFolderPath =~ @"C:\Windows\System32\csrss.exe")
    | where not(InitiatingProcessFolderPath startswith @"c:\program files\microsoft azure ad sync\" and InitiatingProcessFileName =~ "miiserver.exe")
    | extend FileName=tolower(FileName)
    | lookup kind=inner ADWSConnections on DeviceId, $left.ProcessId == $right.InitiatingProcessId, $left.FileName == $right.InitiatingProcessFileName
    | project DeviceId, ProcessId, FileName, InjectorProcessId=InitiatingProcessId, InjectorFileName=InitiatingProcessFileName, InjectorActionType=ActionType
);

Stage 1: source

let ADWSConnections

Stage 2: source

let ADWSFileNames

Stage 3: source

let InjectorProcesses

Stage 4: source

DeviceNetworkEvents

Stage 5: where

where ...

Stage 6: where

where ActionType == "ConnectionSuccess"

Stage 7: where

where RemotePort == 9389

Stage 8: extend

extend InitiatingProcessFileName

Stage 9: kusto:lookup

lookup kind=inner (InjectorProcesses) on DeviceId, $left.InitiatingProcessId, $right.ProcessId, $left.InitiatingProcessFileName, $right.FileName

Stage 10: summarize

summarize InjectionMethods by DeviceId, DeviceName, InitiatingProcessId, InitiatingProcessFileName

Stage 11: extend

extend DnsDomain, HostName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypeeq
  • ConnectionSuccess corpus 4 (kusto 4)
field:"ActionType" kind:eq value:"ConnectionSuccess"
ActionTypein
  • CreateRemoteThreadApiCall
  • MemoryRemoteProtect
  • NtAllocateVirtualMemoryApiCall
  • NtAllocateVirtualMemoryRemoteApiCall
  • NtMapViewOfSectionRemoteApiCall
  • NtProtectVirtualMemoryApiCall
  • QueueUserApcRemoteApiCall
  • SetThreadContextRemoteApiCall
field:"ActionType" kind:in
FileNamein
  • ADWSFileNames
field:"FileName" kind:in value:"ADWSFileNames"
RemotePorteq
  • 9389
field:"RemotePort" kind:eq value:"9389"

Exclusions

The rule actively suppresses these predicates.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceIdsummarize
DeviceNamesummarize
InitiatingProcessFileNamesummarize
InitiatingProcessIdsummarize
InjectionMethodssummarize
DnsDomainextend
HostNameextend

Authentication Method Changed for Privileged Account

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

Identifies authentication methods being changed for a privileged account. This could be an indication of an attacker adding an auth method to the account so they can have continued access. Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor-1

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: feb0a2fb-ae75-4343-8cbc-ed545f1da289
name: Authentication Method Changed for Privileged Account
description: |
  'Identifies authentication methods being changed for a privileged account. This could be an indication of an attacker adding an auth method to the account so they can have continued access.
  Ref : https://docs.microsoft.com/azure/active-directory/fundamentals/security-operations-privileged-accounts#things-to-monitor-1'
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
  - connectorId: BehaviorAnalytics
    dataTypes:
      - IdentityInfo
queryFrequency: 2h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1098
tags:
  - AADSecOpsGuide
query: |
    let VIPUsers = (IdentityInfo
    | where AssignedRoles contains "Admin"
    | summarize by tolower(AccountUPN));
    AuditLogs
    | where TimeGenerated > ago(2h)
    | where Category =~ "UserManagement"
    | where ActivityDisplayName =~ "User registered security info"
    | where LoggedByService =~ "Authentication Methods"
    | extend TargetUserPrincipalName = tostring(TargetResources[0].userPrincipalName)
    | where tolower(TargetUserPrincipalName) in (VIPUsers)
    | 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: AadUserId
        columnName: TargetAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIPAddress
version: 1.0.5
kind: Scheduled

Stages and Predicates

Let Binding: VIPUsers used in Stages 1, 8

let VIPUsers = (IdentityInfo
| where AssignedRoles contains "Admin"
| summarize by tolower(AccountUPN));

Stage 1: source

let VIPUsers

Stage 2: source

AuditLogs

Stage 3: where

where TimeGenerated > ago(7200s)

Stage 4: where

where Category =~ "UserManagement"

Stage 5: where

where ActivityDisplayName =~ "User registered security info"

Stage 6: where

where LoggedByService =~ "Authentication Methods"

Stage 7: extend

extend TargetUserPrincipalName

Stage 8: where

where TargetUserPrincipalName == "VIPUsers"

Stage 9: 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

Awake Security - High Match Counts By Device

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

This query searches for devices with unexpectedly large number of activity match.

Rule Body

id: 90b7ac11-dd6c-4ba1-a99b-737061873859
name: Awake Security - High Match Counts By Device
description: This query searches for devices with unexpectedly large number of activity match.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
  CommonSecurityLog
  | where DeviceVendor == "Arista Networks" and DeviceProduct == "Awake Security"
  | summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), Models=make_set(Activity), ASPMatchURLs=make_set(DeviceCustomString2), SourceIPs=make_set(SourceIP),
    DestinationIPs=make_set(DestinationIP), ModelMatchCount=sum(EventCount), MaxSeverity=max(toint(LogSeverity)) by SourceHostName
  | where ModelMatchCount > 1000 and MaxSeverity > 2
  | extend SeverityName=iff(MaxSeverity == 0, "Informational", iff(MaxSeverity < 5, "Low", iff(MaxSeverity < 8, "Medium", "High")))
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: Awake Security - High Model Match Counts On Device {{SourceHostName}}
  alertDescriptionFormat: |-
    The following Awake model(s):

    {{Models}}

    matched {{ModelMatchCount}} activities, an unexpectedly large number. The destination IPs associated with these matches were:

    {{DestinationIPs}}
  alertTacticsColumnName: null
  alertSeverityColumnName: SeverityName
customDetails:
  Matched_Models: Models
  Matches_ASP_URLs: ASPMatchURLs
  Device: SourceHostName
  Matches_Count: ModelMatchCount
  Matches_Max_Severity: MaxSeverity
  Matches_Dest_IPs: DestinationIPs
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: SourceHostName
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIPs
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: true
    lookbackDuration: 3d
    matchingMethod: Selected
    groupByEntities:
      - Host
    groupByAlertDetails: []
    groupByCustomDetails:
      - Device
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

CommonSecurityLog

Stage 2: where

| where DeviceVendor == "Arista Networks" and DeviceProduct == "Awake Security"

Stage 3: summarize

| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), Models=make_set(Activity), ASPMatchURLs=make_set(DeviceCustomString2), SourceIPs=make_set(SourceIP),
  DestinationIPs=make_set(DestinationIP), ModelMatchCount=sum(EventCount), MaxSeverity=max(toint(LogSeverity)) by SourceHostName

Stage 4: where

| where ModelMatchCount > 1000 and MaxSeverity > 2

Stage 5: extend

| extend SeverityName=iff(MaxSeverity == 0, "Informational", iff(MaxSeverity < 5, "Low", iff(MaxSeverity < 8, "Medium", "High")))
SeverityName =
ifMaxSeverity == 0"Informational"
elseiff((MaxSeverity < 5), "Low", iff((MaxSeverity < 8), "Medium", "High"))

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ASPMatchURLssummarize
DestinationIPssummarize
EndTimesummarize
MaxSeveritysummarize
ModelMatchCountsummarize
Modelssummarize
SourceHostNamesummarize
SourceIPssummarize
StartTimesummarize
SeverityNameextend

AWSCloudTrail - Creation of SSM policy and then privilege escalation

#
Status
available
Severity
medium
Time window
1d
Group by
AccountName, AccountUPNSuffix, EventName, EventSource, PolicyName, RecipientAccountId, SourceIpAddress, UserIdentityArn, UserIdentityType
Source
github.com/Azure/Azure-Sentinel

Identifies creation of IAM policies that grant broad AWS Systems Manager permissions followed by attachment to a user, role, or group. This sequence can indicate an attempt to gain administration capabilities that support privilege escalation.

MITRE ATT&CK Coverage

Telemetry Coverage

Rules Detecting the Same Action

These rules filter on the same operation.

Rule Body

id: aaa2c05e-fdd4-4fa0-9072-6cffe3641b34
name: AWSCloudTrail - Creation of SSM policy and then privilege escalation
description: |
  Identifies creation of IAM policies that grant broad AWS Systems Manager permissions followed by attachment to a user, role, or group. This sequence can indicate an attempt to gain administration capabilities that support privilege escalation.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - PrivilegeEscalation
  - Persistence
relevantTechniques:
  - T1484
  - T1098.003
query: |
  let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
  let createPolicy =  dynamic(["CreatePolicy", "CreatePolicyVersion"]);
  let timeframe = 1d;
  let lookback = 14d;
  // Creating Master table with all the events to use with materialize for better performance
  let EventInfo = materialize(AWSCloudTrail
  | where TimeGenerated >= ago(lookback)
  | where EventName in (EventNameList) or EventName in (createPolicy)
  | 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]), ""));
  //Checking for Policy creation event with Full Admin Privileges since lookback period.
  let FullAdminPolicyEvents = EventInfo
  | where TimeGenerated >= ago(lookback)
  | where EventName in (createPolicy)
  | extend PolicyName = tostring(parse_json(RequestParameters).policyName)
  | extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement
  | mvexpand Statement
  | extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)
  | extend Action = tostring(Action)
  | where Effect =~ "Allow" and (Action contains "ssm:*" or Action contains "ssm:Create*" or Action contains "ssm:CreateAssociation") and Resource == "*" and Condition == ""
  | distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix
  | project-rename StartTime = TimeGenerated;
  let PolicyAttach = EventInfo
  | where TimeGenerated >= ago(timeframe)
  | where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
  | extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
  | summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
  | extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType",   UserIdentityType, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn, "SourceIpAddress", SourceIpAddress)
  | project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount
  ;
  // Joining the list of PolicyNames and checking if it has been attached to any Roles/Users/Groups.
  // These Roles/Users/Groups will be Privileged and can be used by adversaries as pivot point for privilege escalation via multiple ways.
  FullAdminPolicyEvents
  | join kind=leftouter
  (
      PolicyAttach
  )
  on PolicyName
  | project-away PolicyName1
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
      - identifier: CloudAppAccountId
        columnName: RecipientAccountId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
customDetails:
  PolicyName: PolicyName
  EventName: EventName
  RecipientAccountId: RecipientAccountId
  UserIdentityArn: UserIdentityArn
alertDetailsOverride:
  alertDisplayNameFormat: 'AWS Systems Manager privilege escalation policy activity: {{PolicyName}} by {{AccountName}}'
  alertDescriptionFormat: 'Detected {{EventName}} for policy {{PolicyName}} in account {{RecipientAccountId}}.'
version: 1.0.3
kind: Scheduled

Stages and Predicates

Parameters

let EventNameList = dynamic(["AttachUserPolicy","AttachRolePolicy","AttachGroupPolicy"]);
let createPolicy = dynamic(["CreatePolicy", "CreatePolicyVersion"]);
let timeframe = 1d;
let lookback = 14d;

let EventInfo and let FullAdminPolicyEvents are inlined into the numbered stages below.

Let Binding: PolicyAttach used in Stage 15

let PolicyAttach = EventInfo
| where TimeGenerated >= ago(timeframe)
| where EventName in (EventNameList) and isempty(ErrorCode) and isempty(ErrorMessage)
| extend PolicyName = tostring(split(tostring(parse_json(RequestParameters).policyArn),"/")[1])
| summarize AttachEventCount=count(), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by EventSource, EventName,   UserIdentityType , UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName
| extend AttachEvent = pack("StartTime", StartTime, "EndTime", EndTime, "EventName", EventName, "UserIdentityType",   UserIdentityType, "AccountName", AccountName, "AccountUPNSuffix", AccountUPNSuffix, "RecipientAccountId", RecipientAccountId, "UserIdentityArn", UserIdentityArn, "SourceIpAddress", SourceIpAddress)
| project EventSource, PolicyName, AttachEvent, RecipientAccountId, AccountName, AccountUPNSuffix, AttachEventCount;

Stages 1 to 14 define let FullAdminPolicyEvents as the rule's main pipeline source. Stages 15 to 17 use it.

Stage 1: source

AWSCloudTrail

Stage 2: where

| where TimeGenerated >= ago(lookback)

Stage 3: where

| where EventName in (EventNameList) or EventName in (createPolicy)

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: where

| where TimeGenerated >= ago(lookback)

Stage 6: where

| where EventName in (createPolicy)

Stage 7: extend

| extend PolicyName = tostring(parse_json(RequestParameters).policyName)

Stage 8: extend

| extend Statement = parse_json(tostring((parse_json(RequestParameters).policyDocument))).Statement

Stage 9: mv-expand

| mvexpand Statement

Stage 10: extend

| extend Action = parse_json(Statement).Action , Effect = tostring(parse_json(Statement).Effect), Resource = tostring(parse_json(Statement).Resource), Condition = tostring(parse_json(Statement).Condition)

Stage 11: extend

| extend Action = tostring(Action)

Stage 12: where

| where Effect =~ "Allow" and (Action contains "ssm:*" or Action contains "ssm:Create*" or Action contains "ssm:CreateAssociation") and Resource == "*" and Condition == ""

Stage 13: distinct

| distinct TimeGenerated, EventName, PolicyName, SourceIpAddress, UserIdentityArn, RecipientAccountId, AccountName, AccountUPNSuffix

Stage 14: project-rename

| project-rename StartTime = TimeGenerated

Stage 15: join

FullAdminPolicyEvents
| join kind=leftouter
(
    PolicyAttach
)
on PolicyName

Stage 16: project-away

project-away PolicyName1

Stage 17: summarize aggregation inside the join branch

summarize by EventSource, EventName, UserIdentityType, UserIdentityArn, SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, PolicyName

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioncontains
  • ssm:*
  • ssm:Create*
  • ssm:CreateAssociation
field:"Action" kind:contains
Effecteq
  • Allow
field:"Effect" kind:eq value:"Allow"
ErrorCodeis_null
  • (no value, null check)
field:"ErrorCode" kind:is_null
ErrorMessageis_null
  • (no value, null check)
field:"ErrorMessage" kind:is_null
EventNamein
  • AttachGroupPolicy corpus 17 (kusto 15, panther 2)
  • AttachGroupPolicy
  • AttachRolePolicy corpus 17 (kusto 15, panther 2)
  • AttachRolePolicy
  • AttachUserPolicy corpus 17 (kusto 15, panther 2)
  • AttachUserPolicy
  • CreatePolicy corpus 14 (kusto 12, panther 2)
  • CreatePolicy
  • CreatePolicyVersion corpus 14 (kusto 12, panther 2)
  • CreatePolicyVersion
field:"aws::eventName" kind:in
Resourceeq
  • *
field:"Resource" kind:eq value:"*"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNamesummarize
AccountUPNSuffixsummarize
EventNamesummarize
EventSourcesummarize
PolicyNamesummarize
RecipientAccountIdsummarize
SourceIpAddresssummarize
UserIdentityArnsummarize
UserIdentityTypesummarize

Azure Security Benchmark Posture Changed

#
Severity
medium
Time window
7d
Group by
AssessedResourceId, ComplianceDomain, Failed, Passed, PassedControlsPercentage, RecommendationName, Total
Source
github.com/Azure/Azure-Sentinel

This rule monitors Azure policies aligned with the Azure Security Benchmark regulatory compliance initiative and triggers when policy compliance falls below 70% within a 7-day time window.

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: 0610e72f-ceaf-42d1-879e-952a1bd8d07a
name: Azure Security Benchmark Posture Changed
description: This rule monitors Azure policies aligned with the Azure Security Benchmark regulatory compliance initiative
  and triggers when policy compliance falls below 70% within a 7-day time window.
severity: Medium
requiredDataConnectors:
- connectorId: AzureSecurityCenter
  dataTypes:
  - SecurityRecommendation
  - SecurityRegulatoryCompliance
queryFrequency: 7d
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1082
query: | 
  let ComplianceDomainLookup = SecurityRecommendation
  | join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName
  | summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
  | extend ComplianceDomain = iff(ComplianceControl contains "NS.", "Network Security", iff(ComplianceControl contains "IM.", "Identity Management", iff(ComplianceControl contains "PA.", "Privileged Access", iff(ComplianceControl contains "DP.", "Data Protection", iff(ComplianceControl contains "AM.", "Asset Management", iff(ComplianceControl contains "LT.", "Logging & Threat Detection", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "PV.", "Posture & Vulnerability Management", iff(ComplianceControl contains "ES.", "Endpoint Security", iff(ComplianceControl contains "BR.", "Backup & Recovery", iff(ComplianceControl startswith "DS.", "DevOps Security", iff(ComplianceControl contains "GS.", "Governance & Strategy", "Other"))))))))))));
  SecurityRecommendation
  | join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName
  | summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
  | extend ComplianceDomain = iff(ComplianceControl contains "NS.", "Network Security", iff(ComplianceControl contains "IM.", "Identity Management", iff(ComplianceControl contains "PA.", "Privileged Access", iff(ComplianceControl contains "DP.", "Data Protection", iff(ComplianceControl contains "AM.", "Asset Management", iff(ComplianceControl contains "LT.", "Logging & Threat Detection", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "PV.", "Posture & Vulnerability Management", iff(ComplianceControl contains "ES.", "Endpoint Security", iff(ComplianceControl contains "BR.", "Backup & Recovery", iff(ComplianceControl startswith "DS.", "DevOps Security", iff(ComplianceControl contains "GS.", "Governance & Strategy", "Other"))))))))))))
  | summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by ComplianceDomain
  | extend PassedControlsPercentage = iff(Total > 0, (Passed / todouble(Total)) * 100.0, real(null))
  | join kind=leftouter (ComplianceDomainLookup) on ComplianceDomain
  | project ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed, LastEvaluated = TimeGenerated
  | summarize arg_max(LastEvaluated, *) by ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed
  | where PassedControlsPercentage < 70
  | sort by PassedControlsPercentage asc, Passed desc
  | extend RemediationLink = strcat("https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22")
entityMappings:
- entityType: URL
  fieldMappings:
  - identifier: Url
    columnName: RemediationLink
alertDetailsOverride:
  alertDisplayNameFormat: Azure Security Benchmark posture below threshold for {{ComplianceDomain}}
customDetails:
  ComplianceDomain: ComplianceDomain
  TotalControls: Total
  PassedControls: Passed
  FailedControls: Failed
version: 1.0.2
kind: Scheduled

Stages and Predicates

Let Binding: ComplianceDomainLookup used in Stage 7

let ComplianceDomainLookup = SecurityRecommendation
| join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName
| summarize arg_max(TimeGenerated, *) by AssessedResourceId, RecommendationName
| extend ComplianceDomain = iff(ComplianceControl contains "NS.", "Network Security", iff(ComplianceControl contains "IM.", "Identity Management", iff(ComplianceControl contains "PA.", "Privileged Access", iff(ComplianceControl contains "DP.", "Data Protection", iff(ComplianceControl contains "AM.", "Asset Management", iff(ComplianceControl contains "LT.", "Logging & Threat Detection", iff(ComplianceControl contains "IR.", "Incident Response", iff(ComplianceControl contains "PV.", "Posture & Vulnerability Management", iff(ComplianceControl contains "ES.", "Endpoint Security", iff(ComplianceControl contains "BR.", "Backup & Recovery", iff(ComplianceControl startswith "DS.", "DevOps Security", iff(ComplianceControl contains "GS.", "Governance & Strategy", "Other"))))))))))));

Stage 1: source

SecurityRecommendation

Stage 2: join

| join kind=fullouter (SecurityRegulatoryCompliance | where ComplianceStandard == "Azure-Security-Benchmark") on RecommendationName

Stage 3: summarize

summarize by AssessedResourceId, RecommendationName

Stage 4: extend

extend ComplianceDomain
ComplianceDomain =
ifComplianceControl contains "NS.""Network Security"
elseiff((ComplianceControl contains "IM."), "Identity Management", iff((ComplianceControl contains "PA."), "Privileged Access", iff((ComplianceControl contains "DP."), "Data Protection", iff((ComplianceControl contains "AM."), "Asset Management", iff((ComplianceControl contains "LT."), "Logging & Threat Detection", iff((ComplianceControl contains "IR."), "Incident Response", iff((ComplianceControl contains "PV."), "Posture & Vulnerability Management", iff((ComplianceControl contains "ES."), "Endpoint Security", iff((ComplianceControl contains "BR."), "Backup & Recovery", iff((ComplianceControl startswith "DS."), "DevOps Security", iff((ComplianceControl contains "GS."), "Governance & Strategy", "Other")))))))))))

Stage 5: summarize

summarize Failed, Passed, Total by ComplianceDomain

Stage 6: extend

extend PassedControlsPercentage
PassedControlsPercentage =
ifTotal > 0((Passed / todouble(Total)) * 100.0)
elsereal(null)

Stage 7: join

join kind=leftouter (ComplianceDomainLookup) on ComplianceDomain

Stage 8: project

project ComplianceDomain, Failed, LastEvaluated, Passed, PassedControlsPercentage, Total

Stage 9: summarize

summarize by ComplianceDomain, Total, PassedControlsPercentage, Passed, Failed

Stage 10: where

where PassedControlsPercentage < 70

Stage 11: sort

sort by Passed, PassedControlsPercentage

Stage 12: extend

extend RemediationLink

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ComplianceDomainsummarize
Failedsummarize
Passedsummarize
PassedControlsPercentagesummarize
Totalsummarize
RemediationLinkextend

Corelight - Possible Webshell

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

'Detects post requests to unusual extensions.'

MITRE ATT&CK Coverage

TacticTechniques
Persistence

Rule Body

id: f3245aa1-1ca1-471c-a0b7-97ea6b791d5d
name: Corelight - Possible Webshell
description: |
  'Detects post requests to unusual extensions.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Corelight
    dataTypes:
      - corelight_http
      - corelight_http_red
      - corelight_http2
      - corelight_http_agg
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
relevantTechniques:
  - T1505
query: |
  union isfuzzy=true corelight_http, corelight_http_agg
  | where method in~ ('POST', 'PUT')
  | where toint(status_code) between (200 .. 299)
  | where request_body_len != 0 or response_body_len != 0
  | extend fe = extract(@'.*(\.\w+)$', 1, uri)
  | where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: id_orig_h
version: 2.2.0
kind: Scheduled

Stages and Predicates

Union isfuzzy=true (2 Sources)

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

Leg 1: corelight_http

Leg 2: corelight_http_agg

Applied to the Combined Result

| where method in~ ('POST', 'PUT') | where toint(status_code) between (200 .. 299) | where request_body_len != 0 or response_body_len != 0 | extend fe = extract(@'.*(\.\w+)$', 1, uri) | where fe in~ ('.jpg', '.jpeg', '.gif', '.png', '.icon', '.ico', '.xml', '.swf', '.svg', '.ppt', '.pttx', '.doc', '.docx', '.rtf', '.pdf', '.tif', '.zip', '.mov')

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
feextend

CYFIRMA - Medium severity Trojan Network Indicators - Monitor Recommended Rule

#

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

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

"This analytics rule detects network-based indicators flagged by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure. These indicators may represent attacker-controlled endpoints used for persistence, data exfiltration, or command delivery to compromised systems."

MITRE ATT&CK Coverage

Rule Body

id: 104f4574-fc95-4f38-8aa2-02f0b78eba9b
name: CYFIRMA - Medium severity Trojan Network Indicators - Monitor Recommended Rule
description: |
  "This analytics rule detects network-based indicators flagged by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure. 
  These indicators may represent attacker-controlled endpoints used for persistence, data exfiltration, or command delivery to compromised systems."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
  - connectorId: CyfirmaCyberIntelligenceDC
    dataTypes:
      - CyfirmaIndicators_CL
query: |
  //Trojan Network Indicators - Monitor Recommended 
  let timeFrame= 5m;
  CyfirmaIndicators_CL 
  | where (ConfidenceScore < 80 and ConfidenceScore >= 50)
      and TimeGenerated between (ago(timeFrame) .. now())
      and pattern !contains 'file:hashes' and RecommendedActions has 'Monitor' and Roles has 'Trojan'
  | extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
  | extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
  | extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
  | extend parsed = parse_json(extensions)
  | extend extensionKeys = bag_keys(parsed)
  | mv-expand extensionKeys
  | extend extensionKeyStr = tostring(extensionKeys)
  | extend ext = parsed[extensionKeyStr]
  | extend props = ext.properties
  | extend 
      extension_id = extensionKeyStr,
      ASN_Owner = props.asn_owner,
      ASN = props.asn,
      ProviderName = 'CYFIRMA',
      ProductName = 'DeCYFIR/DeTCT'
  | project
      IPv4,
      IPv6,
      URL,
      Domain,
      ThreatActors,
      RecommendedActions,
      Sources,
      Roles,
      Country,
      IPAbuse,
      name,
      Description,
      ConfidenceScore,
      IndicatorID,
      created,
      modified,
      valid_from,
      Tags,
      ThreatType,
      TimeGenerated,
      SecurityVendors,
      ProductName,
      ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
  - Impact
  - Persistence
  - DefenseEvasion
  - CredentialAccess
  - CommandAndControl
  - Execution
  - InitialAccess
relevantTechniques:
  - T1496
  - T1053
  - T1555
  - T1090
  - T1071
  - T1204
  - T1189
  - T1053.005
  - T1555.003
  - T1071.001
  - T1204.001
alertDetailsOverride:
  alertDisplayNameFormat: "High-Confidence Trojan Network Indicators - Monitor Recommended Rule - {{name}} "
  alertDescriptionFormat: "{{Description}} - {{name}} "
  alertDynamicProperties:
    - alertProperty: ProductName
      value: ProductName
    - alertProperty: ProviderName
      value: ProviderName
customDetails:
  ThreatActors: ThreatActors
  Sources: Sources
  RecommendedActions: RecommendedActions
  Roles: Roles
  Country: Country
  Description: Description
  ConfidenceScore: ConfidenceScore
  SecurityVendors: SecurityVendors
  IndicatorID: IndicatorID
  Created: created
  Modified: modified
  ValidFrom: valid_from
  Tags: Tags
  ThreatType: ThreatType
  TimeGenerated: TimeGenerated
  IPAbuse: IPAbuse
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv4
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPv6
  - entityType: DNS
    fieldMappings:
      - identifier: DomainName
        columnName: Domain
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: URL
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Parameters

let timeFrame = 5m;

Stage 1: source

CyfirmaIndicators_CL

Stage 2: where

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

Stage 3: extend (6 consecutive steps)

| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)

Stage 4: mv-expand

| mv-expand extensionKeys

Stage 5: extend (4 consecutive steps)

| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend 
    extension_id = extensionKeyStr,
    ASN_Owner = props.asn_owner,
    ASN = props.asn,
    ProviderName = 'CYFIRMA',
    ProductName = 'DeCYFIR/DeTCT'

Stage 6: project

| project
    IPv4,
    IPv6,
    URL,
    Domain,
    ThreatActors,
    RecommendedActions,
    Sources,
    Roles,
    Country,
    IPAbuse,
    name,
    Description,
    ConfidenceScore,
    IndicatorID,
    created,
    modified,
    valid_from,
    Tags,
    ThreatType,
    TimeGenerated,
    SecurityVendors,
    ProductName,
    ProviderName

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
patterncontainsfile:hashesexcludes:pattern field:"pattern" value:"file:hashes"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ConfidenceScoreproject
Countryproject
Descriptionproject
Domainproject
IPAbuseproject
IPv4project
IPv6project
IndicatorIDproject
ProductNameproject
ProviderNameproject
RecommendedActionsproject
Rolesproject
SecurityVendorsproject
Sourcesproject
Tagsproject
ThreatActorsproject
ThreatTypeproject
TimeGeneratedproject
URLproject
createdproject
modifiedproject
nameproject
valid_fromproject

Dataverse - Audit log data deletion

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

Identifies audit log data deletion activity in Dataverse.

MITRE ATT&CK Coverage

TacticTechniques
Stealth

Telemetry Coverage

Rule Body

id: f1634822-b7e9-44f5-95ac-fa4a04f14513
kind: Scheduled
name: Dataverse - Audit log data deletion
description: Identifies audit log data deletion activity in Dataverse.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1070
query: |
  let query_frequency = 1h;
  DataverseActivity
  | where TimeGenerated >= ago(query_frequency)
  | where Message =~ 'DeleteRecordChangeHistory' or Message =~ 'DeleteAuditData'
  | extend CloudAppId = int(32780)
  | extend AccountName = tostring(split(UserId, "@")[0])
  | extend UPNSuffix = tostring(split(UserId, "@")[1])
  | project
      TimeGenerated,
      UserId,
      ClientIp,
      UserAgent,
      Message,
      EntityName,
      InstanceUrl,
      AccountName,
      UPNSuffix,
      CloudAppId
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
alertDetailsOverride:
  alertDisplayNameFormat: Dataverse - Audit logs deleted in {{InstanceUrl}}
  alertDescriptionFormat: User {{UserId}} deleted audit log data in {{InstanceUrl}}.
    The message type is  {{Message}}.
version: 3.2.0

Stages and Predicates

Parameters

let query_frequency = 1h;

Stage 1: source

DataverseActivity

Stage 2: where

| where TimeGenerated >= ago(query_frequency)

Stage 3: where

| where Message =~ 'DeleteRecordChangeHistory' or Message =~ 'DeleteAuditData'

Stage 4: extend (3 consecutive steps)

| extend CloudAppId = int(32780)
| extend AccountName = tostring(split(UserId, "@")[0])
| extend UPNSuffix = tostring(split(UserId, "@")[1])

Stage 5: project

| project
    TimeGenerated,
    UserId,
    ClientIp,
    UserAgent,
    Message,
    EntityName,
    InstanceUrl,
    AccountName,
    UPNSuffix,
    CloudAppId

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Messageeq
  • DeleteAuditData
  • DeleteRecordChangeHistory
field:"Message" kind:eq

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
ClientIpproject
CloudAppIdproject
EntityNameproject
InstanceUrlproject
Messageproject
TimeGeneratedproject
UPNSuffixproject
UserAgentproject
UserIdproject

DNS events related to mining pools

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

'Identifies IP addresses that may be performing DNS lookups associated with common currency mining pools.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 0d76e9cf-788d-4a69-ac7d-f234826b5bed
name: DNS events related to mining pools
description: |
  'Identifies IP addresses that may be performing DNS lookups associated with common currency mining pools.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: DNS
    dataTypes:
      - DnsEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1496
query: |
  DnsEvents
  | where Name contains "."
  | where Name has_any ("monerohash.com", "do-dear.com", "xmrminerpro.com", "secumine.net", "xmrpool.com", "minexmr.org", "hashanywhere.com",
  "xmrget.com", "mininglottery.eu", "minergate.com", "moriaxmr.com", "multipooler.com", "moneropools.com", "xmrpool.eu", "coolmining.club",
  "supportxmr.com", "minexmr.com", "hashvault.pro", "xmrpool.net", "crypto-pool.fr", "xmr.pt", "miner.rocks", "walpool.com", "herominers.com",
  "gntl.co.uk", "semipool.com", "coinfoundry.org", "cryptoknight.cc", "fairhash.org", "baikalmine.com", "tubepool.xyz", "fairpool.xyz", "asiapool.io",
  "coinpoolit.webhop.me", "nanopool.org", "moneropool.com", "miner.center", "prohash.net", "poolto.be", "cryptoescrow.eu", "monerominers.net", "cryptonotepool.org",
  "extrmepool.org", "webcoin.me", "kippo.eu", "hashinvest.ws", "monero.farm", "supportxmr.com", "xmrpool.eu", "linux-repository-updates.com", "1gh.com",
  "dwarfpool.com", "hash-to-coins.com", "hashvault.pro", "pool-proxy.com", "hashfor.cash", "fairpool.cloud", "litecoinpool.org", "mineshaft.ml", "abcxyz.stream",
  "moneropool.ru", "cryptonotepool.org.uk", "extremepool.org", "extremehash.com", "hashinvest.net", "unipool.pro", "crypto-pools.org", "monero.net",
  "backup-pool.com", "mooo.com", "freeyy.me", "cryptonight.net", "shscrypto.net")
  | extend HostName = iff(Computer has '.', substring(Computer,0,indexof(Computer,'.')),Computer)
  | extend DnsDomain = iff(Computer has '.', substring(Computer,indexof(Computer,'.')+1),"")
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
version: 1.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

DnsEvents

Stage 2: where

| where Name contains "."

Stage 3: where

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

Stage 4: extend

| extend HostName = iff(Computer has '.', substring(Computer,0,indexof(Computer,'.')),Computer)
HostName =
ifComputer has "."substring(Computer, 0, indexof(Computer, '.'))
elseComputer

Stage 5: extend

| extend DnsDomain = iff(Computer has '.', substring(Computer,indexof(Computer,'.')+1),"")
DnsDomain =
ifComputer has "."substring(Computer, (indexof(Computer, '.') + 1))
else""

Indicators

These rows show field, operator, and value matches.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
HostNameextend
DnsDomainextend

License Expiring

#
Status
available
Severity
informational
Time window
1d
Group by
$left.Edition, $left.Type, $right.Edition, $right.Type
Source
github.com/Azure/Azure-Sentinel

Detects when a Veeam license expires shortly.

Rule Body

id: 79adb08a-8218-492d-acf0-f8aa596f3444
name: License Expiring
description: Detects when a Veeam license expires shortly.
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: "let license_editions_lookup = union isfuzzy=true (datatable(Edition:string,\
  \ EditionDescription:string)[]), (_GetWatchlist(\"license_editions_lookup\")); \n\
  let license_types_lookup = union isfuzzy=true (datatable(Type:string, TypeDescription:string)[]),\
  \ (_GetWatchlist(\"license_types_lookup\")); \nVeeam_GetSecurityEvents\n| where\
  \ instanceId == 24020\n| extend Edition = extract(\"Edition=\\\"([^\\\"]*)\\\"\"\
  , 1, SyslogMessage)\n| lookup kind=leftouter (license_editions_lookup) \n    on\
  \ $left.Edition == $right.Edition\n| extend Type = extract(\"Type=\\\"([^\\\"]*)\\\
  \"\", 1, SyslogMessage)\n| lookup kind=leftouter (license_types_lookup) \n    on\
  \ $left.Type == $right.Type\n| extend TenantID = extract(\"TenantID=\\\"([^\\\"\
  ]*)\\\"\", 1, SyslogMessage)\n| extend DaysLeft = extract(\"DaysLeft=\\\"([^\\\"\
  ]*)\\\"\", 1, SyslogMessage)\n| extend SupportLeft = extract(\"SupportLeft=\\\"\
  ([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n    Date = format_datetime(TimeGenerated,\
  \ 'dd.MM.yyyy HH:mm'),\n    DataSource = original_host,\n    EventId = instanceId,\n\
  \    [\"License Edition\"] = EditionDescription,\n    [\"License Type\"] = TypeDescription,\n\
  \    [\"Days Left\"] = DaysLeft,\n    [\"Days of Support Left\"] = SupportLeft,\n\
  \   MessageDetails = Description,\n   Severity = SeverityDescription\n"
version: 1.0.1
kind: Scheduled
customDetails:
  Date: Date
  VbrHostName: DataSource
  EventId: EventId
  MessageDetails: MessageDetails
  Severity: Severity

Stages and Predicates

Parameters

let license_editions_lookup = union isfuzzy=true (datatable(Edition:string, EditionDescription:string)[]), (_GetWatchlist("license_editions_lookup"));
let license_types_lookup = union isfuzzy=true (datatable(Type:string, TypeDescription:string)[]), (_GetWatchlist("license_types_lookup"));

Stage 1: source

Veeam_GetSecurityEvents

Stage 2: where

| where instanceId == 24020

Stage 3: extend

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

Stage 4: kusto:lookup

lookup kind=leftouter (license_editions_lookup) on $left.Edition, $right.Edition

Stage 5: extend

extend Type

Stage 6: kusto:lookup

lookup kind=leftouter (license_types_lookup) on $left.Type, $right.Type

Stage 7: extend (3 consecutive steps)

extend DaysLeft, SupportLeft, TenantID

Stage 8: project

project DataSource, Date, Days Left, Days of Support Left, EventId, License Edition, License Type, MessageDetails, Severity

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DataSourceproject
Dateproject
Days Leftproject
Days of Support Leftproject
EventIdproject
License Editionproject
License Typeproject
MessageDetailsproject
Severityproject

OCI - Instance metadata access

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

'Detects instance metadata access.'

MITRE ATT&CK Coverage

TacticTechniques
Discovery

Rule Body

id: a55b4bbe-a014-4ae9-a50d-441ba5e98b65
name: OCI - Instance metadata access
description: |
  'Detects instance metadata access.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: OracleCloudInfrastructureLogsConnector
    dataTypes:
      - OCILogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1069
query: |
  OCILogs
  | where EventType contains 'vcn.flowlogs'
  | where data_action_s =~ 'ACCEPT'
  | where DstIpAddr == '169.254.169.254'
  | extend IPCustomEntity = SrcIpAddr
entityMappings:
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

OCILogs

Stage 2: where

| where EventType contains 'vcn.flowlogs'

Stage 3: where

| where data_action_s =~ 'ACCEPT'

Stage 4: where

| where DstIpAddr == '169.254.169.254'

Stage 5: 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

Power Apps - App activity from unauthorized geo

#
Status
available
Severity
low
Time window
14d
Group by
$left.SrcIpAddr, $right.IPAddress, ActorName, AppId, AppName, EnvironmentId, EnvironmentName, SrcIpAddr
Source
github.com/Azure/Azure-Sentinel

Identifies Power Apps activity from countries in a predefined list of unauthorized countries.

MITRE ATT&CK Coverage

TacticTechniques
Initial Access

Telemetry Coverage

Rule Body

id: 7ec1e61d-f3b7-4f40-bb1a-357a63913c23
kind: Scheduled
name: Power Apps - App activity from unauthorized geo
description: Identifies Power Apps activity from countries in a predefined list of
  unauthorized countries.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: PowerPlatformAdmin
    dataTypes:
      - PowerPlatformAdminActivity
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1078
query: |
  let unauthorized_country_codes = dynamic([
      // Specify the disallowed two letter country codes
      // example: disallowed_country_codes = dynamic(["RU", "KP", "IR"])
      ]);
  let query_frequency = 1h;
  let query_lookback = 14d;
  let powerapps_events = dynamic(["LaunchPowerApp", "AppDlpEvaluationResultChange", "UpdatePowerApp", "PublishPowerApp", "RecordScopesConsent", "CreatePowerApp", "PowerAppPermissionEdited", "PowerAppPermissionDeleted", "ImportExistingCanvasApp", "DeletePowerApp", "ImportNewCanvasApp", "PromotePowerAppVersion", "RemoveHeroApp", "DeletePowerAppVersion", "PublishSolutionCanvasAppVersion", "AdminModifyAppPermissions", "AdminModifyAppOwner", "AdminQuarantineApp", "AdminDeleteApp", "AdminSetAppBypassConsent", "PatchPowerApp"]);
  PowerPlatformAdminActivity
  | where TimeGenerated >= ago(query_frequency)
  | where EventOriginalType in (powerapps_events)
  | extend Properties = tostring(PropertyCollection)
  | extend SrcIpAddr = extract(@'"enduser.ip_address","Value":"([^"]+)"', 1, Properties)
  | extend SrcIpAddr = iif(SrcIpAddr startswith '::ffff:', replace_string(SrcIpAddr, '::ffff:', ''), SrcIpAddr)
  | extend AppId = extract(@'"powerplatform.analytics.resource.power_app.id","Value":"([^"]+)"', 1, Properties)
  | extend AppId = tolower(replace_string(AppId, '/providers/Microsoft.PowerApps/apps/', ''))
  | extend
      AppName = extract(@'"powerplatform.analytics.resource.power_app.display_name","Value":"([^"]+)"', 1, Properties),
      EnvironmentId = extract(@'"powerplatform.analytics.resource.environment.id","Value":"([^"]+)"', 1, Properties),
      EnvironmentName = extract(@'"powerplatform.analytics.resource.environment.name","Value":"([^"]+)"', 1, Properties)
  | summarize FirstEvent = min(TimeGenerated) by ActorName, SrcIpAddr, AppName, AppId, EnvironmentId, EnvironmentName
  | join kind=inner (
      SigninLogs
      | where TimeGenerated >= ago(query_lookback)
      | where Location in (unauthorized_country_codes)
      | summarize by IPAddress, Location)
      on $left.SrcIpAddr == $right.IPAddress
  | extend
      PowerAppsEntityId = 27593,
      DataverseId = 32780,
      AccountName = tostring(split(ActorName, '@')[0]),
      UPNSuffix = tostring(split(ActorName, '@')[1])
  | project
      FirstEvent,
      ActorName,
      SrcIpAddr,
      Location,
      AppName,
      AppId,
      EnvironmentId,
      EnvironmentName,
      PowerAppsEntityId,
      AccountName,
      UPNSuffix
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SrcIpAddr
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: PowerAppsEntityId
      - identifier: Name
        columnName: AppName
alertDetailsOverride:
  alertDisplayNameFormat: Power Apps activity from an unauthorized location
  alertDescriptionFormat: 'User {{ActorName}} activity associated with app {{AppName}}
    from an unauthorized geolocation: {{Location}}'
customDetails:
  Environment: EnvironmentId
  App: AppId
  EnvironmentName: EnvironmentName
version: 3.2.0

Stages and Predicates

Parameters

let query_frequency = 1h;
let query_lookback = 14d;

Let Binding: unauthorized_country_codes used in Stage 6

let unauthorized_country_codes = dynamic([
    ]);

Let Binding: powerapps_events used in Stage 3

let powerapps_events = dynamic(["LaunchPowerApp", "AppDlpEvaluationResultChange", "UpdatePowerApp", "PublishPowerApp", "RecordScopesConsent", "CreatePowerApp", "PowerAppPermissionEdited", "PowerAppPermissionDeleted", "ImportExistingCanvasApp", "DeletePowerApp", "ImportNewCanvasApp", "PromotePowerAppVersion", "RemoveHeroApp", "DeletePowerAppVersion", "PublishSolutionCanvasAppVersion", "AdminModifyAppPermissions", "AdminModifyAppOwner", "AdminQuarantineApp", "AdminDeleteApp", "AdminSetAppBypassConsent", "PatchPowerApp"]);

Stage 1: source

PowerPlatformAdminActivity

Stage 2: where

| where TimeGenerated >= ago(query_frequency)

Stage 3: where

| where EventOriginalType in (powerapps_events)

Stage 4: extend (6 consecutive steps)

| extend Properties = tostring(PropertyCollection)
| extend SrcIpAddr = extract(@'"enduser.ip_address","Value":"([^"]+)"', 1, Properties)
| extend SrcIpAddr = iif(SrcIpAddr startswith '::ffff:', replace_string(SrcIpAddr, '::ffff:', ''), SrcIpAddr)
| extend AppId = extract(@'"powerplatform.analytics.resource.power_app.id","Value":"([^"]+)"', 1, Properties)
| extend AppId = tolower(replace_string(AppId, '/providers/Microsoft.PowerApps/apps/', ''))
| extend
    AppName = extract(@'"powerplatform.analytics.resource.power_app.display_name","Value":"([^"]+)"', 1, Properties),
    EnvironmentId = extract(@'"powerplatform.analytics.resource.environment.id","Value":"([^"]+)"', 1, Properties),
    EnvironmentName = extract(@'"powerplatform.analytics.resource.environment.name","Value":"([^"]+)"', 1, Properties)

Stage 5: summarize

| summarize FirstEvent = min(TimeGenerated) by ActorName, SrcIpAddr, AppName, AppId, EnvironmentId, EnvironmentName

Stage 6: join

| join kind=inner (
    SigninLogs
    | where TimeGenerated >= ago(query_lookback)
    | where Location in (unauthorized_country_codes)
    | summarize by IPAddress, Location)
    on $left.SrcIpAddr == $right.IPAddress

Stage 7: extend

extend AccountName, DataverseId, PowerAppsEntityId, UPNSuffix

Stage 8: project

project AccountName, ActorName, AppId, AppName, EnvironmentId, EnvironmentName, FirstEvent, Location, PowerAppsEntityId, SrcIpAddr, UPNSuffix

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventOriginalTypein
  • AdminDeleteApp
  • AdminModifyAppOwner
  • AdminModifyAppPermissions
  • AdminQuarantineApp
  • AdminSetAppBypassConsent
  • AppDlpEvaluationResultChange
  • CreatePowerApp
  • DeletePowerApp
  • DeletePowerAppVersion
  • ImportExistingCanvasApp
  • ImportNewCanvasApp
  • LaunchPowerApp
  • PatchPowerApp
  • PowerAppPermissionDeleted
  • PowerAppPermissionEdited
  • PromotePowerAppVersion
  • PublishPowerApp
  • PublishSolutionCanvasAppVersion
  • RecordScopesConsent
  • RemoveHeroApp
  • UpdatePowerApp
field:"EventOriginalType" kind:in
Locationin
  • []
field:"Location" kind:in value:"[]"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
ActorNameproject
AppIdproject
AppNameproject
EnvironmentIdproject
EnvironmentNameproject
FirstEventproject
Locationproject
PowerAppsEntityIdproject
SrcIpAddrproject
UPNSuffixproject

PROD (TM004.1) - OBJECT - Enabled, Disabled, Unlocked, or Password Reset of a Tier Level Object

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

A Tier Model object has been modified to Enable, Disable, or Unlock of a Tier Level Object.

MITRE ATT&CK Coverage

Telemetry Coverage

Rule Body

id: 1da0cf44-c927-4b6f-9b43-91871b2391b3
name: 'PROD (TM004.1) - OBJECT - Enabled, Disabled, Unlocked, or Password Reset of a Tier Level Object'
description: |
  A Tier Model object has been modified to Enable, Disable, or Unlock of a Tier Level Object.
severity: High
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
  - Persistence
relevantTechniques:
  - T1078.002
query: |
  SecurityEvent 
  | where EventID == 5136 
     and (EventData matches regex @"(?i)OU=Domain Controllers"
        or EventData matches regex @"(?i)OU=Tier 0 Accounts" 
        or EventData matches regex @"(?i)OU=Tier 1 Accounts" 
        or EventData matches regex @"(?i)OU=Tier 2 Accounts"
        or EventData matches regex @"(?i)OU=Tier 0 Service Accounts" 
        or EventData matches regex @"(?i)OU=Tier 1 Service Accounts" 
        or EventData matches regex @"(?i)OU=Tier 2 Service Accounts"
        or EventData matches regex @"(?i)OU=Tier 0 PAW"
        or EventData matches regex @"(?i)OU=Tier 1 PAW"
        or EventData matches regex @"(?i)OU=Tier 2 PAW"
        or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
        or EventData matches regex @"(?i)OU=Tier 1 Member Servers")
     and (EventData has 'AttributeValue">512'
        or EventData has 'AttributeLDAPDisplayName">userAccountControl'
        or EventData has 'AttributeLDAPDisplayName">lockoutTime'
        or EventData has 'AttributeLDAPDisplayName">pwdLastSet')
     and (EventData has 'AttributeValue">512'
        or EventData has 'AttributeValue">514'
        or EventData has 'AttributeValue">0'
        or EventData has 'AttributeValue">4130'
        or EventData has 'AttributeValue">4128')
     and EventData has 'OperationType">%%14674'
  | extend ObjectName_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
  | extend ObjectName = extract("(?i)CN=([^,]+)", 1, ObjectName_e)
  | extend ObjectTier_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
  | extend ObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, ObjectTier_e)
  | extend ObjectTier = iff(isempty(ObjectTier), 
                      iff(EventData matches regex @"(?i)OU=Domain Controllers", "Tier 0", "Outside Tier Model"), 
                      ObjectTier)
  | extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
  | extend AttributeValue = extract("<Data Name=\"AttributeValue\">([^<]+)</Data>", 1, EventData)
  | extend AttributeValue = iff(EventData has 'AttributeValue">512' or EventData has 'AttributeValue">4128', "Enabled", tostring(AttributeValue))
  | extend AttributeValue = iff(EventData has 'AttributeValue">514' or EventData has 'AttributeValue">4130', "Disabled", tostring(AttributeValue))
  | extend AttributeValue = iff(EventData has 'AttributeValue">0' and AttributeLDAP == "pwdLastSet", "Password Reset", tostring(AttributeValue))
  | extend AttributeValue = iff(EventData has 'AttributeValue">0' and AttributeLDAP == "lockoutTime", "Account Unlocked", tostring(AttributeValue))
  | extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
  | extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
  | extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
  | project TimeGenerated
     , ObjectName
     , ObjectTier
     , ObjectClass
     , AttributeValue    
     , ActivityType
     , Account
     , Domain
     , Computer
     , Channel
     , EventID
     , EventData
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: ObjectName
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Computer
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
customDetails:
  ObjectName: ObjectName
  ObjectTier: ObjectTier
  ObjectClass: ObjectClass
  AttributeValue: AttributeValue
  ActivityType: ActivityType
  Account: Account
  Domain: Domain
  Computer: Computer
  Channel: Channel
  EventID: EventID
  EventData: EventData
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT5H
    matchingMethod: AllEntities
alertDetailsOverride:
  alertDisplayNameFormat: '(TM004.1) A {{ObjectTier}} {{ObjectClass}} was {{AttributeValue}}'
  alertDescriptionFormat: '{{ObjectTier}} {{ObjectName}} was {{AttributeValue}}.'
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityEvent

Stage 2: where

| where EventID == 5136 
   and (EventData matches regex @"(?i)OU=Domain Controllers"
      or EventData matches regex @"(?i)OU=Tier 0 Accounts" 
      or EventData matches regex @"(?i)OU=Tier 1 Accounts" 
      or EventData matches regex @"(?i)OU=Tier 2 Accounts"
      or EventData matches regex @"(?i)OU=Tier 0 Service Accounts" 
      or EventData matches regex @"(?i)OU=Tier 1 Service Accounts" 
      or EventData matches regex @"(?i)OU=Tier 2 Service Accounts"
      or EventData matches regex @"(?i)OU=Tier 0 PAW"
      or EventData matches regex @"(?i)OU=Tier 1 PAW"
      or EventData matches regex @"(?i)OU=Tier 2 PAW"
      or EventData matches regex @"(?i)OU=Tier 0 Member Servers"
      or EventData matches regex @"(?i)OU=Tier 1 Member Servers")
   and (EventData has 'AttributeValue">512'
      or EventData has 'AttributeLDAPDisplayName">userAccountControl'
      or EventData has 'AttributeLDAPDisplayName">lockoutTime'
      or EventData has 'AttributeLDAPDisplayName">pwdLastSet')
   and (EventData has 'AttributeValue">512'
      or EventData has 'AttributeValue">514'
      or EventData has 'AttributeValue">0'
      or EventData has 'AttributeValue">4130'
      or EventData has 'AttributeValue">4128')
   and EventData has 'OperationType">%%14674'

Stage 3: extend (14 consecutive steps)

| extend ObjectName_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
| extend ObjectName = extract("(?i)CN=([^,]+)", 1, ObjectName_e)
| extend ObjectTier_e = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
| extend ObjectTier = extract("(?i)OU=(Tier [^ ]+)", 1, ObjectTier_e)
| extend ObjectTier = iff(isempty(ObjectTier), 
                    iff(EventData matches regex @"(?i)OU=Domain Controllers", "Tier 0", "Outside Tier Model"), 
                    ObjectTier)
| extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = extract("<Data Name=\"AttributeValue\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = iff(EventData has 'AttributeValue">512' or EventData has 'AttributeValue">4128', "Enabled", tostring(AttributeValue))
| extend AttributeValue = iff(EventData has 'AttributeValue">514' or EventData has 'AttributeValue">4130', "Disabled", tostring(AttributeValue))
| extend AttributeValue = iff(EventData has 'AttributeValue">0' and AttributeLDAP == "pwdLastSet", "Password Reset", tostring(AttributeValue))
| extend AttributeValue = iff(EventData has 'AttributeValue">0' and AttributeLDAP == "lockoutTime", "Account Unlocked", tostring(AttributeValue))
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend ActivityType = extract("([a-zA-Z]+)\\.$", 1, Activity)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)

Stage 4: project

| project TimeGenerated
   , ObjectName
   , ObjectTier
   , ObjectClass
   , AttributeValue    
   , ActivityType
   , Account
   , Domain
   , Computer
   , Channel
   , EventID
   , EventData

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventDatamatch
  • AttributeLDAPDisplayName">lockoutTime transforms: term
  • AttributeLDAPDisplayName">pwdLastSet transforms: term
  • AttributeLDAPDisplayName">userAccountControl transforms: term
  • AttributeValue">0 transforms: term
  • AttributeValue">4128 transforms: term
  • AttributeValue">4130 transforms: term
  • AttributeValue">512 transforms: term
  • AttributeValue">514 transforms: term
  • OperationType">%%14674 transforms: term corpus 3 (kusto 3)
field:"EventData" kind:match
EventDataregex_match
  • (?i)OU=Domain Controllers corpus 8 (kusto 8)
  • (?i)OU=Tier 0 Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 0 Member Servers corpus 8 (kusto 8)
  • (?i)OU=Tier 0 PAW corpus 6 (kusto 6)
  • (?i)OU=Tier 0 Service Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 1 Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 1 Member Servers corpus 8 (kusto 8)
  • (?i)OU=Tier 1 PAW corpus 6 (kusto 6)
  • (?i)OU=Tier 1 Service Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 2 Accounts corpus 4 (kusto 4)
  • (?i)OU=Tier 2 PAW corpus 6 (kusto 6)
  • (?i)OU=Tier 2 Service Accounts corpus 4 (kusto 4)
field:"EventData" kind:regex_match
EventIDeq
  • 5136 corpus 20 (kusto 18, elastic 2)
field:"EventID" kind:eq value:"5136"

Output Fields

These fields are emitted when the rule matches.

FieldSource
Accountproject
ActivityTypeproject
AttributeValueproject
Channelproject
Computerproject
Domainproject
EventDataproject
EventIDproject
ObjectClassproject
ObjectNameproject
ObjectTierproject
TimeGeneratedproject

PulseConnectSecure - Potential Brute Force Attempts

#
Status
available
Severity
low
Time window
1h
Group by
Source_IP, User
Source
github.com/Azure/Azure-Sentinel

'This query identifies evidence of potential brute force attack by looking at multiple failed attempts to log into the VPN server'

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: 34663177-8abf-4db1-b0a4-5683ab273f44
name: PulseConnectSecure - Potential Brute Force Attempts
description: |
  'This query identifies evidence of potential brute force attack by looking at multiple failed attempts to log into the VPN server'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let threshold = 20;
  PulseConnectSecure
  | where Messages contains "Login failed"
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by User, Source_IP
  | where count_ > threshold
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: Source_IP
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 20;

Stage 1: source

PulseConnectSecure

Stage 2: where

| where Messages contains "Login failed"

Stage 3: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by User, Source_IP

Stage 4: where

| where count_ > threshold

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
EndTimesummarize
Source_IPsummarize
StartTimesummarize
Usersummarize

Suspicious link sharing pattern

#
Severity
low
Time window
1d
Group by
Message, User, UserId
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

'Alerts in links that have been shared across multiple Zoom chat channels by the same user in a short space if time. Adjust the threshold figure to change the number of channels a message needs to be posted in before an alert is raised.'

MITRE ATT&CK Coverage

TacticTechniques
Reconnaissance

Rule Body

id: 1218175f-c534-421c-8070-5dcaabf28067
name: Suspicious link sharing pattern
description: |
  'Alerts in links that have been shared across multiple Zoom chat channels by the same user in a short space if time.
  Adjust the threshold figure to change the number of channels a message needs to be posted in before an alert is raised.'
severity: Low
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Reconnaissance
relevantTechniques:
  - T1598
query: |
  let threshold = 3;
  ZoomLogs
  | where Event =~ "chat_message.sent"
  | extend Channel = tostring(parse_json(ChatEvents).Channel)
  | extend Message = tostring(parse_json(ChatEvents).Message)
  | where Message matches regex "http(s?):\\/\\/"
  | summarize Channels = makeset(Channel), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Message, User, UserId
  | extend ChannelCount = arraylength(Channels)
  | where ChannelCount > threshold
  | extend AccountName = tostring(split(User, "@")[0]), AccountUPNSuffix = tostring(split(User, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
version: 1.0.4
kind: Scheduled
metadata:
    source: 
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let threshold = 3;

Stage 1: source

ZoomLogs

Stage 2: where

| where Event =~ "chat_message.sent"

Stage 3: extend

| extend Channel = tostring(parse_json(ChatEvents).Channel)

Stage 4: extend

| extend Message = tostring(parse_json(ChatEvents).Message)

Stage 5: where

| where Message matches regex "http(s?):\\/\\/"

Stage 6: summarize

| summarize Channels = makeset(Channel), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by Message, User, UserId

Stage 7: extend

| extend ChannelCount = arraylength(Channels)

Stage 8: where

| where ChannelCount > threshold

Stage 9: extend

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

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Channelssummarize
EndTimesummarize
Messagesummarize
StartTimesummarize
Usersummarize
UserIdsummarize
ChannelCountextend
AccountNameextend
AccountUPNSuffixextend

TI Map IP Entity to SigninLogs

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

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

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

Rule Body

id: f2eb15bd-8a88-4b24-9281-e133edfba315
name: TI Map IP Entity to SigninLogs
description: |
  'This query maps any IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in SigninLogs.'
severity: Medium
requiredDataConnectors:
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
relevantTechniques:
  - T1071
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let Signins = materialize(union isfuzzy=true
    (SigninLogs
    | where TimeGenerated >= ago(dt_lookBack)),
    (AADNonInteractiveUserSignInLogs
    | where TimeGenerated >= ago(dt_lookBack)
    | extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)));
  let SigninIPs = Signins | summarize make_list(IPAddress);
  let TI = materialize(ThreatIntelligenceIndicator
    | where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)
    | where TimeGenerated >= ago(ioc_lookBack)
    | extend TI_ipEntity = coalesce(NetworkIP, EmailSourceIpAddress, NetworkDestinationIP, NetworkSourceIP)
    | where TI_ipEntity in (SigninIPs)
    | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
    | where Active == true and ExpirationDateTime > now()
    | where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;");
  TI
  // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  | join kind=innerunique (Signins) on $left.TI_ipEntity == $right.IPAddress
  | project-rename SigninLogs_TimeGenerated = TimeGenerated
  | where SigninLogs_TimeGenerated < ExpirationDateTime
  | extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails), StatusReason = tostring(Status.failureReason)
  | summarize SigninLogs_TimeGenerated = arg_max(SigninLogs_TimeGenerated, *) by IndicatorId, IPAddress
  | project SigninLogs_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, TI_ipEntity, IPAddress, UserPrincipalName, AppDisplayName, StatusCode, StatusDetails, StatusReason, NetworkIP, NetworkDestinationIP, NetworkSourceIP, EmailSourceIpAddress, Type
  | extend timestamp = SigninLogs_TimeGenerated, Name = tostring(split(UserPrincipalName, '@', 0)[0]), UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserPrincipalName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.2.8
kind: Scheduled

Stages and Predicates

Parameters

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

let TI is inlined into the numbered stages below.

Let Binding: Signins used in Stage 9

let Signins = materialize(union isfuzzy=true
  (SigninLogs
  | where TimeGenerated >= ago(dt_lookBack)),
  (AADNonInteractiveUserSignInLogs
  | where TimeGenerated >= ago(dt_lookBack)
  | extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)));

Let Binding: SigninIPs used in Stage 5

let SigninIPs = Signins | summarize make_list(IPAddress);

Stages 1 to 8 define let TI as the rule's main pipeline source. Stages 9 to 15 use it.

Stage 1: source

ThreatIntelligenceIndicator

Stage 2: where

| where isnotempty(NetworkIP) or isnotempty(EmailSourceIpAddress) or isnotempty(NetworkDestinationIP) or isnotempty(NetworkSourceIP)

Stage 3: where

| where TimeGenerated >= ago(ioc_lookBack)

Stage 4: extend

| extend TI_ipEntity = coalesce(NetworkIP, EmailSourceIpAddress, NetworkDestinationIP, NetworkSourceIP)

Stage 5: where

| where TI_ipEntity in (SigninIPs)

Stage 6: summarize

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

Stage 7: where

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

Stage 8: where

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

Stage 9: join

join kind=innerunique (Signins) on $left.TI_ipEntity, $right.IPAddress

Stage 10: project-rename

project-rename

Stage 11: where

where SigninLogs_TimeGenerated < ExpirationDateTime

Stage 12: extend

extend StatusCode, StatusDetails, StatusReason

Stage 13: summarize

summarize SigninLogs_TimeGenerated by IndicatorId, IPAddress

Stage 14: project

project ActivityGroupNames, AppDisplayName, ConfidenceScore, Description, EmailSourceIpAddress, ExpirationDateTime, IPAddress, IndicatorId, NetworkDestinationIP, NetworkIP, NetworkSourceIP, SigninLogs_TimeGenerated, StatusCode, StatusDetails, StatusReason, TI_ipEntity, ThreatType, Type, Url, UserPrincipalName

Stage 15: extend

extend Name, UPNSuffix, timestamp

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

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

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActivityGroupNamesproject
AppDisplayNameproject
ConfidenceScoreproject
Descriptionproject
EmailSourceIpAddressproject
ExpirationDateTimeproject
IPAddressproject
IndicatorIdproject
NetworkDestinationIPproject
NetworkIPproject
NetworkSourceIPproject
SigninLogs_TimeGeneratedproject
StatusCodeproject
StatusDetailsproject
StatusReasonproject
TI_ipEntityproject
ThreatTypeproject
Typeproject
Urlproject
UserPrincipalNameproject
Nameextend
UPNSuffixextend
timestampextend

Uniqkey - Data export activity

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

Detects export of credential or vault data from the Uniqkey organization. Exports are legitimate for backup and migration scenarios, but they move secrets outside the platform's protection and are the primary exfiltration vector for a password manager, so each occurrence is raised as its own alert for review. If scheduled exports by known administrative or service accounts create noise, exclude those actors in the query.

MITRE ATT&CK Coverage

TacticTechniques
Credential Access

Rule Body

id: f3125212-6871-4adc-8335-98c9a920639b
name: Uniqkey - Data export activity
version: 1.0.0
kind: Scheduled
description: |-
  Detects export of credential or vault data from the Uniqkey organization. Exports are legitimate for backup and migration scenarios, but they move secrets outside the platform's protection and are the primary exfiltration vector for a password manager, so each occurrence is raised as its own alert for review. If scheduled exports by known administrative or service accounts create noise, exclude those actors in the query.
severity: Medium
requiredDataConnectors:
  - connectorId: UniqkeyEventsConnector
    dataTypes:
      - UniqkeyEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- Exfiltration
relevantTechniques:
- T1555
query: |-
  UniqkeyEvents_CL
  | where Category == "data_export"
  | project TimeGenerated, ActorEmail, ActorType, Action, ActionId, TargetType, TargetName, ClientSystem, SrcIpAddr
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: FullName
    columnName: ActorEmail
- entityType: IP
  fieldMappings:
  - identifier: Address
    columnName: SrcIpAddr
suppressionEnabled: false
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: 5h
    matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
  aggregationKind: AlertPerResult

Stages and Predicates

Stage 1: source

UniqkeyEvents_CL

Stage 2: where

| where Category == "data_export"

Stage 3: project

| project TimeGenerated, ActorEmail, ActorType, Action, ActionId, TargetType, TargetName, ClientSystem, SrcIpAddr

Indicators

These rows show field, operator, and value matches.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Actionproject
ActionIdproject
ActorEmailproject
ActorTypeproject
ClientSystemproject
SrcIpAddrproject
TargetNameproject
TargetTypeproject
TimeGeneratedproject

VMware ESXi - Low temp directory space

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

'This rule is triggered when temp directory space is detected.'

MITRE ATT&CK Coverage

TacticTechniques
Impact

Rule Body

id: 2ee727f7-b7c2-4034-b6c9-d245d5a29343
name: VMware ESXi - Low temp directory space
description: |
  'This rule is triggered when temp directory space is detected.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SyslogAma
    datatypes:
      - Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1529
query: |
  let threshold = 10;
  VMwareESXi
  | where SyslogMessage has_all ('Temp directory', 'free space')
  | extend sp = toreal(extract(@'free space is:\s(\d+)', 1, SyslogMessage)) / 1000000000
  | where sp < threshold
  | extend h = 'Hypervisor'
  | extend HostCustomEntity = h
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: HostCustomEntity
version: 1.0.2
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 10;

Stage 1: source

VMwareESXi

Stage 2: where

| where SyslogMessage has_all ('Temp directory', 'free space')

Stage 3: extend

| extend sp = toreal(extract(@'free space is:\s(\d+)', 1, SyslogMessage)) / 1000000000

Stage 4: where

| where sp < threshold

Stage 5: extend

| extend h = 'Hypervisor'

Stage 6: extend

| extend HostCustomEntity = h

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
SyslogMessagematch
  • Temp directory transforms: term
  • free space transforms: term
field:"SyslogMessage" kind:match
splt
  • 10 transforms: number
field:"sp" kind:lt value:"10"

Output Fields

These fields are emitted when the rule matches.

FieldSource
spextend
hextend
HostCustomEntityextend

Back to all Kusto rules