Query Details

NK Netskope IA Hunting Queries

Query

// =============================================================================
// Netskope Internet Access - Threat Hunting Queries
// Vendor: Netskope
// Table: NetskopeEvents_CL (Netskope Internet Access - Built-in Data Connector)
// Generated: 2026-04-16
// Queries: 19 | HQ50–HQ61 mirror HQ38–HQ49 for NetskopeEvents_CL
//              | HQ62–HQ68 cover new Internet Access detections
// =============================================================================

// =============================================================================
// HQ50 - Netskope BI - Blocked Requests by Malicious Category
// MITRE Techniques: T1071
// Tactics        : CommandAndControl
// Description    : Surfaces all Netskope blocked web requests for malware/phishing/C2/botnet categories (NetskopeEvents_CL).
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
let MaliciousCategories = dynamic([
    "Malware", "Phishing", "Botnet", "Command and Control",
    "Spyware/Adware", "Ransomware", "Cryptomining",
    "Newly Observed Domain", "Newly Registered Domain"]);
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(24h)
| where action_s in ("block", "Block", "blocked", "Blocked")
| where category_s in (MaliciousCategories)
    or severity_s in ("high", "critical")
    or isnotempty(malware_name_s)
| summarize
    BlockCount       = count(),
    UniqueUsers      = dcount(user_s),
    UserList         = make_set(user_s, 20),
    DestDomains      = make_set(domain_s, 20),
    DestIPs          = make_set(dstip_s, 10),
    Categories       = make_set(category_s, 10),
    MalwareNames     = make_set(malware_name_s, 10),
    FirstSeen        = min(TimeGenerated),
    LastSeen         = max(TimeGenerated)
  by category_s, domain_s
| order by BlockCount desc

// =============================================================================
// HQ51 - Netskope BI - DLP Violations Deep Dive
// MITRE Techniques: T1048, T1567
// Tactics        : Exfiltration
// Description    : Surfaces all Netskope DLP policy violations with detailed rule/profile analysis (NetskopeEvents_CL).
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(7d)
| where isnotempty(dlp_rule_s) or isnotempty(dlp_profile_s)
| where isnotempty(user_s)
| summarize
    ViolationCount    = count(),
    MBUploaded        = round(sum(todouble(bytes_uploaded_d)) / 1048576, 2),
    UniqueFiles       = dcount(object_s),
    FileNames         = make_set(object_s, 10),
    FileTypes         = make_set(file_type_s, 10),
    DestDomains       = make_set(domain_s, 10),
    Apps              = make_set(app_s, 10),
    Actions           = make_set(action_s, 5),
    DLPProfiles       = make_set(dlp_profile_s, 5),
    FirstViolation    = min(TimeGenerated),
    LastViolation     = max(TimeGenerated)
  by user_s, dlp_rule_s
| order by ViolationCount desc, MBUploaded desc

// =============================================================================
// HQ52 - Netskope BI - Allowed Traffic to TI-Listed Domains/IPs
// MITRE Techniques: T1071, T1568
// Tactics        : CommandAndControl
// Description    : Correlates Netskope allowed traffic against active TI feed entries (NetskopeEvents_CL).
// =============================================================================
let TI_IOCs =
    ThreatIntelIndicators
    | where TimeGenerated > ago(30d)
    | where isempty(ValidUntil) or ValidUntil > now()
    | where (isnotnull(parse_ipv4(ObservableValue)) or ObservableKey has "domain")
    | where isnotempty(ObservableValue)
    | summarize
        TI_ThreatTypes = make_set(Tags),
        TI_Confidence  = max(Confidence),
        TI_Tags        = make_set(Tags)
      by IOC_Value = ObservableValue;
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(24h)
| where action_s !in ("block", "Block", "blocked", "Blocked")
| where isnotempty(domain_s) or isnotempty(dstip_s)
| extend MatchKey = coalesce(domain_s, tostring(dstip_s))
| summarize
    RequestCount     = count(),
    UniqueUsers      = dcount(user_s),
    UserList         = make_set(user_s, 10),
    BytesRecv        = sum(todouble(bytes_downloaded_d)),
    BytesSent        = sum(todouble(bytes_uploaded_d)),
    Apps             = make_set(app_s, 5),
    FirstSeen        = min(TimeGenerated),
    LastSeen         = max(TimeGenerated)
  by MatchKey, domain_s, dstip_s
