Detection rules › Kusto

Kusto rules: 37

Bitglass - User Agent string has changed for user

#
Status
available
Severity
medium
Time window
14d
Group by
User
Source
github.com/Azure/Azure-Sentinel

'Detects when User Agent string has changed for user.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
Usersummarize
uasummarize
AccountCustomEntityextend

BOLA Finding with API Traffic Anomaly

#
Status
available
Severity
high
Time window
75m
Group by
$left.ApplicationId, $right.ApiId, ApplicationId, StraTenantId
Source
github.com/Azure/Azure-Sentinel

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

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 used in Stage 7

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
ApplicationIdproject
DistinctCallersproject
ErrorCountproject
FindingCountproject
Pathsproject
RuleIdsproject
StraTenantIdproject
TimeGeneratedproject

Cross-Cloud Suspicious Compute resource creation in GCP

#
Severity
low
Time window
1d
Group by
$left.AWSNetworkEntity, $right.GCPUserIp
Source
github.com/Azure/Azure-Sentinel

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

Telemetry Coverage

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 used in Stage 6

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 =
ifSeverity >= 7.0"High"
elifSeverity >= 4.0 and Severity <= 6.9"Medium"
elifSeverity >= 1.0 and Severity <= 3.9"Low"
else"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.

FieldKindValuesSearch
AIPCallmatch
  • name transforms: term
field:"AIPCall" kind:match value:"name"
ActivityTypematch
  • Backdoor:EC2/DenialOfService.UnusualProtocol transforms: term
  • CredentialAccess:Kubernetes/MaliciousIPCaller transforms: term
  • CredentialAccess:Kubernetes/SuccessfulAnonymousAccess transforms: term
  • CredentialAccess:Kubernetes/TorIPCaller transforms: term
  • CredentialAccess:RDS/AnomalousBehavior.SuccessfulBruteForce transforms: term corpus 2 (kusto 2)
  • CredentialAccess:RDS/TorIPCaller.FailedLogin transforms: term corpus 2 (kusto 2)
  • CredentialAccess:RDS/TorIPCaller.SuccessfulLogin transforms: term corpus 2 (kusto 2)
  • Discovery:Kubernetes/MaliciousIPCaller transforms: term
  • Recon:IAMUser/MaliciousIPCaller.Custom transforms: term
  • UnauthorizedAccess:EC2/TorClient transforms: term
  • UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B transforms: term corpus 2 (kusto 2)
  • UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.InsideAWS transforms: term corpus 2 (kusto 2)
  • UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS transforms: term corpus 2 (kusto 2)
  • UnauthorizedAccess:IAMUser/MaliciousIPCaller.Custom transforms: term corpus 2 (kusto 2)
  • UnauthorizedAccess:IAMUser/TorIPCaller transforms: term corpus 2 (kusto 2)
field:"ActivityType" kind:match
GCPUserIpis_not_null
  • (no value, null check)
field:"GCPUserIp" kind:is_not_null
GCPUserIpne
  • private
field:"GCPUserIp" kind:ne value:"private"
MethodNameends_with
  • instances.insert
field:"MethodName" kind:ends_with value:"instances.insert"
ServiceNameeq
  • compute.googleapis.com
field:"ServiceName" kind:eq value:"compute.googleapis.com"
VMOperationeq
  • insert
field:"VMOperation" kind:eq value:"insert"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
GCPUserUPNmatchgserviceaccount.comexcludes:GCPUserUPN field:"GCPUserUPN" value:"gserviceaccount.com"

Output Fields

These fields are emitted when the rule matches.

FieldSource
AWSAlertDescriptionextend
AWSAlertIdextend
AWSAlertLinkextend
AWSAlertTimeextend
AWSAlertTitleextend
AWSAlertUserNameEntityextend
AWSNetworkEntityextend
AWSTargetingServiceextend
AWSresourceTypeextend
InstanceTypeextend
Severityextend

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.

Severity
medium
Time window
5m
Source
github.com/Azure/Azure-Sentinel

"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

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.

FieldKindExcluded valuesSearch
patterncontainsfile:hashesexcludes:pattern field:"pattern" value:"file:hashes"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ConfidenceScoreproject
Countryproject
Descriptionproject
Domainproject
IPAbuseproject
IPv4project
IPv6project
IndicatorIDproject
ProductNameproject
ProviderNameproject
RecommendedActionsproject
Rolesproject
SecurityVendorsproject
Sourcesproject
Tagsproject
ThreatActorsproject
ThreatTypeproject
TimeGeneratedproject
URLproject
createdproject
modifiedproject
nameproject
valid_fromproject

