Detection rules › Kusto
Kusto rules: 1r
AWSCloudTrail - Creation of Access Key for IAM User
#Detects creation of a new IAM access key on an existing user account, which could be used to establish persistence. This action should be validated by the AWS account administrator. Reference: https://stratus-red-team.cloud/attack-techniques/AWS/aws.persistence.iam-backdoor-user/
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Telemetry Coverage
Rules Detecting the Same Action
These rules filter on the same operation.
- ASL AWS Create Access Key (Splunk)
- AWS CreateAccessKey (Splunk)
- AWS Lateral Movement from Kubernetes SA via AssumeRoleWithWebIdentity (Elastic)
- New IAM Credentials Updated (Panther)
- Root Account Access Key Created (Panther)
- User impersonation by Identity Protection alerts (Kusto)
Rule Body
id: 9a6554e6-63d9-4f94-9b32-64d1d40628f2
name: AWSCloudTrail - Creation of Access Key for IAM User
description: |
Detects creation of a new IAM access key on an existing user account, which could be used to establish persistence. This action should be validated by the AWS account administrator. Reference: https://stratus-red-team.cloud/attack-techniques/AWS/aws.persistence.iam-backdoor-user/
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: AWS
dataTypes:
- AWSCloudTrail
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1098.001
query: |
AWSCloudTrail
| where EventName == "CreateAccessKey"
| project-away SourceSystem,Category,Type,TenantId,EventVersion,SessionIssuerAccountId
| 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: SourceIpAddress
customDetails:
EventName: EventName
AWSRegion: AWSRegion
UserAgent: UserAgent
alertDetailsOverride:
alertDisplayNameFormat: IAM access key created by {{Name}} in {{AWSRegion}}
alertDescriptionFormat: User {{Name}} (principal {{UserIdentityPrincipalid}}) created a new IAM access key in region {{AWSRegion}}.
version: 1.0.2
kind: Scheduled
Stages and Predicates
Stage 1: source
AWSCloudTrail
Stage 2: where
| where EventName == "CreateAccessKey"
Stage 3: project-away
| project-away SourceSystem,Category,Type,TenantId,EventVersion,SessionIssuerAccountId
Stage 4: extend
| extend UserName = substring(UserIdentityPrincipalid, indexof_regex(UserIdentityPrincipalid, ":") + 1)
Stage 5: extend
| extend Name = split(UserName,'@')[0],UpnSuffix = split(UserName,'@')[1]
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventName | eq |
| field:"aws::eventName" kind:eq value:"CreateAccessKey" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
UserName | extend |
Name | extend |
UpnSuffix | extend |
Encryption Password Changed
#Detects when an encryption password is updated in Veeam Backup & Replication.
Rule Body
id: 58fcb721-35ec-4ea2-9f29-c99acde676b8
name: Encryption Password Changed
description: Detects when an encryption password is updated in Veeam Backup & Replication.
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 == 31700\n| extend Hint = extract(\"\
Hint=\\\"([^\\\"]*)\\\"\", 1, SyslogMessage)\n| project\n Date = format_datetime(TimeGenerated,\
\ 'dd.MM.yyyy HH:mm'),\n DataSource = original_host,\n EventId = instanceId,\n\
\ [\"User Name\"] = user,\n [\"Hint\"] = Hint,\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 == 31700
Stage 3: extend
| extend Hint = extract("Hint=\"([^\"]*)\"", 1, SyslogMessage)
Stage 4: project
| project
Date = format_datetime(TimeGenerated, 'dd.MM.yyyy HH:mm'),
DataSource = original_host,
EventId = instanceId,
["User Name"] = user,
["Hint"] = Hint,
MessageDetails = Description,
Severity = SeverityDescription
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
instanceId | eq |
| field:"instanceId" kind:eq value:"31700" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DataSource | project |
Date | project |
EventId | project |
Hint | project |
MessageDetails | project |
Severity | project |
User Name | project |
GitLab - Local Auth - No MFA
#'This query checks GitLab Audit Logs to see if a user authenticated without MFA. Ot might mean that MFA was disabled for the GitLab server or that an external authentication provider was bypassed. This rule focuses on 'admin' privileges but the parameter can be adapted to also include all users.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: e0b45487-5c79-482d-8ac0-695de8c031af
name: GitLab - Local Auth - No MFA
description: |
'This query checks GitLab Audit Logs to see if a user authenticated without MFA. Ot might mean that MFA was disabled for the GitLab server or that an external authentication provider was bypassed. This rule focuses on 'admin' privileges but the parameter can be adapted to also include all users.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
dataTypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
query: |
let isAdmin = true;
GitLabAudit
| where AuthenticationType == "standard" and ((isAdmin and TargetDetails contains "Administrator") or (isAdmin==false));
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AuthorUserName
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let isAdmin = true;
Stage 1: source
GitLabAudit
Stage 2: where
| where AuthenticationType == "standard" and ((isAdmin and TargetDetails contains "Administrator") or (isAdmin==false));
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AuthenticationType | eq |
| field:"AuthenticationType" kind:eq value:"standard" |
TargetDetails | contains |
| field:"TargetDetails" kind:contains value:"Administrator" |
Guardian- URL Reachability Policy Violation Detection
#'This alert creates an incident when URL Reachability Policy Violation detected from the Guardian.'
Rule Body
id: 93a56919-105a-4ffe-9a8c-4ef6d0b101ac
name: Guardian- URL Reachability Policy Violation Detection
description: |
'This alert creates an incident when URL Reachability Policy Violation detected from the Guardian.'
severity: Informational
status: Available
requiredDataConnectors:
- connectorId: BoschAIShield
dataTypes:
- Guardian
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: []
relevantTechniques: []
query: |
Guardian
| where PolicyViolatedControlFeature =~ 'URL Reachability'
| where Severity =~ 'Informational'
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- URL Reachability Policy Violation detection
alertDescriptionFormat: |
This query detects URL Reachability 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 =~ 'URL Reachability'
Stage 3: where
| where Severity =~ 'Informational'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
PolicyViolatedControlFeature | eq |
| field:"PolicyViolatedControlFeature" kind:eq value:"URL Reachability" |
Severity | eq |
| field:"Severity" kind:eq value:"Informational" |
M2131_LogRetentionLessThan1Year
#'This alert is designed to monitor log retention within the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when a log analytics workspace in active storage is configured for less than 1 year.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Discovery |
Rule Body
id: 8178a514-1270-4e31-a1d9-aaafeb40122f
name: M2131_LogRetentionLessThan1Year
description: |
'This alert is designed to monitor log retention within the Maturity Model for Event Log Management (M-21-31) standard. The alert triggers when a log analytics workspace in active storage is configured for less than 1 year.'
severity: Medium
status: Available
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1082
query: |
SecurityRecommendation
| summarize arg_max(TimeGenerated, *) by RecommendationDisplayName, AssessedResourceId
| where RecommendationDisplayName == "Activity log should be retained for at least one year"
| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by RecommendationDisplayName, AssessedResourceId
| extend PassedControls = (Passed/todouble(Total))*100
| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
| project RecommendationDisplayName, AssessedResourceId, Total, PassedControls, Passed, Failed, RemediationLink, LastObserved=now()
| where Failed > 0
| sort by Failed desc
| extend URLCustomEntity = RemediationLink, ResourceId = AssessedResourceId
entityMappings:
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URLCustomEntity
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: AssessedResourceId
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SecurityRecommendation
Stage 2: summarize
| summarize arg_max(TimeGenerated, *) by RecommendationDisplayName, AssessedResourceId
Stage 3: where
| where RecommendationDisplayName == "Activity log should be retained for at least one year"
Stage 4: summarize
| summarize Failed = countif(RecommendationState == "Unhealthy"), Passed = countif(RecommendationState == "Healthy"), Total = countif(RecommendationState == "Healthy" or RecommendationState == "Unhealthy") by RecommendationDisplayName, AssessedResourceId
Stage 5: extend
| extend PassedControls = (Passed/todouble(Total))*100
Stage 6: extend
| extend RemediationLink = strcat('https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/22')
Stage 7: project
| project RecommendationDisplayName, AssessedResourceId, Total, PassedControls, Passed, Failed, RemediationLink, LastObserved=now()
Stage 8: where
| where Failed > 0
Stage 9: sort
| sort by Failed desc
Stage 10: extend
| extend URLCustomEntity = RemediationLink, ResourceId = AssessedResourceId
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Failed | gt |
| field:"Failed" kind:gt value:"0" |
RecommendationDisplayName | eq |
| field:"RecommendationDisplayName" kind:eq value:"Activity log should be retained for at least one year" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AssessedResourceId | project |
Failed | project |
LastObserved | project |
Passed | project |
PassedControls | project |
RecommendationDisplayName | project |
RemediationLink | project |
Total | project |
ResourceId | extend |
URLCustomEntity | extend |
OracleDBAudit - Unusual user activity on multiple tables
#'Detects when user queries many tables in short period of time.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Collection |
Rule Body
id: 75024e1c-26e7-4e73-821d-95e5decdd8db
name: OracleDBAudit - Unusual user activity on multiple tables
description: |
'Detects when user queries many tables in short period of time.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
relevantTechniques:
- T1119
query: |
let tbl_threshold = 10;
OracleDatabaseAuditEvent
| where isnotempty(DstUserName)
| where DbAction =~ 'SELECT'
| extend TableName = replace(@'[,\(\)]', '', extract(@'(?i)SELECT(.*?)FROM\s(.*?)\s', 2, Action))
| where isnotempty(TableName)
| where TableName !~ 'SELECT'
| summarize tbl_count = dcount(TableName) by DstUserName, bucket = bin(TimeGenerated, 5m)
| where tbl_count > tbl_threshold
| extend AccountCustomEntity = DstUserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.0.2
kind: Scheduled
Stages and Predicates
Parameters
let tbl_threshold = 10;
Stage 1: source
OracleDatabaseAuditEvent
Stage 2: where
| where isnotempty(DstUserName)
Stage 3: where
| where DbAction =~ 'SELECT'
Stage 4: extend
| extend TableName = replace(@'[,\(\)]', '', extract(@'(?i)SELECT(.*?)FROM\s(.*?)\s', 2, Action))
Stage 5: where
| where isnotempty(TableName)
Stage 6: where
| where TableName !~ 'SELECT'
Stage 7: summarize
| summarize tbl_count = dcount(TableName) by DstUserName, bucket = bin(TimeGenerated, 5m)
Stage 8: where
| where tbl_count > tbl_threshold
Stage 9: extend
| extend AccountCustomEntity = DstUserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
DbAction | eq |
| field:"DbAction" kind:eq value:"SELECT" |
DstUserName | is_not_null | field:"DstUserName" kind:is_not_null | |
TableName | is_not_null | field:"TableName" kind:is_not_null | |
TableName | ne |
| field:"TableName" kind:ne value:"SELECT" |
tbl_count | gt |
| field:"tbl_count" kind:gt value:"10" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DstUserName | summarize |
bucket | summarize |
tbl_count | summarize |
AccountCustomEntity | extend |
Palo Alto Prisma Cloud - High risk score alert
#'Detects alerts with high risk score value.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 617b02d8-0f47-4f3c-afed-1926a45e7b28
name: Palo Alto Prisma Cloud - High risk score alert
description: |
'Detects alerts with high risk score value.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: PaloAltoPrismaCloud
dataTypes:
- PaloAltoPrismaCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1133
query: |
PaloAltoPrismaCloud
| where Reason =~ 'NEW_ALERT'
| where Status =~ 'open'
| extend r_score = 0.85 * toint(RiskDetailRiskScoreMaxScore)
| extend i_RiskDetailRiskScoreScore = toint(RiskDetailRiskScoreScore)
| where i_RiskDetailRiskScoreScore > r_score
| extend AccountCustomEntity = UserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
PaloAltoPrismaCloud
Stage 2: where
| where Reason =~ 'NEW_ALERT'
Stage 3: where
| where Status =~ 'open'
Stage 4: extend
| extend r_score = 0.85 * toint(RiskDetailRiskScoreMaxScore)
Stage 5: extend
| extend i_RiskDetailRiskScoreScore = toint(RiskDetailRiskScoreScore)
Stage 6: where
| where i_RiskDetailRiskScoreScore > r_score
Stage 7: extend
| extend AccountCustomEntity = UserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Reason | eq |
| field:"Reason" kind:eq value:"NEW_ALERT" |
Status | eq |
| field:"Status" kind:eq value:"open" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
r_score | extend |
i_RiskDetailRiskScoreScore | extend |
AccountCustomEntity | extend |
Palo Alto Prisma Cloud - Network ACLs Inbound rule to allow All Traffic
#'Detects Network ACLs with Inbound rule to allow All Traffic.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 6098b34a-1e6b-440a-9e3b-fb4d5944ade1
name: Palo Alto Prisma Cloud - Network ACLs Inbound rule to allow All Traffic
description: |
'Detects Network ACLs with Inbound rule to allow All Traffic.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: PaloAltoPrismaCloud
dataTypes:
- PaloAltoPrismaCloud
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1133
query: |
PaloAltoPrismaCloud
| where Reason =~ 'NEW_ALERT'
| where Status =~ 'open'
| where AlertMessage has 'Network ACLs with Inbound rule to allow All Traffic'
| extend AccountCustomEntity = UserName
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.1
kind: Scheduled
Stages and Predicates
Stage 1: source
PaloAltoPrismaCloud
Stage 2: where
| where Reason =~ 'NEW_ALERT'
Stage 3: where
| where Status =~ 'open'
Stage 4: where
| where AlertMessage has 'Network ACLs with Inbound rule to allow All Traffic'
Stage 5: extend
| extend AccountCustomEntity = UserName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AlertMessage | match |
| field:"AlertMessage" kind:match value:"Network ACLs with Inbound rule to allow All Traffic" |
Reason | eq |
| field:"Reason" kind:eq value:"NEW_ALERT" |
Status | eq |
| field:"Status" kind:eq value:"open" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountCustomEntity | extend |
Pathlock TDnR - Payment Request Changes
#Detects changes to payment requests in SAP, forwarded by Pathlock Threat Detection and Response. Unauthorized modifications to payment requests may indicate financial fraud, duplicate payment schemes, or manipulation of treasury processes.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: 2a3b4c5d-6e7f-4a0b-8c1d-2e3f4a5b6c14
name: Pathlock TDnR - Payment Request Changes
kind: Scheduled
description: >-
Detects changes to payment requests in SAP, forwarded by Pathlock Threat Detection and Response.
Unauthorized modifications to payment requests may indicate financial fraud, duplicate payment
schemes, or manipulation of treasury processes.
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_PAYRQ"
| 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_PAYRQ"
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_PAYRQ" |
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 |
Stale last password change
#'This query searches for stale last password change'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 645A8724-5C7E-4A1F-81CB-C33AFF1439EB
name: Stale last password change
description: |
'This query searches for stale last password change'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: SenservaPro
dataTypes:
- SenservaPro_CL
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
SenservaPro_CL
| where ControlName_s == 'UserStaleLastPasswordChange'
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: ControlName_s
- identifier: AadTenantId
columnName: TenantId
- identifier: DisplayName
columnName: TenantDisplayName_s
- entityType: SecurityGroup
fieldMappings:
- identifier: DistinguishedName
columnName: Group_s
- entityType: AzureResource
fieldMappings:
- identifier: ResourceId
columnName: SourceSystem
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
SenservaPro_CL
Stage 2: where
| where ControlName_s == 'UserStaleLastPasswordChange'
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ControlName_s | eq |
| field:"ControlName_s" kind:eq value:"UserStaleLastPasswordChange" |
Tenable.ad DCSync
#'Searches for DCSync attacks.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule Body
id: 0c8d4de3-adb9-4161-a863-aa1e2c8bd959
name: Tenable.ad DCSync
description: |
'Searches for DCSync attacks.'
severity: High
requiredDataConnectors:
- connectorId: Tenable.ad
dataTypes:
- Tenable_ad_CL
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1003.006
query: |
// For the query to work properly, make sure you have imported the afad_parser.kql parser into the workspace
// Retrieve the parser here: https://raw.githubusercontent.com/tenable/Azure-Sentinel/Tenable.ad-connector/Solutions/TenableAD/Parsers/afad_parser.kql
// Then, create the Kusto Function with alias afad_parser
afad_parser
| where MessageType == 2 and Codename == "DCSync"
| 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 == "DCSync"
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:"DCSync" |
MessageType | eq |
| field:"MessageType" kind:eq value:"2" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DnsDomain | extend |
HostName | 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: a0038239-72f4-4f7b-90ff-37f89f7881e0
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: AzureActiveDirectory
dataTypes:
- EmailUrlInfo
- connectorId: ThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceTaxii
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: MicrosoftDefenderThreatIntelligence
dataTypes:
- ThreatIntelligenceIndicator
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let EmailUrlInfo_ = materialize(EmailUrlInfo
| where isnotempty(Url)
| where TimeGenerated >= ago(dt_lookBack)
| extend Url = tolower(Url)
| 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(ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack)
| where (isnotempty(Url) or isnotempty(DomainName))
| where tolower(Url) in (EmailUrls) or tolower(DomainName) in (EmailUrlDomains)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now());
(union
(TI | join kind=innerunique (EmailUrlInfo_) on Url),
(TI | join kind=innerunique (EmailUrlInfo_) on $left.DomainName == $right.UrlDomain))
| where EmailUrlInfo_TimeGenerated < ExpirationDateTime
| summarize EmailUrlInfo_TimeGenerated = arg_max(EmailUrlInfo_TimeGenerated, *) by IndicatorId, Url
| project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, 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.2
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Let Binding: EmailUrlInfo_
let EmailUrlInfo_ = materialize(EmailUrlInfo
| where isnotempty(Url)
| where TimeGenerated >= ago(dt_lookBack)
| extend Url = tolower(Url)
| 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.DomainName == $right.UrlDomain
Applied to the Combined Result
| where EmailUrlInfo_TimeGenerated < ExpirationDateTime
| summarize EmailUrlInfo_TimeGenerated = arg_max(EmailUrlInfo_TimeGenerated, *) by IndicatorId, Url
| project EmailUrlInfo_TimeGenerated, Description, ActivityGroupNames, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, 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 |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
DomainName | in |
| field:"DomainName" kind:in value:"EmailUrlDomains" |
DomainName | is_not_null | field:"DomainName" kind:is_not_null | |
Url | in |
| field:"Url" kind:in value:"EmailUrls" |
Url | is_not_null | field:"Url" kind:is_not_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 |
ConfidenceScore | project |
Description | project |
EmailUrlInfo_TimeGenerated | project |
ExpirationDateTime | project |
IndicatorId | project |
NetworkMessageId | project |
ThreatType | project |
Url | project |
UrlDomain | project |
UrlLocation | project |
timestamp | extend |
Name | extend |
UPNSuffix | extend |
UniFi Site Manager: IPS signature count dropped >50%
#Identifies when the IPS signature ruleset count drops by more than half versus the 7-day maximum, indicating broken threat-intel feeds or rollback.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Rule Body
id: aa188a24-783a-76a1-cd11-3bcac0e97de9
name: "UniFi Site Manager: IPS signature count dropped >50%"
description: |
Identifies when the IPS signature ruleset count drops by more than half versus the 7-day maximum, indicating broken threat-intel feeds or rollback.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: UniFiSiteManagerConnectorDefinition
dataTypes:
- Unifi_SiteManager_Sites_CL
queryFrequency: 6h
queryPeriod: 7d
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
subTechniques:
- T1562.001
query: |
Unifi_SiteManager_Sites_CL
| where TimeGenerated > ago(7d)
| extend rulesCount = toint(SiteStatistics.gateway.ipsSignature.rulesCount)
| where isnotnull(rulesCount)
| summarize MaxCount = max(rulesCount), arg_max(TimeGenerated, rulesCount) by SiteId, SiteName
| where MaxCount > 100 and toreal(rulesCount) / MaxCount < 0.5
| extend DropPct = round(100.0 * (1 - toreal(rulesCount) / MaxCount), 1)
| extend Activity = strcat('IPS rules dropped to ', rulesCount, ' (from ', MaxCount, ', -', DropPct, '%)')
| project TimeGenerated, SiteId, SiteName, Activity, CurrentRules = rulesCount, PeakRules = MaxCount, DropPct
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SiteId
- identifier: DnsDomain
columnName: SiteName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT12H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.1
Stages and Predicates
Stage 1: source
Unifi_SiteManager_Sites_CL
Stage 2: where
| where TimeGenerated > ago(7d)
Stage 3: extend
| extend rulesCount = toint(SiteStatistics.gateway.ipsSignature.rulesCount)
Stage 4: where
| where isnotnull(rulesCount)
Stage 5: summarize
| summarize MaxCount = max(rulesCount), arg_max(TimeGenerated, rulesCount) by SiteId, SiteName
Stage 6: where
| where MaxCount > 100 and toreal(rulesCount) / MaxCount < 0.5
Stage 7: extend
| extend DropPct = round(100.0 * (1 - toreal(rulesCount) / MaxCount), 1)
Stage 8: extend
| extend Activity = strcat('IPS rules dropped to ', rulesCount, ' (from ', MaxCount, ', -', DropPct, '%)')
Stage 9: project
| project TimeGenerated, SiteId, SiteName, Activity, CurrentRules = rulesCount, PeakRules = MaxCount, DropPct
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
MaxCount | gt |
| field:"MaxCount" kind:gt value:"100" |
rulesCount | is_not_null | field:"rulesCount" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Activity | project |
CurrentRules | project |
DropPct | project |
PeakRules | project |
SiteId | project |
SiteName | project |
TimeGenerated | project |