| join kind=inner TI_IOCs on $left.MatchKey == $right.IOC_Value
| project
    MatchKey, domain_s, dstip_s,
    RequestCount, UniqueUsers, UserList,
    BytesRecv, BytesSent, Apps,
    TI_ThreatTypes, TI_Confidence, TI_Tags,
    FirstSeen, LastSeen
| order by TI_Confidence desc, RequestCount desc

// =============================================================================
// HQ53 - Netskope BI - Off-Hours Activity Profiling
// MITRE Techniques: T1029, T1567
// Tactics        : Exfiltration
// Description    : Profiles user web activity outside business hours (NetskopeEvents_CL).
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(7d)
| where isnotempty(user_s)
| where action_s !in ("block", "Block", "blocked", "Blocked")
| extend
    HourOfDay  = hourofday(TimeGenerated),
    DayOfWeek  = dayofweek(TimeGenerated)
| where (HourOfDay >= 20 or HourOfDay < 6)
    or (DayOfWeek == 6d or DayOfWeek == 0d)
| summarize
    RequestCount    = count(),
    TotalMBUploaded = round(sum(todouble(bytes_uploaded_d)) / 1048576, 2),
    TotalMBDownload = round(sum(todouble(bytes_downloaded_d)) / 1048576, 2),
    UniqueDomains   = dcount(domain_s),
    TopDomains      = make_set(domain_s, 10),
    TopApps         = make_set(app_s, 10),
    UniqueCategories = dcount(category_s),
    SourceIPs       = make_set(srcip_s, 5),
    ActiveDays      = dcount(startofday(TimeGenerated)),
    FirstSeen       = min(TimeGenerated),
    LastSeen        = max(TimeGenerated)
  by user_s
| where TotalMBUploaded > 50 or TotalMBDownload > 200 or RequestCount > 2000
| order by TotalMBUploaded desc, RequestCount desc

// =============================================================================
// HQ54 - Netskope BI - Phishing Campaign Detection - Multi-User Domain Access
// MITRE Techniques: T1566, T1566.002
// Tactics        : InitialAccess
// Description    : Identifies phishing campaigns by detecting multiple users hitting the same phishing domain (NetskopeEvents_CL).
// =============================================================================
let PhishingCategories = dynamic([
    "Phishing", "Phishing and Other Frauds", "Newly Observed Domain",
    "Newly Registered Domain", "Suspicious", "Malware"]);
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(24h)
| where category_s in (PhishingCategories)
    or severity_s in ("high", "critical")
| where isnotempty(user_s) and isnotempty(domain_s)
| summarize
    UniqueUsers      = dcount(user_s),
    UserList         = make_set(user_s, 50),
    TotalRequests    = count(),
    Blocked          = countif(action_s in ("block", "Block", "blocked", "Blocked")),
    Allowed          = countif(action_s !in ("block", "Block", "blocked", "Blocked")),
    Categories       = make_set(category_s, 5),
    URLSamples       = make_set(url_s, 10),
    SourceIPs        = make_set(srcip_s, 20),
    FirstSeen        = min(TimeGenerated),
    LastSeen         = max(TimeGenerated)
  by domain_s
| where UniqueUsers >= 2
| extend CampaignDurationMinutes = datetime_diff('minute', LastSeen, FirstSeen)
| order by UniqueUsers desc, Allowed desc

// =============================================================================
// HQ55 - Netskope BI - Threat Severity Allowed Traffic Analysis
// MITRE Techniques: T1071, T1204
// Tactics        : CommandAndControl, Execution
// Description    : Analyzes traffic Netskope flagged with high/critical severity but allowed through (NetskopeEvents_CL).
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(24h)
| where action_s !in ("block", "Block", "blocked", "Blocked")
| where severity_s in ("high", "critical")
    or isnotempty(malware_name_s)
    or isnotempty(threat_name_s)
