Detection rules › Kusto
Kusto rules: 1c
[Entra ID] Privileged Role Assigned to a New User
#Detects when a privileged role is assigned to a new user account. This can indicate unauthorized elevation of privileges.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Persistence | |
| Privilege Escalation | |
| Stealth |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Azure | any: Entra ID audit event (any operation) |
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] Mass Privileged Role Change Activity Detected (Kusto)
- [Entra ID] Privilege Elevation Request Denied (Kusto)
- [Entra ID] Privileged Role Assigned to User (Kusto)
- Account Elevated to New Role (Kusto)
- Admin promotion after Role Management Application Permission Grant (Kusto)
- App Assigned To Azure RBAC/Microsoft Entra Role (Sigma)
- Azure AD Application Administrator Role Assigned (Splunk)
Rule Body
id: b64ac0e2-a241-4b9a-ad3e-ae572630b295
name: "[Entra ID] Privileged Role Assigned to a New User"
version: 1.0.0
kind: Scheduled
description: |
Detects when a privileged role is assigned to a new user account. This can indicate unauthorized elevation of privileges.
severity: High
status: Available
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- PrivilegeEscalation
- DefenseEvasion
- InitialAccess
relevantTechniques:
- T1078.004
query: |
// Define the start and end times based on input values
let starttime = now() - 1h;
let endtime = now();
// Set a lookback period of 14 days
let lookback = starttime - 14d;
// Define a reusable function to query audit logs
let awsFunc = (start: datetime, end: datetime) {
AuditLogs
| where TimeGenerated between (start .. end)
| where Category =~ "RoleManagement"
| where AADOperationType in ("Assign", "AssignEligibleRole")
| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type in~ ("User", "ServicePrincipal")
| extend
Target = iff(TargetResource.type =~ "ServicePrincipal", tostring(TargetResource.displayName), tostring(TargetResource.userPrincipalName)),
props = TargetResource.modifiedProperties
)
| mv-apply Property = props on
(
where Property.displayName =~ "Role.DisplayName"
| extend RoleName = trim('"', tostring(Property.newValue))
)
| where RoleName contains "Admin" and Result == "success"
};
// Query for audit events in the current day
let EventInfo_CurrentDay = awsFunc(starttime, endtime);
// Query for audit events in the historical period (lookback)
let EventInfo_historical = awsFunc(lookback, starttime);
// Find unseen events by performing a left anti-join
let EventInfo_Unseen = (EventInfo_CurrentDay
| join kind=leftanti(EventInfo_historical) on Target, RoleName, OperationName
);
// Extend and clean up the results
EventInfo_Unseen
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIpAddress = tostring(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
| extend Initiator = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
// You can uncomment the lines below to filter out PIM activations
| where Initiator != "MS-PIM"
//| summarize StartTime=min(TimeGenerated), EndTime=min(TimeGenerated) by OperationName, RoleName, Target, Initiator, Result
// Project specific columns and split them for further analysis
| project
TimeGenerated,
OperationName,
RoleName,
Target,
Initiator,
InitiatingUserPrincipalName,
InitiatingAadUserId,
InitiatingAppName,
InitiatingAppServicePrincipalId,
InitiatingIpAddress,
Result
| extend
TargetName = tostring(split(Target, '@', 0)[0]),
TargetUPNSuffix = tostring(split(Target, '@', 1)[0]),
InitiatorName = tostring(split(InitiatingUserPrincipalName, '@', 0)[0]),
InitiatorUPNSuffix = tostring(split(InitiatingUserPrincipalName, '@', 1)[0])
| extend
Source_Network_IPLocation = "",
ActivityType=OperationName
| project
Alert_Category_en = "Entra ID",
Alert_SubCategory_en = "Anomaly Identity Privilege Modification",
Alert_Name_en = "Privileged Role Assigned to a New User",
Alert_Description_en = strcat(
"At Taiwan time: ",
format_datetime(datetime_utc_to_local(TimeGenerated, "Asia/Taipei"), "yyyy-MM-dd HH:mm:ss"),
"in the Microsoft Entra ID tenant",
"operator: ",
iff(isnotempty(Initiator), Initiator, InitiatingAppName),
", from IP: ",
InitiatingIpAddress,
", assigned privileged role ",
tostring(RoleName),
" to ",
iff(isnotempty(Target), Target, "<NoTargetUser>"),
". This permission change behavior has not appeared for this user recently (within 14 days)."
),
Alert_TriageStep_en = strcat(
"1. Confirm whether the privileged role assignment was performed through an Entra ID PIM-approved change process.",
"2. Confirm with the operator: ",
iff(isnotempty(Initiator), Initiator, "<UnknownInitiator>"),
" whether this was performed by the account owner.",
"3. Check whether the source IP and geolocation are abnormal or not a company public IP."
),
Alert_Containment_en = strcat(
"1. If determined to be an unauthorized role assignment, immediately revoke the target ",
iff(isnotempty(Target), Target, "<NoTargetUser>"),
" 's privileged role (",
tostring(RoleName),
"), and restore the state to before the change.",
"2. Immediately restrict the source identity (user or application) that performed the role assignment, revoke sign-in tokens, and force password and MFA reset if necessary. ",
"3. If the source is an application (",
iff(isnotempty(InitiatingAppName), InitiatingAppName, "<NoServicePrincipal>"),
"), immediately disable the Service Principal and revoke high-privilege API consent and credentials/secrets. "
),
Alert_Remediation_en = strcat(
"1. Allow privileged role access only through Entra ID PIM.",
"2. Enforce Conditional Access and MFA for role management and PIM operations, allowing them only from managed devices and named locations.",
"3. Establish real-time alerts and automated response (SOAR) for privileged role changes to detect abnormal role assignments immediately. "
),
Alert_Time_TW = datetime_utc_to_local(TimeGenerated, "Asia/Taipei"),
Alert_Time_UTC0 = TimeGenerated,
Event_Action = ActivityType,
Event_Status = Result,
//Event_Description = ActivityDisplayName,
Source_Identity_FullName = iff(isnotempty(Initiator), Initiator, InitiatingAppName),
//Source_Identity_ID = ActorID,
Source_Identity_Type = iff(isnotempty(Initiator), "User", "Service"),
Source_Network_IPAddress = InitiatingIpAddress,
Source_Network_IPLocation = Source_Network_IPLocation,
//Source_Resource_Name = InitiatingAppName,
Target_Identity_FullName = Target,
Target_Identity_DomainType = iff(Target contains "EXT", 'External', 'Internal'),
Target_Identity_Type = "User",
Target_Resource_ID = "",
Target_Resource_Type = "Entra ID"
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Source_Identity_FullName
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: Target_Identity_FullName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: Source_Network_IPAddress
Stages and Predicates
Parameters
let starttime = now() - 1h;
let endtime = now();
let lookback = starttime - 14d;
let EventInfo_CurrentDay = awsFunc(starttime, endtime);
let EventInfo_historical = awsFunc(lookback, starttime);
let EventInfo_Unseen is inlined into the numbered stages below.
Let Binding: awsFunc
let awsFunc = (start: datetime, end: datetime) {
AuditLogs
| where TimeGenerated between (start .. end)
| where Category =~ "RoleManagement"
| where AADOperationType in ("Assign", "AssignEligibleRole")
| where ActivityDisplayName has_any ("Add eligible member to role", "Add member to role")
| mv-apply TargetResource = TargetResources on
(
where TargetResource.type in~ ("User", "ServicePrincipal")
| extend
Target = iff(TargetResource.type =~ "ServicePrincipal", tostring(TargetResource.displayName), tostring(TargetResource.userPrincipalName)),
props = TargetResource.modifiedProperties
)
| mv-apply Property = props on
(
where Property.displayName =~ "Role.DisplayName"
| extend RoleName = trim('"', tostring(Property.newValue))
)
| where RoleName contains "Admin" and Result == "success"
};
Stage 1: source
AuditLogs
Stage 2: where
where TimeGenerated >= "start" and TimeGenerated <= "end"
Stage 3: where
where Category =~ "RoleManagement"
Stage 4: where
where AADOperationType in ("Assign", "AssignEligibleRole")
Stage 5: where
where (ActivityDisplayName has "Add eligible member to role" or ActivityDisplayName has "Add member to role")
Stage 6: kusto:mv-apply
kusto:mv-apply TargetResource.type in~ ("ServicePrincipal", "User")
Stage 7: kusto:mv-apply
kusto:mv-apply Property.displayName =~ "Role.DisplayName"
Stage 8: where
where Result == "success" and RoleName contains "Admin"
Stage 9: join (negated)
join kind=leftanti (EventInfo_historical) on Target, RoleName, OperationName
Stage 10: extend (6 consecutive steps)
extend InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserPrincipalName, Initiator
Stage 11: where
where Initiator != "MS-PIM"
Stage 12: project
project InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, InitiatingUserPrincipalName, Initiator, OperationName, Result, RoleName, Target, TimeGenerated
Stage 13: extend
extend InitiatorName, InitiatorUPNSuffix, TargetName, TargetUPNSuffix
Stage 14: extend
extend ActivityType, Source_Network_IPLocation
Stage 15: project
project Alert_Category_en, Alert_Containment_en, Alert_Description_en, Alert_Name_en, Alert_Remediation_en, Alert_SubCategory_en, Alert_Time_TW, Alert_Time_UTC0, Alert_TriageStep_en, Event_Action, Event_Status, Source_Identity_FullName, Source_Identity_Type, Source_Network_IPAddress, Source_Network_IPLocation, Target_Identity_DomainType, Target_Identity_FullName, Target_Identity_Type, Target_Resource_ID, Target_Resource_Type
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AADOperationType | in |
| field:"AADOperationType" kind:in |
ActivityDisplayName | match |
| field:"azure_ad::activity_display_name" kind:match |
Category | eq |
| field:"Category" kind:eq value:"RoleManagement" |
Initiator | ne |
| field:"Initiator" kind:ne value:"MS-PIM" |
Property.displayName | eq |
| field:"Property.displayName" kind:eq value:"Role.DisplayName" |
Result | eq |
| field:"Result" kind:eq value:"success" |
RoleName | contains |
| field:"RoleName" kind:contains value:"Admin" |
TargetResource.type | in |
| field:"TargetResource.type" kind:in |
TimeGenerated | ge |
| field:"TimeGenerated" kind:ge value:"start" |
TimeGenerated | le |
| field:"TimeGenerated" kind:le value:"end" |
Exclusions
The rule actively suppresses these predicates.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Alert_Category_en | project |
Alert_Containment_en | project |
Alert_Description_en | project |
Alert_Name_en | project |
Alert_Remediation_en | project |
Alert_SubCategory_en | project |
Alert_Time_TW | project |
Alert_Time_UTC0 | project |
Alert_TriageStep_en | project |
Event_Action | project |
Event_Status | project |
Source_Identity_FullName | project |
Source_Identity_Type | project |
Source_Network_IPAddress | project |
Source_Network_IPLocation | project |
Target_Identity_DomainType | project |
Target_Identity_FullName | project |
Target_Identity_Type | project |
Target_Resource_ID | project |
Target_Resource_Type | project |
AWSCloudTrail - User IAM Enumeration
#Detects enumeration of IAM account configuration via repeated API calls to list roles, users, groups, and policies within a short time period. The threshold can be adjusted to reduce false positives from authorized cloud scanners such as Wiz.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: cfaaf0bc-16d1-48df-ac8b-9d901bbd516a
name: AWSCloudTrail - User IAM Enumeration
description: |
Detects enumeration of IAM account configuration via repeated API calls to list roles, users, groups, and policies within a short time period. The threshold can be adjusted to reduce false positives from authorized cloud scanners such as Wiz.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1580
query: |
let threshold = 50;
AWSCloudTrail
| where EventName in ("ListAttachedRolePolicies","ListRoles","ListGroupsForUser","ListAttachedUserPolicies","ListAccessKeys","ListUsers")
| summarize
count(),
make_set(AWSRegion),
make_set(UserAgent),
make_set(SourceIpAddress),
make_set(ErrorCode),
make_set(ErrorMessage),
make_set(EventName)
by bin(TimeGenerated, 10m), UserIdentityPrincipalid, UserIdentityArn, UserIdentityAccountId
| where count_ > threshold
| mv-expand set_SourceIpAddress
| extend UserName = substring(UserIdentityPrincipalid, indexof_regex(UserIdentityPrincipalid, ":") + 1)
| extend Name = split(UserName,'@')[0],UpnSuffix = split(UserName,'@')[1]
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UpnSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: set_SourceIpAddress
customDetails:
EventCount: count_
AWSAccountId: UserIdentityAccountId
PrincipalId: UserIdentityPrincipalid
alertDetailsOverride:
alertDisplayNameFormat: IAM enumeration detected - {{count_}} API calls by {{UserIdentityPrincipalid}}
alertDescriptionFormat: User {{UserIdentityPrincipalid}} performed {{count_}} IAM enumeration API calls within a 10-minute window from account {{UserIdentityAccountId}}.
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 50;
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName in ("ListAttachedRolePolicies","ListRoles","ListGroupsForUser","ListAttachedUserPolicies","ListAccessKeys","ListUsers")
Stage 3: summarize
| summarize
count(),
make_set(AWSRegion),
make_set(UserAgent),
make_set(SourceIpAddress),
make_set(ErrorCode),
make_set(ErrorMessage),
make_set(EventName)
by bin(TimeGenerated, 10m), UserIdentityPrincipalid, UserIdentityArn, UserIdentityAccountId
Stage 4: where
| where count_ > threshold
Stage 5: mv-expand
| mv-expand set_SourceIpAddress
Stage 6: extend
| extend UserName = substring(UserIdentityPrincipalid, indexof_regex(UserIdentityPrincipalid, ":") + 1)
Stage 7: extend
| extend Name = split(UserName,'@')[0],UpnSuffix = split(UserName,'@')[1]
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventName | in |
| field:"aws::eventName" kind:in |
count_ | gt |
| field:"count_" kind:gt value:"50" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserIdentityAccountId | summarize |
UserIdentityArn | summarize |
UserIdentityPrincipalid | summarize |
UserName | extend |
Name | extend |
UpnSuffix | extend |
Azure DevOps Audit Stream Disabled
#'Azure DevOps allow for audit logs to be streamed to external storage solutions such as SIEM solutions. An attacker looking to hide malicious Azure DevOps activity from defenders may look to disable data streams before conducting activity and then re-enabling the stream after (so as not to raise data threshold-based alarms). Looking for disabled audit streams can identify this activity, and due to the nature of the action its unlikely to have a high false positive rate.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: 4e8238bd-ff4f-4126-a9f6-09b3b6801b3d
name: Azure DevOps Audit Stream Disabled
description: |
'Azure DevOps allow for audit logs to be streamed to external storage solutions such as SIEM solutions. An attacker looking to hide malicious Azure DevOps activity from defenders may look to disable data streams before conducting activity and then re-enabling the stream after (so as not to raise data threshold-based alarms). Looking for disabled audit streams can identify this activity, and due to the nature of the action its unlikely to have a high false positive rate.'
severity: High
status: Available
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562.008
query: |
ADOAuditLogs
| where OperationName =~ "AuditLog.StreamDisabledByUser"
| extend StreamType = tostring(Data.ConsumerType)
| project-reorder TimeGenerated, Details, ActorUPN, IpAddress, UserAgent, StreamType
| extend timestamp = TimeGenerated
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUPN
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IpAddress
version: 1.0.5
kind: Scheduled
Stages and Predicates
Stage 1: source
ADOAuditLogs
Stage 2: where
| where OperationName =~ "AuditLog.StreamDisabledByUser"
Stage 3: extend
| extend StreamType = tostring(Data.ConsumerType)
Stage 4: project-reorder
| project-reorder TimeGenerated, Details, ActorUPN, IpAddress, UserAgent, StreamType
Stage 5: extend
| extend timestamp = TimeGenerated
Stage 6: extend
| extend AccountName = tostring(split(ActorUPN, "@")[0]), AccountUPNSuffix = tostring(split(ActorUPN, "@")[1])
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | eq |
| field:"OperationName" kind:eq value:"AuditLog.StreamDisabledByUser" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
StreamType | extend |
timestamp | extend |
AccountName | extend |
AccountUPNSuffix | extend |
BloodHound Attack Path Finding - Get Certifcates on Tier Zero Key Vault
#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 - Get Certifcates on Tier Zero Key Vault
Rule Body
id: 21d458a0-b0d5-47bb-9fd1-fdda31f3b7f1
name: BloodHound Attack Path Finding - Get Certifcates on Tier Zero Key Vault
description: BloodHound Attack Path Finding - Get Certifcates on Tier Zero Key Vault
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 == "AzureT0GetCerts"
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 == "AzureT0GetCerts"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"AzureT0GetCerts" |
BloodHound Attack Path Finding - Large Default Groups With Read GMSA Password Privileges
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
BloodHound Attack Path Finding - Large Default Groups With Read GMSA Password Privileges
Rule Body
id: 86eda318-a551-438c-b72a-3db0ba6bc27e
name: BloodHound Attack Path Finding - Large Default Groups With Read GMSA Password
Privileges
description: BloodHound Attack Path Finding - Large Default Groups With Read GMSA
Password Privileges
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: BloodHoundEnterprise
dataTypes:
- BHEAttackPathsData_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: BHEAttackPathsData_CL | where Finding == "LargeDefaultGroupsReadGMSA"
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 == "LargeDefaultGroupsReadGMSA"
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Finding | eq |
| field:"Finding" kind:eq value:"LargeDefaultGroupsReadGMSA" |
CYFIRMA - Attack Surface - Configuration High 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 alert is generated when CYFIRMA detects a critical misconfiguration in a public-facing asset or service. Such misconfigurations may include exposed admin interfaces, default credentials, open directory listings, or insecure protocols, which significantly increase the attack surface."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance | |
| Initial Access | |
| Execution | |
| Persistence | |
| Stealth | |
| Credential Access | |
| Discovery | |
| Collection |
Rule Body
id: 30206b45-75d2-4c6a-87c5-f0861c1f2870
name: CYFIRMA - Attack Surface - Configuration High Rule
description: |
This alert is generated when CYFIRMA detects a critical misconfiguration in a public-facing asset or service.
Such misconfigurations may include exposed admin interfaces, default credentials, open directory listings, or insecure protocols, which significantly increase the attack surface."
version: 1.0.1
kind: Scheduled
severity: High
requiredDataConnectors:
- connectorId: CyfirmaAttackSurfaceAlertsConnector
dataTypes:
- CyfirmaASConfigurationAlerts_CL
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
status: Available
tactics:
- InitialAccess
- Discovery
- Persistence
- Execution
- DefenseEvasion
- CredentialAccess
- Collection
- Reconnaissance
relevantTechniques:
- T1190
- T1087
- T1046
- T1136
- T1059
- T1566
- T1070
- T1027
- T1505
- T1555
- T1114
- T1595
query: |
// High Severity - Attack Surface - Misconfiguration Detected
let timeFrame = 5m;
CyfirmaASConfigurationAlerts_CL
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
Domain=sub_domain,
TopDomain=top_domain,
NetworkIP=ip,
AlertUID=alert_uid,
UID=uid,
Softwares=software,
WebAppFirewall=web_app_firewall,
ClickJackingDefence=click_jacking_defence,
ContentSecurityPolicy=content_security_policy,
CookieXssProtection=cookie_xss_protection,
DataInjectionDefence=data_injection_defence,
DomainStatus=domain_status,
MissingEPPCodes=missing_epp_codes,
SecureCookie=secure_cookie,
SetCookieHttpsOnly=set_cookie_https_only,
XFrameOptions=x_frame_options,
X_XssProtection=x_xss_protection,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
| project
TimeGenerated,
Description,
Domain,
TopDomain,
RiskScore,
FirstSeen,
LastSeen,
NetworkIP,
AlertUID,
UID,
Softwares,
WebAppFirewall,
ClickJackingDefence,
ContentSecurityPolicy,
CookieXssProtection,
DataInjectionDefence,
DomainStatus,
MissingEPPCodes,
SecureCookie,
SetCookieHttpsOnly,
XFrameOptions,
X_XssProtection,
ProviderName,
ProductName
entityMappings:
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: TopDomain
- identifier: DnsDomain
columnName: Domain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: NetworkIP
customDetails:
TimeGenerated: TimeGenerated
RiskScore: RiskScore
FirstSeen: FirstSeen
LastSeen: LastSeen
AlertUID: AlertUID
UID: UID
Softwares: Softwares
WebAppFirewall: WebAppFirewall
ClickJackingDefence: ClickJackingDefence
SecurityPolicy: ContentSecurityPolicy
CookieXssProtection: CookieXssProtection
InjectionDefence: DataInjectionDefence
DomainStatus: DomainStatus
MissingEPPCodes: MissingEPPCodes
SecureCookie: SecureCookie
SetCookieHttpsOnly: SetCookieHttpsOnly
XFrameOptions: XFrameOptions
X_XssProtection: X_XssProtection
alertDetailsOverride:
alertDisplayNameFormat: "CYFIRMA - High Risk Misconfiguration Identified in Assets - Domain: {{Domain}} , IP: {{NetworkIP}} "
alertDescriptionFormat: "CYFIRMA - High Risk Misconfiguration Identified in Assets - {{Description}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
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
CyfirmaASConfigurationAlerts_CL
Stage 2: where
| where severity == 'Critical' and TimeGenerated between (ago(timeFrame) .. now())
Stage 3: extend
| extend
Description=description,
FirstSeen=first_seen,
LastSeen=last_seen,
RiskScore=risk_score,
Domain=sub_domain,
TopDomain=top_domain,
NetworkIP=ip,
AlertUID=alert_uid,
UID=uid,
Softwares=software,
WebAppFirewall=web_app_firewall,
ClickJackingDefence=click_jacking_defence,
ContentSecurityPolicy=content_security_policy,
CookieXssProtection=cookie_xss_protection,
DataInjectionDefence=data_injection_defence,
DomainStatus=domain_status,
MissingEPPCodes=missing_epp_codes,
SecureCookie=secure_cookie,
SetCookieHttpsOnly=set_cookie_https_only,
XFrameOptions=x_frame_options,
X_XssProtection=x_xss_protection,
ProviderName='CYFIRMA',
ProductName='DeCYFIR/DeTCT'
Stage 4: project
| project
TimeGenerated,
Description,
Domain,
TopDomain,
RiskScore,
FirstSeen,
LastSeen,
NetworkIP,
AlertUID,
UID,
Softwares,
WebAppFirewall,
ClickJackingDefence,
ContentSecurityPolicy,
CookieXssProtection,
DataInjectionDefence,
DomainStatus,
MissingEPPCodes,
SecureCookie,
SetCookieHttpsOnly,
XFrameOptions,
X_XssProtection,
ProviderName,
ProductName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
severity | eq |
| field:"severity" kind:eq value:"Critical" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AlertUID | project |
ClickJackingDefence | project |
ContentSecurityPolicy | project |
CookieXssProtection | project |
DataInjectionDefence | project |
Description | project |
Domain | project |
DomainStatus | project |
FirstSeen | project |
LastSeen | project |
MissingEPPCodes | project |
NetworkIP | project |
ProductName | project |
ProviderName | project |
RiskScore | project |
SecureCookie | project |
SetCookieHttpsOnly | project |
Softwares | project |
TimeGenerated | project |
TopDomain | project |
UID | project |
WebAppFirewall | project |
XFrameOptions | project |
X_XssProtection | project |
DNS events related to ToR proxies (ASIM DNS Schema)
#Identifies IP addresses performing DNS lookups associated with common ToR proxies. This analytic rule uses ASIM and supports any built-in or custom source that supports the ASIM DNS schema
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Telemetry Coverage
| Provider | Record / event type |
|---|---|
| Sysmon | Event ID 22: DNSEvent (DNS query) |
Rule Body
id: 3fe3c520-04f1-44b8-8398-782ed21435f8
name: DNS events related to ToR proxies (ASIM DNS Schema)
description: |
'Identifies IP addresses performing DNS lookups associated with common ToR proxies.
This analytic rule uses [ASIM](https://aka.ms/AboutASIM) and supports any built-in or custom source that supports the ASIM DNS schema'
severity: Low
requiredDataConnectors:
- connectorId: DNS
dataTypes:
- DnsEvents
- connectorId: AzureFirewall
dataTypes:
- AzureDiagnostics
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog
- connectorId: InfobloxNIOS
dataTypes:
- Syslog
- connectorId: GCPDNSDataConnector
dataTypes:
- GCP_DNS_CL
- connectorId: NXLogDnsLogs
dataTypes:
- NXLog_DNS_Server_CL
- connectorId: CiscoUmbrellaDataConnector
dataTypes:
- Cisco_Umbrella_dns_CL
- connectorId: Corelight
dataTypes:
- Corelight_CL
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1048
tags:
- ParentAlert: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/DnsEvents/DNS_TorProxies.yaml
version: 1.0.0
- Schema: ASIMDns
SchemaVersion: 0.1.1
query: |
let torProxies=dynamic(["tor2web.org", "tor2web.com", "torlink.co", "onion.to", "onion.ink", "onion.cab", "onion.nu", "onion.link",
"onion.it", "onion.city", "onion.direct", "onion.top", "onion.casa", "onion.plus", "onion.rip", "onion.dog", "tor2web.fi",
"tor2web.blutmagie.de", "onion.sh", "onion.lu", "onion.pet", "t2w.pw", "tor2web.ae.org", "tor2web.io", "tor2web.xyz", "onion.lt",
"s1.tor-gateways.de", "s2.tor-gateways.de", "s3.tor-gateways.de", "s4.tor-gateways.de", "s5.tor-gateways.de", "hiddenservice.net"]);
_Im_Dns(domain_has_any=torProxies)
| extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)
| project-away DomainIndex
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: Dvc
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SrcIpAddr
version: 1.3.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Yaron Fruchtmann
support:
tier: Community
categories:
domains: [ "Security - Network" ]
Stages and Predicates
Let Binding: torProxies
let torProxies = dynamic(["tor2web.org", "tor2web.com", "torlink.co", "onion.to", "onion.ink", "onion.cab", "onion.nu", "onion.link",
"onion.it", "onion.city", "onion.direct", "onion.top", "onion.casa", "onion.plus", "onion.rip", "onion.dog", "tor2web.fi",
"tor2web.blutmagie.de", "onion.sh", "onion.lu", "onion.pet", "t2w.pw", "tor2web.ae.org", "tor2web.io", "tor2web.xyz", "onion.lt",
"s1.tor-gateways.de", "s2.tor-gateways.de", "s3.tor-gateways.de", "s4.tor-gateways.de", "s5.tor-gateways.de", "hiddenservice.net"]);
Stage 1: source
_Im_Dns(domain_has_any=torProxies)
Stage 2: extend
| extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
Stage 3: extend
| extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)
HostNameDomain =DomainIndex != -1substring(Dvc, (DomainIndex + 1))DvcStage 4: project-away
| project-away DomainIndex
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HostName | extend |
HostNameDomain | extend |
Dynatrace Application Security - Non-critical runtime vulnerability detection
#Detect runtime vulnerabilities in your environment insights by snyk
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Stealth | |
| Lateral Movement | |
| Impact |
Rule Body
id: ff0af873-a2f2-4233-8412-0ef4e00b0156
name: Dynatrace Application Security - Non-critical runtime vulnerability detection
description: 'Detect runtime vulnerabilities in your environment insights by snyk'
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: DynatraceRuntimeVulnerabilities
dataTypes:
- DynatraceSecurityProblems
- connectorId: DTRunVulnCCPDefinition
dataTypes:
- DynatraceSecurityProblemsV2_CL
- connectorId: DTRunVulnV3CCPDefinition
dataTypes:
- DynatraceSecurityProblemsV3_CL
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
- Execution
- Impact
- InitialAccess
- LateralMovement
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1140
- T1059
- T1565
- T1659
- T1210
- T1554
- T1548
eventGroupingSettings:
aggregationKind: AlertPerResult
query: |
DynatraceSecurityProblems
| where DAVISRiskLevel != "CRITICAL" and Muted == false
| summarize arg_max(LastUpdatedTimeStamp, *) by SecurityProblemId
alertDetailsOverride:
alertDisplayNameFormat: 'Dynatrace Non-critical runtime vulnerability detected - {{DisplayId}} : {{Title}}'
alertDescriptionFormat: |
Non-critical runtime vulnerability ({{ExternalVulnerabilityId}}) detected in package ({{PackageName}}), more details available from Dynatrace at {{Url}}.
alertSeverityColumnName: Severity
customDetails:
SecProbIdentifier: SecurityProblemId
DisplayIdentifier: DisplayId
SecurityProblemUrl: Url
DAVISRiskLevel: DAVISRiskLevel
PackageName: PackageName
Technology: Technology
VulnerabilityType: VulnerabilityType
CVEIds: CVEIds
ExternVulnIdentifier: ExternalVulnerabilityId
DAVISRiskScore: DAVISRiskScore
DAVISRiskVector: DAVISRiskVector
DAVISExposure: DAVISExposure
DAVISPublicExploit: DAVISPublicExploit
DAVISDataAssets: DAVISDataAssets
DAVISVulnFuncUsage: DAVISVulnerableFunctionUsage
incidentConfiguration:
createIncident: false
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
version: 1.0.4
kind: Scheduled
Stages and Predicates
Stage 1: source
DynatraceSecurityProblems
Stage 2: where
| where DAVISRiskLevel != "CRITICAL" and Muted == false
Stage 3: summarize
| summarize arg_max(LastUpdatedTimeStamp, *) by SecurityProblemId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DAVISRiskLevel | ne |
| field:"DAVISRiskLevel" kind:ne value:"CRITICAL" |
Muted | eq |
| field:"Muted" kind:eq value:"false" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SecurityProblemId | summarize |
HoneyLabs TI Map IP Entity to CommonSecurityLog
#'Identifies source-IP matches in CommonSecurityLog (firewall, proxy and other CEF sources) against IP indicators from HoneyLabs honeypot telemetry. A match means a source IP that ran exploit or loader commands against HoneyLabs sensors also appeared as a source in your own logs. Only the structured SourceIP field is matched; destination IPs and IPs merely mentioned inside the free-text Message are ignored, because those are frequently unrelated and generate false positives. Indicator Confidence reflects how much evidence HoneyLabs holds (90 = 100+ observed attacks, 60 = a single sighting); raise the threshold in the query to trade coverage for precision.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Command & Control |
Rule Body
id: c7061f05-d0ed-40e1-862e-bc52e454e3a1
name: HoneyLabs TI Map IP Entity to CommonSecurityLog
version: 1.0.0
kind: Scheduled
description: |
'Identifies source-IP matches in CommonSecurityLog (firewall, proxy and other CEF sources) against IP indicators from HoneyLabs honeypot telemetry. A match means a source IP that ran exploit or loader commands against HoneyLabs sensors also appeared as a source in your own logs. Only the structured SourceIP field is matched; destination IPs and IPs merely mentioned inside the free-text Message are ignored, because those are frequently unrelated and generate false positives. Indicator Confidence reflects how much evidence HoneyLabs holds (90 = 100+ observed attacks, 60 = a single sighting); raise the threshold in the query to trade coverage for precision.'
severity: Medium
requiredDataConnectors:
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: CEF
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CommandAndControl
relevantTechniques:
- T1190
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let HoneyLabs_IPs = ThreatIntelIndicators
| where TimeGenerated >= ago(ioc_lookBack)
| where IsActive == true and ValidUntil > now()
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where SourceSystem startswith 'HoneyLabs'
| where ObservableKey == 'ipv4-addr:value'
| extend TI_ipEntity = tostring(ObservableValue),
HoneyLabsConfidence = Confidence,
HoneyLabsLabels = tostring(Data.labels),
IndicatorDescription = tostring(Data.description),
HoneyLabsReport = strcat('https://honeylabs.net/lookup/', tostring(ObservableValue));
HoneyLabs_IPs
| join kind=innerunique (
CommonSecurityLog
| where TimeGenerated >= ago(dt_lookBack)
| where isnotempty(SourceIP)
| extend CS_ipEntity = SourceIP
| extend CommonSecurityLog_TimeGenerated = TimeGenerated
) on $left.TI_ipEntity == $right.CS_ipEntity
| where CommonSecurityLog_TimeGenerated < ValidUntil
| summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by Id, CS_ipEntity
| project CommonSecurityLog_TimeGenerated, IndicatorDescription, Id, ValidUntil,
HoneyLabsConfidence, HoneyLabsLabels, HoneyLabsReport, TI_ipEntity, CS_ipEntity, LogSeverity,
DeviceAction, SourceIP, DestinationIP, DeviceName, SourceUserName
| extend timestamp = CommonSecurityLog_TimeGenerated
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CS_ipEntity
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let HoneyLabs_IPs is inlined into the numbered stages below.
Stages 1 to 7 define let HoneyLabs_IPs as the rule's main pipeline source. Stages 8 to 12 use it.
Stage 1: source
ThreatIntelIndicators
Stage 2: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 3: where
| where IsActive == true and ValidUntil > now()
Stage 4: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
Stage 5: where
| where SourceSystem startswith 'HoneyLabs'
Stage 6: where
| where ObservableKey == 'ipv4-addr:value'
Stage 7: extend
| extend TI_ipEntity = tostring(ObservableValue),
HoneyLabsConfidence = Confidence,
HoneyLabsLabels = tostring(Data.labels),
IndicatorDescription = tostring(Data.description),
HoneyLabsReport = strcat('https://honeylabs.net/lookup/', tostring(ObservableValue))
Stage 8: join
HoneyLabs_IPs
| join kind=innerunique (
CommonSecurityLog
| where TimeGenerated >= ago(dt_lookBack)
| where isnotempty(SourceIP)
| extend CS_ipEntity = SourceIP
| extend CommonSecurityLog_TimeGenerated = TimeGenerated
) on $left.TI_ipEntity == $right.CS_ipEntity
Stage 9: where
where CommonSecurityLog_TimeGenerated < ValidUntil
Stage 10: summarize
summarize CommonSecurityLog_TimeGenerated by Id, CS_ipEntity
Stage 11: project
project CS_ipEntity, CommonSecurityLog_TimeGenerated, DestinationIP, DeviceAction, DeviceName, HoneyLabsConfidence, HoneyLabsLabels, HoneyLabsReport, Id, IndicatorDescription, LogSeverity, SourceIP, SourceUserName, TI_ipEntity, ValidUntil
Stage 12: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
ObservableKey | eq |
| field:"ObservableKey" kind:eq value:"ipv4-addr:value" |
SourceIP | is_not_null | field:"SourceIP" kind:is_not_null | |
SourceSystem | starts_with |
| field:"SourceSystem" kind:starts_with value:"HoneyLabs" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CS_ipEntity | project |
CommonSecurityLog_TimeGenerated | project |
DestinationIP | project |
DeviceAction | project |
DeviceName | project |
HoneyLabsConfidence | project |
HoneyLabsLabels | project |
HoneyLabsReport | project |
Id | project |
IndicatorDescription | project |
LogSeverity | project |
SourceIP | project |
SourceUserName | project |
TI_ipEntity | project |
ValidUntil | project |
timestamp | extend |
Hunt for devices organized by subnet
#This rule helps you organize devices by subnet in your networks. By doing this, you can identify how many not-onboarded devices, devices not supporting MDE containment, and types of devices live in your subnet ranges.
References
Telemetry Coverage
Rule Body
let isolationSupportedOS = dynamic(["Windows11", "Windows10", "WindowsServer2025", "WindowsServer2022", "WindowsServer2019", "WindowsServer2016", "WindowsServer2012R2", "Linux", "macOS"]);
let containmentSupportedOS = dynamic(["Windows11", "Windows10", "WindowsServer2025", "WindowsServer2022", "WindowsServer2019", "WindowsServer2016", "WindowsServer2012R2"]);
let base = DeviceNetworkInfo
// Expand all IPs
| mv-expand todynamic(IPAddresses)
// Ignore IPv6 addresses
| where tostring(IPAddresses.IPAddress) !contains ":"
// Save the Prefix as an extra property and set it to /32 when empty
| extend Prefix = iff(isnotempty(tostring(IPAddresses.SubnetPrefix)), tostring(IPAddresses.SubnetPrefix), "32");
let networks = base
// Get network addresses with a non /32 prefix
| where Prefix != "32"
// Get the network address related to the IP
| extend NetworkAddress = format_ipv4(tostring(IPAddresses.IPAddress), tolong(Prefix))
// Build the IP and Network Address with the CIDR notation
| extend IPAddress = strcat(tostring(IPAddresses.IPAddress), "/", Prefix)
| extend NetworkAddress = strcat(NetworkAddress, "/", Prefix)
// Join the Device Info information
| join kind=inner DeviceInfo on DeviceId, ReportId
// Ignore APIPA addresses
| where NetworkAddress != "169.254.0.0/16"
// Ignore merged device IDs
| where MergedToDeviceId == ""
// Make a set of all the Device Objects belonging to the same subnet
| extend DeviceObj = pack(
"DeviceName", DeviceName,
"IPAddress", IPAddress,
"DeviceType", DeviceType,
"DeviceCategory", DeviceCategory,
"IsInternetFacing", IsInternetFacing,
"OnboardingStatus", OnboardingStatus,
"OSDistribution", OSDistribution,
"OSPlatform", OSPlatform
)
// Make a list of the objects in the same subnet
| summarize make_set(DeviceObj) by NetworkAddress;
let device_with_host_prefix = base
// Get network addresses with /32 Prefix to try and match other networks
| where Prefix == "32"
// Build the IP Address with the CIDR notation
| extend IPAddress = strcat(tostring(IPAddresses.IPAddress), "/", Prefix)
// Join the Device Info information
| join kind=inner DeviceInfo on DeviceId, ReportId
// Ignore merged device IDs
| where MergedToDeviceId == ""
// Make a set of all the Device Objects
| extend DeviceObj = pack(
"DeviceName", DeviceName,
"IPAddress", IPAddress,
"DeviceType", DeviceType,
"DeviceCategory", DeviceCategory,
"IsInternetFacing", IsInternetFacing,
"OnboardingStatus", OnboardingStatus,
"OSDistribution", OSDistribution,
"OSPlatform", OSPlatform
)
| extend Joiner = 1;
let network_addresses = base
// Get network addresses with a non /32 prefix
| where Prefix != "32"
// Get the network address related to the IP
| extend NetworkAddress = format_ipv4(tostring(IPAddresses.IPAddress), tolong(Prefix))
| extend NetworkAddress = strcat(NetworkAddress, "/", Prefix)
// Create joiner to find host addresses related to certain networks
| distinct NetworkAddress
| extend Joiner = 1;
let networks2 = device_with_host_prefix
// Try to join /32 IPs
| join kind=inner network_addresses on Joiner
// Check if IP is in the network range, and only return those IPs
| extend InRange = ipv4_is_in_range(IPAddress, NetworkAddress)
| where InRange == 1
// Make a list of the objects in the same subnet
| summarize make_set(DeviceObj) by NetworkAddress;
union networks, networks2
// Expand the Device Objects
| mv-expand set_DeviceObj
// Save the DeviceType, DeviceCategory, and Onboarding Status
| extend DeviceType = set_DeviceObj.DeviceType
| extend DeviceCategory = set_DeviceObj.DeviceCategory
| extend OnboardingStatus = set_DeviceObj.OnboardingStatus
// Count how many servers, workstations, network devices, iot devices, and ot devices exists in a subnet, the onboarding estate, and OS Distribution
| summarize Servers = countif(set_DeviceObj.DeviceType=="Server"),
Workstations = countif(set_DeviceObj.DeviceType=="Workstation"),
NetworkDevices = countif(set_DeviceObj.DeviceCategory=="NetworkDevice"),
IoTDevices = countif(set_DeviceObj.DeviceCategory=="IoT"),
OTDevices = countif(set_DeviceObj.DeviceCategory=="OT"),
Onboarded = countif(set_DeviceObj.OnboardingStatus=="Onboarded"),
NotOnboarded = countif(set_DeviceObj.OnboardingStatus!="Onboarded"),
IsolateSupportedOS = countif((set_DeviceObj.OSDistribution has_any (isolationSupportedOS) or set_DeviceObj.OSPlatform == "Linux") and set_DeviceObj.OnboardingStatus == "Onboarded"),
ContainSupportedOS = countif(set_DeviceObj.OSDistribution has_any (containmentSupportedOS) and set_DeviceObj.OnboardingStatus == "Onboarded") by NetworkAddress
// Join the network subnets so we have the device objects again
| join kind=leftouter networks on NetworkAddress
| join kind=leftouter networks2 on NetworkAddress
// Extend Array Concat
| extend set_DeviceObj = array_concat(set_DeviceObj, set_DeviceObj1)
// Remove duplicate columns
| project-away NetworkAddress1, NetworkAddress2, set_DeviceObj1
// Count how many IPs there are in one subnet
| extend CountIPs = array_length(set_DeviceObj)
| sort by CountIPs desc
Stages and Predicates
Parameters
let containmentSupportedOS = dynamic(["Windows11", "Windows10", "WindowsServer2025", "WindowsServer2022", "WindowsServer2019", "WindowsServer2016", "WindowsServer2012R2"]);
Let Binding: isolationSupportedOS
let isolationSupportedOS = dynamic(["Windows11", "Windows10", "WindowsServer2025", "WindowsServer2022", "WindowsServer2019", "WindowsServer2016", "WindowsServer2012R2", "Linux", "macOS"]);
Let Binding: network_addresses
let network_addresses = base
| where Prefix != "32"
| extend NetworkAddress = format_ipv4(tostring(IPAddresses.IPAddress), tolong(Prefix))
| extend NetworkAddress = strcat(NetworkAddress, "/", Prefix)
| distinct NetworkAddress
| extend Joiner = 1;
Union (2 Sources)
Each leg below queries one source; the rule matches if any leg does. Sources: networks, networks2
Leg 1: networks
Leg 2: networks2
Applied to the Combined Result
| mv-expand set_DeviceObj | extend DeviceType = set_DeviceObj.DeviceType | extend DeviceCategory = set_DeviceObj.DeviceCategory | extend OnboardingStatus = set_DeviceObj.OnboardingStatus | summarize Servers = countif(set_DeviceObj.DeviceType=="Server"),
Workstations = countif(set_DeviceObj.DeviceType=="Workstation"),
NetworkDevices = countif(set_DeviceObj.DeviceCategory=="NetworkDevice"),
IoTDevices = countif(set_DeviceObj.DeviceCategory=="IoT"),
OTDevices = countif(set_DeviceObj.DeviceCategory=="OT"),
Onboarded = countif(set_DeviceObj.OnboardingStatus=="Onboarded"),
NotOnboarded = countif(set_DeviceObj.OnboardingStatus!="Onboarded"),
IsolateSupportedOS = countif((set_DeviceObj.OSDistribution has_any (isolationSupportedOS) or set_DeviceObj.OSPlatform == "Linux") and set_DeviceObj.OnboardingStatus == "Onboarded"),
ContainSupportedOS = countif(set_DeviceObj.OSDistribution has_any (containmentSupportedOS) and set_DeviceObj.OnboardingStatus == "Onboarded") by NetworkAddress | join kind=leftouter networks on NetworkAddress | join kind=leftouter networks2 on NetworkAddress | extend set_DeviceObj = array_concat(set_DeviceObj, set_DeviceObj1) | project-away NetworkAddress1, NetworkAddress2, set_DeviceObj1 | extend CountIPs = array_length(set_DeviceObj) | sort by CountIPs desc
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
InRange | eq |
| field:"InRange" kind:eq value:"1" |
NetworkAddress | ne |
| field:"NetworkAddress" kind:ne value:"169.254.0.0/16" |
Prefix | eq |
| field:"Prefix" kind:eq value:"32" |
Prefix | ne |
| field:"Prefix" kind:ne value:"32" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
IPAddresses.IPAddress | contains | : | excludes:IPAddresses.IPAddress field:"IPAddresses.IPAddress" value:":" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ContainSupportedOS | summarize |
IoTDevices | summarize |
IsolateSupportedOS | summarize |
NetworkAddress | summarize |
NetworkDevices | summarize |
NotOnboarded | summarize |
OTDevices | summarize |
Onboarded | summarize |
Servers | summarize |
Workstations | summarize |
set_DeviceObj | extend |
CountIPs | extend |
Infoblox - High Threat Level Query Not Blocked Detected
#'At least 1 high threat level query generated by single host in 1 hour that is not blocked or redirected. Customize query count, scheduling, responses and more. This rule depends on a parser based on a Kusto Function to work as expected called InfobloxCDC.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: dc7af829-d716-4774-9d6f-03d9aa7c27a4
name: Infoblox - High Threat Level Query Not Blocked Detected
description: |
'At least 1 high threat level query generated by single host in 1 hour that is not blocked or redirected. Customize query count, scheduling, responses and more. This rule depends on a parser based on a Kusto Function to work as expected called [**InfobloxCDC**](https://aka.ms/sentinel-InfobloxCloudDataConnector-parser).'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1498
- T1565
query: |
let threshold = 1;
InfobloxCDC
| where DeviceEventClassID has_cs "RPZ"
| where ThreatLevel_Score >=80
| where InfobloxB1PolicyAction == "Log" or SimplifiedDeviceAction == "PASSTHRU"
| summarize count() by SourceIP
| where count_ > threshold
| join kind=inner (InfobloxCDC
| where DeviceEventClassID has_cs "RPZ"
| where ThreatLevel_Score >=80
| where InfobloxB1PolicyAction == "Log" or SimplifiedDeviceAction == "PASSTHRU"
) on SourceIP
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DeviceName
- identifier: OSVersion
columnName: InfobloxB1SrcOSVersion
- identifier: FullName
columnName: SourceUserName
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DestinationDnsDomain
- entityType: Malware
fieldMappings:
- identifier: Name
columnName: ThreatProperty
- identifier: Category
columnName: ThreatClass
customDetails:
SourceMACAddress: SourceMACAddress
InfobloxB1FeedName: InfobloxB1FeedName
InfobloxB1Network: InfobloxB1Network
InfobloxB1Action: InfobloxB1PolicyAction
InfobloxB1PolicyName: InfobloxB1PolicyName
eventGroupingSettings:
aggregationKind: SingleAlert
incidentConfiguration:
createIncident: true
version: 1.0.4
kind: Scheduled
Stages and Predicates
Parameters
let threshold = 1;
Stage 1: source
InfobloxCDC
Stage 2: where
| where DeviceEventClassID has_cs "RPZ"
Stage 3: where
| where ThreatLevel_Score >=80
Stage 4: where
| where InfobloxB1PolicyAction == "Log" or SimplifiedDeviceAction == "PASSTHRU"
Stage 5: summarize
| summarize count() by SourceIP
Stage 6: where
| where count_ > threshold
Stage 7: join
| join kind=inner (InfobloxCDC
| where DeviceEventClassID has_cs "RPZ"
| where ThreatLevel_Score >=80
| where InfobloxB1PolicyAction == "Log" or SimplifiedDeviceAction == "PASSTHRU"
) on SourceIP
Indicators
These rows show field, operator, and value matches.
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SourceIP | summarize |
Infoblox - SOC Insight Detected - API Source
#'Infoblox SOC Insight detected in logs sourced via REST API. Customize scheduling, responses and more. This rule depends on a parser based on a Kusto Function to work as expected called InfobloxInsight.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: cf9847bb-ab46-4050-bb81-75cab3f893dc
name: Infoblox - SOC Insight Detected - API Source
description: |
'Infoblox SOC Insight detected in logs sourced via REST API. Customize scheduling, responses and more. This rule depends on a parser based on a Kusto Function to work as expected called [**InfobloxInsight**](https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Infoblox%20SOC%20Insights/Parsers/InfobloxInsight.yaml).'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: InfobloxSOCInsightsDataConnector_API
dataTypes:
- InfobloxInsight
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1498
- T1565
query: |
InfobloxInsight
| summarize arg_max(TimeGenerated, *) by InfobloxInsightID
entityMappings:
- entityType: SecurityGroup
fieldMappings:
- identifier: ObjectGuid
columnName: InfobloxInsightID
- entityType: Malware
fieldMappings:
- identifier: Name
columnName: ThreatClass
- identifier: Category
columnName: ThreatProperty
customDetails:
InfobloxInsightID: InfobloxInsightID
Severity: Priority
LastSeen: LastSeen
FirstSeen: FirstSeen
FeedSource: FeedSource
Status: Status
PersistentDate: PersistentDate
SpreadingDate: SpreadingDate
BlockedHits: BlockedCount
UnblockedHits: NotBlockedCount
TotalHits: EventsCount
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: 'Infoblox - SOC Insight - {{ThreatClass}} {{ThreatProperty}}'
alertDescriptionFormat: 'Observed via API. {{ThreatFamily}}. Last Observation: {{LastSeen}}'
alertSeverityColumnName: IncidentSeverity
incidentConfiguration:
createIncident: true
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
InfobloxInsight
Stage 2: summarize
| summarize arg_max(TimeGenerated, *) by InfobloxInsightID
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
InfobloxInsightID | summarize |
Pathlock TDnR - IBAN Change Documents
#Detects changes to IBAN (International Bank Account Number) records in SAP, forwarded by Pathlock Threat Detection and Response. Unauthorized IBAN modifications are a strong indicator of payment fraud or account takeover targeting financial transactions.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c11
name: Pathlock TDnR - IBAN Change Documents
kind: Scheduled
description: >-
Detects changes to IBAN (International Bank Account Number) records in SAP, forwarded by Pathlock
Threat Detection and Response. Unauthorized IBAN modifications are a strong indicator of
payment fraud or account takeover targeting financial transactions.
severity: High
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1565
query: |
Pathlock_TDnR_CL
| where DataSource == "CHANGEDOC_IBAN"
| 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 == "CHANGEDOC_IBAN"
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:"CHANGEDOC_IBAN" |
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 |
Pathlock TDnR - Vendor Change Documents
#Detects changes to vendor (creditor) master records in SAP, forwarded by Pathlock Threat Detection and Response. Unauthorized vendor modifications may indicate fraud targeting accounts payable processes, including payment redirection to attacker-controlled accounts.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c13
name: Pathlock TDnR - Vendor Change Documents
kind: Scheduled
description: >-
Detects changes to vendor (creditor) master records in SAP, forwarded by Pathlock Threat Detection
and Response. Unauthorized vendor modifications may indicate fraud targeting accounts payable
processes, including payment redirection to attacker-controlled accounts.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Pathlock_TDnR
dataTypes:
- Pathlock_TDnR_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1565
query: |
Pathlock_TDnR_CL
| where DataSource == "CHANGEDOC_KRED"
| 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 == "CHANGEDOC_KRED"
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:"CHANGEDOC_KRED" |
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 |
Radiflow - Exploit Detected
#'Generates an incident when the use of an exploit is detected by Radiflow's iSID.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Privilege Escalation | |
| Lateral Movement |
Rule Body
id: 6c028ebd-03ca-41cb-bce7-5727ddb43731
name: Radiflow - Exploit Detected
description: |
'Generates an incident when the use of an exploit is detected by Radiflow's iSID.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: RadiflowIsid
dataTypes:
- RadiflowEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- PrivilegeEscalation
- LateralMovement
relevantTechniques:
- T0819
- T0866
- T0890
query: |
RadiflowEvent
| where DeviceProduct =~ 'iSID'
| where
(
EventClassID in (34, 53, 67, 68, 69, 70, 71, 179)
or EventMessage has 'Exploit'
)
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 1h
matchingMethod: AllEntities
groupByEntities: []
groupByAlertDetails: []
groupByCustomDetails: []
eventGroupingSettings:
aggregationKind: AlertPerResult
alertDetailsOverride:
alertDisplayNameFormat: 'Exploit Detected: {{EventMessage}}'
alertDescriptionFormat: "A possible exploit attempt has been detected. Check the details of this incident for further information.\n\nMessage: {{EventMessage}}\nSource device: {{SourceIP}} \nDestination device (if any): {{DestinationIP}} "
alertSeverityColumnName: EventSeverity
alertDynamicProperties: []
suppressionEnabled: false
suppressionDuration: 5h
customDetails:
SourceMAC: SourceMACAddress
SourceType: SourceType
SourceVendor: SourceVendor
SourceVLAN: SourceVLAN
DestinationMAC: DestinationMACAddress
DestinationType: DestinationType
DestinationVendor: DestinationVendor
Port: Port
Protocol: Protocol
SourceHostName: SourceHostName
SourceIP: SourceIP
DestinationHostName: DestinationHostName
DestinationIP: DestinationIP
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SourceHostName
- identifier: NetBiosName
columnName: SourceHostName
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: DestinationHostName
- identifier: NetBiosName
columnName: DestinationHostName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DestinationIP
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
RadiflowEvent
Stage 2: where
| where DeviceProduct =~ 'iSID'
Stage 3: where
| where
(
EventClassID in (34, 53, 67, 68, 69, 70, 71, 179)
or EventMessage has 'Exploit'
)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DeviceProduct | eq |
| field:"DeviceProduct" kind:eq value:"iSID" |
EventClassID | in |
| field:"EventClassID" kind:in |
EventMessage | match |
| field:"EventMessage" kind:match value:"Exploit" |
SAP BTP - Cloud Integration tampering with security material
#Identifies operations on security material (credentials, certificates, and keys) within SAP Cloud Integration. This includes credentials (passwords/secrets), X.509 certificates and key pairs, and PGP keys. Unauthorized manipulation of security material could indicate an attacker attempting to: - Gain access to external systems using stored credentials - Intercept or tamper with encrypted communications - Establish persistence through certificate manipulation - Cover tracks by deleting security artifacts
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Stealth | |
| Credential Access |
Rule Body
id: 8d5f3a1b-9c2e-4f7d-b8a6-1e4c7f9d2b5a
kind: Scheduled
name: SAP BTP - Cloud Integration tampering with security material
description: |
Identifies operations on security material (credentials, certificates, and keys) within SAP Cloud Integration.
This includes credentials (passwords/secrets), X.509 certificates and key pairs, and PGP keys.
Unauthorized manipulation of security material could indicate an attacker attempting to:
- Gain access to external systems using stored credentials
- Intercept or tamper with encrypted communications
- Establish persistence through certificate manipulation
- Cover tracks by deleting security artifacts
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SAPBTPAuditEvents
dataTypes:
- SAPBTPAuditLog_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
- DefenseEvasion
relevantTechniques:
- T1552
- T1070
query: |
let securityMaterialTypes = dynamic(["Credential", "X.509 Certificate", "X.509 Key-Pair", "PGP Public Keys", "PGP Secret Keys"]);
let keystoreActions = dynamic(["Create", "Update", "Change", "Delete"]);
SAPBTPAuditLog_CL
| where Category == "audit.security-events"
| extend data_s = tostring(Message.data),
ipAddress = tostring(Message.ip)
| extend parsedData = parse_json(data_s)
| extend action = tostring(parsedData.action),
objectType = tostring(parsedData.objectType),
objectId = tostring(parsedData.objectId),
keystoreName = tostring(parsedData.attributes["Keystore Name"])
| where objectType in (securityMaterialTypes)
| where
(objectType == "Credential" and action in ("PasswordStore", "PasswordUpdate", "PasswordDelete"))
or
(objectType != "Credential" and action in (keystoreActions))
| extend normalizedAction = case(
action == "PasswordStore", "created",
action == "PasswordUpdate", "updated",
action == "PasswordDelete", "deleted",
action == "Create", "created",
action == "Update", "updated",
action == "Change", "changed",
action == "Delete", "deleted",
action
)
| extend MessageText = case(
objectType == "Credential", strcat("Security credential '", objectId, "' was ", normalizedAction),
isnotempty(keystoreName), strcat(objectType, " '", objectId, "' was ", normalizedAction, " in keystore '", keystoreName, "'"),
strcat(objectType, " '", objectId, "' was ", normalizedAction)
)
| project
UpdatedOn,
UserName,
MessageText,
ObjectType = objectType,
ObjectId = objectId,
Action = action,
NormalizedAction = normalizedAction,
KeystoreName = keystoreName,
Tenant,
ipAddress,
CloudApp = "SAP Cloud Integration"
| extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]
eventGroupingSettings:
aggregationKind: SingleAlert
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ipAddress
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: CloudApp
alertDetailsOverride:
alertDisplayNameFormat: 'SAP Cloud Integration: {{MessageText}}'
alertDescriptionFormat: |
{{MessageText}} by {{UserName}} from IP {{ipAddress}}.
This could indicate:
- Legitimate security material management
- Unauthorized credential or certificate manipulation
- Attacker tampering with security artifacts to gain access or cover tracks
customDetails:
ObjectType: ObjectType
ObjectId: ObjectId
Action: Action
KeystoreName: KeystoreName
SourceIP: ipAddress
version: 1.0.1
Stages and Predicates
Parameters
let securityMaterialTypes = dynamic(["Credential", "X.509 Certificate", "X.509 Key-Pair", "PGP Public Keys", "PGP Secret Keys"]);
let keystoreActions = dynamic(["Create", "Update", "Change", "Delete"]);
Stage 1: source
SAPBTPAuditLog_CL
Stage 2: where
| where Category == "audit.security-events"
Stage 3: extend (3 consecutive steps)
| extend data_s = tostring(Message.data),
ipAddress = tostring(Message.ip)
| extend parsedData = parse_json(data_s)
| extend action = tostring(parsedData.action),
objectType = tostring(parsedData.objectType),
objectId = tostring(parsedData.objectId),
keystoreName = tostring(parsedData.attributes["Keystore Name"])
Stage 4: where
| where objectType in (securityMaterialTypes)
Stage 5: where
| where
(objectType == "Credential" and action in ("PasswordStore", "PasswordUpdate", "PasswordDelete"))
or
(objectType != "Credential" and action in (keystoreActions))
Stage 6: extend
| extend normalizedAction = case(
action == "PasswordStore", "created",
action == "PasswordUpdate", "updated",
action == "PasswordDelete", "deleted",
action == "Create", "created",
action == "Update", "updated",
action == "Change", "changed",
action == "Delete", "deleted",
action
)
normalizedAction =action == "PasswordStore""created"action == "PasswordUpdate""updated"action == "PasswordDelete""deleted"action == "Create""created"action == "Update""updated"action == "Change""changed"action == "Delete""deleted"actionStage 7: extend
| extend MessageText = case(
objectType == "Credential", strcat("Security credential '", objectId, "' was ", normalizedAction),
isnotempty(keystoreName), strcat(objectType, " '", objectId, "' was ", normalizedAction, " in keystore '", keystoreName, "'"),
strcat(objectType, " '", objectId, "' was ", normalizedAction)
)
MessageText =objectType == "Credential"strcat("Security credential '", objectId, "' was ", normalizedAction)isnotempty(keystoreName)strcat(objectType, " '", objectId, "' was ", normalizedAction, " in keystore '", keystoreName, "'")strcat(objectType, " '", objectId, "' was ", normalizedAction)Stage 8: project
| project
UpdatedOn,
UserName,
MessageText,
ObjectType = objectType,
ObjectId = objectId,
Action = action,
NormalizedAction = normalizedAction,
KeystoreName = keystoreName,
Tenant,
ipAddress,
CloudApp = "SAP Cloud Integration"
Stage 9: extend
| extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Category | eq |
| field:"Category" kind:eq value:"audit.security-events" |
action | in |
| field:"action" kind:in |
objectType | eq |
| field:"objectType" kind:eq value:"Credential" |
objectType | in |
| field:"objectType" kind:in |
objectType | ne |
| field:"objectType" kind:ne value:"Credential" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Action | project |
CloudApp | project |
KeystoreName | project |
MessageText | project |
NormalizedAction | project |
ObjectId | project |
ObjectType | project |
Tenant | project |
UpdatedOn | project |
UserName | project |
ipAddress | project |
AccountName | extend |
UPNSuffix | extend |
SUNBURST network beacons
#Identifies SolarWinds SUNBURST domain beacon IOCs in DeviceNetworkEvents References: - https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html - https://gist.github.com/olafhartong/71ffdd4cab4b6acd5cbcd1a0691ff82f
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceNetworkEvents action ConnectionSuccess: Connection succeeded |
Rule Body
id: ce1e7025-866c-41f3-9b08-ec170e05e73e
name: SUNBURST network beacons
description: |
Identifies SolarWinds SUNBURST domain beacon IOCs in DeviceNetworkEvents
References:
- https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html
- https://gist.github.com/olafhartong/71ffdd4cab4b6acd5cbcd1a0691ff82f
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceNetworkEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Execution
- Persistence
- InitialAccess
relevantTechniques:
- T1195
- T1059
- T1546
query: |
let SunburstURL=dynamic(["panhardware.com","databasegalore.com","avsvmcloud.com","freescanonline.com","thedoccloud.com","deftsecurity.com"]);
DeviceNetworkEvents
| where ActionType == "ConnectionSuccess"
| where RemoteUrl in(SunburstURL)
| extend HashAlgorithm = 'MD5'
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingProcessAccountUpn
- identifier: Name
columnName: InitiatingProcessAccountName
- identifier: UPNSuffix
columnName: InitiatingProcessAccountDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: RemoteIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: RemoteUrl
- entityType: FileHash
fieldMappings:
- identifier: Algorithm
columnName: HashAlgorithm
- identifier: Value
columnName: InitiatingProcessMD5
version: 1.0.5
kind: Scheduled
Stages and Predicates
Parameters
let SunburstURL = dynamic(["panhardware.com","databasegalore.com","avsvmcloud.com","freescanonline.com","thedoccloud.com","deftsecurity.com"]);
Stage 1: source
DeviceNetworkEvents
Stage 2: where
| where ActionType == "ConnectionSuccess"
Stage 3: where
| where RemoteUrl in(SunburstURL)
Stage 4: extend (3 consecutive steps)
| extend HashAlgorithm = 'MD5'
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ConnectionSuccess" |
RemoteUrl | in |
| field:"DestinationHostname" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
HashAlgorithm | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | 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: 4b5a7f32-899d-4d22-8de2-0ec90b911a72
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:
- ThreatIntelIndicators
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelIndicators
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: AzureActiveDirectory
dataTypes:
- AADNonInteractiveUserSignInLogs
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelIndicators
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);
ThreatIntelIndicators
//Filtering the table for Email related IOCs
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "email-addr"
| extend EmailSenderAddress = ObservableValue
| extend EmailSourceDomain = substring(EmailSenderAddress, indexof(EmailSenderAddress, "@") + 1, strlen(EmailSenderAddress) - indexof(EmailSenderAddress, "@") - 1)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend Id = tostring(split(Id, "--")[2])
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
| where TimeGenerated >= ago(ioc_lookBack)
| where EmailSenderAddress in (SigninUPNs)
| 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 *, TrafficLightProtocolLevel, EmailSenderAddress, EmailSourceDomain, Type
| join kind=innerunique (Signins) on $left.EmailSenderAddress == $right.UserPrincipalName
| where SigninLogs_TimeGenerated < ValidUntil
| summarize SigninLogs_TimeGenerated = arg_max(SigninLogs_TimeGenerated, *) by Id, UserPrincipalName
| extend Description = tostring(parse_json(Data).description)
| extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
| project SigninLogs_TimeGenerated, Description, ActivityGroupNames, Id, ValidUntil, Confidence, EmailSenderAddress, IPAddress, UserPrincipalName, AppDisplayName, StatusCode, StatusDetails, NetworkLocationDetails, Type, Url
| 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.12
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
ThreatIntelIndicators
Stage 2: extend
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
Stage 3: where
| where IndicatorType == "email-addr"
Stage 4: extend (5 consecutive steps)
| extend EmailSenderAddress = ObservableValue
| extend EmailSourceDomain = substring(EmailSenderAddress, indexof(EmailSenderAddress, "@") + 1, strlen(EmailSenderAddress) - indexof(EmailSenderAddress, "@") - 1)
| extend TrafficLightProtocolLevel = tostring(parse_json(AdditionalFields).TLPLevel)
| extend Id = tostring(split(Id, "--")[2])
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
Stage 5: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 6: where
| where EmailSenderAddress in (SigninUPNs)
Stage 7: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
Stage 8: where
| where IsActive and (ValidUntil > now() or isempty(ValidUntil))
Stage 9: extend
| extend Description = tostring(parse_json(Data).description)
Stage 10: where
| where Description !contains_cs "State: inactive;" and Description !contains_cs "State: falsepos;"
Stage 11: project-reorder
| project-reorder *, TrafficLightProtocolLevel, EmailSenderAddress, EmailSourceDomain, Type
Stage 12: join
join kind=innerunique (Signins) on $left.EmailSenderAddress, $right.UserPrincipalName
Stage 13: where
where SigninLogs_TimeGenerated < ValidUntil
Stage 14: summarize
summarize SigninLogs_TimeGenerated by Id, UserPrincipalName
Stage 15: extend
extend Description
Stage 16: extend
extend ActivityGroupNames
Stage 17: project
project ActivityGroupNames, AppDisplayName, Confidence, Description, EmailSenderAddress, IPAddress, Id, NetworkLocationDetails, SigninLogs_TimeGenerated, StatusCode, StatusDetails, Type, Url, UserPrincipalName, ValidUntil
Stage 18: extend
extend Name, UPNSuffix
Stage 19: extend
extend timestamp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EmailSenderAddress | in |
| field:"ObservableValue" kind:in value:"SigninUPNs" |
IndicatorType | eq |
| field:"IndicatorType" kind:eq value:"email-addr" |
IsActive | eq |
| field:"IsActive" kind:eq value:"true" |
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-.]+$" |
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 |
AppDisplayName | project |
Confidence | project |
Description | project |
EmailSenderAddress | project |
IPAddress | project |
Id | project |
NetworkLocationDetails | project |
SigninLogs_TimeGenerated | project |
StatusCode | project |
StatusDetails | project |
Type | project |
Url | project |
UserPrincipalName | project |
ValidUntil | project |
Name | extend |
UPNSuffix | extend |
timestamp | extend |
TIE DCShadow
#'Searches for DCShadow attacks.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: 874e3530-552e-437b-ba2e-227979e7e43c
name: TIE DCShadow
description: |
'Searches for DCShadow attacks.'
severity: High
requiredDataConnectors:
- connectorId: TenableIE
dataTypes:
- Tenable_IE_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1207
query: |
// For the query to work properly, make sure you have imported the afad_parser.yaml parser into the workspace
// Retrieve the parser here: https://aka.ms/sentinel-TenableApp-afad-parser
// Then, create the Kusto Function with alias afad_parser
afad_parser
| where MessageType == 2 and Codename == "DCShadow"
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
afad_parser
Stage 2: where
| where MessageType == 2 and Codename == "DCShadow"
Stage 3: extend
| extend HostName = tostring(split(Host, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Codename | eq |
| field:"Codename" kind:eq value:"DCShadow" |
MessageType | eq |
| field:"MessageType" kind:eq value:"2" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | extend |
Unauthenticated API Endpoint with Sensitive Data
#Detects internet-facing API endpoints with no authentication that handle sensitive data classifications (PII, Financial, PHI, Confidential). Uses StratoSecure_ApiInventory_CL from Phase 6 API inventory discovery.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 479bc6ef-ecb3-496f-9a2a-38c9635d77d3
name: Unauthenticated API Endpoint with Sensitive Data
description: |
Detects internet-facing API endpoints with no authentication that handle
sensitive data classifications (PII, Financial, PHI, Confidential).
Uses StratoSecure_ApiInventory_CL from Phase 6 API inventory discovery.
severity: High
status: Available
requiredDataConnectors:
- connectorId: StratoSecurePush
dataTypes:
- StratoSecure_ApiInventory_CL
queryFrequency: 6h
queryPeriod: 375m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1190
query: |
let sensitiveDataClasses = dynamic(["PII", "Financial", "PHI", "Confidential"]);
StratoSecure_ApiInventory_CL
| where TimeGenerated >= ago(6h)
| where InternetExposure == true
| where AuthType =~ "none" or isempty(AuthType)
| where DataClassification has_any (sensitiveDataClasses)
| summarize EndpointCount = count(), Endpoints = make_set(Path, 50),
Methods = make_set(Method), DataClasses = make_set(DataClassification),
EndTime = max(TimeGenerated)
by ApplicationId = ServiceName, StraTenantId
| project TimeGenerated = EndTime, ApplicationId, StraTenantId,
EndpointCount, Endpoints, Methods, DataClasses
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: ApplicationId
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let sensitiveDataClasses = dynamic(["PII", "Financial", "PHI", "Confidential"]);
Stage 1: source
StratoSecure_ApiInventory_CL
Stage 2: where
| where TimeGenerated >= ago(6h)
Stage 3: where
| where InternetExposure == true
Stage 4: where
| where AuthType =~ "none" or isempty(AuthType)
Stage 5: where
| where DataClassification has_any (sensitiveDataClasses)
Stage 6: summarize
| summarize EndpointCount = count(), Endpoints = make_set(Path, 50),
Methods = make_set(Method), DataClasses = make_set(DataClassification),
EndTime = max(TimeGenerated)
by ApplicationId = ServiceName, StraTenantId
Stage 7: project
| project TimeGenerated = EndTime, ApplicationId, StraTenantId,
EndpointCount, Endpoints, Methods, DataClasses
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AuthType | eq |
| field:"AuthType" kind:eq value:"none" |
AuthType | is_null | field:"AuthType" kind:is_null | |
DataClassification | match |
| field:"DataClassification" kind:match |
InternetExposure | eq |
| field:"InternetExposure" kind:eq value:"true" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ApplicationId | project |
DataClasses | project |
EndpointCount | project |
Endpoints | project |
Methods | project |
StraTenantId | project |
TimeGenerated | project |
VMware ESXi - SSH Enable on ESXi Host
#'Detects when vim-cmd is used to enable SSH on an ESXi host'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
Rule Body
id: 4f5b5d79-ff4e-4edd-ae3e-9e1187f0b924
name: VMware ESXi - SSH Enable on ESXi Host
description: |
'Detects when vim-cmd is used to enable SSH on an ESXi host'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
relevantTechniques:
- T1021
query: |
VMwareESXi
| where SyslogMessage has_all ("vim-cmd","hostsvc/enable_ssh")
| extend SrcUsername = extract(@'\[info\]\s+\[(.*?)\]', 1, SyslogMessage)
| extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DstHostname
- entityType: Account
fieldMappings:
- identifier: Name
columnName: SrcUsername
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
VMwareESXi
Stage 2: where
| where SyslogMessage has_all ("vim-cmd","hostsvc/enable_ssh")
Stage 3: extend
| extend SrcUsername = extract(@'\[info\]\s+\[(.*?)\]', 1, SyslogMessage)
Stage 4: extend
| extend DstHostname = extract(@'\[\d+\]\s+\[(.*?)\s+on', 1, SyslogMessage)
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
SyslogMessage | match |
| field:"SyslogMessage" kind:match |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcUsername | extend |
DstHostname | extend |