Detection rules › Kusto
Palo Alto - possible internal to external port scanning
'Identifies a list of internal Source IPs (10.x.x.x Hosts) that have triggered 10 or more non-graceful tcp server resets from one or more Destination IPs which results in an "ApplicationProtocol = incomplete" designation. The server resets coupled with an "Incomplete" ApplicationProtocol designation can be an indication of internal to external port scanning or probing attack. References: https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClUvCAK and https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClTaCAK'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Discovery | T1046 Network Service Discovery |
Rule body kusto
id: 5b72f527-e3f6-4a00-9908-8e4fee14da9f
name: Palo Alto - possible internal to external port scanning
description: |
'Identifies a list of internal Source IPs (10.x.x.x Hosts) that have triggered 10 or more non-graceful tcp server resets from one or more Destination IPs which results in an "ApplicationProtocol = incomplete" designation. The server resets coupled with an "Incomplete" ApplicationProtocol designation can be an indication of internal to external port scanning or probing attack.
References: https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClUvCAK and
https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClTaCAK'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: CefAma
dataTypes:
- CommonSecurityLog
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Discovery
relevantTechniques:
- T1046
query: |
CommonSecurityLog
| where isnotempty(DestinationPort) and DeviceAction !in ("reset-both", "deny")
// filter out common usage ports. Add ports that are legitimate for your environment
| where DestinationPort !in ("443", "53", "389", "80", "0", "880", "8888", "8080")
| where ApplicationProtocol == "incomplete"
// filter out IANA ephemeral or negotiated ports as per https://en.wikipedia.org/wiki/Ephemeral_port
| where DestinationPort !between (toint(49512) .. toint(65535))
| where Computer != ""
| where ipv4_is_private(DestinationIP) == false
| extend Reason = coalesce(
column_ifexists("Reason", ""),
extract("reason=(.+?)(;|$)", 1, AdditionalExtensions),
""
)
// Filter out any graceful reset reasons of AGED OUT which occurs when a TCP session closes with a FIN due to aging out.
| where Reason !has "aged-out"
// Filter out any TCP FIN which occurs when a TCP FIN is used to gracefully close half or both sides of a connection.
| where Reason !has "tcp-fin"
// Uncomment one of the following where clauses to trigger on specific TCP reset reasons
// See Palo Alto article for details - https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClUvCAK
// TCP RST-server - Occurs when the server sends a TCP reset to the client
// | where AdditionalExtensions has "reason=tcp-rst-from-server"
// TCP RST-client - Occurs when the client sends a TCP reset to the server
// | where AdditionalExtensions has "reason=tcp-rst-from-client"
// Already performed
//| extend reason = tostring(split(AdditionalExtensions, ";")[3])
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), count() by DeviceName, SourceUserID, SourceIP, ApplicationProtocol, Reason, DestinationPort, Protocol, DeviceVendor, DeviceProduct, DeviceAction, DestinationIP
| where count_ >= 10
| summarize StartTimeUtc = min(StartTimeUtc), EndTimeUtc = max(EndTimeUtc), makeset(DestinationIP), totalcount = sum(count_) by DeviceName, SourceUserID, SourceIP, ApplicationProtocol, Reason, DestinationPort, Protocol, DeviceVendor, DeviceProduct, DeviceAction
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SourceUserID
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
version: 1.0.8
kind: Scheduled
Stages and Predicates
Stage 1: source
CommonSecurityLog
Stage 2: where
| where isnotempty(DestinationPort) and DeviceAction !in ("reset-both", "deny")
Stage 3: where
| where DestinationPort !in ("443", "53", "389", "80", "0", "880", "8888", "8080")
Stage 4: where
| where ApplicationProtocol == "incomplete"
Stage 5: where
| where DestinationPort !between (toint(49512) .. toint(65535))
Stage 6: where
| where Computer != ""
Stage 7: where
| where ipv4_is_private(DestinationIP) == false
Stage 8: extend
| extend Reason = coalesce(
column_ifexists("Reason", ""),
extract("reason=(.+?)(;|$)", 1, AdditionalExtensions),
""
)
Stage 9: where
| where Reason !has "aged-out"
Stage 10: where
| where Reason !has "tcp-fin"
Stage 11: summarize
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), count() by DeviceName, SourceUserID, SourceIP, ApplicationProtocol, Reason, DestinationPort, Protocol, DeviceVendor, DeviceProduct, DeviceAction, DestinationIP
Stage 12: where
| where count_ >= 10
Stage 13: summarize
| summarize StartTimeUtc = min(StartTimeUtc), EndTimeUtc = max(EndTimeUtc), makeset(DestinationIP), totalcount = sum(count_) by DeviceName, SourceUserID, SourceIP, ApplicationProtocol, Reason, DestinationPort, Protocol, DeviceVendor, DeviceProduct, DeviceAction
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
DeviceAction | in | deny, reset-both |
DestinationPort | in | 0, 389, 443, 53, 80, 8080, 880, 8888 |
DestinationPort | ge | 49512 |
DestinationPort | le | 65535 |
DestinationIP | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 |
Reason | match | aged-out |
Reason | match | tcp-fin |
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 |
|---|---|---|
ApplicationProtocol | eq |
|
DestinationPort | is_not_null | |
count_ | ge |
|
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 |
|---|---|
ApplicationProtocol | summarize |
DestinationPort | summarize |
DeviceAction | summarize |
DeviceName | summarize |
DeviceProduct | summarize |
DeviceVendor | summarize |
EndTimeUtc | summarize |
Protocol | summarize |
Reason | summarize |
SourceIP | summarize |
SourceUserID | summarize |
StartTimeUtc | summarize |
totalcount | summarize |