| summarize
    RequestCount     = count(),
    UniqueUsers      = dcount(user_s),
    UserList         = make_set(user_s, 20),
    Domains          = make_set(domain_s, 20),
    ThreatNames      = make_set(threat_name_s, 10),
    MalwareNames     = make_set(malware_name_s, 10),
    Categories       = make_set(category_s, 10),
    Apps             = make_set(app_s, 10),
    TotalMBRecv      = round(sum(todouble(bytes_downloaded_d)) / 1048576, 2),
    TotalMBSent      = round(sum(todouble(bytes_uploaded_d)) / 1048576, 2),
    FirstSeen        = min(TimeGenerated),
    LastSeen         = max(TimeGenerated)
  by severity_s
| order by RequestCount desc

// =============================================================================
// HQ56 - Netskope BI - ATP/Sandbox Detections Summary
// MITRE Techniques: T1105, T1204.002
// Tactics        : Execution, CommandAndControl
// Description    : Summarizes all malware/threat detections from Netskope ATP/sandbox (NetskopeEvents_CL).
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(7d)
| where isnotempty(malware_name_s) or isnotempty(malware_type_s)
    or isnotempty(threat_name_s)
| summarize
    DetectionCount   = count(),
    UniqueUsers      = dcount(user_s),
    UniqueFiles      = dcount(object_s),
    Users            = make_set(user_s, 20),
    FileNames        = make_set(object_s, 20),
    FileTypes        = make_set(file_type_s, 10),
    MalwareTypes     = make_set(malware_type_s, 10),
    ThreatNames      = make_set(threat_name_s, 10),
    Domains          = make_set(domain_s, 10),
    Apps             = make_set(app_s, 10),
    Actions          = make_set(action_s, 5),
    Blocked          = countif(action_s in ("block", "Block", "blocked", "Blocked")),
    Allowed          = countif(action_s !in ("block", "Block", "blocked", "Blocked")),
    FirstSeen        = min(TimeGenerated),
    LastSeen         = max(TimeGenerated)
  by malware_name_s
| order by Allowed desc, DetectionCount desc

// =============================================================================
// HQ57 - Netskope BI - Tunnel/Proxy/VPN Bypass Hunting
// MITRE Techniques: T1090, T1572
// Tactics        : CommandAndControl, DefenseEvasion
// Description    : Hunts for users attempting to access VPN, proxy, anonymizer, or tunneling services (NetskopeEvents_CL).
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
let BypassCategories = dynamic([
    "Proxy Avoidance", "Anonymizers", "VPN",
    "Remote Access", "Tunneling", "Tor",
    "P2P File Sharing", "Web Proxy"]);
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(7d)
| where isnotempty(user_s)
| where category_s in (BypassCategories)
    or domain_s has_any ("torproject.org", "psiphon", "ultrasurf",
        "nordvpn", "expressvpn", "protonvpn", "mullvad.net",
        "windscribe", "surfshark", "cyberghost")
| summarize
    RequestCount     = count(),
    UniqueApps       = dcount(app_s),
    Apps             = make_set(app_s, 10),
    Categories       = make_set(category_s, 10),
    Domains          = make_set(domain_s, 20),
    Actions          = make_set(action_s, 5),
    Blocked          = countif(action_s in ("block", "Block", "blocked", "Blocked")),
    Allowed          = countif(action_s !in ("block", "Block", "blocked", "Blocked")),
    SourceIPs        = make_set(srcip_s, 10),
    ActiveDays       = dcount(startofday(TimeGenerated)),
    FirstSeen        = min(TimeGenerated),
    LastSeen         = max(TimeGenerated)
  by user_s
| order by Allowed desc, RequestCount desc

// =============================================================================
// HQ58 - Netskope BI - Visibility Loss - Users Gone Silent
// MITRE Techniques: T1562, T1562.001
// Tactics        : DefenseEvasion
// Description    : Detects users with baseline Netskope activity who stopped generating traffic (NetskopeEvents_CL).
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
let baselineWindow = 7d;
let recentWindow   = 4h;
let minBaselineReqsPerDay = 20;
let BaselineUsers =
    union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
    | where TimeGenerated between (ago(baselineWindow) .. ago(recentWindow))
    | where isnotempty(user_s)
    | summarize
        BaselineRequestsPerDay = round(toreal(count()) / (baselineWindow / 1d), 1),
        BaselineApps           = make_set(app_s, 10),
        BaselineIPs            = make_set(srcip_s, 5),
        BaselineCategories     = make_set(category_s, 10)
      by user_s
    | where BaselineRequestsPerDay >= minBaselineReqsPerDay;