Detect Suspicious ncrypt.dll usage by CLI tool or unknown process

#
Author
Robbe Van den Daele
Source
github.com/HybridBrothers/Hunting-Queries-Detection-Rules

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

References

Telemetry Coverage

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.

Detect Windows Update Disabled from Registry

#
Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
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 used in Stage 3

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.

FieldKindValuesSearch
EventTypein
  • RegistryKeyCreated corpus 5 (kusto 5)
  • RegistryValueSet corpus 5 (kusto 5)
field:"EventType" kind:in
RegistryKeymatch
  • HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate transforms: term
  • HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate\AU transforms: term
field:"TargetObject" kind:match
RegistryValuematch
  • AUOptions transforms: term
  • NoAutoUpdate transforms: term
field:"TargetObject" kind:match
RegistryValueDataeq
  • 1 corpus 3 (elastic 2, kusto 1)
field:"Details" kind:eq value:"1"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ActingProcessCommandLineproject
ActingProcessIdproject
ActingProcessNameproject
ActorUsernameproject
ActorUsernameTypeproject
DvcHostnameproject
RegistryKeyproject
RegistryValueproject
RegistryValueDataproject
RegistryValueTypeproject
TimeGeneratedproject
HostNameextend
DnsDomainextend
Usernameextend
NTDomainextend
UPNSuffixextend
RegHiveextend
RegKeyextend

Lumen TI IPAddress in OfficeActivity

#
Severity
medium
Time window
14d
Group by
$left.TI_ipEntity, $right.OA_ipEntity, Id, OA_ipEntity
Source
github.com/Azure/Azure-Sentinel

This query maps Lumen IP indicators of compromise (IOCs) from threat intelligence (TI), by searching for matches in OfficeActivity.

MITRE ATT&CK Coverage

TacticTechniques
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.

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
TI_ipEntitycidr_match10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8excludes:TI_ipEntity
TI_ipEntitystarts_with127.excludes:TI_ipEntity field:"TI_ipEntity" value:"127."
TI_ipEntitystarts_with::excludes:TI_ipEntity field:"TI_ipEntity" value:"::"
TI_ipEntitystarts_withfe80excludes:TI_ipEntity field:"TI_ipEntity" value:"fe80"

Output Fields

These fields are emitted when the rule matches.

FieldSource
ClientIPproject
Confidenceproject
EndTimeproject
Idproject
OA_ipEntityproject
Operationproject
StartTimeproject
TI_ipEntityproject
Tagsproject
Typeproject
UserIdproject
ValidUntilproject
timestampproject

OracleDBAudit - Multiple tables dropped in short time

#
Status
available
Severity
medium
Time window
30m
Group by
DstUserName
Source
github.com/Azure/Azure-Sentinel

'Detects when user drops many tables in short period of time.'

MITRE ATT&CK Coverage

TacticTechniques
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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
DstUserNamesummarize
tbl_countsummarize
AccountCustomEntityextend

Preview - TI map URL entity to Cloud App Events

#
Severity
medium
Time window
14d
Group by
IndicatorId, Url
Source
github.com/Azure/Azure-Sentinel

Identifies compromises and attacks and detect malicious activities in one's URL entity from TI

MITRE ATT&CK Coverage

TacticTechniques
Command & Control

Telemetry Coverage

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.

FieldKindValuesSearch
Activeeq
  • true corpus 60 (kusto 60)
field:"Active" kind:eq value:"true"
Urlis_not_null
  • (no value, null check)
field:"Url" kind:is_not_null

Output Fields

These fields are emitted when the rule matches.

FieldSource
AccountDisplayNameproject
AccountObjectIdproject
Applicationproject
ApplicationIDproject
CloudApps_TimeGeneratedproject
ConfidenceScoreproject
Descriptionproject
ExpirationDateTimeproject
IPAddressproject
IndicatorIdproject
ObjectNameproject
ThreatTypeproject
Urlproject
userPrincipalNameproject

ProofpointPOD - Multiple protected emails to unknown recipient

#
Status
available
Severity
medium
Time window
30m
Group by
SrcUserUpn, commcol
Source
github.com/Azure/Azure-Sentinel

'Detects when multiple protected messages where sent to early not seen recipient.'

MITRE ATT&CK Coverage

