Detection rules › Kusto
Kusto rules: 37
Bitglass - User Agent string has changed for user
#'Detects when User Agent string has changed for user.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 4dd61530-859f-49e7-bd27-a173cb1a4589
name: Bitglass - User Agent string has changed for user
description: |
'Detects when User Agent string has changed for user.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: Bitglass
dataTypes:
- Bitglass
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1078
query: |
Bitglass
| where EventType =~ 'access'
| where EventMessage =~ 'Login'
| summarize ua = makeset(HttpUserAgent) by User
| join (Bitglass
| where EventType =~ 'access'
| where EventMessage =~ 'Login') on User
| where ua !contains HttpUserAgent
| extend AccountCustomEntity = User
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountCustomEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
Bitglass
Stage 2: where
| where EventType =~ 'access'
Stage 3: where
| where EventMessage =~ 'Login'
Stage 4: summarize
| summarize ua = makeset(HttpUserAgent) by User
Stage 5: join
| join (Bitglass
| where EventType =~ 'access'
| where EventMessage =~ 'Login') on User
Stage 6: where
where ua !contains HttpUserAgent
Stage 7: extend
extend AccountCustomEntity
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventMessage | eq |
| field:"EventMessage" kind:eq value:"Login" |
EventType | eq |
| field:"EventType" kind:eq value:"access" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
User | summarize |
ua | summarize |
AccountCustomEntity | extend |
BOLA Finding with API Traffic Anomaly
#Detects applications with open BOLA/IDOR findings (OWASP API1, API3, API5) that also show anomalous 4xx error traffic in Azure API Management. Correlates StratoSecure BOLA findings with APIM gateway error spikes.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Discovery |
Rule Body
id: 3c5be96e-2eeb-439a-9856-1c23aa72cfdd
name: BOLA Finding with API Traffic Anomaly
description: |
Detects applications with open BOLA/IDOR findings (OWASP API1, API3, API5)
that also show anomalous 4xx error traffic in Azure API Management.
Correlates StratoSecure BOLA findings with APIM gateway error spikes.
severity: High
status: Available
requiredDataConnectors:
- connectorId: StratoSecurePush
dataTypes:
- StratoSecure_Findings_CL
queryFrequency: 1h
queryPeriod: 75m
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Discovery
relevantTechniques:
- T1190
- T1083
query: |
let lookback = 1h;
let enumThreshold = 100;
let BolaFindings = StratoSecure_Findings_CL
| where TimeGenerated >= ago(lookback)
| where FixStatus =~ "open"
| where OwaspCategory in ("API1", "API3", "API5")
| where Severity in ("Critical", "High")
| summarize FindingCount = count(), RuleIds = make_set(RuleId)
by ApplicationId, StraTenantId;
let TrafficAnomalies = ApiManagementGatewayLogs
| where TimeGenerated >= ago(lookback)
| where ResponseCode in (403, 401, 404)
| summarize ErrorCount = count(), DistinctCallers = dcount(CallerIpAddress),
Paths = make_set(Url, 30), LastSeen = max(TimeGenerated)
by ApiId, OperationId
| where ErrorCount >= enumThreshold;
BolaFindings
| join kind=inner TrafficAnomalies on $left.ApplicationId == $right.ApiId
| project TimeGenerated = LastSeen, ApplicationId, StraTenantId,
FindingCount, RuleIds, ErrorCount, DistinctCallers, Paths
entityMappings:
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: ApplicationId
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let lookback = 1h;
let enumThreshold = 100;
let BolaFindings is inlined into the numbered stages below.
Let Binding: TrafficAnomalies
let TrafficAnomalies = ApiManagementGatewayLogs
| where TimeGenerated >= ago(lookback)
| where ResponseCode in (403, 401, 404)
| summarize ErrorCount = count(), DistinctCallers = dcount(CallerIpAddress),
Paths = make_set(Url, 30), LastSeen = max(TimeGenerated)
by ApiId, OperationId
| where ErrorCount >= enumThreshold;
Stages 1 to 6 define let BolaFindings as the rule's main pipeline source. Stages 7 to 8 use it.
Stage 1: source
StratoSecure_Findings_CL
Stage 2: where
| where TimeGenerated >= ago(lookback)
Stage 3: where
| where FixStatus =~ "open"
Stage 4: where
| where OwaspCategory in ("API1", "API3", "API5")
Stage 5: where
| where Severity in ("Critical", "High")
Stage 6: summarize
| summarize FindingCount = count(), RuleIds = make_set(RuleId)
by ApplicationId, StraTenantId
Stage 7: join
BolaFindings
| join kind=inner TrafficAnomalies on $left.ApplicationId == $right.ApiId
Stage 8: project
project ApplicationId, DistinctCallers, ErrorCount, FindingCount, Paths, RuleIds, StraTenantId, TimeGenerated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ErrorCount | ge |
| field:"ErrorCount" kind:ge value:"100" |
FixStatus | eq |
| field:"FixStatus" kind:eq value:"open" |
OwaspCategory | in |
| field:"OwaspCategory" kind:in |
ResponseCode | in |
| field:"ResponseCode" kind:in |
Severity | in |
| field:"Severity" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ApplicationId | project |
DistinctCallers | project |
ErrorCount | project |
FindingCount | project |
Paths | project |
RuleIds | project |
StraTenantId | project |
TimeGenerated | project |
Cross-Cloud Suspicious Compute resource creation in GCP
#This detection identifies potential suspicious activity across multi-cloud environments by combining AWS GuardDuty findings with GCP Audit Logs. It focuses on AWS activities related to unauthorized access, credential abuse, and unusual behaviors, as well as GCP instances creation with non-Google service account users. The query aims to provide a comprehensive view of cross-cloud security incidents for proactive threat detection and response.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Persistence | |
| Privilege Escalation | |
| Credential Access | |
| Discovery |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| GCP | any: compute.googleapis.com (any method) |
Rules Detecting the Same Action
These rules filter on the same operation.
Rule Body
id: 5c847e47-0a07-4c01-ab99-5817ad6cb11e
name: Cross-Cloud Suspicious Compute resource creation in GCP
description: |
'This detection identifies potential suspicious activity across multi-cloud environments by combining AWS GuardDuty findings with GCP Audit Logs. It focuses on AWS activities related to unauthorized access, credential abuse, and unusual behaviors, as well as GCP instances creation with non-Google service account users. The query aims to provide a comprehensive view of cross-cloud security incidents for proactive threat detection and response.'
severity: Low
requiredDataConnectors:
- connectorId: GCPAuditLogsDefinition
dataTypes:
- GCPAuditLogs
- connectorId: AWSS3
dataTypes:
- AWSGuardDuty
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- Execution
- Persistence
- PrivilegeEscalation
- CredentialAccess
- Discovery
- LateralMovement
relevantTechniques:
- T1566
- T1059
- T1078
- T1547
- T1548
- T1069
- T1552
query: |
// Materialize AWS GuardDuty findings
let AwsAlert = materialize (
AWSGuardDuty
// Filter for specific activity types in AWS GuardDuty
| where ActivityType has_any (
"Backdoor:EC2/DenialOfService.UnusualProtocol",
"CredentialAccess:Kubernetes/MaliciousIPCaller",
"CredentialAccess:Kubernetes/SuccessfulAnonymousAccess",
"CredentialAccess:Kubernetes/TorIPCaller",
"CredentialAccess:RDS/AnomalousBehavior.SuccessfulBruteForce",
"CredentialAccess:RDS/TorIPCaller.FailedLogin",
"CredentialAccess:RDS/TorIPCaller.SuccessfulLogin",
"Discovery:Kubernetes/MaliciousIPCaller",
"Recon:IAMUser/MaliciousIPCaller.Custom",
"UnauthorizedAccess:EC2/TorClient",
"UnauthorizedAccess:IAMUser/TorIPCaller",
"UnauthorizedAccess:IAMUser/MaliciousIPCaller.Custom",
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS",
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.InsideAWS",
"UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B"
)
// Extract and transform AWS GuardDuty attributes
| extend
AWSAlertId = Id,
AWSAlertTitle = Title,
AWSAlertDescription = Description,
AWSresourceType = tostring(parse_json(ResourceDetails).resourceType),
AWSNetworkEntity = tostring(parse_json(ServiceDetails).action.awsApiCallAction.remoteIpDetails.ipAddressV4),
AWSAlertUserNameEntity = tostring(parse_json(ResourceDetails).accessKeyDetails.userName),
InstanceType = tostring(parse_json(ResourceDetails).instanceDetails.instanceType),
AWSTargetingService = parse_json(ServiceDetails).additionalInfo.apiCalls,
AWSAlertTime = TimeCreated,
AWSAlertLink= tostring(strcat('https://us-east-1.console.aws.amazon.com/guardduty/home?region=us-east-1#/findings?macros=current&fId=', Id)),
Severity =
case (
Severity >= 7.0,
"High",
Severity between (4.0 .. 6.9),
"Medium",
Severity between (1.0 .. 3.9),
"Low",
"Unknown"
)
// Extract API call details and count
| mv-apply AIPCall = AWSTargetingService on
(
where AIPCall has "name"
| extend APICallName = tostring(AIPCall.name), APICallCount = tostring(AIPCall["count"])
)
// Select distinct attributes for further analysis
| distinct
AWSAlertTime,
ActivityType,
Severity,
AWSAlertId,
AWSAlertTitle,
AWSAlertDescription,
AWSAlertLink,
Arn,
AWSresourceType,
AWSNetworkEntity,
AWSAlertUserNameEntity,
InstanceType,
APICallName,
APICallCount
);
// Materialize GCP Audit Logs related to VM instance creation
let GCPVMActivity= materialize(
GCPAuditLogs
// Filter for Compute Engine instances insertions
| where ServiceName == "compute.googleapis.com" and MethodName endswith "instances.insert"
// Extract and transform relevant GCP Audit Log attributes
| extend
GCPUserUPN= tostring(parse_json(AuthenticationInfo).principalEmail),
GCPUserIp = tostring(parse_json(RequestMetadata).callerIp),
GCPUserUA= tostring(parse_json(RequestMetadata).callerSuppliedUserAgent),
VMDetails= parse_json(AuthorizationInfo),
VMStatus = tostring(parse_json(Response).status),
VMOperation=tostring(parse_json(Response).operationType),
VMName= tostring(parse_json(Request).name),
VMDescription= tostring(parse_json(Request).description),
VMType = tostring(split(parse_json(Request).machineType, "/")[-1]),
Tags= tostring(parse_json(Request).tags),
RequestJS = parse_json(Request)
// Filter out service account-related activities and private IP addresses
| where GCPUserUPN !has "gserviceaccount.com"
| extend Name = tostring(split(GCPUserUPN, "@")[0]), UPNSuffix = tostring(split(GCPUserUPN, "@")[1])
| where VMOperation == "insert" and isnotempty(GCPUserIp) and GCPUserIp != "private"
// Select relevant attributes for further analysis
| project
GCPOperationTime=TimeGenerated,
VMName,
VMStatus,
MethodName,
GCPUserUPN,
ProjectId,
GCPUserIp,
GCPUserUA,
VMOperation,
VMType,
Name,
UPNSuffix
);
// Join AWS and GCP activities based on matching IP addresses
AwsAlert
| join kind= inner (GCPVMActivity)
on
$left.AWSNetworkEntity == $right.GCPUserIp
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: GCPUserIp
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: GCPUserUPN
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
customDetails:
AWSAlertUserName: AWSAlertUserNameEntity
AWSArn: Arn
AWSresourceType: AWSresourceType
AWSInstanceType: InstanceType
AWSAPICallName: APICallName
AWSAPICallCount: APICallCount
GCPUserAgent: GCPUserUA
GCPVMName: VMName
GCPProjectId: ProjectId
GCPVMType: VMType
CorrelationWith: "GCPAuditLogs"
alertDetailsOverride:
alertDisplayNameFormat: "{{AWSNetworkEntity}} from {{AWSAlertTitle}} observed in GCP compute activity with {{GCPUserUPN}}"
alertDescriptionFormat: " This detection compiles and correlates unauthorized user access alerts originating from AWS GuardDuty With Alert Description '{{AWSAlertDescription}}' assocated with GCP compute activities. It focuses on AWS GuardDuty alerts related to unauthorized user access, specifically targeting network IP associations tied to activities such as logins from malicious IP addresses or instance credential exfiltration attempts. The detection leverages these common network IP advisories to detect and pinpoint unauthorized users attempting to access both AWS and Azure resources. \n\n AWS ALert Link : '{{AWSAlertLink}}' \n\n Find More Details :https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_finding-types-active.html"
alertSeverityColumnName: Severity
alertDynamicProperties:
- alertProperty: AlertLink
value: AWSAlertLink
- alertProperty: ProviderName
value: "AWS"
- alertProperty: ProductComponentName
value: "AWSGuarduty"
kind: Scheduled
version: 1.0.2
Stages and Predicates
let AwsAlert is inlined into the numbered stages below.
Let Binding: GCPVMActivity
let GCPVMActivity = materialize(
GCPAuditLogs
| where ServiceName == "compute.googleapis.com" and MethodName endswith "instances.insert"
| extend
GCPUserUPN= tostring(parse_json(AuthenticationInfo).principalEmail),
GCPUserIp = tostring(parse_json(RequestMetadata).callerIp),
GCPUserUA= tostring(parse_json(RequestMetadata).callerSuppliedUserAgent),
VMDetails= parse_json(AuthorizationInfo),
VMStatus = tostring(parse_json(Response).status),
VMOperation=tostring(parse_json(Response).operationType),
VMName= tostring(parse_json(Request).name),
VMDescription= tostring(parse_json(Request).description),
VMType = tostring(split(parse_json(Request).machineType, "/")[-1]),
Tags= tostring(parse_json(Request).tags),
RequestJS = parse_json(Request)
| where GCPUserUPN !has "gserviceaccount.com"
| extend Name = tostring(split(GCPUserUPN, "@")[0]), UPNSuffix = tostring(split(GCPUserUPN, "@")[1])
| where VMOperation == "insert" and isnotempty(GCPUserIp) and GCPUserIp != "private"
| project
GCPOperationTime=TimeGenerated,
VMName,
VMStatus,
MethodName,
GCPUserUPN,
ProjectId,
GCPUserIp,
GCPUserUA,
VMOperation,
VMType,
Name,
UPNSuffix
);
Stages 1 to 5 define let AwsAlert as the rule's main pipeline source. Stage 6 uses it.
Stage 1: source
AWSGuardDuty
Stage 2: where
| where ActivityType has_any (
"Backdoor:EC2/DenialOfService.UnusualProtocol",
"CredentialAccess:Kubernetes/MaliciousIPCaller",
"CredentialAccess:Kubernetes/SuccessfulAnonymousAccess",
"CredentialAccess:Kubernetes/TorIPCaller",
"CredentialAccess:RDS/AnomalousBehavior.SuccessfulBruteForce",
"CredentialAccess:RDS/TorIPCaller.FailedLogin",
"CredentialAccess:RDS/TorIPCaller.SuccessfulLogin",
"Discovery:Kubernetes/MaliciousIPCaller",
"Recon:IAMUser/MaliciousIPCaller.Custom",
"UnauthorizedAccess:EC2/TorClient",
"UnauthorizedAccess:IAMUser/TorIPCaller",
"UnauthorizedAccess:IAMUser/MaliciousIPCaller.Custom",
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS",
"UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.InsideAWS",
"UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B"
)
Stage 3: extend
| extend
AWSAlertId = Id,
AWSAlertTitle = Title,
AWSAlertDescription = Description,
AWSresourceType = tostring(parse_json(ResourceDetails).resourceType),
AWSNetworkEntity = tostring(parse_json(ServiceDetails).action.awsApiCallAction.remoteIpDetails.ipAddressV4),
AWSAlertUserNameEntity = tostring(parse_json(ResourceDetails).accessKeyDetails.userName),
InstanceType = tostring(parse_json(ResourceDetails).instanceDetails.instanceType),
AWSTargetingService = parse_json(ServiceDetails).additionalInfo.apiCalls,
AWSAlertTime = TimeCreated,
AWSAlertLink= tostring(strcat('https://us-east-1.console.aws.amazon.com/guardduty/home?region=us-east-1#/findings?macros=current&fId=', Id)),
Severity =
case (
Severity >= 7.0,
"High",
Severity between (4.0 .. 6.9),
"Medium",
Severity between (1.0 .. 3.9),
"Low",
"Unknown"
)
Severity =Severity >= 7.0"High"Severity >= 4.0 and Severity <= 6.9"Medium"Severity >= 1.0 and Severity <= 3.9"Low""Unknown"Stage 4: kusto:mv-apply
| mv-apply AIPCall = AWSTargetingService on
(
where AIPCall has "name"
| extend APICallName = tostring(AIPCall.name), APICallCount = tostring(AIPCall["count"])
)
Stage 5: distinct
| distinct
AWSAlertTime,
ActivityType,
Severity,
AWSAlertId,
AWSAlertTitle,
AWSAlertDescription,
AWSAlertLink,
Arn,
AWSresourceType,
AWSNetworkEntity,
AWSAlertUserNameEntity,
InstanceType,
APICallName,
APICallCount
Stage 6: join
AwsAlert
| join kind= inner (GCPVMActivity)
on
$left.AWSNetworkEntity == $right.GCPUserIp
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
AIPCall | match |
| field:"AIPCall" kind:match value:"name" |
ActivityType | match |
| field:"ActivityType" kind:match |
GCPUserIp | is_not_null | field:"GCPUserIp" kind:is_not_null | |
GCPUserIp | ne |
| field:"GCPUserIp" kind:ne value:"private" |
MethodName | ends_with |
| field:"MethodName" kind:ends_with value:"instances.insert" |
ServiceName | eq |
| field:"ServiceName" kind:eq value:"compute.googleapis.com" |
VMOperation | eq |
| field:"VMOperation" kind:eq value:"insert" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
GCPUserUPN | match | gserviceaccount.com | excludes:GCPUserUPN field:"GCPUserUPN" value:"gserviceaccount.com" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AWSAlertDescription | extend |
AWSAlertId | extend |
AWSAlertLink | extend |
AWSAlertTime | extend |
AWSAlertTitle | extend |
AWSAlertUserNameEntity | extend |
AWSNetworkEntity | extend |
AWSTargetingService | extend |
AWSresourceType | extend |
InstanceType | extend |
Severity | extend |
CYFIRMA - Medium severity Malicious Network Indicators Associated with Malware - Block Recommended Rule
#This is a third-party alert feed, not a detection over modeled telemetry. The vendor product raised the finding; this rule forwards it into the SIEM. It is searchable for reference but is excluded from the detection-rule browse and the ATT&CK coverage matrix.
"This analytics rule detects network-based indicators identified by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure. These may represent attacker-controlled endpoints used to maintain persistence, exfiltrate data, or receive commands from malware-infected hosts."
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Command & Control |
Rule Body
id: 77226d6e-1263-42b1-a40d-8a756407ad4c
name: CYFIRMA - Medium severity Malicious Network Indicators Associated with Malware - Block Recommended Rule
description: |
"This analytics rule detects network-based indicators identified by CYFIRMA threat intelligence as associated with Command & Control (C2) infrastructure.
These may represent attacker-controlled endpoints used to maintain persistence, exfiltrate data, or receive commands from malware-infected hosts."
version: 1.0.1
kind: Scheduled
severity: Medium
enabled: false
requiredDataConnectors:
- connectorId: CyfirmaCyberIntelligenceDC
dataTypes:
- CyfirmaIndicators_CL
query: |
//Network Indicators Associated with Malware - Block Recommended Rule
let timeFrame= 5m;
CyfirmaIndicators_CL
| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and Roles has 'Malware'
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
| mv-expand extensionKeys
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: GreaterThan
triggerThreshold: 0
suppressionDuration: 5m
suppressionEnabled: true
tactics:
- InitialAccess
- Execution
- CommandAndControl
relevantTechniques:
- T1566
- T1189
- T1204
- T1071
- T1090
- T1059
- T1568
- T1566.002
- T1204.001
- T1071.001
- T1568.002
alertDetailsOverride:
alertDisplayNameFormat: "High-Confidence Malicious Network Indicators Associated with Malware - Block Recommended - {{name}} "
alertDescriptionFormat: "{{Description}} - {{name}} "
alertDynamicProperties:
- alertProperty: ProductName
value: ProductName
- alertProperty: ProviderName
value: ProviderName
customDetails:
ThreatActors: ThreatActors
Sources: Sources
RecommendedActions: RecommendedActions
Roles: Roles
Country: Country
Description: Description
ConfidenceScore: ConfidenceScore
SecurityVendors: SecurityVendors
IndicatorID: IndicatorID
Created: created
Modified: modified
ValidFrom: valid_from
Tags: Tags
ThreatType: ThreatType
TimeGenerated: TimeGenerated
IPAbuse: IPAbuse
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv4
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPv6
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: Domain
- entityType: URL
fieldMappings:
- identifier: Url
columnName: URL
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: false
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
eventGroupingSettings:
aggregationKind: AlertPerResult
Stages and Predicates
Parameters
let timeFrame = 5m;
Stage 1: source
CyfirmaIndicators_CL
Stage 2: where
| where (ConfidenceScore < 80 and ConfidenceScore >= 50)
and TimeGenerated between (ago(timeFrame) .. now())
and pattern !contains 'file:hashes' and RecommendedActions has 'Block' and Roles has 'Malware'
Stage 3: extend (6 consecutive steps)
| extend IPv4 = extract(@"ipv4-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend IPv6 = extract(@"ipv6-addr:value\s*=\s*'([^']+)'", 1, pattern)
| extend URL = extract(@"url:value\s*=\s*'([^']+)'", 1, pattern)
| extend Domain = extract(@"domain-name:value\s*=\s*'([^']+)'", 1, pattern)
| extend parsed = parse_json(extensions)
| extend extensionKeys = bag_keys(parsed)
Stage 4: mv-expand
| mv-expand extensionKeys
Stage 5: extend (4 consecutive steps)
| extend extensionKeyStr = tostring(extensionKeys)
| extend ext = parsed[extensionKeyStr]
| extend props = ext.properties
| extend
extension_id = extensionKeyStr,
ASN_Owner = props.asn_owner,
ASN = props.asn,
ProviderName = 'CYFIRMA',
ProductName = 'DeCYFIR/DeTCT'
Stage 6: project
| project
IPv4,
IPv6,
URL,
Domain,
ThreatActors,
RecommendedActions,
Sources,
Roles,
Country,
IPAbuse,
name,
Description,
ConfidenceScore,
IndicatorID,
created,
modified,
valid_from,
Tags,
ThreatType,
TimeGenerated,
SecurityVendors,
ProductName,
ProviderName
Indicators
These rows show field, operator, and value matches.
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
pattern | contains | file:hashes | excludes:pattern field:"pattern" value:"file:hashes" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ConfidenceScore | project |
Country | project |
Description | project |
Domain | project |
IPAbuse | project |
IPv4 | project |
IPv6 | project |
IndicatorID | project |
ProductName | project |
ProviderName | project |
RecommendedActions | project |
Roles | project |
SecurityVendors | project |
Sources | project |
Tags | project |
ThreatActors | project |
ThreatType | project |
TimeGenerated | project |
URL | project |
created | project |
modified | project |
name | project |
valid_from | project |
Detect Suspicious ncrypt.dll usage by CLI tool or unknown process
#This detection rule uses a WDAC audit policy to ingest missing DeviceImageLoad events in MDE, and check for suspicious processes using the ncrypt.dll. More information on the attack scenario this is detection is applicable for can be found in the references.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
References
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | DeviceEvents action any: Defender event |
Rule Body
let cli_tools = dynamic(["powershell", "python"]);
// Get suspicious ncrypt.dll usage via WDAC audit policy
DeviceEvents
| where ActionType startswith "AppControl" and FileName =~ "ncrypt.dll"
| invoke FileProfile(InitiatingProcessSHA1, 1000)
| where (
// Flag CLI tools
InitiatingProcessFileName has_any (cli_tools) or
// Flag unknown processes
GlobalPrevalence < 250
)
| sort by TimeGenerated desc
Stages and Predicates
Parameters
let cli_tools = dynamic(["powershell", "python"]);
Stage 1: source
DeviceEvents
Stage 2: where
| where ActionType startswith "AppControl" and FileName =~ "ncrypt.dll"
Stage 3: invoke
| invoke FileProfile(InitiatingProcessSHA1, 1000)
Stage 4: where
| where (
InitiatingProcessFileName has_any (cli_tools) or
GlobalPrevalence < 250
)
Stage 5: sort
| sort by TimeGenerated desc
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | starts_with |
| field:"ActionType" kind:starts_with value:"AppControl" |
FileName | eq |
| field:"file_name" kind:eq value:"ncrypt.dll" |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt value:"250" |
InitiatingProcessFileName | match |
| field:"InitiatingProcessFileName" kind:match |
Detect Windows Update Disabled from Registry
#This analytic rule detects any registry value creation or modification of Windows Update registry keys to disable Windows Update. This could be an indication of defense evasion by an adversary on a compromised host.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Telemetry Coverage
Rule Body
id: f1443a87-78d5-40c3-b051-f468f0f2def0
name: Detect Windows Update Disabled from Registry
description: |
This analytic rule detects any registry value creation or modification of Windows Update registry keys to disable Windows Update. This could be an indication of defense evasion by an adversary on a compromised host.
severity: Medium
status: Available
tags:
- Schema: _ASim_RegistryEvent
SchemaVersion: 0.1.2
requiredDataConnectors:
- connectorId: CrowdStrikeFalconEndpointProtection
dataTypes:
- CommonSecurityLog
- connectorId: MicrosoftThreatProtection
dataTypes:
- SecurityAlert
- connectorId: SentinelOne
dataTypes:
- SentinelOne_CL
- connectorId: VMwareCarbonBlack
dataTypes:
- CarbonBlackEvents_CL
- connectorId: CiscoSecureEndpoint
dataTypes:
- CiscoSecureEndpoint_CL
- connectorId: TrendMicroApexOne
dataTypes:
- TMApexOneEvent
- connectorId: TrendMicroApexOneAma
dataTypes:
- TMApexOneEvent
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- DefenseEvasion
relevantTechniques:
- T1562
query: |
// List of Windows Firewall registry keys to monitor
let windowsUpdateRegistryList = dynamic([
'HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate',
'HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU'
]);
_ASim_RegistryEvent
| where EventType in ('RegistryValueSet', 'RegistryKeyCreated')
| where RegistryKey has_any (windowsUpdateRegistryList)
| where RegistryValue has_any ('AUOptions', 'NoAutoUpdate') and RegistryValueData == '1'
| project
TimeGenerated,
DvcHostname,
ActorUsername,
ActorUsernameType,
ActingProcessId,
ActingProcessName,
ActingProcessCommandLine,
RegistryKey,
RegistryValue,
RegistryValueType,
RegistryValueData
| extend HostName = tostring(split(DvcHostname, '.')[0])
| extend DnsDomain = tostring(strcat_array(array_slice(split(DvcHostname, '.'), 1, -1), '.'))
| extend Username = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[1]), ActorUsername)
| extend NTDomain = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[0]), ActorUsername)
| extend Username = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[0]), Username)
| extend UPNSuffix = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[1]), '')
| extend RegHive = tostring(split(RegistryKey, '\\')[0]), RegKey = tostring(strcat_array(array_slice(split(RegistryKey, '\\'), 1, -1), '\\'))
entityMappings:
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: DnsDomain
- identifier: NTDomain
columnName: NTDomain
- entityType: Account
fieldMappings:
- identifier: Name
columnName: Username
- identifier: UPNSuffix
columnName: UPNSuffix
- identifier: NTDomain
columnName: NTDomain
- entityType: Process
fieldMappings:
- identifier: ProcessId
columnName: ActingProcessId
- identifier: CommandLine
columnName: ActingProcessCommandLine
- entityType: RegistryKey
fieldMappings:
- identifier: Hive
columnName: RegHive
- identifier: Key
columnName: RegKey
- entityType: RegistryValue
fieldMappings:
- identifier: Name
columnName: RegistryValue
- identifier: Value
columnName: RegistryValueData
- identifier: ValueType
columnName: RegistryValueType
eventGroupingSettings:
aggregationKind: singleAlert
alertDetailsOverride:
alertDisplayNameFormat: "Windows Update Disabled from Registry on {{HostName}}"
alertDescriptionFormat: "Windows Update Disabled from Registry {{RegKey}} on {{HostName}} by {{Username}}"
version: 1.0.0
kind: Scheduled
Stages and Predicates
Let Binding: windowsUpdateRegistryList
let windowsUpdateRegistryList = dynamic([
'HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate',
'HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU'
]);
Stage 1: source
_ASim_RegistryEvent
Stage 2: where
| where EventType in ('RegistryValueSet', 'RegistryKeyCreated')
Stage 3: where
| where RegistryKey has_any (windowsUpdateRegistryList)
Stage 4: where
| where RegistryValue has_any ('AUOptions', 'NoAutoUpdate') and RegistryValueData == '1'
Stage 5: project
| project
TimeGenerated,
DvcHostname,
ActorUsername,
ActorUsernameType,
ActingProcessId,
ActingProcessName,
ActingProcessCommandLine,
RegistryKey,
RegistryValue,
RegistryValueType,
RegistryValueData
Stage 6: extend (7 consecutive steps)
| extend HostName = tostring(split(DvcHostname, '.')[0])
| extend DnsDomain = tostring(strcat_array(array_slice(split(DvcHostname, '.'), 1, -1), '.'))
| extend Username = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[1]), ActorUsername)
| extend NTDomain = iff(tostring(ActorUsernameType) == 'Windows', tostring(split(ActorUsername, '\\')[0]), ActorUsername)
| extend Username = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[0]), Username)
| extend UPNSuffix = iff(tostring(ActorUsernameType) == 'UPN', tostring(split(ActorUsername, '@')[1]), '')
| extend RegHive = tostring(split(RegistryKey, '\\')[0]), RegKey = tostring(strcat_array(array_slice(split(RegistryKey, '\\'), 1, -1), '\\'))
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | in |
| field:"EventType" kind:in |
RegistryKey | match |
| field:"TargetObject" kind:match |
RegistryValue | match |
| field:"TargetObject" kind:match |
RegistryValueData | eq |
| field:"Details" kind:eq value:"1" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ActingProcessCommandLine | project |
ActingProcessId | project |
ActingProcessName | project |
ActorUsername | project |
ActorUsernameType | project |
DvcHostname | project |
RegistryKey | project |
RegistryValue | project |
RegistryValueData | project |
RegistryValueType | project |
TimeGenerated | project |
HostName | extend |
DnsDomain | extend |
Username | extend |
NTDomain | extend |
UPNSuffix | extend |
RegHive | extend |
RegKey | extend |
Lumen TI IPAddress in OfficeActivity
#This query maps Lumen IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in OfficeActivity.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Rule Body
id: 0e96c419-68eb-4235-947e-7e86e136cda0
name: Lumen TI IPAddress in OfficeActivity
displayName: Lumen TI IPAddress in OfficeActivity
description: |
This query maps Lumen IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in OfficeActivity.
severity: Medium
requiredDataConnectors:
- connectorId: LumenThreatFeedConnector
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: ThreatIntelligenceUploadIndicatorsAPI
dataTypes:
- ThreatIntelligenceIndicator
- connectorId: Office365
dataTypes:
- OfficeActivity
queryFrequency: 4h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
suppressionDuration: 5h
suppressionEnabled: true
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
let dt_lookBack = 1d; // Data lookback for OfficeActivity
let ioc_lookBack = 14d; // TI lookback
let IP_Indicators = ThreatIntelIndicators
| where TimeGenerated >= ago(ioc_lookBack)
| where IsActive == true and ValidUntil > now()
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
| where SourceSystem == 'Lumen'
| where ObservableKey == 'ipv4-addr:value'
| extend TI_ipEntity = ObservableValue
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith 'fe80' and TI_ipEntity !startswith '::' and TI_ipEntity !startswith '127.';
IP_Indicators
| join kind=innerunique (
OfficeActivity
| where TimeGenerated >= ago(dt_lookBack)
| extend OA_ipEntity = ClientIP
| extend OfficeActivity_TimeGenerated = TimeGenerated
) on $left.TI_ipEntity == $right.OA_ipEntity
| where OfficeActivity_TimeGenerated < ValidUntil
| summarize arg_max(OfficeActivity_TimeGenerated, *), StartTime = min(OfficeActivity_TimeGenerated), EndTime = max(OfficeActivity_TimeGenerated) by Id, OA_ipEntity
| project timestamp = EndTime, StartTime, EndTime, UserId, ClientIP, Operation, Id, Tags, ValidUntil, Confidence, TI_ipEntity, OA_ipEntity, Type
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: OA_ipEntity
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1d;
let ioc_lookBack = 14d;
let IP_Indicators is inlined into the numbered stages below.
Stages 1 to 8 define let IP_Indicators as the rule's main pipeline source. Stages 9 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 == 'Lumen'
Stage 6: where
| where ObservableKey == 'ipv4-addr:value'
Stage 7: extend
| extend TI_ipEntity = ObservableValue
Stage 8: where
| where ipv4_is_private(TI_ipEntity) == false and TI_ipEntity !startswith 'fe80' and TI_ipEntity !startswith '::' and TI_ipEntity !startswith '127.'
Stage 9: join
IP_Indicators
| join kind=innerunique (
OfficeActivity
| where TimeGenerated >= ago(dt_lookBack)
| extend OA_ipEntity = ClientIP
| extend OfficeActivity_TimeGenerated = TimeGenerated
) on $left.TI_ipEntity == $right.OA_ipEntity
Stage 10: where
where OfficeActivity_TimeGenerated < ValidUntil
Stage 11: summarize
summarize EndTime, StartTime by Id, OA_ipEntity
Stage 12: project
project ClientIP, Confidence, EndTime, Id, OA_ipEntity, Operation, StartTime, TI_ipEntity, Tags, Type, UserId, ValidUntil, 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" |
SourceSystem | eq |
| field:"SourceSystem" kind:eq value:"Lumen" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
TI_ipEntity | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 | excludes:TI_ipEntity |
TI_ipEntity | starts_with | 127. | excludes:TI_ipEntity field:"TI_ipEntity" value:"127." |
TI_ipEntity | starts_with | :: | excludes:TI_ipEntity field:"TI_ipEntity" value:"::" |
TI_ipEntity | starts_with | fe80 | excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
ClientIP | project |
Confidence | project |
EndTime | project |
Id | project |
OA_ipEntity | project |
Operation | project |
StartTime | project |
TI_ipEntity | project |
Tags | project |
Type | project |
UserId | project |
ValidUntil | project |
timestamp | project |
OracleDBAudit - Multiple tables dropped in short time
#'Detects when user drops many tables in short period of time.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Impact |
Rule Body
id: b3aa0e5a-75a2-4613-80ec-93a1be3aeb8f
name: OracleDBAudit - Multiple tables dropped in short time
description: |
'Detects when user drops many tables in short period of time.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: SyslogAma
datatypes:
- Syslog
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
relevantTechniques:
- T1485
query: |
let tbl_threshold = 10;
OracleDatabaseAuditEvent
| where isnotempty(DstUserName)
| where DbAction =~ 'DROP'
| extend TableName = replace(@'[,\(\)]', '', extract(@'(?i)SELECT(.*?)FROM\s(.*?)\s', 2, Action))
| where isnotempty(TableName)
| where TableName !~ 'SELECT'
| summarize tbl_count = dcount(TableName) by DstUserName
| 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 =~ 'DROP'
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
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:"DROP" |
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 |
tbl_count | summarize |
AccountCustomEntity | extend |
Preview - TI map URL entity to Cloud App Events
#Identifies compromises and attacks and detect malicious activities in one's URL entity from TI
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Telemetry Coverage
| Platform | Record / event type |
|---|---|
| Microsoft Defender for Endpoint | CloudAppEvents action any: Cloud app activity |
Rule Body
id: e8ae92dd-1d41-4530-8be8-85c5014c7b47
name: Preview - TI map URL entity to Cloud App Events
description: |
'Identifies compromises and attacks and detect malicious activities in one's URL entity from TI'
severity: Medium
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- CloudAppEvents
- 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;
ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack)
| where isnotempty(Url)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
| where Active == true and ExpirationDateTime > now()
| join kind=innerunique (CloudAppEvents
| where TimeGenerated >= ago(dt_lookBack)
| extend Url = extract("(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)", 1, tostring(ActivityObjects))
| extend userPrincipalName = tostring(RawEventData.UserId)
| extend TargetResourceDisplayName = tostring(ActivityObjects[0].displayName)
| extend CloudApps_TimeGenerated = TimeGenerated) on Url
| where CloudApps_TimeGenerated < ExpirationDateTime
| summarize CloudApps_TimeGenerated = argmax(CloudApps_TimeGenerated, *) by IndicatorId, Url
| extend Description = max_CloudApps_TimeGenerated_Description, ActivityGroupNames = max_CloudApps_TimeGenerated_ActivityGroupNames, ThreatType = max_CloudApps_TimeGenerated_ThreatType, ExpirationDateTime = max_CloudApps_TimeGenerated_ExpirationDateTime, ConfidenceScore = max_CloudApps_TimeGenerated_ConfidenceScore, IPAddress = max_CloudApps_TimeGenerated_IPAddress, AccountObjectId = max_CloudApps_TimeGenerated_AccountObjectId, AccountDisplayName = max_CloudApps_TimeGenerated_AccountDisplayName, ObjectName = max_CloudApps_TimeGenerated_ObjectName, Application= max_CloudApps_TimeGenerated_Application, ApplicationID = max_CloudApps_TimeGenerated_ApplicationId, userPrincipalName = max_CloudApps_TimeGenerated_userPrincipalName
| project CloudApps_TimeGenerated, Description, IndicatorId, ThreatType, ExpirationDateTime, ConfidenceScore, Url, IPAddress, userPrincipalName, AccountObjectId, AccountDisplayName, ObjectName, Application, ApplicationID
entityMappings:
- entityType: Account
fieldMappings:
- identifier: ObjectGuid
columnName: AccountObjectId
- identifier: FullName
columnName: userPrincipalName
- identifier: DisplayName
columnName: AccountDisplayName
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Url
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: Application
- identifier: AppId
columnName: ApplicationID
version: 1.0.3
kind: Scheduled
Stages and Predicates
Parameters
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
Stage 1: source
ThreatIntelligenceIndicator
Stage 2: where
| where TimeGenerated >= ago(ioc_lookBack)
Stage 3: where
| where isnotempty(Url)
Stage 4: summarize
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
Stage 5: where
| where Active == true and ExpirationDateTime > now()
Stage 6: join
| join kind=innerunique (CloudAppEvents
| where TimeGenerated >= ago(dt_lookBack)
| extend Url = extract("(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)", 1, tostring(ActivityObjects))
| extend userPrincipalName = tostring(RawEventData.UserId)
| extend TargetResourceDisplayName = tostring(ActivityObjects[0].displayName)
| extend CloudApps_TimeGenerated = TimeGenerated) on Url
Stage 7: where
where CloudApps_TimeGenerated < ExpirationDateTime
Stage 8: summarize
summarize CloudApps_TimeGenerated by IndicatorId, Url
Stage 9: extend
extend AccountDisplayName, AccountObjectId, ActivityGroupNames, Application, ApplicationID, ConfidenceScore, Description, ExpirationDateTime, IPAddress, ObjectName, ThreatType, userPrincipalName
Stage 10: project
project AccountDisplayName, AccountObjectId, Application, ApplicationID, CloudApps_TimeGenerated, ConfidenceScore, Description, ExpirationDateTime, IPAddress, IndicatorId, ObjectName, ThreatType, Url, userPrincipalName
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Active | eq |
| field:"Active" kind:eq value:"true" |
Url | is_not_null | field:"Url" kind:is_not_null |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountDisplayName | project |
AccountObjectId | project |
Application | project |
ApplicationID | project |
CloudApps_TimeGenerated | project |
ConfidenceScore | project |
Description | project |
ExpirationDateTime | project |
IPAddress | project |
IndicatorId | project |
ObjectName | project |
ThreatType | project |
Url | project |
userPrincipalName | project |
ProofpointPOD - Multiple protected emails to unknown recipient
#'Detects when multiple protected messages where sent to early not seen recipient.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Rule Body
id: f8127962-7739-4211-a4a9-390a7a00e91f
name: ProofpointPOD - Multiple protected emails to unknown recipient
description: |
'Detects when multiple protected messages where sent to early not seen recipient.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: ProofpointPOD
dataTypes:
- ProofpointPOD_message_CL
queryFrequency: 30m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Exfiltration
relevantTechniques:
- T1567
query: |
let lbtime = 30m;
let lbperiod = 14d;
let knownrecipients = ProofpointPOD
| where TimeGenerated > ago(lbperiod)
| where EventType == 'message'
| where NetworkDirection == 'outbound'
| where SrcUserUpn != ''
| where array_length(todynamic(DstUserUpn)) == 1
| summarize recipients = make_set(tostring(todynamic(DstUserUpn)[0])) by SrcUserUpn
| extend commcol = SrcUserUpn;
ProofpointPOD
| where TimeGenerated between (ago(lbtime) .. now())
| where EventType == 'message'
| where NetworkDirection == 'outbound'
| extend isProtected = todynamic(MsgParts)[0]['isProtected']
| extend mimePgp = todynamic(MsgParts)[0]['detectedMime']
| where isProtected == 'true' or mimePgp == 'application/pgp-encrypted'
| extend DstUserMail = tostring(todynamic(DstUserUpn)[0])
| extend commcol = tostring(todynamic(DstUserUpn)[0])
| join knownrecipients on commcol
| where recipients !contains DstUserMail
| project SrcUserUpn, DstUserMail
| extend AccountCustomEntity = SrcUserUpn
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 1.1.1
kind: Scheduled
Stages and Predicates
Parameters
let lbtime = 30m;
let lbperiod = 14d;
Let Binding: knownrecipients
let knownrecipients = ProofpointPOD
| where TimeGenerated > ago(lbperiod)
| where EventType == 'message'
| where NetworkDirection == 'outbound'
| where SrcUserUpn != ''
| where array_length(todynamic(DstUserUpn)) == 1
| summarize recipients = make_set(tostring(todynamic(DstUserUpn)[0])) by SrcUserUpn
| extend commcol = SrcUserUpn;
Stage 1: source
ProofpointPOD
Stage 2: where
| where TimeGenerated between (ago(lbtime) .. now())
Stage 3: where
| where EventType == 'message'
Stage 4: where
| where NetworkDirection == 'outbound'
Stage 5: extend
| extend isProtected = todynamic(MsgParts)[0]['isProtected']
Stage 6: extend
| extend mimePgp = todynamic(MsgParts)[0]['detectedMime']
Stage 7: where
| where isProtected == 'true' or mimePgp == 'application/pgp-encrypted'
Stage 8: extend
| extend DstUserMail = tostring(todynamic(DstUserUpn)[0])
Stage 9: extend
| extend commcol = tostring(todynamic(DstUserUpn)[0])
Stage 10: join
| join knownrecipients on commcol
Stage 11: where
where recipients !contains DstUserMail
Stage 12: project
project DstUserMail, SrcUserUpn
Stage 13: extend
extend AccountCustomEntity
Stage 14: summarize aggregation inside the join branch
summarize by SrcUserUpn
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventType | eq |
| field:"EventType" kind:eq value:"message" |
NetworkDirection | eq |
| field:"NetworkDirection" kind:eq value:"outbound" |
isProtected | eq |
| field:"isProtected" kind:eq value:"true" |
mimePgp | eq |
| field:"mimePgp" kind:eq value:"application/pgp-encrypted" |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
SrcUserUpn | summarize |
SailPointIdentityNowFailedEventsBasedOnTime
#'Detects failed events based on created time.'
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Initial Access |
Rule Body
id: 175b79ef-0fc3-4b27-b92a-89b2db6c85c2
name: SailPointIdentityNowFailedEventsBasedOnTime
description: |
'Detects failed events based on created time.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: SailPointIdentityNow
dataTypes:
- SailPointIDN_Events
- connectorId: SailPointIdentityNowConnector
dataTypes:
- SailPointIDN_Events
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
relevantTechniques:
- T1133
query: |
declare query_parameters(lbperiod:timespan = 14d, createdTime:string = "2020-08-19T20:34:38.676Z");
SailPointIDN_Events
| where TimeGenerated > ago(lbperiod)
| where Status == "FAILED"
| where Created >= todatetime(createdTime)
| sort by Created
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: TechnicalName
version: 1.1.0
kind: Scheduled
Stages and Predicates
Stage 1: source
declare query_parameters(lbperiod:timespan = 14d, createdTime:string = "2020-08-19T20:34:38.676Z");
SailPointIDN_Events
Stage 2: where
| where TimeGenerated > ago(lbperiod)
Stage 3: where
| where Status == "FAILED"
Stage 4: where
| where Created >= todatetime(createdTime)
Stage 5: sort
| sort by Created
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Status | eq |
| field:"Status" kind:eq value:"FAILED" |
SAP BTP - User added to sensitive privileged role collection
#Identifies identity management actions whereby a user is added to a set of monitored privileged role collections.
MITRE ATT&CK Coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | |
| Persistence | |
| Lateral Movement |
Rule Body
id: 5acbe4cb-a379-4acc-9ad3-28dc48ad33d3
kind: Scheduled
name: SAP BTP - User added to sensitive privileged role collection
description: Identifies identity management actions whereby a user is added to a set
of monitored privileged role collections.
severity: Low
status: Available
requiredDataConnectors:
- connectorId: SAPBTPAuditEvents
dataTypes:
- SAPBTPAuditLog_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- LateralMovement
- PrivilegeEscalation
relevantTechniques:
- T0859
- T1078
query: |
let monitored_rolecollections = dynamic(["Subaccount Service Administrator", "Subaccount Administrator", "Connectivity and Destination Administrator", "Destination Administrator", "Cloud Connector Administrator"]);
SAPBTPAuditLog_CL
| where Message.object has "xs_rolecollection2user"
| extend ObjectId = parse_json((Message.object).id)
| where ObjectId.crudType == "CREATE"
| extend RoleCollection = ObjectId.rolecollection_name, TargetUserId = ObjectId.user_id
| where RoleCollection in (monitored_rolecollections)
| project UpdatedOn, UserName, RoleCollection, TargetUserId, Tenant, SpaceId, CloudApp = "SAP BTP"
| 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: CloudApplication
fieldMappings:
- identifier: Name
columnName: CloudApp
version: 3.0.6
Stages and Predicates
Let Binding: monitored_rolecollections
let monitored_rolecollections = dynamic(["Subaccount Service Administrator", "Subaccount Administrator", "Connectivity and Destination Administrator", "Destination Administrator", "Cloud Connector Administrator"]);
Stage 1: source
SAPBTPAuditLog_CL
Stage 2: where
| where Message.object has "xs_rolecollection2user"
Stage 3: extend
| extend ObjectId = parse_json((Message.object).id)
Stage 4: where
| where ObjectId.crudType == "CREATE"
Stage 5: extend
| extend RoleCollection = ObjectId.rolecollection_name, TargetUserId = ObjectId.user_id
Stage 6: where
| where RoleCollection in (monitored_rolecollections)
Stage 7: project
| project UpdatedOn, UserName, RoleCollection, TargetUserId, Tenant, SpaceId, CloudApp = "SAP BTP"
Stage 8: extend
| extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Message.object | match |
| field:"Message.object" kind:match value:"xs_rolecollection2user" |
ObjectId.crudType | eq |
| field:"ObjectId.crudType" kind:eq value:"CREATE" |
RoleCollection | in |
| field:"RoleCollection" kind:in |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CloudApp | project |
RoleCollection | project |
SpaceId | project |
TargetUserId | project |
Tenant | project |
UpdatedOn | project |
UserName | project |
AccountName | extend |
UPNSuffix | extend |
Unsigned Windows System Binary
#This query searches for invocations of a number of commonly used and signed Windows binaries. It then finds invocations of these binaries where they are not properly signed.
Known False Positives
- In case a legitimate software package uses a file name that is the same as that of a known system binary, this rule may trigger.
MITRE ATT&CK Coverage
References
Telemetry Coverage
Rule Body
let timeframe = 2*1h;
let default_global_prevalence = 0;
let signedSystemFiles = dynamic(["aitstatic.exe", "ApplicationFrameHost.exe", "ApplyTrustOffline.exe", "AppVClient.exe", "AppVDllSurrogate.exe", "AppVNice.exe", "AppVShNotify.exe", "audiodg.exe", "AuthHost.exe", "backgroundTaskHost.exe", "bcdedit.exe", "bdeunlock.exe", "BioIso.exe", "bootsect.exe", "browser_broker.exe", "CameraSettingsUIHost.exe", "CastSrv.exe", "CExecSvc.exe", "changepk.exe", "ClipRenew.exe", "ClipUp.exe", "CloudExperienceHostBroker.exe", "CloudNotifications.exe", "cmdiag.exe", "CompatTelRunner.exe", "consent.exe", "convertvhd.exe", "CredentialEnrollmentManager.exe", "CredentialUIBroker.exe", "csrss.exe", "DataExchangeHost.exe", "DeviceCensus.exe", "Dism.exe", "DisplaySwitch.exe", "dllhost.exe", "DTUHandler.exe", "easinvoker.exe", "ErgonomicKBNotificationService.exe", "fontdrvhost.exe", "FsIso.exe", "fsutil.exe", "GenValObj.exe", "hcsdiag.exe", "hvax64.exe", "hvc.exe", "hvix64.exe", "hvsievaluator.exe", "hvsimgr.exe", "hvsirdpclient.exe", "hvsirpcd.exe", "HvsiSettingsWorker.exe", "iotstartup.exe", "LicensingUI.exe", "LockAppHost.exe", "LockScreenContentServer.exe", "LsaIso.exe", "lsass.exe", "mavinject.exe", "mfpmp.exe", "MRT.exe", "MusNotifyIcon.exe", "NDKPing.exe", "NgcIso.exe", "nmbind.exe", "nmscrub.exe", "ntoskrnl.exe", "nvspinfo.exe", "OpenWith.exe", "PasswordOnWakeSettingFlyout.exe", "phoneactivate.exe", "PickerHost.exe", "PktMon.exe", "ProximityUxHost.exe", "prproc.exe", "ResetEngine.exe", "RuntimeBroker.exe", "ScriptRunner.exe", "securekernel.exe", "SecurityHealthHost.exe", "SecurityHealthService.exe", "services.exe", "sessionmsg.exe", "SettingSyncHost.exe", "SgrmBroker.exe", "SgrmLpac.exe", "SIHClient.exe", "SlideToShutDown.exe", "smss.exe", "SndVol.exe", "spaceman.exe", "sppsvc.exe", "svchost.exe", "SyncAppvPublishingServer.exe", "SysResetErr.exe", "systemreset.exe", "SystemSettingsAdminFlows.exe", "SystemSettingsBroker.exe", "SystemSettingsRemoveDevice.exe", "taskhostw.exe", "Taskmgr.exe", "tcblaunch.exe", "ttdinject.exe", "tttracer.exe", "ucsvc.exe", "upfc.exe", "UserAccountBroker.exe", "verifier.exe", "vmcompute.exe", "VmComputeAgent.exe", "vmms.exe", "vmplatformca.exe", "vmsp.exe", "vmwp.exe", "wcsetupagent.exe", "WerFault.exe", "WerFaultSecure.exe", "wermgr.exe", "wifitask.exe", "wimserv.exe", "wininit.exe", "winload.exe", "winresume.exe", "wkspbroker.exe", "wlrmdr.exe", "WpcMon.exe", "wuauclt.exe", "WUDFCompanionHost.exe", "WWAHost.exe", "AdtAgent.exe", "appverif.exe", "iaStorAfsNative.exe", "iaStorAfsService.exe", "MCU.exe", "microsoft.windows.softwarelogo.showdesktop.exe", "MpSigStub.exe", "RtkAudUService64.exe", "TsWpfWrp.exe"]);
let uniqueHashes = materialize(
DeviceProcessEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType =~ "ProcessCreated"
| where FileName in~ (signedSystemFiles) and not(isempty(SHA1))
// Begin environment-specific filter.
// End environment-specific filter.
// FileProfile is case sensistive and works on lower-case hashes
| extend SHA1=tolower(SHA1)
| summarize MachineCount=dcount(DeviceId) by SHA1
);
let unsignedHashes = materialize(
uniqueHashes
// Take 1000 of the most unique hashes as files with high prevelance are very likely to be signed in a legit manner.
| top 1000 by MachineCount asc
// FileProfile is case-sensitive and works on lower-case hashes.
| extend SHA1=tolower(SHA1)
| invoke FileProfile(SHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where IsCertificateValid != 1 or (IsRootSignerMicrosoft != 1 and coalesce(GlobalPrevalence,default_global_prevalence) < 200)
| where not(SignatureState =~ "Unknown" and coalesce(GlobalPrevalence,default_global_prevalence) > 30000) // Workaround for a bug in MDE that reports some valid MS signed files as 'Unknown'.
);
DeviceProcessEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType =~ "ProcessCreated"
| where SHA1 in~ ((unsignedHashes | project SHA1)) // This is for performance improvement.
| join kind=inner unsignedHashes on SHA1
| summarize arg_min(Timestamp, *) by DeviceId, FolderPath // Show only the first invocation per device.
Stages and Predicates
Parameters
let timeframe = 2*1h;
let default_global_prevalence = 0;
Let Binding: signedSystemFiles
let signedSystemFiles = dynamic(["aitstatic.exe", "ApplicationFrameHost.exe", "ApplyTrustOffline.exe", "AppVClient.exe", "AppVDllSurrogate.exe", "AppVNice.exe", "AppVShNotify.exe", "audiodg.exe", "AuthHost.exe", "backgroundTaskHost.exe", "bcdedit.exe", "bdeunlock.exe", "BioIso.exe", "bootsect.exe", "browser_broker.exe", "CameraSettingsUIHost.exe", "CastSrv.exe", "CExecSvc.exe", "changepk.exe", "ClipRenew.exe", "ClipUp.exe", "CloudExperienceHostBroker.exe", "CloudNotifications.exe", "cmdiag.exe", "CompatTelRunner.exe", "consent.exe", "convertvhd.exe", "CredentialEnrollmentManager.exe", "CredentialUIBroker.exe", "csrss.exe", "DataExchangeHost.exe", "DeviceCensus.exe", "Dism.exe", "DisplaySwitch.exe", "dllhost.exe", "DTUHandler.exe", "easinvoker.exe", "ErgonomicKBNotificationService.exe", "fontdrvhost.exe", "FsIso.exe", "fsutil.exe", "GenValObj.exe", "hcsdiag.exe", "hvax64.exe", "hvc.exe", "hvix64.exe", "hvsievaluator.exe", "hvsimgr.exe", "hvsirdpclient.exe", "hvsirpcd.exe", "HvsiSettingsWorker.exe", "iotstartup.exe", "LicensingUI.exe", "LockAppHost.exe", "LockScreenContentServer.exe", "LsaIso.exe", "lsass.exe", "mavinject.exe", "mfpmp.exe", "MRT.exe", "MusNotifyIcon.exe", "NDKPing.exe", "NgcIso.exe", "nmbind.exe", "nmscrub.exe", "ntoskrnl.exe", "nvspinfo.exe", "OpenWith.exe", "PasswordOnWakeSettingFlyout.exe", "phoneactivate.exe", "PickerHost.exe", "PktMon.exe", "ProximityUxHost.exe", "prproc.exe", "ResetEngine.exe", "RuntimeBroker.exe", "ScriptRunner.exe", "securekernel.exe", "SecurityHealthHost.exe", "SecurityHealthService.exe", "services.exe", "sessionmsg.exe", "SettingSyncHost.exe", "SgrmBroker.exe", "SgrmLpac.exe", "SIHClient.exe", "SlideToShutDown.exe", "smss.exe", "SndVol.exe", "spaceman.exe", "sppsvc.exe", "svchost.exe", "SyncAppvPublishingServer.exe", "SysResetErr.exe", "systemreset.exe", "SystemSettingsAdminFlows.exe", "SystemSettingsBroker.exe", "SystemSettingsRemoveDevice.exe", "taskhostw.exe", "Taskmgr.exe", "tcblaunch.exe", "ttdinject.exe", "tttracer.exe", "ucsvc.exe", "upfc.exe", "UserAccountBroker.exe", "verifier.exe", "vmcompute.exe", "VmComputeAgent.exe", "vmms.exe", "vmplatformca.exe", "vmsp.exe", "vmwp.exe", "wcsetupagent.exe", "WerFault.exe", "WerFaultSecure.exe", "wermgr.exe", "wifitask.exe", "wimserv.exe", "wininit.exe", "winload.exe", "winresume.exe", "wkspbroker.exe", "wlrmdr.exe", "WpcMon.exe", "wuauclt.exe", "WUDFCompanionHost.exe", "WWAHost.exe", "AdtAgent.exe", "appverif.exe", "iaStorAfsNative.exe", "iaStorAfsService.exe", "MCU.exe", "microsoft.windows.softwarelogo.showdesktop.exe", "MpSigStub.exe", "RtkAudUService64.exe", "TsWpfWrp.exe"]);
Let Binding: uniqueHashes
let uniqueHashes = materialize(
DeviceProcessEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType =~ "ProcessCreated"
| where FileName in~ (signedSystemFiles) and not(isempty(SHA1))
| extend SHA1=tolower(SHA1)
| summarize MachineCount=dcount(DeviceId) by SHA1
);
Let Binding: unsignedHashes
let unsignedHashes = materialize(
uniqueHashes
| top 1000 by MachineCount asc
| extend SHA1=tolower(SHA1)
| invoke FileProfile(SHA1, 1000)
| where not(ProfileAvailability =~ "Error")
| where IsCertificateValid != 1 or (IsRootSignerMicrosoft != 1 and coalesce(GlobalPrevalence,default_global_prevalence) < 200)
| where not(SignatureState =~ "Unknown" and coalesce(GlobalPrevalence,default_global_prevalence) > 30000)
);
Stage 1: source
let uniqueHashes
Stage 2: source
let unsignedHashes
Stage 3: source
DeviceProcessEvents
Stage 4: where
where ...
Stage 5: where
where ActionType =~ "ProcessCreated"
Stage 6: where
where /* macro: (SHA1 in~ (unsignedHashes | project)) */
Stage 7: join
join kind=inner (unsignedHashes) on SHA1
Stage 8: summarize
summarize by DeviceId, FolderPath
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"ActionType" kind:eq value:"ProcessCreated" |
FileName | in |
| field:"FileName" kind:in |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt value:"200" |
IsCertificateValid | ne |
| field:"IsCertificateValid" kind:ne value:"1" |
IsRootSignerMicrosoft | ne |
| field:"IsRootSignerMicrosoft" kind:ne value:"1" |
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
GlobalPrevalence | gt | 30000 | excludes:GlobalPrevalence field:"GlobalPrevalence" value:"30000" |
SignatureState | eq | Unknown | excludes:SignatureState field:"SignatureState" value:"Unknown" |
ProfileAvailability | eq | Error | excludes:ProfileAvailability field:"ProfileAvailability" value:"Error" |
SHA1 | is_null | excludes:SHA1 |
Output Fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceId | summarize |
FolderPath | summarize |