Detection rules › Kusto
Kusto rules: 2t
1Password - Potential insider privilege escalation via vault
#This will alert when an actor grants, or updates their own permissions via a vault. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same. Ref: https://1password.com/ Ref: https://github.com/securehats/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| 1Password | any: User vault access (catch-all) |
Rule Body
id: a00ffbd8-1d1c-47a3-b0a6-7d70bd8017ed
name: 1Password - Potential insider privilege escalation via vault
version: 1.0.0
kind: Scheduled
description: |-
This will alert when an actor grants, or updates their own permissions via a vault. Once this analytics rule is triggered it will group all related future alerts for upto an hour when all related entities are the same.
Ref: https://1password.com/
Ref: https://github.com/securehats/
severity: Medium
requiredDataConnectors:
- connectorId: 1Password
dataTypes:
- OnePasswordEventLogs_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques:
- T1078
query: |-
OnePasswordEventLogs_CL
| where log_source == "auditevents"
| where action has_any("grant", "update")
| where object_type == "uva"
| where tostring(actor_details.email) == tostring(aux_details.email)
| extend
TargetUsername = aux_details.email
, ActorUsername = actor_details.email
, SrcIpAddr = session.ip
subTechniques:
- T1078.004
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUsername
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUsername
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
Stages and Predicates
Stage 1: source
OnePasswordEventLogs_CL
Stage 2: where
| where log_source == "auditevents"
Stage 3: where
| where action has_any("grant", "update")
Stage 4: where
| where object_type == "uva"
Stage 5: where
| where tostring(actor_details.email) == tostring(aux_details.email)
Stage 6: extend
| extend
TargetUsername = aux_details.email
, ActorUsername = actor_details.email
, SrcIpAddr = session.ip
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | match |
| field:"action" kind:match |
log_source | eq |
| field:"log_source" kind:eq value:"auditevents" |
object_type | eq |
| field:"object_type" kind:eq value:"uva" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActorUsername | extend |
SrcIpAddr | extend |
TargetUsername | extend |
[Entra ID] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles
#Detects when an application receives permission to assign Microsoft Entra ID roles. This can enable directory privilege escalation and should be reviewed immediately.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Add app role assignment to service principal |
| Azure | Add delegated permission grant |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- Added Credentials to Existing Application (Sigma)
- Added Owner To Application (Sigma)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Granted Microsoft Permissions (Sigma)
- App Granted Privileged Delegated Or App Permissions (Sigma)
- Application AppID Uri Configuration Changes (Sigma)
- Application ID URI Changed (Kusto)
Rule Body
id: ef34b272-930c-41c8-a682-8c2093cd2024
name: "[Entra ID] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles"
version: 1.0.0
kind: Scheduled
description: |
Detects when an application receives permission to assign Microsoft Entra ID roles. This can enable directory privilege escalation and should be reviewed immediately.
severity: High
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- Impact
- PrivilegeEscalation
relevantTechniques:
- T1098.003
query: |
AuditLogs
| where Category =~ "ApplicationManagement"
and LoggedByService =~ "Core Directory"
and OperationName in~ ("Add delegated permission grant", "Add app role assignment to service principal")
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
and array_length(TargetResource.modifiedProperties) > 0
and isnotnull(TargetResource.displayName)
| extend props = TargetResource.modifiedProperties
)
| mv-apply Property = props on
(
where Property.displayName in~ ("AppRole.Value", "DelegatedPermissionGrant.Scope")
| extend
DisplayName = tostring(Property.displayName),
PermissionGrant = trim('"', tostring(Property.newValue))
)
| where PermissionGrant has "RoleManagement.ReadWrite.Directory"
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.DisplayName"
| extend TargetAppDisplayName = trim('"', tostring(Property.newValue))
)
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.ObjectID"
| extend TargetAppServicePrincipalId = trim('"', tostring(Property.newValue))
)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend TargetAppDisplayName = tostring(iff(isnotempty(TargetAppDisplayName), TargetAppDisplayName, TargetAppServicePrincipalId))
| extend
InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]),
InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend Initiator = InitiatingAccountName, Target = TargetAppDisplayName
| extend
Source_Network_IPLocation = ""
| project
Alert_Category_en = "Entra ID",
Alert_SubCategory_en = "Anomaly Identity Privilege Modification",
Alert_Name_en = "Application Granted Administrative Permission to Assign Microsoft Entra ID Roles",
Alert_Description_en = strcat("At Taiwan time", format_datetime(datetime_utc_to_local(TimeGenerated, "Asia/Taipei"), "yyyy-MM-dd HH:mm:ss"), ". ", "EntraID:", "", ". ", "detected source user/service principal ", iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName), " granted application: ", TargetAppDisplayName, ".", " administrative permission to assign Microsoft Entra ID roles: ", ActivityDisplayName, ". Source IP: ", InitiatingIpAddress, ", source location: ", Source_Network_IPLocation),
Alert_TriageStep_en = strcat(
"1. Confirm whether the operating user/service principal: ",
iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
", source IP: ",
InitiatingIpAddress,
" is abnormal. ",
"2. Review the authorized target application ",
TargetAppDisplayName,
" authorization content: ",
ActivityDisplayName,
" to determine whether it is an expected operation or maintenance task."
),
Alert_Containment_en = strcat(
"1. If determined to be unauthorized or suspicious privilege escalation, immediately remove the permissions from target application: ",
TargetAppDisplayName,
" and disable it.",
"2. Perform risk handling for the initiator ",
iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
" (revoke sessions/force re-authentication, and suspend directory management permissions if necessary)."
),
Alert_Remediation_en =
"1. Strengthen application and permission governance: apply least privilege and administrator approval processes for high-risk Graph/Directory permissions, and include Service Principal credential/permission changes in regular audits. "
"2. Strengthen detection and automation: establish automated response (SOAR) for notification/source blocking/user verification.",
Alert_Time_TW = datetime_utc_to_local(TimeGenerated, "Asia/Taipei"),
Alert_Time_UTC0 = TimeGenerated,
Event_Action = ActivityDisplayName,
Event_Status = Result,
Event_Description = PermissionGrant,
Source_Identity_FullName = iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
Source_Identity_ID = iff(isnotempty(InitiatingAadUserId), InitiatingAadUserId, InitiatingAppServicePrincipalId),
Source_Identity_Type = iff(isnotempty(InitiatingUserPrincipalName), "User", "Service"),
Source_Network_IPAddress = InitiatingIpAddress,
Source_Network_IPLocation = Source_Network_IPLocation,
Target_Identity_FullName = TargetAppDisplayName,
Target_Identity_ID = TargetAppServicePrincipalId,
Target_Identity_Type = "Service",
Target_Resource_ID = "",
Target_Resource_Name = "Microsoft Entra ID",
Target_Resource_Type = "Microsoft Entra ID"
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Source_Identity_FullName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: Source_Network_IPAddress
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: Target_Identity_FullName
alertDetailsOverride:
alertDynamicProperties: []
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where Category =~ "ApplicationManagement"
and LoggedByService =~ "Core Directory"
and OperationName in~ ("Add delegated permission grant", "Add app role assignment to service principal")
Stage 3: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
and array_length(TargetResource.modifiedProperties) > 0
and isnotnull(TargetResource.displayName)
| extend props = TargetResource.modifiedProperties
)
Stage 4: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName in~ ("AppRole.Value", "DelegatedPermissionGrant.Scope")
| extend
DisplayName = tostring(Property.displayName),
PermissionGrant = trim('"', tostring(Property.newValue))
)
Stage 5: where
| where PermissionGrant has "RoleManagement.ReadWrite.Directory"
Stage 6: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.DisplayName"
| extend TargetAppDisplayName = trim('"', tostring(Property.newValue))
)
Stage 7: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.ObjectID"
| extend TargetAppServicePrincipalId = trim('"', tostring(Property.newValue))
)
Stage 8: extend (9 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend TargetAppDisplayName = tostring(iff(isnotempty(TargetAppDisplayName), TargetAppDisplayName, TargetAppServicePrincipalId))
| extend
InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]),
InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend Initiator = InitiatingAccountName, Target = TargetAppDisplayName
| extend
Source_Network_IPLocation = ""
Stage 9: project
| project
Alert_Category_en = "Entra ID",
Alert_SubCategory_en = "Anomaly Identity Privilege Modification",
Alert_Name_en = "Application Granted Administrative Permission to Assign Microsoft Entra ID Roles",
Alert_Description_en = strcat("At Taiwan time", format_datetime(datetime_utc_to_local(TimeGenerated, "Asia/Taipei"), "yyyy-MM-dd HH:mm:ss"), ". ", "EntraID:", "", ". ", "detected source user/service principal ", iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName), " granted application: ", TargetAppDisplayName, ".", " administrative permission to assign Microsoft Entra ID roles: ", ActivityDisplayName, ". Source IP: ", InitiatingIpAddress, ", source location: ", Source_Network_IPLocation),
Alert_TriageStep_en = strcat(
"1. Confirm whether the operating user/service principal: ",
iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
", source IP: ",
InitiatingIpAddress,
" is abnormal. ",
"2. Review the authorized target application ",
TargetAppDisplayName,
" authorization content: ",
ActivityDisplayName,
" to determine whether it is an expected operation or maintenance task."
),
Alert_Containment_en = strcat(
"1. If determined to be unauthorized or suspicious privilege escalation, immediately remove the permissions from target application: ",
TargetAppDisplayName,
" and disable it.",
"2. Perform risk handling for the initiator ",
iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
" (revoke sessions/force re-authentication, and suspend directory management permissions if necessary)."
),
Alert_Remediation_en =
"1. Strengthen application and permission governance: apply least privilege and administrator approval processes for high-risk Graph/Directory permissions, and include Service Principal credential/permission changes in regular audits. "
"2. Strengthen detection and automation: establish automated response (SOAR) for notification/source blocking/user verification.",
Alert_Time_TW = datetime_utc_to_local(TimeGenerated, "Asia/Taipei"),
Alert_Time_UTC0 = TimeGenerated,
Event_Action = ActivityDisplayName,
Event_Status = Result,
Event_Description = PermissionGrant,
Source_Identity_FullName = iff(isnotempty(InitiatingUserPrincipalName), InitiatingUserPrincipalName, InitiatingAppName),
Source_Identity_ID = iff(isnotempty(InitiatingAadUserId), InitiatingAadUserId, InitiatingAppServicePrincipalId),
Source_Identity_Type = iff(isnotempty(InitiatingUserPrincipalName), "User", "Service"),
Source_Network_IPAddress = InitiatingIpAddress,
Source_Network_IPLocation = Source_Network_IPLocation,
Target_Identity_FullName = TargetAppDisplayName,
Target_Identity_ID = TargetAppServicePrincipalId,
Target_Identity_Type = "Service",
Target_Resource_ID = "",
Target_Resource_Name = "Microsoft Entra ID",
Target_Resource_Type = "Microsoft Entra ID"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"ApplicationManagement" |
LoggedByService | eq |
| field:"azure_ad::logged_by_service" kind:eq value:"Core Directory" |
OperationName | in |
| field:"OperationName" kind:in |
PermissionGrant | match |
| field:"PermissionGrant" kind:match value:"RoleManagement.ReadWrite.Directory" |
Property.displayName | eq |
| field:"Property.displayName" kind:eq |
Property.displayName | in |
| field:"Property.displayName" kind:in |
TargetResource.displayName | is_not_null | field:"TargetResource.displayName" kind:is_not_null | |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"ServicePrincipal" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Alert_Category_en | project |
Alert_Containment_en | project |
Alert_Description_en | project |
Alert_Name_en | project |
Alert_Remediation_en | project |
Alert_SubCategory_en | project |
Alert_Time_TW | project |
Alert_Time_UTC0 | project |
Alert_TriageStep_en | project |
Event_Action | project |
Event_Description | project |
Event_Status | project |
Source_Identity_FullName | project |
Source_Identity_ID | project |
Source_Identity_Type | project |
Source_Network_IPAddress | project |
Source_Network_IPLocation | project |
Target_Identity_FullName | project |
Target_Identity_ID | project |
Target_Identity_Type | project |
Target_Resource_ID | project |
Target_Resource_Name | project |
Target_Resource_Type | project |
Access to AWS without MFA
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
This detects users with access to AWS (IAM or Federated via Okta) without enabled MFA. This is a default definition by Authomize and can be updated using the edit modal.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 48a9478b-440a-4330-b42c-94bd84dc904c
name: Access to AWS without MFA
kind: Scheduled
description: This detects users with access to AWS (IAM or Federated via Okta) without enabled MFA. This is a default definition by Authomize and can be updated using the edit modal.
severity: Medium
status: Available
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |-
Authomize_v2_CL
| where ingestion_time() >= ago(30m)
| extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s
| where Policy has "Access to AWS without MFA"
| project EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
requiredDataConnectors:
- connectorId: Authomize
dataTypes: [ "Authomize_v2_CL" ]
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertnameFormat: Alert from Authomize - Access to AWS without MFA
alertDescriptionFormat: |
Refactor AWS policy based on activities in the last 60 days. This is a recommended update to IAM policy on AWS. Review the policy and apply it according to change control process. Authomize will have a recommended policy to be downloaded. See the URL for further details within the event details in your Authomize Tenant.
alertSeverity: Severity
alertTactics: Tactics
alertDynamicProperties:
- alertProperty: AlertLink
value: URL
customDetails:
AuthomizeEventID: EventID
EventName: Policy
EventDescription: Description
EventRecommendation: Recommendation
ReferencedURL: URL
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.2
Stages and Predicates
Stage 1: source
Authomize_v2_CL
Stage 2: where
| where ingestion_time() >= ago(30m)
Stage 3: extend
| extend EventID = id_s, Policy = policy_name_s, Severity = severity_s,Description = description_s,Recommendation = recommendation_s,URL = url_s,Tactics = tactics_s
Stage 4: where
| where Policy has "Access to AWS without MFA"
Stage 5: project
| project EventID, Policy, Severity, Description, Recommendation, URL, Category, Tactics
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Policy | match |
| field:"policy_name_s" kind:match value:"Access to AWS without MFA" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Category | project |
Description | project |
EventID | project |
Policy | project |
Recommendation | project |
Severity | project |
Tactics | project |
URL | project |
Base64 encoded Windows process command-lines
#Identifies instances of a base64-encoded PE file header seen in the process command line parameter.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution | |
| Stealth |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 4688: A new process has been created. |
Rule Body
id: ca67c83e-7fff-4127-a3e3-1af66d6d4cad
name: Base64 encoded Windows process command-lines
description: |
'Identifies instances of a base64-encoded PE file header seen in the process command line parameter.'
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:
- Execution
- DefenseEvasion
relevantTechniques:
- T1059
- T1027
- T1140
query: |
let ProcessCreationEvents=(union isfuzzy=true
(SecurityEvent
| where EventID==4688
| where isnotempty(CommandLine)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), count() by Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName,
FileName = Process, CommandLine, ParentProcessName
),
(WindowsEvent
| where EventID==4688
| where EventData has "TVqQAAMAAAAEAAA"
| extend CommandLine = tostring(EventData.CommandLine)
| where isnotempty(CommandLine)
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), count() by Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName,
FileName = Process, CommandLine, ParentProcessName));
ProcessCreationEvents
| where CommandLine contains "TVqQAAMAAAAEAAA"
| 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.5
kind: Scheduled
Stages and Predicates
Stage 0: let
let ProcessCreationEvents = (union <inlined as stages below>;
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)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), count() by Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName,
FileName = Process, CommandLine, ParentProcessName
Leg 2: WindowsEvent
WindowsEvent
| where EventID==4688
| where EventData has "TVqQAAMAAAAEAAA"
| extend CommandLine = tostring(EventData.CommandLine)
| where isnotempty(CommandLine)
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| extend SubjectUserName = tostring(EventData.SubjectUserName)
| extend SubjectDomainName = tostring(EventData.SubjectDomainName)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), count() by Computer, Account = SubjectUserName, AccountDomain = SubjectDomainName,
FileName = Process, CommandLine, ParentProcessName
Applied to the Combined Result
| where CommandLine contains "TVqQAAMAAAAEAAA" | 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.
| Field | Kind | Values | Search |
|---|---|---|---|
CommandLine | contains |
| field:"CommandLine" kind:contains value:"TVqQAAMAAAAEAAA" |
CommandLine | is_not_null | field:"CommandLine" kind:is_not_null | |
EventData | match |
| field:"EventData" kind:match value:"TVqQAAMAAAAEAAA" |
EventID | eq |
| field:"EventID" kind:eq value:"4688" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | summarize |
AccountDomain | summarize |
CommandLine | summarize |
Computer | summarize |
EndTimeUtc | summarize |
FileName | summarize |
ParentProcessName | summarize |
StartTimeUtc | summarize |
DnsDomain | extend |
HostName | extend |
BloodHound Attack Path Finding - Contributor Role on Tier Zero Resource
#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.
BloodHound Attack Path Finding - Contributor Role on Tier Zero Resource
Rule Body
id: 36191460-f26a-4c4e-8492-71e6049e6bbb
name: BloodHound Attack Path Finding - Contributor Role on Tier Zero Resource
description: BloodHound Attack Path Finding - Contributor Role on Tier Zero Resource
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "AzureT0Contributor"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "AzureT0Contributor"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"AzureT0Contributor" |
BloodHound Attack Path Finding - Large Default Groups With GenericWrite Privileges
#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.
BloodHound Attack Path Finding - Large Default Groups With GenericWrite Privileges
Rule Body
id: 8193f4fc-383e-45b0-8ef5-15ef1eeae7e3
name: BloodHound Attack Path Finding - Large Default Groups With GenericWrite Privileges
description: BloodHound Attack Path Finding - Large Default Groups With GenericWrite
Privileges
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "LargeDefaultGroupsGenericWrite"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "LargeDefaultGroupsGenericWrite"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"LargeDefaultGroupsGenericWrite" |
Chia_Crypto_Mining IOC - June 2021
#Identifies a match across IOC's related to Chia cryptocurrency farming/plotting activity
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 4688: A new process has been created. |
Rule Body
id: 4d173248-439b-4741-8b37-f63ad0c896ae
name: Chia_Crypto_Mining IOC - June 2021
description: |
'Identifies a match across IOC's related to Chia cryptocurrency farming/plotting activity'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1496
tags:
- Chia
- ParentAlert: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/MultipleDataSources/ChiaCryptoMining.yaml
version: 1.1.0
query: |
let iocs = externaldata(DateAdded:string,IoC:string,Type:string,TLP:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/ChiaCryptoIOC.csv"] with (format="csv", ignoreFirstRecord=True);
let process = (iocs | where Type =~ "process" | project IoC);
//This query uses sysmon data, sections that have - | where Source == "Microsoft-Windows-Sysmon" - may need to be updated with latest
WindowsEvent
| where EventID == '4688' and EventData has_any (process)
| extend NewProcessName = tostring(EventData.NewProcessName)
| where NewProcessName has_any (process)
| extend ParentProcessName = tostring(EventData.ParentProcessName)
, Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
, NewProcessId = tostring(EventData.NewProcessId)
| extend Computer, Account, File = tostring(split(NewProcessName, '\\', -1)[-1]), AlertDetail = 'Chia crypto IOC detected'
| extend FilePath = replace_string(NewProcessName, File, '')
| project TimeGenerated, File, AlertDetail, FilePath,Computer, NewProcessName, ParentProcessName, Account, NewProcessId, Type
| extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Account
- identifier: Name
columnName: AccountName
- identifier: NTDomain
columnName: AccountNTDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: File
fieldMappings:
- identifier: Name
columnName: File
- identifier: Directory
columnName: FilePath
version: 1.0.4
kind: Scheduled
Stages and Predicates
Let Binding: iocs
let iocs = externaldata(DateAdded:string,IoC:string,Type:string,TLP:string) [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/ChiaCryptoIOC.csv"] with (format="csv", ignoreFirstRecord=True);
Let Binding: process
let process = (iocs | where Type =~ "process" | project IoC);
Stage 1: source
WindowsEvent
Stage 2: where
| where EventID == '4688' and EventData has_any (process)
Stage 3: extend
| extend NewProcessName = tostring(EventData.NewProcessName)
Stage 4: where
| where NewProcessName has_any (process)
Stage 5: extend (3 consecutive steps)
| extend ParentProcessName = tostring(EventData.ParentProcessName)
, Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
, NewProcessId = tostring(EventData.NewProcessId)
| extend Computer, Account, File = tostring(split(NewProcessName, '\\', -1)[-1]), AlertDetail = 'Chia crypto IOC detected'
| extend FilePath = replace_string(NewProcessName, File, '')
Stage 6: project
| project TimeGenerated, File, AlertDetail, FilePath,Computer, NewProcessName, ParentProcessName, Account, NewProcessId, Type
Stage 7: extend (3 consecutive steps)
| extend AccountName = tostring(split(Account, @'\')[1]), AccountNTDomain = tostring(split(Account, @'\')[0])
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventData | match |
| field:"EventData" kind:match value:"process" |
EventID | eq |
| field:"EventID" kind:eq value:"4688" |
NewProcessName | match |
| field:"NewProcessName" kind:match value:"process" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
AlertDetail | project |
Computer | project |
File | project |
FilePath | project |
NewProcessId | project |
NewProcessName | project |
ParentProcessName | project |
TimeGenerated | project |
Type | project |
AccountNTDomain | extend |
AccountName | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
File Server Deleted
#Detects when a file server is deleted from Veeam Backup & Replication. This might indicate unauthorized removal of critical components.
Rule Body
id: d7f23105-6756-43ae-973c-48b7441559d5
name: File Server Deleted
description: Detects when a file server is deleted from Veeam Backup & Replication.
This might indicate unauthorized removal of critical components.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 28950\n| extend Name = extract(\"\
Name=\\\"(?<Name>[^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ UserName = user,\n [\"Object Name\"] = Name,\n MessageDetails = Description,\n\
\ Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 28950
Stage 3: extend
| extend Name = extract("Name=\"(?<Name>[^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
["Object Name"] = Name,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"28950" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Object Name | project |
Severity | project |
UserName | project |
GCP IAM - Publicly exposed storage bucket
#Detects possible misconfiguration for bucket policy making it publicly available.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GCP | google.iam.IAMPolicy.SetIamPolicy: Set IAM policy (IAMPolicy service) |
Rule Body
id: 4a433846-4b05-4a27-99d7-92093feded79
name: GCP IAM - Publicly exposed storage bucket
description: |
'Detects possible misconfiguration for bucket policy making it publicly available.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: GCPIAMDataConnector
dataTypes:
- GCP_IAM
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1069
query: |
GCP_IAM
| where PayloadMethodname =~ 'google.iam.v1.IAMPolicy.SetIamPolicy'
| where ResourceType has 'bucket'
| extend members = parse_json(todynamic(PayloadRequestPolicyBindings))[0]['members']
| extend action = parse_json(todynamic(PayloadRequestPolicyBindings))[0]['action']
| where members in~ ('allUsers', 'allAuthenticatedUsers')
| where action =~ 'ADD'
| extend AccountName = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[0]), AccountUPNSuffix = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: PayloadAuthenticationinfoPrincipalemail
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
GCP_IAM
Stage 2: where
| where PayloadMethodname =~ 'google.iam.v1.IAMPolicy.SetIamPolicy'
Stage 3: where
| where ResourceType has 'bucket'
Stage 4: extend
| extend members = parse_json(todynamic(PayloadRequestPolicyBindings))[0]['members']
Stage 5: extend
| extend action = parse_json(todynamic(PayloadRequestPolicyBindings))[0]['action']
Stage 6: where
| where members in~ ('allUsers', 'allAuthenticatedUsers')
Stage 7: where
| where action =~ 'ADD'
Stage 8: extend
| extend AccountName = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[0]), AccountUPNSuffix = tostring(split(PayloadAuthenticationinfoPrincipalemail, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PayloadMethodname | eq |
| field:"gcp::method_name" kind:eq value:"google.iam.v1.IAMPolicy.SetIamPolicy" |
ResourceType | match |
| field:"ResourceType" kind:match value:"bucket" |
action | eq |
| field:"action" kind:eq value:"ADD" |
members | in |
| field:"members" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
members | extend |
action | extend |
AccountName | extend |
AccountUPNSuffix | extend |
GitHub - User was blocked
#Detect activities when a user was blocked on the repository. This query runs every day and its severity is Medium.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GitHub | Audit log action org.block_user: User blocked from organization repositories |
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: 0b85a077-8ba5-4cb5-90f7-1e882afe10c8
name: GitHub - User was blocked
description: |
'Detect activities when a user was blocked on the repository. 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 == "org.block_user"
| project Actor, Action
| extend Name = iif(Actor contains "@", split(Actor, "@")[0], Actor)
| extend UPNSuffix = iif(Actor contains "@", split(Actor, "@")[1], "")
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Actor
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
GitHubAuditData
Stage 2: where
| where Action == "org.block_user"
Stage 3: project
| project Actor, Action
Stage 4: extend
| extend Name = iif(Actor contains "@", split(Actor, "@")[0], Actor)
Stage 5: extend
| extend UPNSuffix = iif(Actor contains "@", split(Actor, "@")[1], "")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | eq |
| field:"Action" kind:eq value:"org.block_user" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
Actor | project |
Name | extend |
UPNSuffix | extend |
Google DNS - UNC2452 (Nobelium) APT Group activity
#'Detects UNC2452 (Nobelium) APT Group activity.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 22a613ea-c338-4f91-bbd3-3be97b00ebf9
name: Google DNS - UNC2452 (Nobelium) APT Group activity
description: |
'Detects UNC2452 (Nobelium) APT Group activity.'
severity: High
requiredDataConnectors:
- connectorId: GCPDNSDataConnector
dataTypes:
- GCPCloudDNS
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1095
query: |
GCPCloudDNS
| where Query has_any ('tomasubiera.com', 'theandersonco.com', 'stonecrestnews.com', 'nordicmademedia.com')
| extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DNSCustomEntity
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GCPCloudDNS
Stage 2: where
| where Query has_any ('tomasubiera.com', 'theandersonco.com', 'stonecrestnews.com', 'nordicmademedia.com')
Stage 3: extend
| extend DNSCustomEntity = Query, IPCustomEntity = SrcIpAddr
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Query | match |
| field:"Query" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DNSCustomEntity | extend |
IPCustomEntity | extend |
Guardian- Content Access Control Blocked List Policy Violation Detection
#'This alert creates an incident when Content Access Control Blocked List Policy Violation detected from the Guardian.'
Rule Body
id: 4c7f0b49-d972-4d26-81ab-36cbe43ac437
name: Guardian- Content Access Control Blocked List Policy Violation Detection
description: |
'This alert creates an incident when Content Access Control Blocked List Policy Violation detected from the Guardian.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'Blocked List'
| where Severity =~ 'Medium'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Content Access Control Blocked List Policy Violation detection
alertDescriptionFormat: |
This query detects Content Access Control Blocked List Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'Blocked List'
Stage 3: where
| where Severity =~ 'Medium'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Blocked List" |
Severity | eq |
| field:"Severity" kind:eq value:"Medium" |
Guardian- Not Safe For Work Policy Violation Detection
#'This alert creates an incident when Not Safe For Work Policy Violation detected from the Guardian.'
Rule Body
id: f472bd5e-ffb3-4c8e-8abd-97aaa8ebcdff
name: Guardian- Not Safe For Work Policy Violation Detection
description: |
'This alert creates an incident when Not Safe For Work Policy Violation detected from the Guardian.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'Not Safe For Work'
| where Severity =~ 'Low'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Not Safe For Work Policy Violation detection
alertDescriptionFormat: |
This query detects Not Safe For Work Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'Not Safe For Work'
Stage 3: where
| where Severity =~ 'Low'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Not Safe For Work" |
Severity | eq |
| field:"Severity" kind:eq value:"Low" |
Known Malware Detected
#'This creates an incident when a known Malware is detected on a endpoint managed by a Carbon Black.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Rule Body
id: 9f86885f-f31f-4e66-a39d-352771ee789e
name: Known Malware Detected
description: |
'This creates an incident when a known Malware is detected on a endpoint managed by a Carbon Black.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: VMwareCarbonBlack
dataTypes:
- CarbonBlackEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1204
query: |
CarbonBlackEvents_CL
| extend eventTime = datetime(1970-01-01) + tolong(eventTime_d/1000) * 1sec
| where targetApp_effectiveReputation_s =~ "KNOWN_MALWARE"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by eventTime, deviceDetails_deviceName_s, deviceDetails_deviceIpAddress_s, processDetails_fullUserName_s, processDetails_targetName_s
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: processDetails_fullUserName_s
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: deviceDetails_deviceName_s
- entityType: IP
fieldMappings:
- identifier: Address
columnName: deviceDetails_deviceIpAddress_s
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
CarbonBlackEvents_CL
Stage 2: extend
| extend eventTime = datetime(1970-01-01) + tolong(eventTime_d/1000) * 1sec
Stage 3: where
| where targetApp_effectiveReputation_s =~ "KNOWN_MALWARE"
Stage 4: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by eventTime, deviceDetails_deviceName_s, deviceDetails_deviceIpAddress_s, processDetails_fullUserName_s, processDetails_targetName_s
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
targetApp_effectiveReputation_s | eq |
| field:"targetApp_effectiveReputation_s" kind:eq value:"KNOWN_MALWARE" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
EndTime | summarize |
StartTime | summarize |
deviceDetails_deviceIpAddress_s | summarize |
deviceDetails_deviceName_s | summarize |
eventTime | summarize |
processDetails_fullUserName_s | summarize |
processDetails_targetName_s | summarize |
Microsoft Entra ID Role Management Permission Grant
#Identifies when the Microsoft Graph RoleManagement.ReadWrite.Directory (Delegated or Application) permission is granted to a service principal. This permission allows an application to read and manage the role-based access control (RBAC) settings for your company's directory. An adversary could use this permission to add an Microsoft Entra ID object to an Admin directory role and escalate privileges. Ref : https://docs.microsoft.com/graph/permissions-reference#role-management-permissions Ref : https://docs.microsoft.com/graph/api/directoryrole-post-members?view=graph-rest-1.0&tabs=http
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Add app role assignment to service principal |
| Azure | Add delegated permission grant |
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Application Assigned Administrator Permissions Immediately After Obtaining Role Management Permissions (Kusto)
- [Entra ID] Application Granted Administrative Permission to Assign Microsoft Entra ID Roles (Kusto)
- Added Credentials to Existing Application (Sigma)
- Added Owner To Application (Sigma)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Granted Microsoft Permissions (Sigma)
- App Granted Privileged Delegated Or App Permissions (Sigma)
- Application AppID Uri Configuration Changes (Sigma)
Rule Body
id: 1ff56009-db01-4615-8211-d4fda21da02d
name: Microsoft Entra ID Role Management Permission Grant
description: |
'Identifies when the Microsoft Graph RoleManagement.ReadWrite.Directory (Delegated or Application) permission is granted to a service principal.
This permission allows an application to read and manage the role-based access control (RBAC) settings for your company's directory.
An adversary could use this permission to add an Microsoft Entra ID object to an Admin directory role and escalate privileges.
Ref : https://docs.microsoft.com/graph/permissions-reference#role-management-permissions
Ref : https://docs.microsoft.com/graph/api/directoryrole-post-members?view=graph-rest-1.0&tabs=http'
severity: High
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- Persistence
- Impact
relevantTechniques:
- T1098.003
- T1078.004
tags:
- SimuLand
query: |
AuditLogs
| where Category =~ "ApplicationManagement" and LoggedByService =~ "Core Directory" and OperationName in~ ("Add delegated permission grant", "Add app role assignment to service principal")
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal" and array_length(TargetResource.modifiedProperties) > 0 and isnotnull(TargetResource.displayName)
| extend props = TargetResource.modifiedProperties
)
| mv-apply Property = props on
(
where Property.displayName in~ ("AppRole.Value","DelegatedPermissionGrant.Scope")
| extend DisplayName = tostring(Property.displayName), PermissionGrant = trim('"',tostring(Property.newValue))
)
| where PermissionGrant has "RoleManagement.ReadWrite.Directory"
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.DisplayName"
| extend TargetAppDisplayName = trim('"',tostring(Property.newValue))
)
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.ObjectID"
| extend TargetAppServicePrincipalId = trim('"',tostring(Property.newValue))
)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| project TimeGenerated, OperationName, Result, PermissionGrant, TargetAppDisplayName, TargetAppServicePrincipalId, InitiatingAppName, InitiatingAppServicePrincipalId,
InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, TargetResources, AdditionalDetails, CorrelationId
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: TargetAppDisplayName
- identifier: AadUserId
columnName: TargetAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: Name
columnName: InitiatingAppName
- identifier: AadUserId
columnName: InitiatingAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingUserPrincipalName
- identifier: Name
columnName: InitiatingAccountName
- identifier: UPNSuffix
columnName: InitiatingAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: InitiatingAadUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: InitiatingIpAddress
version: 1.1.1
kind: Scheduled
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where Category =~ "ApplicationManagement" and LoggedByService =~ "Core Directory" and OperationName in~ ("Add delegated permission grant", "Add app role assignment to service principal")
Stage 3: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal" and array_length(TargetResource.modifiedProperties) > 0 and isnotnull(TargetResource.displayName)
| extend props = TargetResource.modifiedProperties
)
Stage 4: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName in~ ("AppRole.Value","DelegatedPermissionGrant.Scope")
| extend DisplayName = tostring(Property.displayName), PermissionGrant = trim('"',tostring(Property.newValue))
)
Stage 5: where
| where PermissionGrant has "RoleManagement.ReadWrite.Directory"
Stage 6: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.DisplayName"
| extend TargetAppDisplayName = trim('"',tostring(Property.newValue))
)
Stage 7: kusto:mv-apply
| mv-apply Property = props on
(
where Property.displayName =~ "ServicePrincipal.ObjectID"
| extend TargetAppServicePrincipalId = trim('"',tostring(Property.newValue))
)
Stage 8: extend (5 consecutive steps)
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
Stage 9: project
| project TimeGenerated, OperationName, Result, PermissionGrant, TargetAppDisplayName, TargetAppServicePrincipalId, InitiatingAppName, InitiatingAppServicePrincipalId,
InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingIpAddress, TargetResources, AdditionalDetails, CorrelationId
Stage 10: extend
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"ApplicationManagement" |
LoggedByService | eq |
| field:"azure_ad::logged_by_service" kind:eq value:"Core Directory" |
OperationName | in |
| field:"OperationName" kind:in |
PermissionGrant | match |
| field:"PermissionGrant" kind:match value:"RoleManagement.ReadWrite.Directory" |
Property.displayName | eq |
| field:"Property.displayName" kind:eq |
Property.displayName | in |
| field:"Property.displayName" kind:in |
TargetResource.displayName | is_not_null | field:"TargetResource.displayName" kind:is_not_null | |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"ServicePrincipal" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AdditionalDetails | project |
CorrelationId | project |
InitiatingAadUserId | project |
InitiatingAppName | project |
InitiatingAppServicePrincipalId | project |
InitiatingIpAddress | project |
InitiatingUserPrincipalName | project |
OperationName | project |
PermissionGrant | project |
Result | project |
TargetAppDisplayName | project |
TargetAppServicePrincipalId | project |
TargetResources | project |
TimeGenerated | project |
InitiatingAccountName | extend |
InitiatingAccountUPNSuffix | extend |
OracleDBAudit - Query on Sensitive Table
#'Detects when user queries sensitive tables.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: d7fdcad5-ce96-4db6-9a5e-4a86a5166e5e
name: OracleDBAudit - Query on Sensitive Table
description: |
'Detects when user queries sensitive tables.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
relevantTechniques:
- T1005
query: |
let sensitive_tbls = dynamic(['table_name1', 'table_name2']);
OracleDatabaseAuditEvent
| where isnotempty(DstUserName)
| where isnotempty(Action)
| extend TableName = replace(@'[,\(\)]', '', extract(@'(?i)SELECT(.*?)FROM\s(.*?)\s', 2, Action))
| where isnotempty(TableName)
| where TableName in (sensitive_tbls)
| project TableName, DstUserName, DbAction
| extend AccountCustomEntity = DstUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let sensitive_tbls = dynamic(['table_name1', 'table_name2']);
Stage 1: source
OracleDatabaseAuditEvent
Stage 2: where
| where isnotempty(DstUserName)
Stage 3: where
| where isnotempty(Action)
Stage 4: extend
| extend TableName = replace(@'[,\(\)]', '', extract(@'(?i)SELECT(.*?)FROM\s(.*?)\s', 2, Action))
Stage 5: where
| where isnotempty(TableName)
Stage 6: where
| where TableName in (sensitive_tbls)
Stage 7: project
| project TableName, DstUserName, DbAction
Stage 8: extend
| extend AccountCustomEntity = DstUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Action | is_not_null | field:"Action" kind:is_not_null | |
DstUserName | is_not_null | field:"DstUserName" kind:is_not_null | |
TableName | in |
| field:"TableName" kind:in |
TableName | is_not_null | field:"TableName" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DbAction | project |
DstUserName | project |
TableName | project |
AccountCustomEntity | extend |
PROD (TM008.1) - GPO - Linked, Unlinked, or Enforced at Root of Domain
#Linked, Unlinked, or Enforced GPOs at the Root of the Domain which could overwrite Tier Model GPOs.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Defense Impairment |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5136: A directory service object was modified. |
Rule Body
id: fe468726-b1dc-4e76-adad-3480de4a2c56
name: 'PROD (TM008.1) - GPO - Linked, Unlinked, or Enforced at Root of Domain'
description: |
Linked, Unlinked, or Enforced GPOs at the Root of the Domain which could overwrite Tier Model GPOs.
severity: High
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- DefenseEvasion
relevantTechniques:
- T1484.001
query: |
SecurityEvent
| where EventID == 5136
and EventData matches regex @"(?i)DC="
and Activity has "modified"
and EventData has '"ObjectClass">domainDNS'
and EventData has '<Data Name="AttributeLDAPDisplayName">gPLink</Data>'
and (EventData has ';0]'
or EventData has ';1]'
or EventData has ';2]')
and EventData has '"OperationType">%%14674'
| extend ObjectName = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
| where ObjectName startswith "DC=" or ObjectName startswith "dc="
| extend ObjectTier = "Tier 0"
| extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = extract("\\[([^\\]]+)\\]", 1, EventData)
| extend LastThreeChars = substring(AttributeValue, strlen(AttributeValue) - 2, 3)
| extend AttributeValueDescription = case(
LastThreeChars == ";0", "Linked",
LastThreeChars == ";1", "Unlinked",
LastThreeChars == ";2", "Enforced",
"Unknown")
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
| project TimeGenerated
, ObjectName
, ObjectTier
, ObjectClass
, ActivityType = AttributeValueDescription
, AttributeLDAP
, 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
ActivityType: ActivityType
AttributeLDAP: AttributeLDAP
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: '(TM008.1) A Tier 0 GPO was {{ActivityType}} at the Root of the Domain'
alertDescriptionFormat: 'Changes to the Root of the Domain GPO could impact the Tier Model GPOs and other security controls.'
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityEvent
Stage 2: where
| where EventID == 5136
and EventData matches regex @"(?i)DC="
and Activity has "modified"
and EventData has '"ObjectClass">domainDNS'
and EventData has '<Data Name="AttributeLDAPDisplayName">gPLink</Data>'
and (EventData has ';0]'
or EventData has ';1]'
or EventData has ';2]')
and EventData has '"OperationType">%%14674'
Stage 3: extend
| extend ObjectName = extract("<Data Name=\"ObjectDN\">([^<]+)</Data>", 1, EventData)
Stage 4: where
| where ObjectName startswith "DC=" or ObjectName startswith "dc="
Stage 5: extend (7 consecutive steps)
| extend ObjectTier = "Tier 0"
| extend AttributeLDAP = extract("<Data Name=\"AttributeLDAPDisplayName\">([^<]+)</Data>", 1, EventData)
| extend AttributeValue = extract("\\[([^\\]]+)\\]", 1, EventData)
| extend LastThreeChars = substring(AttributeValue, strlen(AttributeValue) - 2, 3)
| extend AttributeValueDescription = case(
LastThreeChars == ";0", "Linked",
LastThreeChars == ";1", "Unlinked",
LastThreeChars == ";2", "Enforced",
"Unknown")
| extend ObjectClass = extract("<Data Name=\"ObjectClass\">([^<]+)</Data>", 1, EventData)
| extend Domain = extract("([^.]+\\.[^.]+)$", 1, Computer)
Stage 6: project
| project TimeGenerated
, ObjectName
, ObjectTier
, ObjectClass
, ActivityType = AttributeValueDescription
, AttributeLDAP
, Account
, Domain
, Computer
, Channel
, EventID
, EventData
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Activity | match |
| field:"Activity" kind:match value:"modified" |
EventData | match |
| field:"EventData" kind:match |
EventData | regex_match |
| field:"EventData" kind:regex_match value:"(?i)DC=" |
EventID | eq |
| field:"EventID" kind:eq value:"5136" |
ObjectName | starts_with |
| field:"ObjectName" kind:starts_with |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
ActivityType | project |
AttributeLDAP | project |
Channel | project |
Computer | project |
Domain | project |
EventData | project |
EventID | project |
ObjectClass | project |
ObjectName | project |
ObjectTier | project |
TimeGenerated | project |
PRODAFT USTA - Payment card exposed
#'Identifies a new PRODAFT USTA Payment Card Fraud Intelligence ticket that exposes a compromised payment card belonging to the monitored company. These typically originate from card breaches, skimmers, or infostealer infections and indicate that a payment card may be available to fraudsters. The full card number is never stored - only the BIN, last 4 digits, brand, and length are retained. Investigate the exposure and, where the card is your own, coordinate reissue with the issuing bank.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: bd550c7f-5455-48de-b23d-30fced82d02b
name: PRODAFT USTA - Payment card exposed
description: |
'Identifies a new PRODAFT USTA Payment Card Fraud Intelligence ticket that exposes a
compromised payment card belonging to the monitored company. These typically originate
from card breaches, skimmers, or infostealer infections and indicate that a payment card
may be available to fraudsters. The full card number is never stored - only the BIN,
last 4 digits, brand, and length are retained. Investigate the exposure and, where the
card is your own, coordinate reissue with the issuing bank.'
severity: Medium
status: Available
tactics:
- Impact
relevantTechniques:
- T1657
requiredDataConnectors:
- connectorId: PRODAFTUstaPCFICCPDefinition
dataTypes:
- PRODAFTUstaCompromisedCards_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
query: |
PRODAFTUstaCompromisedCards
| where Created > ago(1h)
| project
TimeGenerated,
Created,
TicketId,
CompanyName,
CardBrand,
CardBin,
CardLast4,
CardMasked,
CardLength,
ExpirationDate,
Status
customDetails:
Ticket: TicketId
Company: CompanyName
Brand: CardBrand
Card: CardMasked
Expiration: ExpirationDate
Status: Status
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AnyAlert
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
PRODAFTUstaCompromisedCards
Stage 2: where
| where Created > ago(1h)
Stage 3: project
| project
TimeGenerated,
Created,
TicketId,
CompanyName,
CardBrand,
CardBin,
CardLast4,
CardMasked,
CardLength,
ExpirationDate,
Status
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CardBin | project |
CardBrand | project |
CardLast4 | project |
CardLength | project |
CardMasked | project |
CompanyName | project |
Created | project |
ExpirationDate | project |
Status | project |
TicketId | project |
TimeGenerated | project |
PulseConnectSecure - Large Number of Distinct Failed User Logins
#'This query identifies evidence of failed login attempts from a large number of distinct users on a Pulse Connect Secure VPN server'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 1fa1528e-f746-4794-8a41-14827f4cb798
name: PulseConnectSecure - Large Number of Distinct Failed User Logins
description: |
'This query identifies evidence of failed login attempts from a large number of distinct users on a Pulse Connect Secure VPN server'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let threshold = 100;
PulseConnectSecure
| where Messages startswith "Login failed"
| summarize dcount(User) by Computer, bin(TimeGenerated, 15m)
| where dcount_User > threshold
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 100;
Stage 1: source
PulseConnectSecure
Stage 2: where
| where Messages startswith "Login failed"
Stage 3: summarize
| summarize dcount(User) by Computer, bin(TimeGenerated, 15m)
Stage 4: where
| where dcount_User > threshold
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Messages | starts_with |
| field:"Messages" kind:starts_with value:"Login failed" |
dcount_User | gt |
| field:"dcount_User" kind:gt value:"100" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Computer | summarize |
Snowflake - Abnormal query process time
#'Detects query with abnormal proccess time.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 1376f5e5-855a-4f88-8591-19eba4575a0f
name: Snowflake - Abnormal query process time
description: |
'Detects query with abnormal proccess time.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Snowflake
dataTypes:
- Snowflake
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1499
query: |
let threshold = 10000;
Snowflake
| where QueryTotalElapsedTime > threshold
| extend AccountCustomEntity = TargetUsername
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 10000;
Stage 1: source
Snowflake
Stage 2: where
| where QueryTotalElapsedTime > threshold
Stage 3: extend
| extend AccountCustomEntity = TargetUsername
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
QueryTotalElapsedTime | gt |
| field:"QueryTotalElapsedTime" kind:gt value:"10000" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
Suspicious LDAP Queries from Information Gathering Tools
#This rule detects usage of LDAP information gathering tools such as BloodHound, SharpHound or potential custom tools mimicking the behavior of the legitimate tool ADExplorer from Sysinternals. The rule detects tool-specific LDAP queries and also contains a custom "Signature" field, providing information about the exact tool that most probably created the detected LDAP query.
Known False Positives
- None expected.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | IdentityQueryEvents action LDAP query: LDAP query |
Rule Body
let timeframe = 2*1h;
let SharphoundCheck = IdentityQueryEvents
| where ActionType == "LDAP query"
| where QueryType contains "AllSecurityPrincipals" and QueryType contains "AllDomains" // Sharphound-specific behavior.
| where ingestion_time() >= ago(timeframe)
| extend Signature = "Sharphound";
let BloodhoundCheck = IdentityQueryEvents
| where ActionType == "LDAP query"
| where Query contains "sAMAccountType=805306369" and Query contains "userAccountControl&2" // Bloodhound.py-specific behavior.
| where ingestion_time() >= ago(timeframe)
| extend Signature = "Bloodhound.py";
let ADExplorerMimickCheck = IdentityQueryEvents
| where ActionType == "LDAP query"
| where Query contains "GUID=*" // ADExplorer-specific behavior.
| where ingestion_time() >= ago(timeframe)
| extend Signature = "ADExplorer mimicker";
SharphoundCheck | union ADExplorerMimickCheck, BloodhoundCheck
| summarize arg_min(Timestamp,*),Signature=make_set(Signature) by DeviceName, Query, ReportId
| sort by Timestamp desc
// Begin environment-specific filter.
// End environment-specific filter.
Stages and Predicates
Parameters
let timeframe = 2*1h;
let SharphoundCheck, let BloodhoundCheck and let ADExplorerMimickCheck are inlined into the numbered stages below.
Stage 1: source
let SharphoundCheck
Stage 2: source
let BloodhoundCheck
Stage 3: source
let ADExplorerMimickCheck
Stage 4: source
IdentityQueryEvents
Stage 5: where
where ActionType == "LDAP query"
Stage 6: where
where QueryType contains "AllDomains" and QueryType contains "AllSecurityPrincipals"
Stage 7: where
where ...
Stage 8: extend
extend Signature
Stage 9: union
union of 2 branches
Stage 10: source
IdentityQueryEvents
Stage 11: where
where ActionType == "LDAP query"
Stage 12: where
where Query contains "GUID=*"
Stage 13: where
where ...
Stage 14: extend
extend Signature
Stage 15: source
IdentityQueryEvents
Stage 16: where
where ActionType == "LDAP query"
Stage 17: where
where Query contains "sAMAccountType=805306369" and Query contains "userAccountControl&2"
Stage 18: where
where ...
Stage 19: extend
extend Signature
Stage 20: summarize
summarize Signature by DeviceName, Query, ReportId
Stage 21: sort
sort by Timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"LDAP query" |
Query | contains |
| field:"Query" kind:contains |
QueryType | contains |
| field:"QueryType" kind:contains |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceName | summarize |
Query | summarize |
ReportId | summarize |
Signature | summarize |
TI map Domain entity to PaloAlto CommonSecurityLog
#Identifies a match in PaloAlto CommonSecurityLog table from any Domain IOC from TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: dd0a6029-ecef-4507-89c4-fc355ac52111
name: TI map Domain entity to PaloAlto CommonSecurityLog
description: |
Identifies a match in PaloAlto CommonSecurityLog table from any Domain IOC from TI
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h; // Look back 1 hour
let ioc_lookBack = 14d; // Look back 14 days
// Create a list of top-level domains (TLDs) from the threat feed data for later validation
let SecurityLog = materialize(
CommonSecurityLog
// Filter common security logs based on the specified time range
| extend IngestionTime = ingestion_time()
| where IngestionTime > ago(dt_lookBack)
| where DeviceEventClassID =~ 'url'
// Uncomment the line below to only alert on allowed connections
//| where DeviceAction !~ "block-url"
// Extract the domain from RequestURL, if not present, extract it from AdditionalExtensions
| extend PA_Url = column_ifexists("RequestURL", "None")
| extend PA_Url = iif(isempty(PA_Url) and AdditionalExtensions !startswith "PanOS", extract("([^\\\"]+)", 1, tolower(AdditionalExtensions)), trim('"', PA_Url))
| extend PA_Url = iif(PA_Url !startswith "http://" and ApplicationProtocol !~ "ssl", strcat('http://', PA_Url), iif(PA_Url !startswith "https://" and ApplicationProtocol =~ "ssl", strcat('https://', PA_Url), PA_Url))
| extend Domain = trim('"', tostring(parse_url(PA_Url).Host))
| where isnotempty(Domain)
| extend Domain = tolower(Domain)
| extend CommonSecurityLog_TimeGenerated = TimeGenerated
);
let LogDomains = SecurityLog | distinct Domain | summarize make_list(Domain);
// Retrieve threat intelligence indicators within the specified time range
let Domain_Indicators = materialize(
ThreatIntelligenceIndicator
| where isnotempty(DomainName)
| where TimeGenerated >= ago(ioc_lookBack)
| extend TI_DomainEntity = tolower(DomainName)
| where TI_DomainEntity in (LogDomains)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now());
// Join threat intelligence indicators with common security logs
Domain_Indicators | join kind=innerunique (SecurityLog) on $left.TI_DomainEntity == $right.Domain
| where CommonSecurityLog_TimeGenerated < ExpirationDateTime
| summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId
| project CommonSecurityLog_TimeGenerated, Description, ActivityGroupNames, PA_Url, Domain, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, DeviceAction, DestinationIP, DestinationPort, DeviceName, SourceIP, SourcePort, ApplicationProtocol, RequestMethod, Type, TI_DomainEntity
| extend timestamp = CommonSecurityLog_TimeGenerated
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DeviceName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: PA_Url
version: 1.4.2
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let Domain_Indicators is inlined into the numbered stages below.
Let Binding: SecurityLog
let SecurityLog = materialize(
CommonSecurityLog
| extend IngestionTime = ingestion_time()
| where IngestionTime > ago(dt_lookBack)
| where DeviceEventClassID =~ 'url'
| extend PA_Url = column_ifexists("RequestURL", "None")
| extend PA_Url = iif(isempty(PA_Url) and AdditionalExtensions !startswith "PanOS", extract("([^\\\"]+)", 1, tolower(AdditionalExtensions)), trim('"', PA_Url))
| extend PA_Url = iif(PA_Url !startswith "http://" and ApplicationProtocol !~ "ssl", strcat('http://', PA_Url), iif(PA_Url !startswith "https://" and ApplicationProtocol =~ "ssl", strcat('https://', PA_Url), PA_Url))
| extend Domain = trim('"', tostring(parse_url(PA_Url).Host))
| where isnotempty(Domain)
| extend Domain = tolower(Domain)
| extend CommonSecurityLog_TimeGenerated = TimeGenerated
);
Let Binding: LogDomains
let LogDomains = SecurityLog | distinct Domain | summarize make_list(Domain);
Stages 1 to 7 define let Domain_Indicators as the rule's main pipeline source. Stages 8 to 12 use it.
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(DomainName)
Stage 3: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 4: extend
| extend TI_DomainEntity = tolower(DomainName)
Stage 5: where
| where TI_DomainEntity in (LogDomains)
Stage 6: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 7: where
| where Active == true and ExpirationDateTime > now()
Stage 8: join
Domain_Indicators
| join kind=innerunique (SecurityLog) on $left.TI_DomainEntity == $right.Domain
Stage 9: where
where CommonSecurityLog_TimeGenerated < ExpirationDateTime
Stage 10: summarize
summarize CommonSecurityLog_TimeGenerated by IndicatorId
Stage 11: project
project ActivityGroupNames, ApplicationProtocol, CommonSecurityLog_TimeGenerated, ConfidenceScore, Description, DestinationIP, DestinationPort, DeviceAction, DeviceName, Domain, ExpirationDateTime, IndicatorId, PA_Url, RequestMethod, SourceIP, SourcePort, TI_DomainEntity, ThreatType, Type
Stage 12: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
DeviceEventClassID | eq |
| field:"DeviceEventClassID" kind:eq value:"url" |
Domain | is_not_null | field:"Domain" kind:is_not_null | |
DomainName | is_not_null | field:"DomainName" kind:is_not_null | |
TI_DomainEntity | in |
| field:"TI_DomainEntity" kind:in value:"LogDomains" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
ApplicationProtocol | project |
CommonSecurityLog_TimeGenerated | project |
ConfidenceScore | project |
Description | project |
DestinationIP | project |
DestinationPort | project |
DeviceAction | project |
DeviceName | project |
Domain | project |
ExpirationDateTime | project |
IndicatorId | project |
PA_Url | project |
RequestMethod | project |
SourceIP | project |
SourcePort | project |
TI_DomainEntity | project |
ThreatType | project |
Type | project |
timestamp | extend |