Detection rules › Kusto

Netskope - Suspicious Network Context (Unusual IPs/Geo/Ports)

Status
available
Severity
medium
Time window
1h
Group by
CsUsername, XCCountry, XCLocation, XCsSrcIp
Source
github.com/Azure/Azure-Sentinel

Detects suspicious network activity based on unusual source/destination IPs, geographic anomalies, uncommon ports, and high traffic volumes.

MITRE ATT&CK coverage

Rule body

id: 6d989fb0-933e-4ae6-88fa-10e7b51c8897
name: Netskope - Suspicious Network Context (Unusual IPs/Geo/Ports)
description: |
  Detects suspicious network activity based on unusual source/destination IPs, geographic anomalies, uncommon ports, and high traffic volumes.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: NetskopeWebTxConnector
    dataTypes:
      - NetskopeWebTransactions_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - Exfiltration
  - Discovery
relevantTechniques:
  - T1071
  - T1048
  - T1046
query: |
  NetskopeWebTransactions_CL
  | where TimeGenerated > ago(1h)
  | where isnotempty(CsUsername)
  | extend 
      DstPort = coalesce(XCsDstPort, XSrDstPort, CsUriPort),
      SrcIP = coalesce(XCsSrcIp, CIp),
      DstIP = coalesce(XCsDstIp, XSrDstIp, SIp)
  | summarize 
      EventCount = count(),
      TotalBytes = sum(Bytes),
      UniqueDstIPs = dcount(DstIP),
      DstIPs = make_set(DstIP, 20),
      UniqueDstPorts = dcount(DstPort),
      DstPorts = make_set(DstPort, 20),
      UniqueHosts = dcount(CsHost),
      Hosts = make_set(CsHost, 20),
      Countries = make_set(XSCountry),
      SuspiciousPortHits = countif(DstPort in (20, 21, 22, 23, 25, 445, 1433, 1434, 3306, 3389, 5432, 5900, 5901)),
      HighRiskCountryHits = countif(XSCountry in ('RU', 'CN', 'KP', 'IR', 'SY'))
      by CsUsername, XCsSrcIp, XCCountry, XCLocation, bin(TimeGenerated, 1h)
  | where SuspiciousPortHits > 0 or HighRiskCountryHits > 0 or UniqueDstIPs > 50 or TotalBytes > 1073741824
  | extend 
      TotalMB = round(TotalBytes / 1048576.0, 2),
      RiskFactors = strcat_array(array_concat(
          iff(SuspiciousPortHits > 0, dynamic(['Suspicious Ports']), dynamic([])),
          iff(HighRiskCountryHits > 0, dynamic(['High Risk Country']), dynamic([])),
          iff(UniqueDstIPs > 50, dynamic(['Many Destinations']), dynamic([])),
          iff(TotalBytes > 1073741824, dynamic(['High Volume']), dynamic([]))
      ), ', ')
  | project 
      TimeGenerated,
      User = CsUsername,
      SourceIP = XCsSrcIp,
      SourceCountry = XCCountry,
      SourceLocation = XCLocation,
      DestinationIPs = DstIPs,
      UniqueDstIPCount = UniqueDstIPs,
      DestinationPorts = DstPorts,
      TargetHosts = Hosts,
      DestinationCountries = Countries,
      SuspiciousPortAccessCount = SuspiciousPortHits,
      HighRiskCountryAccessCount = HighRiskCountryHits,
      TotalDataMB = TotalMB,
      EventCount,
      RiskFactors
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: User
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

NetskopeWebTransactions_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where isnotempty(CsUsername)

Stage 4: extend

| extend 
    DstPort = coalesce(XCsDstPort, XSrDstPort, CsUriPort),
    SrcIP = coalesce(XCsSrcIp, CIp),
    DstIP = coalesce(XCsDstIp, XSrDstIp, SIp)

Stage 5: summarize

| summarize 
    EventCount = count(),
    TotalBytes = sum(Bytes),
    UniqueDstIPs = dcount(DstIP),
    DstIPs = make_set(DstIP, 20),
    UniqueDstPorts = dcount(DstPort),
    DstPorts = make_set(DstPort, 20),
    UniqueHosts = dcount(CsHost),
    Hosts = make_set(CsHost, 20),
    Countries = make_set(XSCountry),
    SuspiciousPortHits = countif(DstPort in (20, 21, 22, 23, 25, 445, 1433, 1434, 3306, 3389, 5432, 5900, 5901)),
    HighRiskCountryHits = countif(XSCountry in ('RU', 'CN', 'KP', 'IR', 'SY'))
    by CsUsername, XCsSrcIp, XCCountry, XCLocation, bin(TimeGenerated, 1h)

Stage 6: where

| where SuspiciousPortHits > 0 or HighRiskCountryHits > 0 or UniqueDstIPs > 50 or TotalBytes > 1073741824

Stage 7: extend

| extend 
    TotalMB = round(TotalBytes / 1048576.0, 2),
    RiskFactors = strcat_array(array_concat(
        iff(SuspiciousPortHits > 0, dynamic(['Suspicious Ports']), dynamic([])),
        iff(HighRiskCountryHits > 0, dynamic(['High Risk Country']), dynamic([])),
        iff(UniqueDstIPs > 50, dynamic(['Many Destinations']), dynamic([])),
        iff(TotalBytes > 1073741824, dynamic(['High Volume']), dynamic([]))
    ), ', ')

Stage 8: project

| project 
    TimeGenerated,
    User = CsUsername,
    SourceIP = XCsSrcIp,
    SourceCountry = XCCountry,
    SourceLocation = XCLocation,
    DestinationIPs = DstIPs,
    UniqueDstIPCount = UniqueDstIPs,
    DestinationPorts = DstPorts,
    TargetHosts = Hosts,
    DestinationCountries = Countries,
    SuspiciousPortAccessCount = SuspiciousPortHits,
    HighRiskCountryAccessCount = HighRiskCountryHits,
    TotalDataMB = TotalMB,
    EventCount,
    RiskFactors

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
DestinationCountriesproject
DestinationIPsproject
DestinationPortsproject
EventCountproject
HighRiskCountryAccessCountproject
RiskFactorsproject
SourceCountryproject
SourceIPproject
SourceLocationproject
SuspiciousPortAccessCountproject
TargetHostsproject
TimeGeneratedproject
TotalDataMBproject
UniqueDstIPCountproject
Userproject