Detection rules › Kusto

Netskope - Suspicious Application Activity (Low Confidence / Risky App)

Status
available
Severity
medium
Time window
1h
Group by
DeviceClassification, Hostname, User, Userip
Source
github.com/Azure/Azure-Sentinel

Detects activity involving risky or low Cloud Confidence Level (CCL) applications, blocked application actions, or sensitive activities (upload, share, download) on unsanctioned apps. Helps surface Shadow IT and potential data leakage via risky cloud applications.

MITRE ATT&CK coverage

TacticTechniques
Command & Control
Exfiltration

Rule body

id: b2e7d3c5-9c46-4f2a-8d1b-3e5f7a9c2d41
name: Netskope - Suspicious Application Activity (Low Confidence / Risky App)
description: |
  Detects activity involving risky or low Cloud Confidence Level (CCL) applications,
  blocked application actions, or sensitive activities (upload, share, download) on
  unsanctioned apps. Helps surface Shadow IT and potential data leakage via risky
  cloud applications.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: NetskopeAlertEventsConnector
    dataTypes:
      - NetskopeAlertEvents_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - CommandAndControl
relevantTechniques:
  - T1567
  - T1102
query: |
  let riskyCcl = dynamic(["low", "poor"]);
  let sensitiveActivities = dynamic(["Upload", "Share", "Download", "Post", "Send"]);
  NetskopeAlertEvents_CL
  | where TimeGenerated > ago(1h)
  | where isnotempty(App)
  | where tolower(Ccl) in (riskyCcl)
       or Action =~ "block"
       or (Activity in~ (sensitiveActivities) and tolower(Ccl) != "excellent")
  | summarize
      EventCount = count(),
      Activities = make_set(Activity, 20),
      Actions = make_set(Action, 10),
      CCLs = make_set(Ccl, 10),
      Categories = make_set(Appcategory, 20),
      DistinctApps = dcount(App),
      Apps = make_set(App, 20),
      LastSeen = max(TimeGenerated)
      by User, Userip, Hostname, DeviceClassification
  | where EventCount > 5 or DistinctApps > 3
  | extend RiskIndicators = strcat_array(array_concat(
      iff(set_has_element(CCLs, "low") or set_has_element(CCLs, "poor"), dynamic(["Low Confidence App"]), dynamic([])),
      iff(set_has_element(Actions, "block"), dynamic(["Blocked Activity"]), dynamic([])),
      iff(DistinctApps > 3, dynamic(["Multiple Risky Apps"]), dynamic([]))
  ), ", ")
  | order by EventCount desc
  | project
      LastSeen,
      User,
      Userip,
      Hostname,
      DeviceClassification,
      EventCount,
      DistinctApps,
      Apps,
      Categories,
      Activities,
      Actions,
      CCLs,
      RiskIndicators
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: User
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: Userip
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: Hostname
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let riskyCcl = dynamic(["low", "poor"]);
let sensitiveActivities = dynamic(["Upload", "Share", "Download", "Post", "Send"]);

Stage 1: source

NetskopeAlertEvents_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where isnotempty(App)

Stage 4: where

| where tolower(Ccl) in (riskyCcl)
     or Action =~ "block"
     or (Activity in~ (sensitiveActivities) and tolower(Ccl) != "excellent")

Stage 5: summarize

| summarize
    EventCount = count(),
    Activities = make_set(Activity, 20),
    Actions = make_set(Action, 10),
    CCLs = make_set(Ccl, 10),
    Categories = make_set(Appcategory, 20),
    DistinctApps = dcount(App),
    Apps = make_set(App, 20),
    LastSeen = max(TimeGenerated)
    by User, Userip, Hostname, DeviceClassification

Stage 6: where

| where EventCount > 5 or DistinctApps > 3

Stage 7: extend

| extend RiskIndicators = strcat_array(array_concat(
    iff(set_has_element(CCLs, "low") or set_has_element(CCLs, "poor"), dynamic(["Low Confidence App"]), dynamic([])),
    iff(set_has_element(Actions, "block"), dynamic(["Blocked Activity"]), dynamic([])),
    iff(DistinctApps > 3, dynamic(["Multiple Risky Apps"]), dynamic([]))
), ", ")

Stage 8: sort

| order by EventCount desc

Stage 9: project

| project
    LastSeen,
    User,
    Userip,
    Hostname,
    DeviceClassification,
    EventCount,
    DistinctApps,
    Apps,
    Categories,
    Activities,
    Actions,
    CCLs,
    RiskIndicators

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Actioneq
  • block
field:"Action" kind:eq value:"block"
Activityin
  • Download
  • Post
  • Send
  • Share
  • Upload
field:"Activity" kind:in
Appis_not_null
  • (no value, null check)
field:"App" kind:is_not_null
Cclin
  • low transforms: tolower
  • poor transforms: tolower
field:"Ccl" kind:in
Cclne
  • excellent transforms: tolower
field:"Ccl" kind:ne value:"excellent"
DistinctAppsgt
  • 3
field:"DistinctApps" kind:gt value:"3"
EventCountgt
  • 5
field:"EventCount" kind:gt value:"5"

Output fields

These fields are emitted when the rule matches.

FieldSource
Actionsproject
Activitiesproject
Appsproject
CCLsproject
Categoriesproject
DeviceClassificationproject
DistinctAppsproject
EventCountproject
Hostnameproject
LastSeenproject
RiskIndicatorsproject
Userproject
Useripproject