Detection rules › Kusto
Kusto rules: 2n
1Password - Vault export prior to account suspension or deletion
#This will alert when a successful vault export has occurred within the last 14 days prior to an account being suspended or deleted from 1Password. Ref: https://1password.com/ Ref: https://github.com/securehats/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry Coverage
Rule Body
id: 51617533-cf51-4415-9020-b15bd47d69d2
name: 1Password - Vault export prior to account suspension or deletion
version: 1.0.0
kind: Scheduled
description: |-
This will alert when a successful vault export has occurred within the last 14 days prior to an account being suspended or deleted from 1Password.
Ref: https://1password.com/
Ref: https://github.com/securehats/
severity: Medium
requiredDataConnectors:
- connectorId: 1Password
dataTypes:
- OnePasswordEventLogs_CL
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1555
query: |-
let ruleFrequency = 1h;
let lookback = 14d;
let onePasswordVaultExportCheck =
OnePasswordEventLogs_CL
| where TimeGenerated between (ago(lookback) .. ago(now() - ago(ruleFrequency)))
| where action == "export" and object_type == "vault"
;
OnePasswordEventLogs_CL
| where (action == "suspend" and object_type == "user") or (action == "delete" and object_type == "user")
| extend
userUuid = tostring(object_details.uuid)
, auditevents = bag_pack("action", action, "object_type", object_type)
| join (
onePasswordVaultExportCheck
| extend userUuid = tostring(actor_details.uuid)
)
on $left.userUuid == $right.userUuid
| extend
TargetUsername = actor_details1.email
, SrcIpAddr = session1.ip_address
| project
TimeGenerated
, auditevents
, actor_details
, location_details = location
, session_details = session
, exported_vault = object_details
, TargetUsername
, SrcIpAddr
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetUsername
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
suppressionEnabled: false
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
suppressionDuration: 5h
eventGroupingSettings:
aggregationKind: SingleAlert
Stages and Predicates
Parameters
let ruleFrequency = 1h;
let lookback = 14d;
Let Binding: onePasswordVaultExportCheck
let onePasswordVaultExportCheck = OnePasswordEventLogs_CL
| where TimeGenerated between (ago(lookback) .. ago(now() - ago(ruleFrequency)))
| where action == "export" and object_type == "vault";
Stage 1: source
OnePasswordEventLogs_CL
Stage 2: where
| where (action == "suspend" and object_type == "user") or (action == "delete" and object_type == "user")
Stage 3: extend
| extend
userUuid = tostring(object_details.uuid)
, auditevents = bag_pack("action", action, "object_type", object_type)
Stage 4: join
| join (
onePasswordVaultExportCheck
| extend userUuid = tostring(actor_details.uuid)
)
on $left.userUuid == $right.userUuid
Stage 5: extend
extend SrcIpAddr, TargetUsername
Stage 6: project
project SrcIpAddr, TargetUsername, TimeGenerated, actor_details, auditevents, exported_vault, location_details, session_details
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq |
object_type | eq |
| field:"object_type" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcIpAddr | project |
TargetUsername | project |
TimeGenerated | project |
actor_details | project |
auditevents | project |
exported_vault | project |
location_details | project |
session_details | project |
AIShield - Image Segmentation AI Model extraction high suspicious vulnerability detection
#'This alert creates an incident when Image Segmentation AI Model extraction high suspicious, high severity vulnerability detected from the AIShield.'
Rule Body
id: 1ed02dcc-0bc9-465e-94b4-bd8969221602
name: AIShield - Image Segmentation AI Model extraction high suspicious vulnerability detection
description: |
'This alert creates an incident when Image Segmentation AI Model extraction high suspicious, high severity vulnerability detected from the AIShield.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- AIShield
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
AIShield
| where Message has 'Image Segmentation AI Model Extraction Attack Identified'
| where Severity =~ 'High'
| where SuspiciousLevel =~ 'high suspicious attack'
| extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceName
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: AIShield - Image Segmentation AI Model extraction high suspicious vulnerability detected.
alertDescriptionFormat: |
This query detects Image Segmentation AI Model extraction high suspicious, high severity alert from AIShield generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
AIShield
Stage 2: where
| where Message has 'Image Segmentation AI Model Extraction Attack Identified'
Stage 3: where
| where Severity =~ 'High'
Stage 4: where
| where SuspiciousLevel =~ 'high suspicious attack'
Stage 5: extend
| extend NTDomain = tostring(split(Computer, '\\')[0]), HostName = tostring(split(Computer, '.')[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Message | match |
| field:"Message" kind:match value:"Image Segmentation AI Model Extraction Attack Identified" |
Severity | eq |
| field:"Severity" kind:eq value:"High" |
SuspiciousLevel | eq |
| field:"SuspiciousLevel" kind:eq value:"high suspicious attack" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostName | extend |
NTDomain | extend |
Application Group Settings Updated
#Detects when application group settings are updated in Veeam Backup & Replication. This might indicate configuration changes that require review.
Rule Body
id: ff3ceb7d-bed0-4ed2-8dbf-1feb9047810f
name: Application Group Settings Updated
description: Detects when application group settings are updated in Veeam Backup &
Replication. This might indicate configuration changes that require review.
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 30400\n| extend Name = extract(\"\
Name=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ UserName = user,\n [\"Application Group Name\"] = Name,\n MessageDetails\
\ = Description,\n Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 30400
Stage 3: extend
| extend Name = extract("Name=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
UserName = user,
["Application Group Name"] = Name,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"30400" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Application Group Name | project |
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Severity | project |
UserName | project |
BloodHound Attack Path Finding - Non-Tier Zero Computer Hosting EnterpriseCA Trusted for NT Authentication
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - Non-Tier Zero Computer Hosting EnterpriseCA Trusted for NT Authentication
Rule Body
id: 64ba60b8-67d4-4356-8b88-0dfbf98e454a
name: BloodHound Attack Path Finding - Non-Tier Zero Computer Hosting EnterpriseCA
Trusted for NT Authentication
description: BloodHound Attack Path Finding - Non-Tier Zero Computer Hosting EnterpriseCA
Trusted for NT Authentication
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "T0GoldenCert"
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: domain_name
customDetails:
Description: ShortDescription
Remediation: ShortRemediation
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
BHEAttackPathsData_CL
Stage 2: where
| where Finding == "T0GoldenCert"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"T0GoldenCert" |
Cortex XDR Incident - High
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
'A new incident was created in the Cortex XDR portal with a severity "High". Click on the events for incident details. '
Rule Body
id: f96728eb-9802-4522-b715-47fb66c2ecf5
name: Cortex XDR Incident - High
description: |
'A new incident was created in the Cortex XDR portal with a severity "High". Click on the events for incident details. '
severity: High
requiredDataConnectors:
- connectorId: CortexXDR
dataTypes:
- CortexXDR_Incidents_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: false
tactics: []
relevantTechniques: []
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride: null
customDetails: null
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: users_s
- entityType: URL
fieldMappings:
- identifier: Url
columnName: xdr_url_s
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: hosts_s
sentinelEntitiesMappings: null
query: |
CortexXDR_Incidents_CL
| where severity_s contains "high"
| where status_s !contains "resolved_auto"
// the below line filters for incidents that has at least one user or one host in it. Comment the below line if you want to generated incidents that have 0 user or 0 host as well
| where host_count_d > 0 or user_count_d > 0
| project TimeGenerated, incident_id_s, status_s, severity_s, incident_sources_s, description_s, xdr_url_s, hosts_s, users_s, alert_categories_s, host_count_d, user_count_d, alert_count_d, creation_time_d, modification_time_d
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
CortexXDR_Incidents_CL
Stage 2: where
| where severity_s contains "high"
Stage 3: where
| where status_s !contains "resolved_auto"
Stage 4: where
| where host_count_d > 0 or user_count_d > 0
Stage 5: project
| project TimeGenerated, incident_id_s, status_s, severity_s, incident_sources_s, description_s, xdr_url_s, hosts_s, users_s, alert_categories_s, host_count_d, user_count_d, alert_count_d, creation_time_d, modification_time_d
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
host_count_d | gt |
| field:"host_count_d" kind:gt value:"0" |
severity_s | contains |
| field:"okta::severity" kind:contains value:"high" |
user_count_d | gt |
| field:"user_count_d" kind:gt value:"0" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
status_s | contains | resolved_auto | excludes:status_s field:"status_s" value:"resolved_auto" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TimeGenerated | project |
alert_categories_s | project |
alert_count_d | project |
creation_time_d | project |
description_s | project |
host_count_d | project |
hosts_s | project |
incident_id_s | project |
incident_sources_s | project |
modification_time_d | project |
severity_s | project |
status_s | project |
user_count_d | project |
users_s | project |
xdr_url_s | project |
Credential added after admin consented to Application
#This query will identify instances where Service Principal credentials were added to an application by one user after the application was granted admin consent rights by another user. If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential. Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow. For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation | |
| Credential Access |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | Add service principal credentials |
| Azure | Consent to application |
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: 707494a5-8e44-486b-90f8-155d1797a8eb
name: Credential added after admin consented to Application
description: |
'This query will identify instances where Service Principal credentials were added to an application by one user after the application was granted admin consent rights by another user.
If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential.
Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow.
For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1d
queryPeriod: 2d
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- CredentialAccess
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1555
- T1098
tags:
- Solorigate
- NOBELIUM
query: |
let auditLookbackStart = 2d;
let auditLookbackEnd = 1d;
AuditLogs
| where TimeGenerated >= ago(auditLookbackStart)
| where OperationName =~ "Consent to application"
| where Result =~ "success"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend targetResourceName = tostring(TargetResource.displayName),
targetResourceID = tostring(TargetResource.id),
targetResourceType = tostring(TargetResource.type),
targetModifiedProp = TargetResource.modifiedProperties
)
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "ConsentContext.IsAdminConsent"
| extend isAdminConsent = trim(@'"',tostring(Property.newValue))
)
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "ConsentAction.Permissions"
| extend Consent_TargetPermissions = trim(@'"',tostring(Property.newValue))
)
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "TargetId.ServicePrincipalNames"
| extend Consent_TargetServicePrincipalNames = tostring(extract_all(@"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",trim(@'"',tostring(Property.newValue)))[0])
)
| extend Consent_InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| extend Consent_InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend Consent_InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend Consent_InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend Consent_InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend Consent_InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| join kind=inner (
AuditLogs
| where TimeGenerated >= ago(auditLookbackEnd)
| where OperationName =~ "Add service principal credentials"
| where Result =~ "success"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend targetResourceName = tostring(TargetResource.displayName),
targetResourceID = tostring(TargetResource.id),
targetModifiedProp = TargetResource.modifiedProperties
)
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "KeyDescription"
| extend Credential_TargetKeyDescription = trim(@'"',tostring(Property.newValue))
)
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "Included Updated Properties"
| extend UpdatedProperties = trim(@'"',tostring(Property.newValue))
)
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "TargetId.ServicePrincipalNames"
| extend Credential_TargetServicePrincipalNames = tostring(extract_all(@"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",trim(@'"',tostring(Property.newValue)))[0])
)
| extend Credential_InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| extend Credential_InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend Credential_InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend Credential_InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend Credential_InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend Credential_InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
) on targetResourceName, targetResourceID
| extend TimeConsent = TimeGenerated, TimeCred = TimeGenerated1
| where TimeConsent < TimeCred
| project TimeConsent, TimeCred, targetResourceName, targetResourceType, isAdminConsent,
Consent_InitiatingUserOrApp, Consent_TargetServicePrincipalNames, Consent_TargetPermissions,
Consent_InitiatingAppName, Consent_InitiatingAppServicePrincipalId, Consent_InitiatingUserPrincipalName, Consent_InitiatingAadUserId, Consent_InitiatingIpAddress,
Credential_InitiatingUserOrApp, Credential_TargetServicePrincipalNames, Credential_TargetKeyDescription,
Credential_InitiatingAppName, Credential_InitiatingAppServicePrincipalId, Credential_InitiatingUserPrincipalName, Credential_InitiatingAadUserId, Credential_InitiatingIpAddress
| extend Consent_AccountName = tostring(split(Consent_InitiatingUserPrincipalName, "@")[0]), Consent_UPNSuffix = tostring(split(Consent_InitiatingUserPrincipalName, "@")[1])
| extend Credential_AccountName = tostring(split(Credential_InitiatingUserPrincipalName, "@")[0]), Credential_UPNSuffix = tostring(split(Credential_InitiatingUserPrincipalName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Consent_InitiatingAppName
- identifier: AadUserId
columnName: Consent_InitiatingAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Credential_InitiatingAppName
- identifier: AadUserId
columnName: Credential_InitiatingAppServicePrincipalId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Consent_InitiatingUserPrincipalName
- identifier: Name
columnName: Consent_AccountName
- identifier: UPNSuffix
columnName: Consent_UPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: Consent_InitiatingAadUserId
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Credential_InitiatingUserPrincipalName
- identifier: Name
columnName: Credential_AccountName
- identifier: UPNSuffix
columnName: Credential_UPNSuffix
- entityType: Account
fieldMappings:
- identifier: AadUserId
columnName: Credential_InitiatingAadUserId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: Consent_InitiatingIpAddress
- entityType: IP
fieldMappings:
- identifier: Address
columnName: Credential_InitiatingIpAddress
version: 1.1.1
kind: Scheduled
Stages and Predicates
Parameters
let auditLookbackStart = 2d;
let auditLookbackEnd = 1d;
Stage 1: source
AuditLogs
Stage 2: where
| where TimeGenerated >= ago(auditLookbackStart)
Stage 3: where
| where OperationName =~ "Consent to application"
Stage 4: where
| where Result =~ "success"
Stage 5: kusto:mv-apply
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend targetResourceName = tostring(TargetResource.displayName),
targetResourceID = tostring(TargetResource.id),
targetResourceType = tostring(TargetResource.type),
targetModifiedProp = TargetResource.modifiedProperties
)
Stage 6: kusto:mv-apply
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "ConsentContext.IsAdminConsent"
| extend isAdminConsent = trim(@'"',tostring(Property.newValue))
)
Stage 7: kusto:mv-apply
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "ConsentAction.Permissions"
| extend Consent_TargetPermissions = trim(@'"',tostring(Property.newValue))
)
Stage 8: kusto:mv-apply
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "TargetId.ServicePrincipalNames"
| extend Consent_TargetServicePrincipalNames = tostring(extract_all(@"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",trim(@'"',tostring(Property.newValue)))[0])
)
Stage 9: extend (6 consecutive steps)
| extend Consent_InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| extend Consent_InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend Consent_InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend Consent_InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend Consent_InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend Consent_InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
Consent_InitiatingUserOrApp =isnotempty(InitiatedBy.user.userPrincipalName)tostring(InitiatedBy.user.userPrincipalName)tostring(InitiatedBy.app.displayName)Stage 10: join
| join kind=inner (
AuditLogs
| where TimeGenerated >= ago(auditLookbackEnd)
| where OperationName =~ "Add service principal credentials"
| where Result =~ "success"
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type =~ "ServicePrincipal"
| extend targetResourceName = tostring(TargetResource.displayName),
targetResourceID = tostring(TargetResource.id),
targetModifiedProp = TargetResource.modifiedProperties
)
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "KeyDescription"
| extend Credential_TargetKeyDescription = trim(@'"',tostring(Property.newValue))
)
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "Included Updated Properties"
| extend UpdatedProperties = trim(@'"',tostring(Property.newValue))
)
| mv-apply Property = targetModifiedProp on
(
where Property.displayName =~ "TargetId.ServicePrincipalNames"
| extend Credential_TargetServicePrincipalNames = tostring(extract_all(@"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",trim(@'"',tostring(Property.newValue)))[0])
)
| extend Credential_InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| extend Credential_InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend Credential_InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend Credential_InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend Credential_InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend Credential_InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
) on targetResourceName, targetResourceID
Stage 11: extend
extend TimeConsent, TimeCred
Stage 12: where
where TimeConsent < TimeCred
Stage 13: project
project Consent_InitiatingAadUserId, Consent_InitiatingAppName, Consent_InitiatingAppServicePrincipalId, Consent_InitiatingIpAddress, Consent_InitiatingUserOrApp, Consent_InitiatingUserPrincipalName, Consent_TargetPermissions, Consent_TargetServicePrincipalNames, Credential_InitiatingAadUserId, Credential_InitiatingAppName, Credential_InitiatingAppServicePrincipalId, Credential_InitiatingIpAddress, Credential_InitiatingUserOrApp, Credential_InitiatingUserPrincipalName, Credential_TargetKeyDescription, Credential_TargetServicePrincipalNames, TimeConsent, TimeCred, isAdminConsent, targetResourceName, targetResourceType
Stage 14: extend
extend Consent_AccountName, Consent_UPNSuffix
Stage 15: extend
extend Credential_AccountName, Credential_UPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | eq |
| field:"OperationName" kind:eq |
Property.displayName | eq |
| field:"Property.displayName" kind:eq |
Result | eq |
| field:"Result" kind:eq value:"success" |
TargetResource.type | eq |
| field:"TargetResource.type" kind:eq value:"ServicePrincipal" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Consent_InitiatingAadUserId | project |
Consent_InitiatingAppName | project |
Consent_InitiatingAppServicePrincipalId | project |
Consent_InitiatingIpAddress | project |
Consent_InitiatingUserOrApp | project |
Consent_InitiatingUserPrincipalName | project |
Consent_TargetPermissions | project |
Consent_TargetServicePrincipalNames | project |
Credential_InitiatingAadUserId | project |
Credential_InitiatingAppName | project |
Credential_InitiatingAppServicePrincipalId | project |
Credential_InitiatingIpAddress | project |
Credential_InitiatingUserOrApp | project |
Credential_InitiatingUserPrincipalName | project |
Credential_TargetKeyDescription | project |
Credential_TargetServicePrincipalNames | project |
TimeConsent | project |
TimeCred | project |
isAdminConsent | project |
targetResourceName | project |
targetResourceType | project |
Consent_AccountName | extend |
Consent_UPNSuffix | extend |
Credential_AccountName | extend |
Credential_UPNSuffix | extend |
Cyble Vision Alerts Postman API Exposure Detection
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
'Detects exposed Postman requests, collections or endpoints referencing monitored entities. Alerts analysts to possible API enumeration, leaked endpoints, or unintended exposure.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Credential Access | |
| Exfiltration |
Rule Body
id: 99ca8956-5aad-4542-9fbc-8254182b424d
name: Cyble Vision Alerts Postman API Exposure Detection
description: |
'Detects exposed Postman requests, collections or endpoints referencing monitored entities. Alerts analysts to possible API enumeration, leaked endpoints, or unintended exposure.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CybleVisionAlerts
dataTypes:
- CybleVisionAlerts_CL
enabled: true
queryfrequency: 30m
queryPeriod: 30m
triggerOperator: GreaterThan
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics:
- Reconnaissance
- CredentialAccess
- Exfiltration
relevantTechniques:
- T1595
- T1552
- T1041
query: |
Alerts_postman
| where Service == "postman"
| extend MappedSeverity = Severity
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
alertDetailsOverride:
alertDisplayNameFormat: Exposed Postman API Request {{PM_Title}}
alertDescriptionFormat: |
A Postman API request referencing monitored entity {{KeywordName}} was discovered. Public Workspace {{PM_IsPublic}}. Developer {{PM_DevelopedBy}}. This may indicate exposed API endpoints, leaked request parameters or sensitive testing data.
customDetails:
MappedSeverity: Severity
Status: Status
AlertID: AlertID
Service: Service
PM_Title: PM_Title
PM_DevelopedBy: PM_DevelopedBy
PM_DevelopedByUrl: PM_DevelopedByUrl
PM_Category: PM_Category
PM_PostmanKey: PM_PostmanKey
PM_Url: PM_Url
entityMappings:
- entityType: Url
fieldMappings:
- identifier: Url
columnName: PM_Url
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: KeywordName
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Alerts_postman
Stage 2: where
| where Service == "postman"
Stage 3: extend
| extend MappedSeverity = Severity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Service | eq |
| field:"Service" kind:eq value:"postman" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
MappedSeverity | extend |
CYFIRMA - Medium severity File Hash Indicators with Block Action and Malware
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This KQL query retrieves file hash indicators (MD5, SHA1, SHA256) from the CyfirmaIndicators_CL table within the last 5 minutes. It filters records with a confidence score of 80 or higher, containing file hash patterns, a recommended action of 'Block', and roles marked as 'Malware'. Extracted hashes and key threat intelligence details are projected for Blocking and investigation."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Stealth | |
| Defense Impairment | |
| Credential Access | |
| Discovery | |
| Lateral Movement | |
| Collection | |
| Impact |
Rule Body
id: 24dcff02-123c-4e10-a531-2a22a609120a
name: CYFIRMA - Medium severity File Hash Indicators with Block Action and Malware
description: |
"This KQL query retrieves file hash indicators (MD5, SHA1, SHA256) from the CyfirmaIndicators_CL table within the last 5 minutes.
It filters records with a confidence score of 80 or higher, containing file hash patterns, a recommended action of 'Block', and roles marked as 'Malware'.
Extracted hashes and key threat intelligence details are projected for Blocking and investigation."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
// File Hash Indicators with Block Action and Malware
let timeFrame = 5m;
CyfirmaIndicators_CL
| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (Roles contains "Malware")
| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
Algo_MD5='md5',
Algo_SHA1= 'SHA1',
Algo_SHA256='SHA256',
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
MD5,
Algo_MD5,
SHA1,
Algo_SHA1,
SHA256,
Algo_SHA256,
ThreatActors,
Sources,
RecommendedActions,
Roles,
Country,
name,
Description,
ConfidenceScore,
SecurityVendors,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- InitialAccess
- Execution
- Persistence
- PrivilegeEscalation
- DefenseEvasion
- CredentialAccess
- Discovery
- LateralMovement
- Collection
- Impact
relevantTechniques:
- T1566
- T1203
- T1059
- T1204
- T1547
- T1053
- T1055
- T1027
- T1562
- T1036
- T1003
- T1555
- T1082
- T1057
- T1021
- T1113
- T1486
- T1566.001
- T1059.001
- T1059.003
- T1547.001
- T1053.005
- T1562.001
- T1003.001
- T1555.003
- T1021.002
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence File Hash Indicators with Block Action and Malware - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProviderName
value: ProviderName
- alertProperty: ProductName
value: ProductName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
created: created
modified: modified
valid_from: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
entityMappings:
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_MD5
- identifier: Value
columnName: MD5
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_SHA1
- identifier: Value
columnName: SHA1
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_SHA256
- identifier: Value
columnName: SHA256
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern contains 'file:hashes' and RecommendedActions has 'Block' and (Roles contains "Malware")
Stage 3: extend (4 consecutive steps)
| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
Algo_MD5='md5',
Algo_SHA1= 'SHA1',
Algo_SHA256='SHA256',
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 4: project
| project
MD5,
Algo_MD5,
SHA1,
Algo_SHA1,
SHA256,
Algo_SHA256,
ThreatActors,
Sources,
RecommendedActions,
Roles,
Country,
name,
Description,
ConfidenceScore,
SecurityVendors,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"50" |
ConfidenceScore | lt |
| field:"ConfidenceScore" kind:lt value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Block" |
Roles | contains |
| field:"Roles" kind:contains value:"Malware" |
pattern | contains |
| field:"pattern" kind:contains value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Algo_MD5 | project |
Algo_SHA1 | project |
Algo_SHA256 | project |
ConfidenceScore | project |
Country | project |
Description | project |
IndicatorID | project |
MD5 | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SHA1 | project |
SHA256 | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
created | project |
modified | project |
name | project |
valid_from | project |
CYFIRMA - Medium severity Trojan File Hash Indicators with Monitor Action Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This KQL query extracts file hash indicators associated with Trojan activity from the CyfirmaIndicators_CL table. It specifically targets indicators containing file hashes linked to Trojan behavior and retrieves MD5, SHA1, and SHA256 values. The query also includes contextual threat intelligence such as threat actors, tags, sources, and geolocation information."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Stealth | |
| Credential Access | |
| Command & Control |
Rule Body
id: b89c893e-650f-4569-afc3-c487efee2472
name: CYFIRMA - Medium severity Trojan File Hash Indicators with Monitor Action Rule
description: |
"This KQL query extracts file hash indicators associated with Trojan activity from the CyfirmaIndicators_CL table.
It specifically targets indicators containing file hashes linked to Trojan behavior and retrieves MD5, SHA1, and SHA256 values.
The query also includes contextual threat intelligence such as threat actors, tags, sources, and geolocation information."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//Trojan File Hash Indicators with Monitor Action
let timeFrame = 5m;
CyfirmaIndicators_CL
| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern contains 'file:hashes' and RecommendedActions has 'Monitor' and (Roles contains 'Trojan')
| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
Algo_MD5='md5',
Algo_SHA1= 'SHA1',
Algo_SHA256='SHA256',
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
MD5,
Algo_MD5,
SHA1,
Algo_SHA1,
SHA256,
Algo_SHA256,
ThreatActors,
Sources,
RecommendedActions,
Roles,
Country,
name,
Description,
ConfidenceScore,
SecurityVendors,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- InitialAccess
- Execution
- Persistence
- DefenseEvasion
- CommandAndControl
- CredentialAccess
relevantTechniques:
- T1566
- T1204
- T1547
- T1027
- T1071
- T1003
- T1566.001
- T1547.001
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence Trojan File Hash Indicators with Monitor Action - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
created: created
modified: modified
ValidFrom: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
entityMappings:
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_MD5
- identifier: Value
columnName: MD5
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_SHA1
- identifier: Value
columnName: SHA1
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: Algo_SHA256
- identifier: Value
columnName: SHA256
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern contains 'file:hashes' and RecommendedActions has 'Monitor' and (Roles contains 'Trojan')
Stage 3: extend (4 consecutive steps)
| extend MD5 = extract(@"file:hashes\.md5\s*=\s*'([a-fA-F0-9]{32})'", 1, pattern)
| extend SHA1 = extract(@"file:hashes\.'SHA-1'\s*=\s*'([a-fA-F0-9]{40})'", 1, pattern)
| extend SHA256 = extract(@"file:hashes\.'SHA-256'\s*=\s*'([a-fA-F0-9]{64})'", 1, pattern)
| extend
Algo_MD5='md5',
Algo_SHA1= 'SHA1',
Algo_SHA256='SHA256',
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 4: project
| project
MD5,
Algo_MD5,
SHA1,
Algo_SHA1,
SHA256,
Algo_SHA256,
ThreatActors,
Sources,
RecommendedActions,
Roles,
Country,
name,
Description,
ConfidenceScore,
SecurityVendors,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ConfidenceScore | ge |
| field:"ConfidenceScore" kind:ge value:"50" |
ConfidenceScore | lt |
| field:"ConfidenceScore" kind:lt value:"80" |
RecommendedActions | match |
| field:"RecommendedActions" kind:match value:"Monitor" |
Roles | contains |
| field:"Roles" kind:contains value:"Trojan" |
pattern | contains |
| field:"pattern" kind:contains value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Algo_MD5 | project |
Algo_SHA1 | project |
Algo_SHA256 | project |
ConfidenceScore | project |
Country | project |
Description | project |
IndicatorID | project |
MD5 | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SHA1 | project |
SHA256 | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
created | project |
modified | project |
name | project |
valid_from | project |
Detect .NET runtime being loaded in JScript for code execution
#This query detects .NET being loaded from wscript or cscript to run .NET code, such as cactustorch and sharpshooter. All based on the DotNetToJScript by James Foreshaw documented here https://github.com/tyranid/DotNetToJScript.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Execution |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceImageLoadEvents action any: Image load |
Rule Body
id: 9f921513-65f3-48a2-ae7d-326c5901c55e
name: Detect .NET runtime being loaded in JScript for code execution
description: |
This query detects .NET being loaded from wscript or cscript to run .NET code, such as cactustorch and sharpshooter.
All based on the DotNetToJScript by James Foreshaw documented here https://github.com/tyranid/DotNetToJScript.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceImageLoadEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
relevantTechniques:
- T1204
query: |
DeviceImageLoadEvents
| where FileName in~ ("mscoree.dll", "mscorlib.dll", "mscorlib.ni.dll")
| where tolower(InitiatingProcessFileName) in ("wscript.exe", "cscript.exe", "mshta.exe")
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: InitiatingProcessAccountSid
- identifier: Name
columnName: InitiatingProcessAccountName
- identifier: NTDomain
columnName: InitiatingProcessAccountDomain
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- entityType: Process
fieldMappings:
- identifier: CommandLine
columnName: InitiatingProcessCommandLine
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
DeviceImageLoadEvents
Stage 2: where
| where FileName in~ ("mscoree.dll", "mscorlib.dll", "mscorlib.ni.dll")
Stage 3: where
| where tolower(InitiatingProcessFileName) in ("wscript.exe", "cscript.exe", "mshta.exe")
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
FileName | in |
| field:"image_loaded_basename" kind:in |
InitiatingProcessFileName | in |
| field:"process_basename" kind:in |
Detect PIM elevation with user risk
#When an account with eligible roles in Entra ID is compromised, the attacker will probably escalate their privileges via Microsoft PIM. With this rule you can detect when a risky user is elevating their privileges with PIM.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
References
Telemetry Coverage
Rule Body
AuditLogs
| where TimeGenerated > ago(1h)
| where OperationName contains "PIM activation" and OperationName contains "completed"
| extend UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| join kind=inner (AADUserRiskEvents | where TimeGenerated > ago(1d)) on UserPrincipalName
CloudAppEvents
| where TimeGenerated > ago(1h)
| where ActionType == "Add member to role."
| extend UserPrincipalName = tostring(RawEventData.ObjectId)
| join kind=inner (AADUserRiskEvents | where TimeGenerated > ago(1d)) on UserPrincipalName
Stages and Predicates
Stage 1: source
AuditLogs
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: where
| where OperationName contains "PIM activation" and OperationName contains "completed"
Stage 4: extend
| extend UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
Stage 5: join
| join kind=inner (AADUserRiskEvents | where TimeGenerated > ago(1d)) on UserPrincipalName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | contains |
| field:"OperationName" kind:contains |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserPrincipalName | extend |
Exchange Server Suspicious File Downloads.
#'This query looks for messages related to file downloads of suspicious file types on an Exchange Server. This could indicate attempted deployment of webshells. This query uses the Exchange HttpProxy AOBGeneratorLog, you will need to onboard this log as a custom log under the table http_proxy_oab_CL before using this query. This log is commonly found at C:\Program Files\Microsoft\Exchange Server\V15\Logging\OABGeneratorLog on the Exchange server. Details on collecting custom logs into Sentinel can be found here: https://learn.microsoft.com/en-us/azure/sentinel/connect-custom-logs-ama'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 8955c0fb-3408-47b0-a3b9-a1faec41e427
name: Exchange Server Suspicious File Downloads.
description: |
'This query looks for messages related to file downloads of suspicious file types on an Exchange Server. This could indicate attempted deployment of webshells.
This query uses the Exchange HttpProxy AOBGeneratorLog, you will need to onboard this log as a custom log under the table http_proxy_oab_CL before using this query.
This log is commonly found at C:\Program Files\Microsoft\Exchange Server\V15\Logging\OABGeneratorLog on the Exchange server. Details on collecting custom logs into Sentinel
can be found here: https://learn.microsoft.com/en-us/azure/sentinel/connect-custom-logs-ama'
severity: Medium
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
query: |
let scriptExtensions = dynamic([".php", ".jsp", ".js", ".aspx", ".asmx", ".asax", ".cfm", ".shtml"]);
http_proxy_oab_CL
| where RawData contains "Download failed and temporary file"
| extend File = extract("([^\\\\]*)(\\\\[^']*)",2,RawData)
| extend Extension = strcat(".",split(File, ".")[-1])
| extend InteractiveFile = iif(Extension in (scriptExtensions), "Yes", "No")
// Uncomment the following line to alert only on interactive file download type
//| where InteractiveFile =~ "Yes"
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
version: 1.0.5
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Application" ]
Stages and Predicates
Parameters
let scriptExtensions = dynamic([".php", ".jsp", ".js", ".aspx", ".asmx", ".asax", ".cfm", ".shtml"]);
Stage 1: source
http_proxy_oab_CL
Stage 2: where
| where RawData contains "Download failed and temporary file"
Stage 3: extend (5 consecutive steps)
| extend File = extract("([^\\\\]*)(\\\\[^']*)",2,RawData)
| extend Extension = strcat(".",split(File, ".")[-1])
| extend InteractiveFile = iif(Extension in (scriptExtensions), "Yes", "No")
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
RawData | contains |
| field:"RawData" kind:contains value:"Download failed and temporary file" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
File | extend |
Extension | extend |
InteractiveFile | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
Four-Eyes Authorization Disabled
#Detects when four-eyes authorization is disabled.
Rule Body
id: 57a8ff3a-d4aa-456f-9c13-186026bae52e
name: Four-Eyes Authorization Disabled
description: Detects when four-eyes authorization is disabled.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
eventGroupingSettings:
aggregationKind: AlertPerResult
tactics: []
relevantTechniques: []
query: "Veeam_GetSecurityEvents\n| where instanceId == 42401\n| extend Operation =\
\ extract(\"Operation=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date\
\ = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n\
\ EventId = instanceId,\n [\"Operation\"] = Operation,\n MessageDetails\
\ = Description,\n Severity = SeverityDescription"
version: 1.0.1
kind: Scheduled
customDetails:
Date: Date
VbrHostName: DataSource
EventId: EventId
MessageDetails: MessageDetails
Severity: Severity
Stages and Predicates
Stage 1: source
Veeam_GetSecurityEvents
Stage 2: where
| where instanceId == 42401
Stage 3: extend
| extend Operation = extract("Operation=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["Operation"] = Operation,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"42401" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
MessageDetails | project |
Operation | project |
Severity | project |
Guardian- Malicious URL Policy Violation Detection
#'This alert creates an incident when Malicious URL Policy Violation detected from the Guardian.'
Rule Body
id: 8644d9e0-b810-4b55-889b-b8a9b3795b5d
name: Guardian- Malicious URL Policy Violation Detection
description: |
'This alert creates an incident when Malicious URL Policy Violation detected from the Guardian.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'Malicious URL'
| where Severity =~ 'Medium'
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: NTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
eventGroupingSettings:
aggregationKind: SingleAlert
alertDetailsOverride:
alertDisplayNameFormat: Guardian- Malicious URL Policy Violation detection
alertDescriptionFormat: |
This query detects Malicious URL Policy Violation detected from the Guardian generated at {{TimeGenerated}}.\n\nPlease check the source for more information and investigate further.
alertTacticsColumnName: null
alertSeverityColumnName: Severity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Guardian
Stage 2: where
| where PolicyViolatedControlFeature =~ 'Malicious URL'
Stage 3: where
| where Severity =~ 'Medium'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"Malicious URL" |
Severity | eq |
| field:"Severity" kind:eq value:"Medium" |
High risk Office operation conducted by IP Address that recently attempted to log into a disabled account
#It is possible that a disabled user account is compromised and another account on the same IP is used to perform operations that are not typical for that user. The query filters the SigninLogs for entries where ResultType is indicates a disabled account and the TimeGenerated is within a defined time range. It then summarizes these entries by IPAddress and AppId, calculating various statistics such as number of login attempts, distinct UPNs, App IDs etc and joins these results with another set of results from SigninLogs, filtering for entries with less than normal number of successful sign-ins. It then filters out entries where there were no successful sign-ins or where successful sign-ins did not occur within the same lookback period as the failed sign-ins, later projecting relevant fields by the count of login attempts, and expands the set of successful sign-ins into individual events. Finally, it joins these results with entries from OfficeActivity where certain operations deemed rare and high risk have been performed, ensuring their occurrance within a certain time range of the successful sign-ins.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Collection |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- [Entra ID] Suspicious Continuous OAuth Token Usage (Kusto)
- Account Disabled or Blocked for Sign in Attempts (Sigma)
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
Rule Body
id: 9adbd1c3-a4be-44ef-ac2f-503fd25692ee
name: High risk Office operation conducted by IP Address that recently attempted to log into a disabled account
description: |
'It is possible that a disabled user account is compromised and another account on the same IP is used to perform operations that are not typical for that user.
The query filters the SigninLogs for entries where ResultType is indicates a disabled account and the TimeGenerated is within a defined time range.
It then summarizes these entries by IPAddress and AppId, calculating various statistics such as number of login attempts, distinct UPNs, App IDs etc and joins these results with another set of results from SigninLogs, filtering for entries with less than normal number of successful sign-ins.
It then filters out entries where there were no successful sign-ins or where successful sign-ins did not occur within the same lookback period as the failed sign-ins, later projecting relevant fields by the count of login attempts, and expands the set of successful sign-ins into individual events.
Finally, it joins these results with entries from OfficeActivity where certain operations deemed rare and high risk have been performed, ensuring their occurrance within a certain time range of the successful sign-ins.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: Office365
dataTypes:
- OfficeActivity
queryFrequency: 1d
queryPeriod: 8d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Persistence
- Collection
relevantTechniques:
- T1078
- T1098
- T1114
query: |
let threshold = 100;
let timeRange = ago(7d);
let timeBuffer = 1;
SigninLogs
| where TimeGenerated > timeRange
| where ResultType == "50057"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), disabledAccountLoginAttempts = count(),
disabledAccountsTargeted = dcount(UserPrincipalName), applicationsTargeted = dcount(AppDisplayName), disabledAccountSet = make_set(UserPrincipalName),
applicationSet = make_set(AppDisplayName) by IPAddress, AppId
| order by disabledAccountLoginAttempts desc
| join kind=inner (
// IPs are considered suspicious - and any related successful sign-ins are detected
SigninLogs
| where TimeGenerated > timeRange
| where ResultType == 0
| summarize successSigninStart = min(TimeGenerated), successSigninEnd = max(TimeGenerated), successfulAccountSigninCount = dcount(UserPrincipalName), successfulAccountSigninSet = make_set(UserPrincipalName, 15) by IPAddress
// Assume IPs associated with sign-ins from 100+ distinct user accounts are safe
| where successfulAccountSigninCount < threshold
) on IPAddress
// IPs where attempts to authenticate as disabled user accounts originated, and had a non-zero success rate for some other account
| where successfulAccountSigninCount != 0
// Successful Account Signins occur within the same lookback period as the failed
| extend SuccessBeforeFailure = iff(successSigninStart >= StartTime and successSigninEnd <= EndTime, true, false)
| project StartTime, EndTime, IPAddress, disabledAccountLoginAttempts, disabledAccountsTargeted, disabledAccountSet, applicationSet,
successfulAccountSigninCount, successfulAccountSigninSet, successSigninStart, successSigninEnd, AppId
| order by disabledAccountLoginAttempts
// Break up the string of Succesfully signed into accounts into individual events
| mvexpand successfulAccountSigninSet
| extend JoinedOnIp = IPAddress
| join kind = inner (
OfficeActivity
| where TimeGenerated > timeRange
| where Operation in~ ( "Add-MailboxPermission", "Add-MailboxFolderPermission", "Set-Mailbox", "New-ManagementRoleAssignment", "New-InboxRule", "Set-InboxRule", "Set-TransportRule") and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount'))
// Remove port from the end of the IP and/or square brackets around IP, if they exist
| extend JoinedOnIp = case(
ClientIP matches regex @'\[((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\]-\d{1,5}', tostring(extract('\\[([0-9]+\\.[0-9]+\\.[0-9]+)\\]-[0-9]+', 1, ClientIP)),
ClientIP matches regex @'\[((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\]', tostring(extract('\\[([0-9]+\\.[0-9]+\\.[0-9]+)\\]', 1, ClientIP)),
ClientIP matches regex @'(((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?))-\d{1,5}', tostring(extract('([0-9]+\\.[0-9]+\\.[0-9]+)-[0-9]+', 1, ClientIP)),
ClientIP matches regex @'((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)', ClientIP,
ClientIP matches regex @'\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})\]-\d{1,5}', tostring(extract('\\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})\\]-[0-9]+', 1, ClientIP)),
ClientIP matches regex @'\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})\]', tostring(extract('\\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})\\]', 1, ClientIP)),
ClientIP matches regex @'((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})-\d{1,5}', tostring(extract('((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})-[0-9]+', 1, ClientIP)),
ClientIP matches regex @'((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})', ClientIP,
"")
| where isnotempty(JoinedOnIp)
| extend OfficeTimeStamp = ElevationTime, UserPrincipalName = UserId
) on JoinedOnIp
// Rare and risky operations only happen within a certain time range of the successful sign-in
| where OfficeTimeStamp >= successSigninStart and datetime_diff('day', OfficeTimeStamp, successSigninEnd) <= timeBuffer
| extend AccountName = tostring(split(UserPrincipalName, "@")[0]), AccountUPNSuffix = tostring(split(UserPrincipalName, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: JoinedOnIp
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: ApplicationId
version: 1.0.1
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Threat Intelligence", "Security - Others", "Identity" ]
Stages and Predicates
Parameters
let threshold = 100;
let timeRange = ago(7d);
let timeBuffer = 1;
Stage 1: source
SigninLogs
Stage 2: where
| where TimeGenerated > timeRange
Stage 3: where
| where ResultType == "50057"
Stage 4: summarize
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), disabledAccountLoginAttempts = count(),
disabledAccountsTargeted = dcount(UserPrincipalName), applicationsTargeted = dcount(AppDisplayName), disabledAccountSet = make_set(UserPrincipalName),
applicationSet = make_set(AppDisplayName) by IPAddress, AppId
Stage 5: sort
| order by disabledAccountLoginAttempts desc
Stage 6: join
| join kind=inner (
SigninLogs
| where TimeGenerated > timeRange
| where ResultType == 0
| summarize successSigninStart = min(TimeGenerated), successSigninEnd = max(TimeGenerated), successfulAccountSigninCount = dcount(UserPrincipalName), successfulAccountSigninSet = make_set(UserPrincipalName, 15) by IPAddress
| where successfulAccountSigninCount < threshold
) on IPAddress
Stage 7: where
where successfulAccountSigninCount != 0
Stage 8: extend
extend SuccessBeforeFailure
SuccessBeforeFailure =successSigninStart >= StartTime and successSigninEnd <= EndTimetruefalseStage 9: project
project AppId, EndTime, IPAddress, StartTime, applicationSet, disabledAccountLoginAttempts, disabledAccountSet, disabledAccountsTargeted, successSigninEnd, successSigninStart, successfulAccountSigninCount, successfulAccountSigninSet
Stage 10: sort
sort by disabledAccountLoginAttempts
Stage 11: mv-expand
mv-expand successfulAccountSigninSet
Stage 12: extend
extend JoinedOnIp
Stage 13: join
| join kind = inner (
OfficeActivity
| where TimeGenerated > timeRange
| where Operation in~ ( "Add-MailboxPermission", "Add-MailboxFolderPermission", "Set-Mailbox", "New-ManagementRoleAssignment", "New-InboxRule", "Set-InboxRule", "Set-TransportRule") and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount'))
| extend JoinedOnIp = case(
ClientIP matches regex @'\[((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\]-\d{1,5}', tostring(extract('\\[([0-9]+\\.[0-9]+\\.[0-9]+)\\]-[0-9]+', 1, ClientIP)),
ClientIP matches regex @'\[((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\]', tostring(extract('\\[([0-9]+\\.[0-9]+\\.[0-9]+)\\]', 1, ClientIP)),
ClientIP matches regex @'(((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?))-\d{1,5}', tostring(extract('([0-9]+\\.[0-9]+\\.[0-9]+)-[0-9]+', 1, ClientIP)),
ClientIP matches regex @'((25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]2[0-4][0-9]|[01]?[0-9][0-9]?)', ClientIP,
ClientIP matches regex @'\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})\]-\d{1,5}', tostring(extract('\\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})\\]-[0-9]+', 1, ClientIP)),
ClientIP matches regex @'\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})\]', tostring(extract('\\[((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})\\]', 1, ClientIP)),
ClientIP matches regex @'((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})-\d{1,5}', tostring(extract('((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\\d{1,3}(?:\\.\\d{1,3}){3})-[0-9]+', 1, ClientIP)),
ClientIP matches regex @'((?:[0-9a-fA-F]{1,4}::?){1,8}[0-9a-fA-F]{1,4}|\d{1,3}(?:\.\d{1,3}){3})', ClientIP,
"")
| where isnotempty(JoinedOnIp)
| extend OfficeTimeStamp = ElevationTime, UserPrincipalName = UserId
) on JoinedOnIp
Stage 14: where OfficeTimeStamp - successSigninEnd <= 1d
where OfficeTimeStamp >= successSigninStart
Stage 15: extend
extend AccountName, AccountUPNSuffix
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
JoinedOnIp | is_not_null | field:"JoinedOnIp" kind:is_not_null | |
Operation | in |
| field:"Operation" kind:in |
ResultType | eq |
| field:"ResultType" kind:eq |
successfulAccountSigninCount | lt |
| field:"successfulAccountSigninCount" kind:lt value:"100" |
successfulAccountSigninCount | ne |
| field:"successfulAccountSigninCount" kind:ne value:"0" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
UserId | match | NT AUTHORITY\SYSTEM (Microsoft.Exchange.ServiceHost), NT AUTHORITY\SYSTEM (w3wp), devilfish-applicationaccount | excludes:UserId field:"UserId" value:"NT AUTHORITY\SYSTEM (Microsoft.Exchange.ServiceHost)" field:"UserId" value:"NT AUTHORITY\SYSTEM (w3wp)" field:"UserId" value:"devilfish-applicationaccount" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AppId | project |
EndTime | project |
IPAddress | project |
StartTime | project |
applicationSet | project |
disabledAccountLoginAttempts | project |
disabledAccountSet | project |
disabledAccountsTargeted | project |
successSigninEnd | project |
successSigninStart | project |
successfulAccountSigninCount | project |
successfulAccountSigninSet | project |
JoinedOnIp | extend |
AccountName | extend |
AccountUPNSuffix | extend |
New UserAgent observed in last 24 hours
#'Identifies new UserAgents observed in the last 24 hours versus the previous 14 days. This detection extracts words from user agents to build the baseline and determine rareity rather than perform a direct comparison. This avoids FPs caused by version numbers and other high entropy user agent components. These new UserAgents could be benign. However, in normally stable environments, these new UserAgents could provide a starting point for investigating malicious activity. Note: W3CIISLog can be noisy depending on the environment, however OfficeActivity and AWSCloudTrail are usually stable with low numbers of detections.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Command & Control |
Rule Body
id: b725d62c-eb77-42ff-96f6-bdc6745fc6e0
name: New UserAgent observed in last 24 hours
description: |
'Identifies new UserAgents observed in the last 24 hours versus the previous 14 days. This detection extracts words from user agents to build the baseline and determine rareity rather than perform a direct comparison. This avoids FPs caused by version numbers and other high entropy user agent components.
These new UserAgents could be benign. However, in normally stable environments, these new UserAgents could provide a starting point for investigating malicious activity.
Note: W3CIISLog can be noisy depending on the environment, however OfficeActivity and AWSCloudTrail are usually stable with low numbers of detections.'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
- connectorId: Office365
dataTypes:
- OfficeActivity
- connectorId: AzureMonitor(IIS)
dataTypes:
- W3CIISLog
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CommandAndControl
- Execution
relevantTechniques:
- T1189
- T1071
- T1203
query: |
let starttime = 14d;
let endtime = 1d;
let UserAgentAll =
(union isfuzzy=true
(OfficeActivity
| where TimeGenerated >= ago(starttime)
| where isnotempty(UserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP, Account = UserId, Type, RecordType, Operation
),
(
W3CIISLog
| where TimeGenerated >= ago(starttime)
| where isnotempty(csUserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = csUserAgent, SourceIP = cIP, Account = csUserName, Type, sSiteName, csMethod, csUriStem
),
(
AWSCloudTrail
| where TimeGenerated >= ago(starttime)
| where isnotempty(UserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = SourceIpAddress, Account = UserIdentityUserName, Type, EventSource, EventName
))
// remove wordSize blocks of non-numeric hex characters prior to word extraction
| extend UserAgentNoHexAlphas = replace("([A-Fa-f]{4,})", "x", UserAgent)
// once blocks of hex chars are removed, extract wordSize blocks of a-z
| extend Tokens = extract_all("([A-Za-z]{4,})", UserAgentNoHexAlphas)
// concatenate extracted words to create a summarized user agent for baseline and comparison
| extend NormalizedUserAgent = strcat_array(Tokens, "|")
| project-away UserAgentNoHexAlphas, Tokens;
UserAgentAll
| where StartTime >= ago(endtime)
| summarize StartTime = min(StartTime), EndTime = max(EndTime), count() by UserAgent, NormalizedUserAgent, SourceIP, Account, Type, RecordType, Operation, EventSource, EventName, sSiteName, csMethod, csUriStem
| join kind=leftanti
(
UserAgentAll
| where StartTime < ago(endtime)
| summarize by NormalizedUserAgent, SourceIP, Account, Type, RecordType, Operation, EventSource, EventName, sSiteName, csMethod, csUriStem
)
on NormalizedUserAgent
| extend timestamp = StartTime
| extend Name = tostring(split(Account, '@', 0)[0]), UPNSuffix = tostring(split(Account, '@', 1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
version: 1.0.5
kind: Scheduled
Stages and Predicates
Parameters
let starttime = 14d;
let endtime = 1d;
Union isfuzzy=true (3 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: OfficeActivity, W3CIISLog, AWSCloudTrail
Leg 1: OfficeActivity
OfficeActivity
| where TimeGenerated >= ago(starttime)
| where isnotempty(UserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP, Account = UserId, Type, RecordType, Operation
Leg 2: W3CIISLog
W3CIISLog
| where TimeGenerated >= ago(starttime)
| where isnotempty(csUserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = csUserAgent, SourceIP = cIP, Account = csUserName, Type, sSiteName, csMethod, csUriStem
Leg 3: AWSCloudTrail
AWSCloudTrail
| where TimeGenerated >= ago(starttime)
| where isnotempty(UserAgent)
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = SourceIpAddress, Account = UserIdentityUserName, Type, EventSource, EventName
Applied to the Combined Result
| extend UserAgentNoHexAlphas = replace("([A-Fa-f]{4,})", "x", UserAgent)
| extend Tokens = extract_all("([A-Za-z]{4,})", UserAgentNoHexAlphas)
| extend NormalizedUserAgent = strcat_array(Tokens, "|")
| project-away UserAgentNoHexAlphas, Tokens | where StartTime >= ago(endtime) | summarize StartTime = min(StartTime), EndTime = max(EndTime), count() by UserAgent, NormalizedUserAgent, SourceIP, Account, Type, RecordType, Operation, EventSource, EventName, sSiteName, csMethod, csUriStem | join kind=leftanti
(
UserAgentAll
| where StartTime < ago(endtime)
| summarize by NormalizedUserAgent, SourceIP, Account, Type, RecordType, Operation, EventSource, EventName, sSiteName, csMethod, csUriStem
)
on NormalizedUserAgent | extend timestamp = StartTime | extend Name = tostring(split(Account, '@', 0)[0]), UPNSuffix = tostring(split(Account, '@', 1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
UserAgent | is_not_null | field:"UserAgent" kind:is_not_null | |
csUserAgent | is_not_null | field:"csUserAgent" kind:is_not_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
UserAgent | is_not_null | excludes:UserAgent | |
csUserAgent | is_not_null | excludes:csUserAgent |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | summarize |
EndTime | summarize |
EventName | summarize |
EventSource | summarize |
NormalizedUserAgent | summarize |
Operation | summarize |
RecordType | summarize |
SourceIP | summarize |
StartTime | summarize |
Type | summarize |
UserAgent | summarize |
csMethod | summarize |
csUriStem | summarize |
sSiteName | summarize |
timestamp | extend |
Name | extend |
UPNSuffix | extend |
NordPass - User data detected in breach
#This will alert you when Data Breach Scanner discovers data related to a member of your organization on the dark web.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: c4d2eb42-a4ab-4db6-a270-3d2ed7e057a0
name: NordPass - User data detected in breach
kind: Scheduled
version: 1.0.0
displayName: User data detected in breach
description: |
This will alert you when Data Breach Scanner discovers data related to a member of your organization on the dark web.
severity: High
query: |
NordPassEventLogs_CL
| where event_type == "breach"
| where action == "email_breached"
| extend TargetEmail = user_email
requiredDataConnectors:
- connectorId: NordPass
dataTypes:
- NordPassEventLogs_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1020
incidentConfiguration:
createIncident: false
entityMappings:
- entityType: Mailbox
fieldMappings:
- identifier: MailboxPrimaryAddress
columnName: TargetEmail
Stages and Predicates
Stage 1: source
NordPassEventLogs_CL
Stage 2: where
| where event_type == "breach"
Stage 3: where
| where action == "email_breached"
Stage 4: extend
| extend TargetEmail = user_email
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"email_breached" |
event_type | eq |
| field:"event_type" kind:eq value:"breach" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
TargetEmail | extend |
Pathlock TDnR - Missing SAP Security Notes
#Detects alerts for missing SAP security notes (OSS Notes) in the environment, forwarded by Pathlock Threat Detection and Response. Unpatched SAP systems with known CVEs represent a significant attack surface and may be actively exploited by threat actors targeting SAP vulnerabilities.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c37
name: Pathlock TDnR - Missing SAP Security Notes
kind: Scheduled
description: >-
Detects alerts for missing SAP security notes (OSS Notes) in the environment, forwarded by Pathlock
Threat Detection and Response. Unpatched SAP systems with known CVEs represent a significant
attack surface and may be actively exploited by threat actors targeting SAP vulnerabilities.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
Pathlock_TDnR_CL
| where DataSource == "MISSING_OSS_NOTES"
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Bname
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Hostname
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIp
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
matchingMethod: AnyAlert
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
suppressionDuration: 5h
suppressionEnabled: false
version: 1.0.0
Stages and Predicates
Stage 1: source
Pathlock_TDnR_CL
Stage 2: where
| where DataSource == "MISSING_OSS_NOTES"
Stage 3: project
| project TimeGenerated, Sysid, DataSource, Eventid, Instance, Hostname, Bname,
Tcode, Report, Area, Subid, SrcIp, DestIp, AffectedUser, LogLine,
MsgType, MsgId, MsgNo, MessageV1, MessageV2, MessageV3, MessageV4, CentralTs
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DataSource | eq |
| field:"DataSource" kind:eq value:"MISSING_OSS_NOTES" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AffectedUser | project |
Area | project |
Bname | project |
CentralTs | project |
DataSource | project |
DestIp | project |
Eventid | project |
Hostname | project |
Instance | project |
LogLine | project |
MessageV1 | project |
MessageV2 | project |
MessageV3 | project |
MessageV4 | project |
MsgId | project |
MsgNo | project |
MsgType | project |
Report | project |
SrcIp | project |
Subid | project |
Sysid | project |
Tcode | project |
TimeGenerated | project |
ProofpointPOD - Possible data exfiltration to private email
#'Detects when sender sent email to the non-corporate domain and recipient's username is the same as sender's username.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: aedc5b33-2d7c-42cb-a692-f25ef637cbb1
name: ProofpointPOD - Possible data exfiltration to private email
description: |
'Detects when sender sent email to the non-corporate domain and recipient's username is the same as sender's username.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: ProofpointPOD
dataTypes:
- ProofpointPOD_message_CL
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
let lbtime = 10m;
ProofpointPOD
| where TimeGenerated > ago(lbtime)
| where EventType == 'message'
| where NetworkDirection == 'outbound'
| where array_length(todynamic(DstUserUpn)) == 1
| extend sender = extract(@'\A(.*?)@', 1, SrcUserUpn)
| extend sender_domain = extract(@'@(.*)$', 1, SrcUserUpn)
| extend recipient = extract(@'\A(.*?)@', 1, tostring(todynamic(DstUserUpn)[0]))
| extend recipient_domain = extract(@'@(.*)$', 1, tostring(todynamic(DstUserUpn)[0]))
| where sender =~ recipient
| where sender_domain != recipient_domain
| project SrcUserUpn, DstUserUpn
| extend AccountCustomEntity = SrcUserUpn
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let lbtime = 10m;
Stage 1: source
ProofpointPOD
Stage 2: where
| where TimeGenerated > ago(lbtime)
Stage 3: where
| where EventType == 'message'
Stage 4: where
| where NetworkDirection == 'outbound'
Stage 5: where
| where array_length(todynamic(DstUserUpn)) == 1
Stage 6: extend (4 consecutive steps)
| extend sender = extract(@'\A(.*?)@', 1, SrcUserUpn)
| extend sender_domain = extract(@'@(.*)$', 1, SrcUserUpn)
| extend recipient = extract(@'\A(.*?)@', 1, tostring(todynamic(DstUserUpn)[0]))
| extend recipient_domain = extract(@'@(.*)$', 1, tostring(todynamic(DstUserUpn)[0]))
Stage 7: where
| where sender =~ recipient
Stage 8: where
| where sender_domain != recipient_domain
Stage 9: project
| project SrcUserUpn, DstUserUpn
Stage 10: extend
| extend AccountCustomEntity = SrcUserUpn
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"message" |
NetworkDirection | eq |
| field:"NetworkDirection" kind:eq value:"outbound" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstUserUpn | project |
SrcUserUpn | project |
AccountCustomEntity | extend |
SAP LogServ - HANA DB - User Admin actions
#Identifies user administration actions. Souirce Action: Create/Update/Delete a DB User. Data Sources: SAP LogServ - HANA DB (Syslog)
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | No specific technique |
Rule Body
id: a9e4b02a-5a8c-4c59-9836-a204d1028632
kind: Scheduled
name: SAP LogServ - HANA DB - User Admin actions
description: |
Identifies user administration actions.
Souirce Action: Create/Update/Delete a DB User.
*Data Sources: SAP LogServ - HANA DB (Syslog)*
severity: High
status: Available
requiredDataConnectors:
- connectorId: SAPLogServ
dataTypes:
- Syslog
queryFrequency: 10m
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- PrivilegeEscalation
relevantTechniques: []
query: |
let AuditTimeAgo = 60m;
Syslog
| where TimeGenerated >= ago(AuditTimeAgo)
| where SourceSystem == "HANADB" and CollectorHostName == "SAPLogServ"
| extend raw_split = split(SyslogMessage, ";")
| extend
event_timestamp__col_0 = tostring(raw_split[0]),
service_name__col_1 = tostring(raw_split[1]),
hostname__col_2 = tostring(raw_split[2]),
sid__col_3 = tostring(raw_split[3]),
instance_number__col_4 = tostring(raw_split[4]),
port_number__col_5 = tostring(raw_split[5]),
database_name__col_6 = tostring(raw_split[6]),
client_ip_address__col_7 = tostring(raw_split[7]),
client_name__col_8 = tostring(raw_split[8]),
client_process_id__col_9 = tostring(raw_split[9]),
client_port_number__col_10 = tostring(raw_split[10]),
policy_name__col_11 = tostring(raw_split[11]),
audit_level__col_12 = tostring(raw_split[12]),
audit_action__col_13 = tostring(raw_split[13]),
session_user__col_14 = tostring(raw_split[14]),
target_schema__col_15 = tostring(raw_split[15]),
target_object__col_16 = tostring(raw_split[16]),
privilege_name__col_17 = tostring(raw_split[17]),
grantable__col_18 = tostring(raw_split[18]),
role_name__col_19 = tostring(raw_split[19]),
target_principal__col_20 = tostring(raw_split[20]),
action_status__col_21 = tostring(raw_split[21]),
component__col_22 = tostring(raw_split[22]),
section__col_23 = tostring(raw_split[23]),
parameter__col_24 = tostring(raw_split[24]),
old_value__col_25 = tostring(raw_split[25]),
new_value__col_26 = tostring(raw_split[26]),
comment__col_27 = tostring(raw_split[27]),
executed_statement__col_28 = tostring(raw_split[28]),
session_id__col_29 = tostring(raw_split[29]),
application_user_name__col_30 = tostring(raw_split[30]),
role_schema_name__col_31 = tostring(raw_split[31]),
grantee_schema_name__col_32 = tostring(raw_split[32]),
origin_database_name__col_33 = tostring(raw_split[33]),
origin_user_name__col_34 = tostring(raw_split[34]),
xs_application_user_name__col_35 = tostring(raw_split[35]),
application_name__col_36 = tostring(raw_split[36]),
statement_user_name__col_37 = tostring(raw_split[37]),
create_time__col_38 = tostring(raw_split[38]),
xsa_message_ip__col_39 = tostring(raw_split[39]),
xsa_tenant__col_40 = tostring(raw_split[40]),
xsa_uuid__col_41 = tostring(raw_split[41]),
xsa_channel__col_42 = tostring(raw_split[42]),
xsa_attachment_id__col_43 = tostring(raw_split[43]),
xsa_attachment_name__col_44 = tostring(raw_split[44]),
xsa_organization_id__col_45 = tostring(raw_split[45]),
xsa_space_id__col_46 = tostring(raw_split[46]),
xsa_instance_id__col_47 = tostring(raw_split[47]),
xsa_binding_id__col_48 = tostring(raw_split[48]),
xsa_object__col_49 = tostring(raw_split[49]),
xsa_data_subject__col_50 = tostring(raw_split[50])
| where
audit_action__col_13 =~ "CREATE USER" or
audit_action__col_13 =~ 'ALTER USER' or
audit_action__col_13 =~ 'DROP USER' or
audit_action__col_13 =~ 'DROP SCHEMA'
| extend AlertRuleUniqueName = 'hanadb-useradminactions-logserv'
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: sid__col_3
- identifier: InstanceName
columnName: database_name__col_6
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: hostname__col_2
- entityType: IP
fieldMappings:
- identifier: Address
columnName: client_ip_address__col_7
alertDetailsOverride:
alertDisplayNameFormat: 'SAP LogServ - HANA DB - User Admin actions'
alertDescriptionFormat: |
{{comment__col_27}}
customDetails:
SAP_User: session_user__col_14
version: 1.0.1
Stages and Predicates
Parameters
let AuditTimeAgo = 60m;
Stage 1: source
Syslog
Stage 2: where
| where TimeGenerated >= ago(AuditTimeAgo)
Stage 3: where
| where SourceSystem == "HANADB" and CollectorHostName == "SAPLogServ"
Stage 4: extend
| extend raw_split = split(SyslogMessage, ";")
Stage 5: extend
| extend
event_timestamp__col_0 = tostring(raw_split[0]),
service_name__col_1 = tostring(raw_split[1]),
hostname__col_2 = tostring(raw_split[2]),
sid__col_3 = tostring(raw_split[3]),
instance_number__col_4 = tostring(raw_split[4]),
port_number__col_5 = tostring(raw_split[5]),
database_name__col_6 = tostring(raw_split[6]),
client_ip_address__col_7 = tostring(raw_split[7]),
client_name__col_8 = tostring(raw_split[8]),
client_process_id__col_9 = tostring(raw_split[9]),
client_port_number__col_10 = tostring(raw_split[10]),
policy_name__col_11 = tostring(raw_split[11]),
audit_level__col_12 = tostring(raw_split[12]),
audit_action__col_13 = tostring(raw_split[13]),
session_user__col_14 = tostring(raw_split[14]),
target_schema__col_15 = tostring(raw_split[15]),
target_object__col_16 = tostring(raw_split[16]),
privilege_name__col_17 = tostring(raw_split[17]),
grantable__col_18 = tostring(raw_split[18]),
role_name__col_19 = tostring(raw_split[19]),
target_principal__col_20 = tostring(raw_split[20]),
action_status__col_21 = tostring(raw_split[21]),
component__col_22 = tostring(raw_split[22]),
section__col_23 = tostring(raw_split[23]),
parameter__col_24 = tostring(raw_split[24]),
old_value__col_25 = tostring(raw_split[25]),
new_value__col_26 = tostring(raw_split[26]),
comment__col_27 = tostring(raw_split[27]),
executed_statement__col_28 = tostring(raw_split[28]),
session_id__col_29 = tostring(raw_split[29]),
application_user_name__col_30 = tostring(raw_split[30]),
role_schema_name__col_31 = tostring(raw_split[31]),
grantee_schema_name__col_32 = tostring(raw_split[32]),
origin_database_name__col_33 = tostring(raw_split[33]),
origin_user_name__col_34 = tostring(raw_split[34]),
xs_application_user_name__col_35 = tostring(raw_split[35]),
application_name__col_36 = tostring(raw_split[36]),
statement_user_name__col_37 = tostring(raw_split[37]),
create_time__col_38 = tostring(raw_split[38]),
xsa_message_ip__col_39 = tostring(raw_split[39]),
xsa_tenant__col_40 = tostring(raw_split[40]),
xsa_uuid__col_41 = tostring(raw_split[41]),
xsa_channel__col_42 = tostring(raw_split[42]),
xsa_attachment_id__col_43 = tostring(raw_split[43]),
xsa_attachment_name__col_44 = tostring(raw_split[44]),
xsa_organization_id__col_45 = tostring(raw_split[45]),
xsa_space_id__col_46 = tostring(raw_split[46]),
xsa_instance_id__col_47 = tostring(raw_split[47]),
xsa_binding_id__col_48 = tostring(raw_split[48]),
xsa_object__col_49 = tostring(raw_split[49]),
xsa_data_subject__col_50 = tostring(raw_split[50])
Stage 6: where
| where
audit_action__col_13 =~ "CREATE USER" or
audit_action__col_13 =~ 'ALTER USER' or
audit_action__col_13 =~ 'DROP USER' or
audit_action__col_13 =~ 'DROP SCHEMA'
Stage 7: extend
| extend AlertRuleUniqueName = 'hanadb-useradminactions-logserv'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
CollectorHostName | eq |
| field:"CollectorHostName" kind:eq value:"SAPLogServ" |
SourceSystem | eq |
| field:"SourceSystem" kind:eq value:"HANADB" |
audit_action__col_13 | eq |
| field:"audit_action__col_13" kind:eq |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
raw_split | extend |
action_status__col_21 | extend |
application_name__col_36 | extend |
application_user_name__col_30 | extend |
audit_action__col_13 | extend |
audit_level__col_12 | extend |
client_ip_address__col_7 | extend |
client_name__col_8 | extend |
client_port_number__col_10 | extend |
client_process_id__col_9 | extend |
comment__col_27 | extend |
component__col_22 | extend |
create_time__col_38 | extend |
database_name__col_6 | extend |
event_timestamp__col_0 | extend |
executed_statement__col_28 | extend |
grantable__col_18 | extend |
grantee_schema_name__col_32 | extend |
hostname__col_2 | extend |
instance_number__col_4 | extend |
new_value__col_26 | extend |
old_value__col_25 | extend |
origin_database_name__col_33 | extend |
origin_user_name__col_34 | extend |
parameter__col_24 | extend |
policy_name__col_11 | extend |
port_number__col_5 | extend |
privilege_name__col_17 | extend |
role_name__col_19 | extend |
role_schema_name__col_31 | extend |
section__col_23 | extend |
service_name__col_1 | extend |
session_id__col_29 | extend |
session_user__col_14 | extend |
sid__col_3 | extend |
statement_user_name__col_37 | extend |
target_object__col_16 | extend |
target_principal__col_20 | extend |
target_schema__col_15 | extend |
xs_application_user_name__col_35 | extend |
xsa_attachment_id__col_43 | extend |
xsa_attachment_name__col_44 | extend |
xsa_binding_id__col_48 | extend |
xsa_channel__col_42 | extend |
xsa_data_subject__col_50 | extend |
xsa_instance_id__col_47 | extend |
xsa_message_ip__col_39 | extend |
xsa_object__col_49 | extend |
xsa_organization_id__col_45 | extend |
xsa_space_id__col_46 | extend |
xsa_tenant__col_40 | extend |
xsa_uuid__col_41 | extend |
AlertRuleUniqueName | extend |
Squid proxy events related to mining pools
#'Checks for Squid proxy events in Syslog associated with common mining pools. This query presumes the default Squid log format is being used. http://www.squid-cache.org/Doc/config/access_log/'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 80733eb7-35b2-45b6-b2b8-3c51df258206
name: Squid proxy events related to mining pools
description: |
'Checks for Squid proxy events in Syslog associated with common mining pools. This query presumes the default Squid log format is being used.
http://www.squid-cache.org/Doc/config/access_log/'
severity: Low
requiredDataConnectors:
- connectorId: Syslog
dataTypes:
- Syslog
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1102
query: |
let DomainList = dynamic(["monerohash.com", "do-dear.com", "xmrminerpro.com", "secumine.net", "xmrpool.com", "minexmr.org", "hashanywhere.com", "xmrget.com",
"mininglottery.eu", "minergate.com", "moriaxmr.com", "multipooler.com", "moneropools.com", "xmrpool.eu", "coolmining.club", "supportxmr.com",
"minexmr.com", "hashvault.pro", "xmrpool.net", "crypto-pool.fr", "xmr.pt", "miner.rocks", "walpool.com", "herominers.com", "gntl.co.uk", "semipool.com",
"coinfoundry.org", "cryptoknight.cc", "fairhash.org", "baikalmine.com", "tubepool.xyz", "fairpool.xyz", "asiapool.io", "coinpoolit.webhop.me", "nanopool.org",
"moneropool.com", "miner.center", "prohash.net", "poolto.be", "cryptoescrow.eu", "monerominers.net", "cryptonotepool.org", "extrmepool.org", "webcoin.me",
"kippo.eu", "hashinvest.ws", "monero.farm", "supportxmr.com", "xmrpool.eu", "linux-repository-updates.com", "1gh.com", "dwarfpool.com", "hash-to-coins.com",
"hashvault.pro", "pool-proxy.com", "hashfor.cash", "fairpool.cloud", "litecoinpool.org", "mineshaft.ml", "abcxyz.stream", "moneropool.ru", "cryptonotepool.org.uk",
"extremepool.org", "extremehash.com", "hashinvest.net", "unipool.pro", "crypto-pools.org", "monero.net", "backup-pool.com", "mooo.com", "freeyy.me", "cryptonight.net",
"shscrypto.net"]);
Syslog
| where ProcessName contains "squid"
| extend URL = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :]*)",3,SyslogMessage),
SourceIP = extract("([0-9]+ )(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3}))",2,SyslogMessage),
Status = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))",1,SyslogMessage),
HTTP_Status_Code = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))/([0-9]{3})",8,SyslogMessage),
User = extract("(CONNECT |GET )([^ ]* )([^ ]+)",3,SyslogMessage),
RemotePort = extract("(CONNECT |GET )([^ ]*)(:)([0-9]*)",4,SyslogMessage),
Domain = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :\\/]*)",3,SyslogMessage),
Bytes = toint(extract("([A-Z]+\\/[0-9]{3} )([0-9]+)",2,SyslogMessage)),
contentType = extract("([a-z/]+$)",1,SyslogMessage)
| extend TLD = extract("\\.[a-z]*$",0,Domain)
| where HTTP_Status_Code == '200'
| where Domain contains "."
| where Domain has_any (DomainList)
| extend AccountName = tostring(split(User, "@")[0]), AccountUPNSuffix = tostring(split(User, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: User
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
version: 1.0.4
kind: Scheduled
Stages and Predicates
Let Binding: DomainList
let DomainList = dynamic(["monerohash.com", "do-dear.com", "xmrminerpro.com", "secumine.net", "xmrpool.com", "minexmr.org", "hashanywhere.com", "xmrget.com",
"mininglottery.eu", "minergate.com", "moriaxmr.com", "multipooler.com", "moneropools.com", "xmrpool.eu", "coolmining.club", "supportxmr.com",
"minexmr.com", "hashvault.pro", "xmrpool.net", "crypto-pool.fr", "xmr.pt", "miner.rocks", "walpool.com", "herominers.com", "gntl.co.uk", "semipool.com",
"coinfoundry.org", "cryptoknight.cc", "fairhash.org", "baikalmine.com", "tubepool.xyz", "fairpool.xyz", "asiapool.io", "coinpoolit.webhop.me", "nanopool.org",
"moneropool.com", "miner.center", "prohash.net", "poolto.be", "cryptoescrow.eu", "monerominers.net", "cryptonotepool.org", "extrmepool.org", "webcoin.me",
"kippo.eu", "hashinvest.ws", "monero.farm", "supportxmr.com", "xmrpool.eu", "linux-repository-updates.com", "1gh.com", "dwarfpool.com", "hash-to-coins.com",
"hashvault.pro", "pool-proxy.com", "hashfor.cash", "fairpool.cloud", "litecoinpool.org", "mineshaft.ml", "abcxyz.stream", "moneropool.ru", "cryptonotepool.org.uk",
"extremepool.org", "extremehash.com", "hashinvest.net", "unipool.pro", "crypto-pools.org", "monero.net", "backup-pool.com", "mooo.com", "freeyy.me", "cryptonight.net",
"shscrypto.net"]);
Stage 1: source
Syslog
Stage 2: where
| where ProcessName contains "squid"
Stage 3: extend
| extend URL = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :]*)",3,SyslogMessage),
SourceIP = extract("([0-9]+ )(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3}))",2,SyslogMessage),
Status = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))",1,SyslogMessage),
HTTP_Status_Code = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))/([0-9]{3})",8,SyslogMessage),
User = extract("(CONNECT |GET )([^ ]* )([^ ]+)",3,SyslogMessage),
RemotePort = extract("(CONNECT |GET )([^ ]*)(:)([0-9]*)",4,SyslogMessage),
Domain = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :\\/]*)",3,SyslogMessage),
Bytes = toint(extract("([A-Z]+\\/[0-9]{3} )([0-9]+)",2,SyslogMessage)),
contentType = extract("([a-z/]+$)",1,SyslogMessage)
Stage 4: extend
| extend TLD = extract("\\.[a-z]*$",0,Domain)
Stage 5: where
| where HTTP_Status_Code == '200'
Stage 6: where
| where Domain contains "."
Stage 7: where
| where Domain has_any (DomainList)
Stage 8: extend
| extend AccountName = tostring(split(User, "@")[0]), AccountUPNSuffix = tostring(split(User, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Domain | contains |
| field:"Domain" kind:contains value:"." |
Domain | match |
| field:"Domain" kind:match |
HTTP_Status_Code | eq |
| field:"HTTP_Status_Code" kind:eq value:"200" |
ProcessName | contains |
| field:"process_name" kind:contains value:"squid" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Bytes | extend |
Domain | extend |
HTTP_Status_Code | extend |
RemotePort | extend |
SourceIP | extend |
Status | extend |
URL | extend |
User | extend |
contentType | extend |
TLD | extend |
AccountName | extend |
AccountUPNSuffix | extend |
TI map Email entity to SigninLogs
#Identifies a match in SigninLogs table from any Email IOC from TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Telemetry Coverage
Rule Body
id: 30fa312c-31eb-43d8-b0cc-bcbdfb360822
name: TI map Email entity to SigninLogs
description: |
'Identifies a match in SigninLogs table from any Email IOC from TI'
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
let Signins = materialize(union isfuzzy=true
( SigninLogs | where TimeGenerated >= ago(dt_lookBack)),
( AADNonInteractiveUserSignInLogs | where TimeGenerated >= ago(dt_lookBack)
| extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails))
| where isnotempty(UserPrincipalName) and UserPrincipalName matches regex emailregex
| extend UserPrincipalName = tolower(UserPrincipalName)
| extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
| extend SigninLogs_TimeGenerated = TimeGenerated);
let SigninUPNs = Signins | distinct UserPrincipalName | summarize make_list(UserPrincipalName);
ThreatIntelligenceIndicator
//Filtering the table for Email related IOCs
| where isnotempty(EmailSenderAddress)
| where TimeGenerated >= ago(ioc_lookBack)
| where EmailSenderAddress in (SigninUPNs)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
| join kind=innerunique (Signins) on $left.EmailSenderAddress == $right.UserPrincipalName
| where SigninLogs_TimeGenerated < ExpirationDateTime
| summarize SigninLogs_TimeGenerated = arg_max(SigninLogs_TimeGenerated, *) by IndicatorId, UserPrincipalName
| project SigninLogs_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, Url, ExpirationDateTime, ConfidenceScore, EmailSenderName, EmailRecipient, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, FileHashValue, FileHashType, IPAddress, UserPrincipalName, AppDisplayName, StatusCode, StatusDetails, NetworkIP, NetworkDestinationIP, NetworkSourceIP, Type
| extend Name = tostring(split(UserPrincipalName, '@', 0)[0]), UPNSuffix = tostring(split(UserPrincipalName, '@', 1)[0])
| extend timestamp = SigninLogs_TimeGenerated
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.2.7
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let emailregex = @'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$';
Let Binding: Signins
let Signins = materialize(union isfuzzy=true
( SigninLogs | where TimeGenerated >= ago(dt_lookBack)),
( AADNonInteractiveUserSignInLogs | where TimeGenerated >= ago(dt_lookBack)
| extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails))
| where isnotempty(UserPrincipalName) and UserPrincipalName matches regex emailregex
| extend UserPrincipalName = tolower(UserPrincipalName)
| extend Status = todynamic(Status), LocationDetails = todynamic(LocationDetails)
| extend StatusCode = tostring(Status.errorCode), StatusDetails = tostring(Status.additionalDetails)
| extend State = tostring(LocationDetails.state), City = tostring(LocationDetails.city), Region = tostring(LocationDetails.countryOrRegion)
| extend SigninLogs_TimeGenerated = TimeGenerated);
Let Binding: SigninUPNs
let SigninUPNs = Signins | distinct UserPrincipalName | summarize make_list(UserPrincipalName);
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where isnotempty(EmailSenderAddress)
Stage 3: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 4: where
| where EmailSenderAddress in (SigninUPNs)
Stage 5: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 6: where
| where Active == true and ExpirationDateTime > now()
Stage 7: where
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
Stage 8: join
join kind=innerunique (Signins) on $left.EmailSenderAddress, $right.UserPrincipalName
Stage 9: where
where SigninLogs_TimeGenerated < ExpirationDateTime
Stage 10: summarize
summarize SigninLogs_TimeGenerated by IndicatorId, UserPrincipalName
Stage 11: project
project ActivityGroupNames, AppDisplayName, ConfidenceScore, Description, EmailRecipient, EmailSenderName, EmailSourceDomain, EmailSourceIpAddress, EmailSubject, ExpirationDateTime, FileHashType, FileHashValue, IPAddress, IndicatorId, NetworkDestinationIP, NetworkIP, NetworkSourceIP, SigninLogs_TimeGenerated, StatusCode, StatusDetails, ThreatType, Type, Url, UserPrincipalName
Stage 12: extend
extend Name, UPNSuffix
Stage 13: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
EmailSenderAddress | in |
| field:"EmailSenderAddress" kind:in value:"SigninUPNs" |
EmailSenderAddress | is_not_null | field:"EmailSenderAddress" kind:is_not_null | |
UserPrincipalName | is_not_null | field:"UserPrincipalName" kind:is_not_null | |
UserPrincipalName | regex_match |
| field:"UserPrincipalName" kind:regex_match value:"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Description | contains | State: falsepos; | excludes:Description field:"Description" value:"State: falsepos;" |
Description | contains | State: inactive; | excludes:Description field:"Description" value:"State: inactive;" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
AppDisplayName | project |
ConfidenceScore | project |
Description | project |
EmailRecipient | project |
EmailSenderName | project |
EmailSourceDomain | project |
EmailSourceIpAddress | project |
EmailSubject | project |
ExpirationDateTime | project |
FileHashType | project |
FileHashValue | project |
IPAddress | project |
IndicatorId | project |
NetworkDestinationIP | project |
NetworkIP | project |
NetworkSourceIP | project |
SigninLogs_TimeGenerated | project |
StatusCode | project |
StatusDetails | project |
ThreatType | project |
Type | project |
Url | project |
UserPrincipalName | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
TI Map URL Entity to EmailUrlInfo
#This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in EmailUrlInfo.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
Rule Body
id: 9e32e545-e60c-47de-9941-f9ca1ada0a42
name: TI Map URL Entity to EmailUrlInfo
description: |
'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in EmailUrlInfo.'
severity: Medium
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- EmailUrlInfo
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let EmailUrlInfo_ = materialize(EmailUrlInfo
| where TimeGenerated >= ago(dt_lookBack)
| extend EmailUrlInfo_TimeGenerated = TimeGenerated);
let EmailUrls = EmailUrlInfo_ | distinct Url | summarize make_list(Url);
let EmailUrlDomains = EmailUrlInfo_ | distinct UrlDomain | summarize make_list(UrlDomain);
let EmailEvents_ = materialize(EmailEvents
| where TimeGenerated >= ago(dt_lookBack));
let TI = materialize(ThreatIntelIndicators
//extract key part of kv pair
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "url"
| extend Url = ObservableValue
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend IndicatorId = tostring(split(Id, "--")[2])
| where TimeGenerated >= ago(ioc_lookBack)
| where tolower(Url) in (EmailUrls) or tolower(Url) in (EmailUrlDomains)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil)));
(union
(TI | join kind=innerunique (EmailUrlInfo_) on Url),
(TI | join kind=innerunique (EmailUrlInfo_) on $left.Url == $right.UrlDomain))
| where EmailUrlInfo_TimeGenerated < ValidUntil
| summarize EmailUrlInfo_TimeGenerated = arg_max(EmailUrlInfo_TimeGenerated, *) by IndicatorId, Url
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, IndicatorId, Type, ValidUntil, Confidence, Url, UrlDomain, UrlLocation, NetworkMessageId
| extend timestamp = EmailUrlInfo_TimeGenerated
| join kind=inner (EmailEvents_) on NetworkMessageId
| where DeliveryAction !has "Blocked"
| extend Name = tostring(split(RecipientEmailAddress, '@', 0)[0]), UPNSuffix = tostring(split(RecipientEmailAddress, '@', 1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: RecipientEmailAddress
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.0.5
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Let Binding: EmailUrlInfo_
let EmailUrlInfo_ = materialize(EmailUrlInfo
| where TimeGenerated >= ago(dt_lookBack)
| extend EmailUrlInfo_TimeGenerated = TimeGenerated);
Let Binding: EmailUrls
let EmailUrls = EmailUrlInfo_ | distinct Url | summarize make_list(Url);
Let Binding: EmailUrlDomains
let EmailUrlDomains = EmailUrlInfo_ | distinct UrlDomain | summarize make_list(UrlDomain);
Let Binding: EmailEvents_
let EmailEvents_ = materialize(EmailEvents
| where TimeGenerated >= ago(dt_lookBack));
Union (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: TI, TI
Leg 1: TI
TI | join kind=innerunique (EmailUrlInfo_) on Url
Leg 2: TI
TI | join kind=innerunique (EmailUrlInfo_) on $left.Url == $right.UrlDomain
Applied to the Combined Result
| where EmailUrlInfo_TimeGenerated < ValidUntil
| summarize EmailUrlInfo_TimeGenerated = arg_max(EmailUrlInfo_TimeGenerated, *) by IndicatorId, Url
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, IndicatorId, Type, ValidUntil, Confidence, Url, UrlDomain, UrlLocation, NetworkMessageId
| extend timestamp = EmailUrlInfo_TimeGenerated
| join kind=inner (EmailEvents_) on NetworkMessageId
| where DeliveryAction !has "Blocked"
| extend Name = tostring(split(RecipientEmailAddress, '@', 0)[0]), UPNSuffix = tostring(split(RecipientEmailAddress, '@', 1)[0])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"url" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
Url | in |
| field:"Url" kind:in |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
DeliveryAction | match | Blocked | excludes:DeliveryAction field:"DeliveryAction" value:"Blocked" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
Confidence | project |
Description | project |
EmailUrlInfo_TimeGenerated | project |
IndicatorId | project |
NetworkMessageId | project |
Type | project |
Url | project |
UrlDomain | project |
UrlLocation | project |
ValidUntil | project |
timestamp | extend |
Name | extend |
UPNSuffix | extend |
TI Map URL Entity to SecurityAlert Data
#'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in SecurityAlert data.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 3b6bdb38-93c5-452f-ab3a-97a3d1320d16
name: TI Map URL Entity to SecurityAlert Data
description: |
'This query identifies any URL indicators of compromise (IOCs) from threat intelligence (TI) by searching for matches in SecurityAlert data.'
severity: Medium
requiredDataConnectors:
- connectorId: MicrosoftCloudAppSecurity
dataTypes:
- SecurityAlert
- connectorId: AzureSecurityCenter
dataTypes:
- SecurityAlert
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let URLRegex = "((https?|ftp|ldap|wss?|file):\\/\\/(([\\:\\%\\w\\_\\-]+(\\.|@))*((xn--)?[a-zA-Z0-9\\-]+\\.)+(xn--[a-z0-9]+|[A-Za-z]+)|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{0,3})[.,:\\w@?^=%&\\/~+#-]*[\\w@?^=%&\\/~+#-])";
let SecurityEvents = materialize( SecurityAlert
| where TimeGenerated >= ago(dt_lookBack)
| extend MSTI = case(AlertName has "TI map" and VendorName == "Microsoft" and ProductName == 'Azure Sentinel', true, false)
| where MSTI == false
| mv-expand Entity = parse_json(Entities)
| extend RawUrl = coalesce(Entity.Url, Entity.Urls)
| mv-expand RawUrl
| extend Url = tostring(RawUrl)
| extend Compromised_Host = tostring(parse_json(ExtendedProperties).["Compromised Host"])
| extend Alert_TimeGenerated = TimeGenerated);
let EventUrls = materialize(SecurityEvents | distinct Url | summarize make_list(Url));
ThreatIntelIndicators
//extract key part of kv pair
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "url"
| extend Url = ObservableValue
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| where TimeGenerated >= ago(ioc_lookBack)
| where tolower(Url) in (EventUrls)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
| extend Description = tostring(parse_json(Data).description)
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
| project-reorder *, Tags, TrafficLightProtocolLevel, Url, Type
// using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
| join kind=innerunique (SecurityEvents) on Url
| where Alert_TimeGenerated < ValidUntil
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| summarize Alert_TimeGenerated = arg_max(Alert_TimeGenerated, *) by Id, AlertName
| project timestamp = Alert_TimeGenerated, ActivityGroupNames, Id, Type, ValidUntil, Confidence, AlertName, AlertSeverity, Description, Url, Compromised_Host
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: Compromised_Host
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
version: 1.2.11
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Let Binding: URLRegex
let URLRegex = "((https?|ftp|ldap|wss?|file):\\/\\/(([\\:\\%\\w\\_\\-]+(\\.|@))*((xn--)?[a-zA-Z0-9\\-]+\\.)+(xn--[a-z0-9]+|[A-Za-z]+)|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{0,3})[.,:\\w@?^=%&\\/~+#-]*[\\w@?^=%&\\/~+#-])";
Let Binding: SecurityEvents
let SecurityEvents = materialize( SecurityAlert
| where TimeGenerated >= ago(dt_lookBack)
| extend MSTI = case(AlertName has "TI map" and VendorName == "Microsoft" and ProductName == 'Azure Sentinel', true, false)
| where MSTI == false
| mv-expand Entity = parse_json(Entities)
| extend RawUrl = coalesce(Entity.Url, Entity.Urls)
| mv-expand RawUrl
| extend Url = tostring(RawUrl)
| extend Compromised_Host = tostring(parse_json(ExtendedProperties).["Compromised Host"])
| extend Alert_TimeGenerated = TimeGenerated);
Let Binding: EventUrls
let EventUrls = materialize(SecurityEvents | distinct Url | summarize make_list(Url));
Stage 1: source
ThreatIntelIndicators
Stage 2: extend
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
Stage 3: where
| where IndicatorType == "url"
Stage 4: extend
| extend Url = ObservableValue
Stage 5: extend
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
Stage 6: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 7: where
| where tolower(Url) in (EventUrls)
Stage 8: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
Stage 9: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 10: extend
| extend Description = tostring(parse_json(Data).description)
Stage 11: where
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
Stage 12: project-reorder
| project-reorder *, Tags, TrafficLightProtocolLevel, Url, Type
Stage 13: join
| join kind=innerunique (SecurityEvents) on Url
Stage 14: where
where Alert_TimeGenerated < ValidUntil
Stage 15: extend
extend Description
Stage 16: extend
extend ActivityGroupNames
Stage 17: summarize
summarize Alert_TimeGenerated by Id, AlertName
Stage 18: project
project ActivityGroupNames, AlertName, AlertSeverity, Compromised_Host, Confidence, Description, Id, Type, Url, ValidUntil, timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"url" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
MSTI | eq |
| field:"MSTI" kind:eq value:"false" |
Url | in |
| field:"Url" kind:in value:"EventUrls" |
ValidUntil | is_null | field:"ValidUntil" kind:is_null |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
Description | contains | State: falsepos; | excludes:Description field:"Description" value:"State: falsepos;" |
Description | contains | State: inactive; | excludes:Description field:"Description" value:"State: inactive;" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActivityGroupNames | project |
AlertName | project |
AlertSeverity | project |
Compromised_Host | project |
Confidence | project |
Description | project |
Id | project |
Type | project |
Url | project |
ValidUntil | project |
timestamp | project |
User account created and deleted within 10 mins
#Identifies when a user account is created and then deleted within 10 minutes. This can be an indication of compromise and an adversary attempting to hide in the noise.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry Coverage
Rule Body
id: 4b93c5af-d20b-4236-b696-a28b8c51407f
name: User account created and deleted within 10 mins
description: |
'Identifies when a user account is created and then deleted within 10 minutes. This can be an indication of compromise and an adversary attempting to hide in the noise.'
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 25h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1098
- T1078
query: |
let timeframe = 1d;
let spanoftime = 10m;
let threshold = 0;
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated > ago(timeframe+spanoftime)
// A user account was created
| where EventID == 4720
| where AccountType =~ "User"
| project creationTime = TimeGenerated, CreateEventID = EventID, CreateActivity = Activity, Computer = toupper(Computer),
TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid,
AccountUsedToCreate = SubjectAccount, CreatedBySubjectUserName = SubjectUserName, CreatedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToCreate = SubjectUserSid, UserPrincipalName
),
(
WindowsEvent
| where TimeGenerated > ago(timeframe+spanoftime)
// A user account was created
| where EventID == 4720
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| where AccountType =~ "User"
| extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
| extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
| extend TargetSid = tostring(EventData.TargetSid)
| extend UserPrincipalName = tostring(EventData.UserPrincipalName)
| extend Activity = "4720 - A user account was created."
| extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName)
| project creationTime = TimeGenerated, CreateEventID = EventID, CreateActivity = Activity, Computer = toupper(Computer),
TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid,
AccountUsedToCreate = SubjectAccount, CreatedBySubjectUserName = SubjectUserName, CreatedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToCreate = SubjectUserSid, UserPrincipalName
)
)
| join kind = inner
(
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated > ago(timeframe)
// A user account was deleted
| where EventID == 4726
| where AccountType == "User"
| project deletionTime = TimeGenerated, DeleteEventID = EventID, DeleteActivity = Activity, Computer = toupper(Computer),
TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid,
AccountUsedToDelete = SubjectAccount, DeletedBySubjectUserName = SubjectUserName, DeletedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDelete = SubjectUserSid, UserPrincipalName
),
(WindowsEvent
| where TimeGenerated > ago(timeframe)
// A user account was deleted
| where EventID == 4726
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend AccountType=case(SubjectAccount endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| where AccountType == "User"
| extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
| extend TargetSid = tostring(EventData.TargetSid)
| extend UserPrincipalName = tostring(EventData.UserPrincipalName)
| extend Activity = "4726 - A user account was deleted."
| extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName)
| extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
| project deletionTime = TimeGenerated, DeleteEventID = EventID, DeleteActivity = Activity, Computer = toupper(Computer),
TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid,
AccountUsedToDelete = SubjectAccount, DeletedBySubjectUserName = SubjectUserName, DeletedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDelete = SubjectUserSid, UserPrincipalName
)
)
) on Computer, TargetAccount
| where deletionTime - creationTime < spanoftime
| extend TimeDelta = deletionTime - creationTime
| where tolong(TimeDelta) >= threshold
| project TimeDelta, creationTime, CreateEventID, CreateActivity, Computer, TargetAccount, TargetSid, UserPrincipalName, AccountUsedToCreate, SIDofAccountUsedToCreate,
deletionTime, DeleteEventID, DeleteActivity, AccountUsedToDelete, SIDofAccountUsedToDelete, TargetUserName, TargetDomainName,
CreatedBySubjectUserName, CreatedBySubjectDomainName, DeletedBySubjectUserName, DeletedBySubjectDomainName
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountUsedToCreate
- identifier: Name
columnName: CreatedBySubjectUserName
- identifier: NTDomain
columnName: CreatedBySubjectDomainName
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountUsedToDelete
- identifier: Name
columnName: DeletedBySubjectUserName
- identifier: NTDomain
columnName: DeletedBySubjectDomainName
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: TargetAccount
- identifier: Name
columnName: TargetUserName
- identifier: NTDomain
columnName: TargetDomainName
- entityType: Account
fieldMappings:
- identifier: Sid
columnName: TargetSid
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Computer
- identifier: HostName
columnName: HostName
- identifier: NTDomain
columnName: HostNameDomain
version: 1.2.2
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Others", "Identity" ]
Stages and Predicates
Parameters
let timeframe = 1d;
let spanoftime = 10m;
let threshold = 0;
Union isfuzzy=true (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: SecurityEvent, WindowsEvent
Leg 1: SecurityEvent
SecurityEvent
| where TimeGenerated > ago(timeframe+spanoftime)
| where EventID == 4720
| where AccountType =~ "User"
| project creationTime = TimeGenerated, CreateEventID = EventID, CreateActivity = Activity, Computer = toupper(Computer),
TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid,
AccountUsedToCreate = SubjectAccount, CreatedBySubjectUserName = SubjectUserName, CreatedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToCreate = SubjectUserSid, UserPrincipalName
Leg 2: WindowsEvent
WindowsEvent
| where TimeGenerated > ago(timeframe+spanoftime)
| where EventID == 4720
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend AccountType=case(EventData.SubjectUserName endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| where AccountType =~ "User"
| extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
| extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
| extend TargetSid = tostring(EventData.TargetSid)
| extend UserPrincipalName = tostring(EventData.UserPrincipalName)
| extend Activity = "4720 - A user account was created."
| extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName)
| project creationTime = TimeGenerated, CreateEventID = EventID, CreateActivity = Activity, Computer = toupper(Computer),
TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid,
AccountUsedToCreate = SubjectAccount, CreatedBySubjectUserName = SubjectUserName, CreatedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToCreate = SubjectUserSid, UserPrincipalName
Applied to the Combined Result
| join kind = inner
(
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4726
| where AccountType == "User"
| project deletionTime = TimeGenerated, DeleteEventID = EventID, DeleteActivity = Activity, Computer = toupper(Computer),
TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid,
AccountUsedToDelete = SubjectAccount, DeletedBySubjectUserName = SubjectUserName, DeletedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDelete = SubjectUserSid, UserPrincipalName
),
(WindowsEvent
| where TimeGenerated > ago(timeframe)
| where EventID == 4726
| extend SubjectUserSid = tostring(EventData.SubjectUserSid)
| extend SubjectAccount = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend AccountType=case(SubjectAccount endswith "$" or SubjectUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(SubjectUserSid), "", "User")
| where AccountType == "User"
| extend SubjectDomainName = tostring(EventData.SubjectDomainName), SubjectUserName = tostring(EventData.SubjectUserName)
| extend TargetSid = tostring(EventData.TargetSid)
| extend UserPrincipalName = tostring(EventData.UserPrincipalName)
| extend Activity = "4726 - A user account was deleted."
| extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName)
| extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
| project deletionTime = TimeGenerated, DeleteEventID = EventID, DeleteActivity = Activity, Computer = toupper(Computer),
TargetAccount = tolower(TargetAccount), TargetUserName, TargetDomainName, TargetSid,
AccountUsedToDelete = SubjectAccount, DeletedBySubjectUserName = SubjectUserName, DeletedBySubjectDomainName = SubjectDomainName, SIDofAccountUsedToDelete = SubjectUserSid, UserPrincipalName
)
)
) on Computer, TargetAccount
| where deletionTime - creationTime < spanoftime
| extend TimeDelta = deletionTime - creationTime
| where tolong(TimeDelta) >= threshold
| project TimeDelta, creationTime, CreateEventID, CreateActivity, Computer, TargetAccount, TargetSid, UserPrincipalName, AccountUsedToCreate, SIDofAccountUsedToCreate,
deletionTime, DeleteEventID, DeleteActivity, AccountUsedToDelete, SIDofAccountUsedToDelete, TargetUserName, TargetDomainName,
CreatedBySubjectUserName, CreatedBySubjectDomainName, DeletedBySubjectUserName, DeletedBySubjectDomainName
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AccountType | eq |
| field:"AccountType" kind:eq |
EventID | eq |
| field:"EventID" kind:eq |
TimeDelta | ge |
| field:"TimeDelta" kind:ge value:"0" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountUsedToCreate | project |
AccountUsedToDelete | project |
Computer | project |
CreateActivity | project |
CreateEventID | project |
CreatedBySubjectDomainName | project |
CreatedBySubjectUserName | project |
DeleteActivity | project |
DeleteEventID | project |
DeletedBySubjectDomainName | project |
DeletedBySubjectUserName | project |
SIDofAccountUsedToCreate | project |
SIDofAccountUsedToDelete | project |
TargetAccount | project |
TargetDomainName | project |
TargetSid | project |
TargetUserName | project |
TimeDelta | project |
UserPrincipalName | project |
creationTime | project |
deletionTime | project |
HostName | extend |
HostNameDomain | extend |