let RecentActiveUsers =
    union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
    | where TimeGenerated > ago(recentWindow)
    | where isnotempty(user_s)
    | distinct user_s;
BaselineUsers
| join kind=leftanti RecentActiveUsers on user_s
| project
    user_s, BaselineRequestsPerDay, BaselineApps, BaselineIPs, BaselineCategories
| order by BaselineRequestsPerDay desc

// =============================================================================
// HQ59 - Netskope BI - LOTL C2 via Legitimate Platforms
// MITRE Techniques: T1102, T1105
// Tactics        : CommandAndControl
// Description    : Hunts for abuse of GitHub, Pastebin, Google Docs, Discord as C2 staging (NetskopeEvents_CL).
// =============================================================================
let LotLDomains = dynamic([
    "raw.githubusercontent.com", "gist.githubusercontent.com", "gist.github.com",
    "pastebin.com", "paste.ee", "hastebin.com", "ghostbin.com", "rentry.co",
    "docs.google.com", "drive.google.com",
    "cdn.discordapp.com", "media.discordapp.net",
    "onedrive.live.com", "1drv.ms", "transfer.sh",
    "anonfiles.com", "gofile.io", "file.io"]);
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(24h)
| where isnotempty(domain_s)
| where domain_s has_any (LotLDomains)
| where isnotempty(user_s)
| where action_s !in ("block", "Block", "blocked", "Blocked")
| summarize
    RequestCount     = count(),
    TotalBytesRecv   = sum(todouble(bytes_downloaded_d)),
    TotalBytesSent   = sum(todouble(bytes_uploaded_d)),
    UniqueURLs       = dcount(url_s),
    URLSamples       = make_set(url_s, 10),
    FileTypes        = make_set(file_type_s, 5),
    SourceIPs        = make_set(srcip_s, 5),
    FirstSeen        = min(TimeGenerated),
    LastSeen         = max(TimeGenerated)
  by user_s, domain_s
| extend TotalMBReceived = round(toreal(TotalBytesRecv) / 1048576, 2)
| where TotalMBReceived > 10 or RequestCount > 50
| project
    user_s, domain_s,
    RequestCount, TotalMBReceived, TotalBytesSent,
    URLSamples, FileTypes,
    FirstSeen, LastSeen
| order by TotalMBReceived desc, RequestCount desc

// =============================================================================
// HQ60 - Netskope BI + SecurityAlert Cross-Source Corroboration
// MITRE Techniques: T1071, T1078
// Tactics        : CommandAndControl
// Description    : Correlates Netskope C2 blocks with Defender/Sentinel security alerts on same host (NetskopeEvents_CL).
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
let NetskopeBlocks =
    union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
    | where TimeGenerated > ago(24h)
    | where action_s in ("block", "Block", "blocked", "Blocked")
    | where category_s has_any (
        "Malware", "Phishing", "Botnet", "Command and Control",
        "Ransomware", "Cryptomining", "Spyware/Adware")
        or isnotempty(malware_name_s)
    | summarize
        NK_BlockCount    = count(),
        NK_DestDomains   = make_set(domain_s, 10),
        NK_Categories    = make_set(category_s, 5),
        NK_MalwareNames  = make_set(malware_name_s, 5),
        NK_Users         = make_set(user_s, 5),
        NK_FirstSeen     = min(TimeGenerated)
      by NK_SrcIP = srcip_s;
SecurityAlert
| where TimeGenerated > ago(24h)
| where AlertSeverity in ("High", "Medium")
| mv-expand todynamic(Entities)
| extend
    EntityIP   = tostring(Entities.Address),
    EntityHost = tostring(Entities.HostName)
| where isnotempty(EntityIP)
| summarize
    Alert_Count      = count(),
    Alert_Names      = make_set(AlertName, 5),
    Alert_Products   = make_set(ProductName, 5),
    Alert_Severity   = make_set(AlertSeverity, 3),
    Alert_FirstSeen  = min(TimeGenerated)
  by EntityIP, EntityHost
