Detection rules › Kusto

SAP ETD - Login from unexpected network

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.

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

Identifies logons from an unexpected network. Source Action: Logon to the backend system from an IP address which is not assigned to one of the networks. networks can be maintained in the "SAP - Networks" watchlist of the Microsoft Sentinel Solution for SAP package. Data Sources: SAP Enterprise Thread Detection Solution - Alerts

MITRE ATT&CK coverage

TacticTechniques
DiscoveryNo specific technique

Rule body kusto

id: 5dd72ebe-03ac-43ac-851b-68cfe5106e4f
kind: Scheduled
name: SAP ETD - Login from unexpected network
description: |
  Identifies logons from an unexpected network.
  Source Action: Logon to the backend system from an IP address which is not assigned to one of the networks.
  networks can be maintained in the "SAP - Networks" watchlist of the Microsoft Sentinel Solution for SAP package.
  
  *Data Sources: SAP Enterprise Thread Detection Solution -  Alerts*
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SAPETDAlerts
    dataTypes:
      - SAPETDAlerts_CL
queryFrequency: 5m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics: 
  - Discovery
relevantTechniques: []
query: |
  let AuditTimeAgo = 60m;
  let minThreshold= 1;
  let minScore= 50;
  let regex_sid = @"^([A-Z0-9]{3})/"; 
  let regex_client = @"/(\d{3})$";
  let SAPNetworks = _GetWatchlist('SAP - Networks');
  SAPETDAlerts_CL
  | where TimeGenerated > ago(AuditTimeAgo)
  | where Threshold >= minThreshold and Score >= minScore
  | where PatternName in ("Logon from external with SAP standard users","Access via unallowed IP Address")
  | mv-expand NormalizedTriggeringEvents
  | extend sapOriginalEvent = tostring(NormalizedTriggeringEvents)
  | extend Id_ = NormalizedTriggeringEvents.Id
  | extend extracted_user_ip = tostring(NormalizedTriggeringEvents.NetworkIPAddressInitiator)
  | where isnotempty(extracted_user_ip)
  | extend extracted_sap_user = NormalizedTriggeringEvents.UserAccountTargeted
  | extend extracted_sid = extract(regex_sid, 1, tostring(NormalizedTriggeringEvents.SystemIdActor))
  | extend extracted_client = extract(regex_client, 1, tostring(NormalizedTriggeringEvents.SystemIdActor))
  | extend extracted_instance_name = NormalizedTriggeringEvents.NetworkHostnameActor
  | extend extracted_instance_host = NormalizedTriggeringEvents.NetworkHostnameInitiator
  | evaluate ipv4_lookup(SAPNetworks, extracted_user_ip, Network, return_unmatched = true)
  | where isempty(Network)
  | project TimeGenerated, extracted_user_ip, extracted_sap_user, extracted_sid, extracted_client, extracted_instance_name, extracted_instance_host, AlertId, PatternName, PatternDescription, Status, NormalizedTriggeringEvents, Users
  | extend GeoLocation= iff(ipv4_is_private(extracted_user_ip), dynamic({"IsPrivate": true}), geo_info_from_ip_address(extracted_user_ip))
  | mv-expand Users
  | extend
    UserAccountName = tostring(Users.UserAccountName),
    UserEmail = tostring(Users.EmailAddresses[0])
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserAccountName
  - entityType: Mailbox
    fieldMappings:
      - identifier: MailboxPrimaryAddress
        columnName: UserEmail
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: extracted_sid
      - identifier: InstanceName
        columnName: extracted_instance_name
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: extracted_instance_host
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: extracted_user_ip
alertDetailsOverride:
  alertDisplayNameFormat: 'SAP ETD - {{PatternName}} '
  alertDescriptionFormat: |
    {{PatternDescription}}
customDetails:
  SAP_User: extracted_sap_user
  SAP_UserEmail: UserEmail
  ETD_AlertNumber: AlertId
version: 1.0.4

Stages and Predicates

Parameters

let AuditTimeAgo = 60m;
let minThreshold = 1;
let minScore = 50;
let regex_sid = @"^([A-Z0-9]{3})/";
let regex_client = @"/(\d{3})$";
let SAPNetworks = _GetWatchlist('SAP - Networks');

Stage 1: source

SAPETDAlerts_CL

Stage 2: where

| where TimeGenerated > ago(AuditTimeAgo)

Stage 3: where

| where Threshold >= minThreshold and Score >= minScore

Stage 4: where

| where PatternName in ("Logon from external with SAP standard users","Access via unallowed IP Address")

Stage 5: mv-expand

| mv-expand NormalizedTriggeringEvents

Stage 6: extend (3 consecutive steps)

| extend sapOriginalEvent = tostring(NormalizedTriggeringEvents)
| extend Id_ = NormalizedTriggeringEvents.Id
| extend extracted_user_ip = tostring(NormalizedTriggeringEvents.NetworkIPAddressInitiator)

Stage 7: where

| where isnotempty(extracted_user_ip)

Stage 8: extend (5 consecutive steps)

| extend extracted_sap_user = NormalizedTriggeringEvents.UserAccountTargeted
| extend extracted_sid = extract(regex_sid, 1, tostring(NormalizedTriggeringEvents.SystemIdActor))
| extend extracted_client = extract(regex_client, 1, tostring(NormalizedTriggeringEvents.SystemIdActor))
| extend extracted_instance_name = NormalizedTriggeringEvents.NetworkHostnameActor
| extend extracted_instance_host = NormalizedTriggeringEvents.NetworkHostnameInitiator

Stage 9: evaluate

| evaluate ipv4_lookup(SAPNetworks, extracted_user_ip, Network, return_unmatched = true)

Stage 10: where

| where isempty(Network)

Stage 11: project

| project TimeGenerated, extracted_user_ip, extracted_sap_user, extracted_sid, extracted_client, extracted_instance_name, extracted_instance_host, AlertId, PatternName, PatternDescription, Status, NormalizedTriggeringEvents, Users

Stage 12: extend

| extend GeoLocation= iff(ipv4_is_private(extracted_user_ip), dynamic({"IsPrivate": true}), geo_info_from_ip_address(extracted_user_ip))
GeoLocation =
ifipv4_is_in_range(extracted_user_ip, "10.0.0.0/8")dynamic({<dynamic>, <dynamic>})
elsegeo_info_from_ip_address(extracted_user_ip)

Stage 13: mv-expand

| mv-expand Users

Stage 14: extend

| extend
  UserAccountName = tostring(Users.UserAccountName),
  UserEmail = tostring(Users.EmailAddresses[0])

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
Networkis_null
  • (no value, null check)
PatternNamein
  • Access via unallowed IP Address transforms: cased
  • Logon from external with SAP standard users transforms: cased
Scorege
  • 50 transforms: cased
Thresholdge
  • 1 transforms: cased
extracted_user_ipis_not_null
  • (no value, null check)

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
AlertIdproject
NormalizedTriggeringEventsproject
PatternDescriptionproject
PatternNameproject
Statusproject
TimeGeneratedproject
Usersproject
extracted_clientproject
extracted_instance_hostproject
extracted_instance_nameproject
extracted_sap_userproject
extracted_sidproject
extracted_user_ipproject
GeoLocationextend
UserAccountNameextend
UserEmailextend