Detection rules › Kusto
UniFi Site Manager: External WAN IP changed
Identifies when a site reports more than one distinct WAN external IP within an hour, which may indicate ISP DHCP refresh, WAN reconfiguration, or routing hijack.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Reconnaissance |
Rule body
id: ef1a293a-9e2b-b087-7816-2610814ed2d4
name: "UniFi Site Manager: External WAN IP changed"
description: |
Identifies when a site reports more than one distinct WAN external IP within an hour, which may indicate ISP DHCP refresh, WAN reconfiguration, or routing hijack.
severity: High
status: Available
requiredDataConnectors:
- connectorId: UniFiSiteManagerConnectorDefinition
dataTypes:
- Unifi_SiteManager_Sites_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Reconnaissance
relevantTechniques:
- T1590
subTechniques:
- T1590.005
query: |
Unifi_SiteManager_Sites_CL
| where TimeGenerated > ago(1h)
| extend SiteName = tostring(Meta.name),
currentIp = tostring(SiteStatistics.wans.WAN.externalIp)
| where isnotempty(currentIp)
| summarize DistinctIps = make_set(currentIp), arg_max(TimeGenerated, currentIp) by SiteId, SiteName
| where array_length(DistinctIps) > 1
| extend Activity = strcat('WAN IP changed; observed: ', tostring(DistinctIps))
| project TimeGenerated, SiteId, SiteName, Activity, CurrentIp = currentIp, ObservedIps = DistinctIps
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CurrentIp
- entityType: Host
fieldMappings:
- identifier: HostName
columnName: SiteName
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT5H
matchingMethod: AllEntities
kind: Scheduled
version: 1.0.0
Stages and Predicates
Stage 1: source
Unifi_SiteManager_Sites_CL
Stage 2: where
| where TimeGenerated > ago(1h)
Stage 3: extend
| extend SiteName = tostring(Meta.name),
currentIp = tostring(SiteStatistics.wans.WAN.externalIp)
Stage 4: where
| where isnotempty(currentIp)
Stage 5: summarize
| summarize DistinctIps = make_set(currentIp), arg_max(TimeGenerated, currentIp) by SiteId, SiteName
Stage 6: where
| where array_length(DistinctIps) > 1
Stage 7: extend
| extend Activity = strcat('WAN IP changed; observed: ', tostring(DistinctIps))
Stage 8: project
| project TimeGenerated, SiteId, SiteName, Activity, CurrentIp = currentIp, ObservedIps = DistinctIps
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
currentIp | is_not_null | field:"currentIp" kind:is_not_null |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Activity | project |
CurrentIp | project |
ObservedIps | project |
SiteId | project |
SiteName | project |
TimeGenerated | project |