| join kind=inner NetskopeBlocks on $left.EntityIP == $right.NK_SrcIP
| project
    NK_SrcIP, EntityHost,
    NK_BlockCount, NK_DestDomains, NK_Categories, NK_Users,
    Alert_Count, Alert_Names, Alert_Products, Alert_Severity,
    NK_FirstSeen, Alert_FirstSeen
| order by NK_BlockCount desc, Alert_Count desc

// =============================================================================
// HQ61 - Netskope BI - Low & Slow Exfiltration (Statistical Z-Score)
// MITRE Techniques: T1041, T1567.002
// Tactics        : Exfiltration
// Description    : Detects exfiltration via small sessions across cloud channels using Z-score (NetskopeEvents_CL).
// =============================================================================
let HuntWindow = 7d;
let ExfilCategories = dynamic([
    "Cloud Storage", "File Sharing", "Online Storage and Backup",
    "Personal Sites & Blogs", "Webmail", "Social Networking"]);
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
let Daily =
    union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
    | where TimeGenerated > ago(HuntWindow)
    | where action_s !in ("block", "Block", "blocked", "Blocked")
    | where isnotempty(user_s)
    | where category_s in (ExfilCategories) or activity_s == "Upload"
    | summarize
        DaySentBytes    = sum(todouble(bytes_uploaded_d)),
        DayRequestCount = count(),
        DayDestCount    = dcount(domain_s)
      by user_s, Day = bin(TimeGenerated, 1d);
let Users =
    Daily
    | summarize
        TotalMBSent   = round(toreal(sum(DaySentBytes)) / 1048576, 2),
        ActiveDays    = dcount(Day),
        TotalRequests = sum(DayRequestCount),
        UniqueDestCnt = sum(DayDestCount)
      by user_s;
let globalAvg = toscalar(Users | summarize avg(TotalMBSent));
let globalStd = toscalar(Users | summarize stdev(TotalMBSent));
Users
| extend
    Zscore = iff(globalStd > 0,
                 round((TotalMBSent - globalAvg) / globalStd, 2),
                 0.0)
| where Zscore >= 2.5
    or (ActiveDays >= 5 and TotalMBSent > 50)
| project
    user_s,
    TotalMBSent, ActiveDays, TotalRequests, UniqueDestCnt,
    Zscore
| order by Zscore desc, TotalMBSent desc

// =============================================================================
// HQ62 - Netskope IA - Unauthorized Cloud App Access (Shadow IT)
// MITRE Techniques: T1567, T1537
// Tactics        : Exfiltration, DefenseEvasion
// Description    : Hunts for users accessing unsanctioned cloud apps with low Cloud Confidence Level.
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
let RiskyCCL = dynamic(["poor", "low", "unknown"]);
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(7d)
| where isnotempty(user_s) and isnotempty(app_s)
| where action_s !in ("block", "Block", "blocked", "Blocked")
| where ccl_s in (RiskyCCL) or isempty(ccl_s)
| where category_s !in ("Business", "Technology", "Government", "Education")
| summarize
    RequestCount      = count(),
    TotalMBUploaded   = round(sum(todouble(bytes_uploaded_d)) / 1048576, 2),
    TotalMBDownloaded = round(sum(todouble(bytes_downloaded_d)) / 1048576, 2),
    UniqueApps        = dcount(app_s),
    Apps              = make_set(app_s, 20),
    Categories        = make_set(category_s, 10),
    Domains           = make_set(domain_s, 20),
    CCLValues         = make_set(ccl_s, 5),
    AccessMethods     = make_set(access_method_s, 5),
    ActiveDays        = dcount(startofday(TimeGenerated)),
    FirstSeen         = min(TimeGenerated),
    LastSeen          = max(TimeGenerated)
  by user_s
| where UniqueApps >= 2 or TotalMBUploaded > 25
| order by UniqueApps desc, TotalMBUploaded desc