TacticTechniques
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 used in Stage 10

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.

Output Fields

These fields are emitted when the rule matches.

FieldSource
SrcUserUpnsummarize

SailPointIdentityNowFailedEventsBasedOnTime

#
Status
available
Severity
high
Time window
14d
Source
github.com/Azure/Azure-Sentinel

'Detects failed events based on created time.'

MITRE ATT&CK Coverage

TacticTechniques
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.

FieldKindValuesSearch
Statuseq
  • FAILED
field:"Status" kind:eq value:"FAILED"

SAP BTP - User added to sensitive privileged role collection

#
Status
available
Severity
low
Time window
15m
Source
github.com/Azure/Azure-Sentinel

Identifies identity management actions whereby a user is added to a set of monitored privileged role collections.

MITRE ATT&CK Coverage

TacticTechniques
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 used in Stage 6

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.

FieldKindValuesSearch
Message.objectmatch
  • xs_rolecollection2user transforms: term, kusto_dynamic_path
field:"Message.object" kind:match value:"xs_rolecollection2user"
ObjectId.crudTypeeq
  • CREATE transforms: kusto_dynamic_path
field:"ObjectId.crudType" kind:eq value:"CREATE"
RoleCollectionin
  • Cloud Connector Administrator
  • Connectivity and Destination Administrator
  • Destination Administrator
  • Subaccount Administrator
  • Subaccount Service Administrator
field:"RoleCollection" kind:in

Output Fields

These fields are emitted when the rule matches.

FieldSource
CloudAppproject
RoleCollectionproject
SpaceIdproject
TargetUserIdproject
Tenantproject
UpdatedOnproject
UserNameproject
AccountNameextend
UPNSuffixextend

Unsigned Windows System Binary

#
Group by
DeviceId, FolderPath, SHA1
Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

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 used in Stage 1

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 used in Stages 2, 6, 7

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.

FieldKindValuesSearch
ActionTypeeq
  • ProcessCreated corpus 4 (kusto 4)
field:"ActionType" kind:eq value:"ProcessCreated"
FileNamein
  • AdtAgent.exe
  • AppVClient.exe
  • AppVDllSurrogate.exe
  • AppVNice.exe
  • AppVShNotify.exe
  • ApplicationFrameHost.exe
  • ApplyTrustOffline.exe
  • AuthHost.exe
  • BioIso.exe
  • CExecSvc.exe
  • CameraSettingsUIHost.exe
  • CastSrv.exe
  • ClipRenew.exe
  • ClipUp.exe
  • CloudExperienceHostBroker.exe
  • CloudNotifications.exe
  • CompatTelRunner.exe
  • CredentialEnrollmentManager.exe
  • CredentialUIBroker.exe
  • DTUHandler.exe
  • DataExchangeHost.exe
  • DeviceCensus.exe
  • Dism.exe
  • DisplaySwitch.exe
  • ErgonomicKBNotificationService.exe
  • FsIso.exe
  • GenValObj.exe
  • HvsiSettingsWorker.exe
  • LicensingUI.exe
  • LockAppHost.exe
  • LockScreenContentServer.exe
  • LsaIso.exe
  • MCU.exe
  • MRT.exe
  • MpSigStub.exe
  • MusNotifyIcon.exe
  • NDKPing.exe
  • NgcIso.exe
  • OpenWith.exe
  • PasswordOnWakeSettingFlyout.exe
  • +97 more values (see full rule source)
field:"FileName" kind:in
GlobalPrevalencelt
  • 200 transforms: coalesce_default:0
field:"GlobalPrevalence" kind:lt value:"200"
IsCertificateValidne
  • 1
field:"IsCertificateValid" kind:ne value:"1"
IsRootSignerMicrosoftne
  • 1
field:"IsRootSignerMicrosoft" kind:ne value:"1"

Exclusions

The rule actively suppresses these predicates.

FieldKindExcluded valuesSearch
GlobalPrevalencegt30000excludes:GlobalPrevalence field:"GlobalPrevalence" value:"30000"
SignatureStateeqUnknownexcludes:SignatureState field:"SignatureState" value:"Unknown"
ProfileAvailabilityeqErrorexcludes:ProfileAvailability field:"ProfileAvailability" value:"Error"
SHA1is_null(no value, null check)excludes:SHA1

Output Fields

These fields are emitted when the rule matches.

FieldSource
DeviceIdsummarize
FolderPathsummarize

Back to all Kusto rules