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.
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
| Tactic | Techniques |
|---|---|
| Discovery | No 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 =ipv4_is_in_range(extracted_user_ip, "10.0.0.0/8")dynamic({<dynamic>, <dynamic>})geo_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.
| Field | Kind | Values |
|---|---|---|
Network | is_null | |
PatternName | in |
|
Score | ge |
|
Threshold | ge |
|
extracted_user_ip | is_not_null |
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.
| Field | Source |
|---|---|
AlertId | project |
NormalizedTriggeringEvents | project |
PatternDescription | project |
PatternName | project |
Status | project |
TimeGenerated | project |
Users | project |
extracted_client | project |
extracted_instance_host | project |
extracted_instance_name | project |
extracted_sap_user | project |
extracted_sid | project |
extracted_user_ip | project |
GeoLocation | extend |
UserAccountName | extend |
UserEmail | extend |