// =============================================================================
// HQ63 - Netskope IA - Impossible Travel Detection
// MITRE Techniques: T1078
// Tactics        : InitialAccess, CredentialAccess
// Description    : Hunts for users accessing from multiple countries within a short window.
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(24h)
| where isnotempty(user_s) and isnotempty(src_country_s)
| where src_country_s != "Unknown" and src_country_s != ""
| summarize
    Countries        = make_set(src_country_s, 10),
    CountryCount     = dcount(src_country_s),
    SourceIPs        = make_set(srcip_s, 20),
    UniqueIPs        = dcount(srcip_s),
    RequestCount     = count(),
    Apps             = make_set(app_s, 10),
    AccessMethods    = make_set(access_method_s, 5),
    FirstSeen        = min(TimeGenerated),
    LastSeen         = max(TimeGenerated)
  by user_s, bin(TimeGenerated, 2h)
| where CountryCount >= 2
| extend TravelWindowMinutes = datetime_diff('minute', LastSeen, FirstSeen)
| project
    user_s, Countries, CountryCount, SourceIPs, UniqueIPs,
    RequestCount, Apps, AccessMethods,
    TravelWindowMinutes, FirstSeen, LastSeen
| order by CountryCount desc, UniqueIPs desc

// =============================================================================
// HQ64 - Netskope IA - Credential Phishing Submission Hunting
// MITRE Techniques: T1566, T1056
// Tactics        : InitialAccess, CredentialAccess
// Description    : Hunts for data submissions (POST/Upload) to phishing-categorized domains.
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
let PhishingCategories = dynamic([
    "Phishing", "Phishing and Other Frauds", "Suspicious",
    "Newly Observed Domain", "Newly Registered Domain"]);
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(7d)
| where isnotempty(user_s) and isnotempty(domain_s)
| where category_s in (PhishingCategories) or severity_s in ("high", "critical")
| where activity_s has_any ("Upload", "Post", "Submit", "Login", "FormSubmit")
    or todouble(bytes_uploaded_d) > 100
| summarize
    SubmissionCount   = count(),
    TotalBytesPosted  = sum(todouble(bytes_uploaded_d)),
    UniqueURLs        = dcount(url_s),
    URLSamples        = make_set(url_s, 10),
    Categories        = make_set(category_s, 5),
    Activities        = make_set(activity_s, 5),
    SourceIPs         = make_set(srcip_s, 5),
    DstCountries      = make_set(dst_country_s, 5),
    Actions           = make_set(action_s, 5),
    FirstSeen         = min(TimeGenerated),
    LastSeen          = max(TimeGenerated)
  by user_s, domain_s
| order by SubmissionCount desc

// =============================================================================
// HQ65 - Netskope IA - Suspicious File Download from Uncategorized Domains
// MITRE Techniques: T1105, T1204.002
// Tactics        : Execution, CommandAndControl
// Description    : Hunts for file downloads from newly registered, uncategorized, or suspicious domains.
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
let SuspiciousCategories = dynamic([
    "Uncategorized", "Unknown", "Newly Observed Domain",
    "Newly Registered Domain", "Suspicious", "Parked",
    "Dynamic DNS Host"]);
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(7d)
| where isnotempty(user_s) and isnotempty(domain_s)
| where activity_s has_any ("Download", "download") or todouble(bytes_downloaded_d) > 1048576
| where category_s in (SuspiciousCategories)
| where isnotempty(file_type_s) or isnotempty(object_s)
| summarize
    DownloadCount     = count(),
    TotalMBDownloaded = round(sum(todouble(bytes_downloaded_d)) / 1048576, 2),
    UniqueFiles       = dcount(object_s),
    FileNames         = make_set(object_s, 20),
    FileTypes         = make_set(file_type_s, 10),
    Domains           = make_set(domain_s, 20),
    Categories        = make_set(category_s, 5),
    DstCountries      = make_set(dst_country_s, 10),
    FirstSeen         = min(TimeGenerated),
    LastSeen          = max(TimeGenerated)
  by user_s
| order by TotalMBDownloaded desc, DownloadCount desc

// =============================================================================
// HQ66 - Netskope IA - High-Volume Data Transfer to Risky Cloud Apps
// MITRE Techniques: T1567.002, T1537
// Tactics        : Exfiltration, Collection
// Description    : Hunts for large data uploads to cloud apps with low/poor Cloud Confidence Level.
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
let RiskyCCL = dynamic(["poor", "low", "unknown"]);
let ExfilCategories = dynamic([
    "Cloud Storage", "File Sharing", "Online Storage and Backup",
    "Personal Sites & Blogs", "Webmail"]);
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(7d)
| where isnotempty(user_s) and isnotempty(app_s)
| where action_s !in ("block", "Block", "blocked", "Blocked")
| where ccl_s in (RiskyCCL) or isempty(ccl_s)
| where category_s in (ExfilCategories) or activity_s has_any ("Upload", "Share", "Post")
| summarize
    UploadCount       = count(),
    TotalMBUploaded   = round(sum(todouble(bytes_uploaded_d)) / 1048576, 2),
    UniqueFiles       = dcount(object_s),
    FileNames         = make_set(object_s, 20),
    FileTypes         = make_set(file_type_s, 10),
    Apps              = make_set(app_s, 20),
    Domains           = make_set(domain_s, 20),
    CCLValues         = make_set(ccl_s, 5),
    ActiveDays        = dcount(startofday(TimeGenerated)),
    FirstSeen         = min(TimeGenerated),
    LastSeen          = max(TimeGenerated)
  by user_s
| where TotalMBUploaded > 25 or UploadCount > 20
| order by TotalMBUploaded desc, UploadCount desc

// =============================================================================
// HQ67 - Netskope IA - Geo Anomaly - Traffic to High-Risk Countries
// MITRE Techniques: T1071, T1048
// Tactics        : CommandAndControl, Exfiltration
// Description    : Hunts for traffic to destination servers in high-risk or sanctioned countries.
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
let HighRiskCountries = dynamic([
    "RU", "CN", "KP", "IR", "SY", "CU", "VE", "BY",
    "Russia", "China", "North Korea", "Iran", "Syria", "Cuba", "Venezuela", "Belarus"]);
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(7d)
| where isnotempty(user_s) and isnotempty(dst_country_s)
| where dst_country_s in (HighRiskCountries)
| where action_s !in ("block", "Block", "blocked", "Blocked")
| summarize
    RequestCount      = count(),
    TotalMBUploaded   = round(sum(todouble(bytes_uploaded_d)) / 1048576, 2),
    TotalMBDownloaded = round(sum(todouble(bytes_downloaded_d)) / 1048576, 2),
    UniqueDomains     = dcount(domain_s),
    Domains           = make_set(domain_s, 20),
    DestIPs           = make_set(dstip_s, 10),
    Apps              = make_set(app_s, 10),
    Categories        = make_set(category_s, 10),
    SourceIPs         = make_set(srcip_s, 10),
    ActiveDays        = dcount(startofday(TimeGenerated)),
    FirstSeen         = min(TimeGenerated),
    LastSeen          = max(TimeGenerated)
  by user_s, dst_country_s
| order by TotalMBUploaded desc, RequestCount desc

// =============================================================================
// HQ68 - Netskope IA - Multi-Protocol C2 Beaconing Pattern
// MITRE Techniques: T1071, T1573
// Tactics        : CommandAndControl
// Description    : Hunts for regular-interval beaconing patterns to suspicious domains with low CCL.
// =============================================================================
let _NetskopeEmpty = datatable(TimeGenerated:datetime, action_s:string, category_s:string, severity_s:string, malware_name_s:string, malware_type_s:string, threat_name_s:string, user_s:string, domain_s:string, dstip_s:string, srcip_s:string, bytes_uploaded_d:real, bytes_downloaded_d:real, app_s:string, url_s:string, dlp_rule_s:string, dlp_profile_s:string, activity_s:string, file_type_s:string, object_s:string, dst_country_s:string, src_country_s:string, ccl_s:string, access_method_s:string, traffic_type_s:string)[];
let SuspiciousCategories = dynamic([
    "Uncategorized", "Unknown", "Newly Observed Domain",
    "Newly Registered Domain", "Suspicious", "Parked",
    "Dynamic DNS Host", "Malware", "Command and Control"]);
let RiskyCCL = dynamic(["poor", "low", "unknown"]);
union isfuzzy=true _NetskopeEmpty, NetskopeEvents_CL
| where TimeGenerated > ago(24h)
| where isnotempty(user_s) and isnotempty(domain_s)
| where category_s in (SuspiciousCategories) or ccl_s in (RiskyCCL)
| summarize
    RequestCount     = count(),
    TotalBytesSent   = sum(todouble(bytes_uploaded_d)),
    TotalBytesRecv   = sum(todouble(bytes_downloaded_d)),
    SourceIPs        = make_set(srcip_s, 5),
    Categories       = make_set(category_s, 5),
    CCL              = take_any(ccl_s),
    DstIPs           = make_set(dstip_s, 5),
    DstCountries     = make_set(dst_country_s, 5),
    FirstSeen        = min(TimeGenerated),
    LastSeen         = max(TimeGenerated)
  by user_s, domain_s
| where RequestCount >= 15
| extend
    DurationMinutes  = datetime_diff('minute', LastSeen, FirstSeen),
    AvgIntervalSec   = iff(RequestCount > 1,
                           round(toreal(datetime_diff('second', LastSeen, FirstSeen)) / (RequestCount - 1), 1),
                           0.0)
| where DurationMinutes >= 60
| where AvgIntervalSec between (10.0 .. 600.0)
| project
    user_s, domain_s, RequestCount,
    AvgIntervalSec, DurationMinutes,
    TotalBytesSent, TotalBytesRecv,
    Categories, CCL, DstIPs, DstCountries, SourceIPs,
    FirstSeen, LastSeen
| order by RequestCount desc, AvgIntervalSec asc

Explanation

This document contains a series of KQL (Kusto Query Language) queries designed for threat hunting using data from Netskope Internet Access. Each query is labeled with a unique identifier (HQ50 to HQ68) and focuses on different aspects of network security monitoring. Here's a brief summary of each query:

  1. HQ50: Identifies blocked web requests categorized as malicious, such as malware or phishing, within the last 24 hours.
  2. HQ51: Analyzes Data Loss Prevention (DLP) policy violations over the past week, providing details on rules and profiles.
  3. HQ52: Correlates allowed traffic with threat intelligence feeds to identify potential threats that were not blocked.
  4. HQ53: Profiles user web activity outside business hours to detect unusual behavior.
  5. HQ54: Detects phishing campaigns by identifying multiple users accessing the same phishing domain.
  6. HQ55: Analyzes high-severity traffic that was allowed through, potentially indicating a security risk.
  7. HQ56: Summarizes malware and threat detections from Netskope's Advanced Threat Protection (ATP) and sandbox.
  8. HQ57: Hunts for attempts to bypass security using VPNs, proxies, or anonymizers.
  9. HQ58: Detects users who have stopped generating traffic, indicating potential visibility loss.
  10. HQ59: Identifies potential abuse of legitimate platforms (e.g., GitHub, Google Docs) for command and control (C2) activities.
  11. HQ60: Correlates Netskope blocks with security alerts from other sources to identify corroborated threats.
  12. HQ61: Detects low and slow data exfiltration using statistical analysis.
  13. HQ62: Identifies unauthorized access to unsanctioned cloud applications, known as Shadow IT.
  14. HQ63: Detects impossible travel scenarios where users access services from multiple countries in a short time.
  15. HQ64: Hunts for credential phishing submissions to domains categorized as phishing.
  16. HQ65: Identifies suspicious file downloads from uncategorized or newly registered domains.
  17. HQ66: Detects high-volume data transfers to cloud apps with low confidence levels.
  18. HQ67: Hunts for traffic directed to high-risk or sanctioned countries.
  19. HQ68: Identifies regular-interval beaconing patterns to suspicious domains, indicating potential C2 activity.

Each query is designed to surface specific types of security events or anomalies, aiding in the detection and investigation of potential threats within an organization's network.

Details

David Alonso profile picture

David Alonso

Released: April 16, 2026

Tables

NetskopeEvents_CL ThreatIntelIndicators SecurityAlert

Keywords

NetskopeInternetAccessThreatHuntingQueriesNetskopeEvents_CLDevicesIntuneUserSecurityAlertThreatIntelIndicators

Operators

letdatatabledynamicunionisfuzzywhereagoinisnotemptysummarizecountdcountmake_setminmaxbyorderdescroundtodoubleextendcoalescejoinkindonprojecthasmv-expandtostringbetweenleftantiifftorealtake_anydatetime_diffbinhas_anyisemptydistinctmv-expandtodynamicparse_ipv4nowtake_any

Actions