mirror of
https://github.com/smittix/intercept.git
synced 2026-04-25 07:10:00 -07:00
Backend: - New utils/wifi/ package with models, scanner, parsers, channel analyzer - Quick Scan mode using system tools (nmcli, iw, iwlist, airport) - Deep Scan mode using airodump-ng with monitor mode - Hidden SSID correlation engine - Channel utilization analysis with recommendations - v2 API endpoints at /wifi/v2/* with SSE streaming - TSCM integration updated to use new scanner (backwards compatible) Frontend: - WiFi mode controller (wifi.js) with dual-mode support - Channel utilization chart component (channel-chart.js) - Updated wifi.html template with scan mode tabs and export Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
447 lines
14 KiB
Python
447 lines
14 KiB
Python
"""
|
|
WiFi-specific constants for the unified scanner.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
# =============================================================================
|
|
# SCANNER SETTINGS
|
|
# =============================================================================
|
|
|
|
# Default quick scan timeout in seconds
|
|
DEFAULT_QUICK_SCAN_TIMEOUT = 15
|
|
|
|
# Default deep scan channel dwell time (seconds)
|
|
DEFAULT_CHANNEL_DWELL_TIME = 2
|
|
|
|
# Maximum RSSI samples to keep per network
|
|
MAX_RSSI_SAMPLES = 300
|
|
|
|
# Network expiration time (seconds since last seen)
|
|
NETWORK_STALE_TIMEOUT = 300 # 5 minutes
|
|
|
|
# Client expiration time (seconds since last seen)
|
|
CLIENT_STALE_TIMEOUT = 180 # 3 minutes
|
|
|
|
# Probe request retention time (seconds)
|
|
PROBE_REQUEST_RETENTION = 600 # 10 minutes
|
|
|
|
# =============================================================================
|
|
# WIFI BANDS
|
|
# =============================================================================
|
|
|
|
BAND_2_4_GHZ = '2.4GHz'
|
|
BAND_5_GHZ = '5GHz'
|
|
BAND_6_GHZ = '6GHz'
|
|
BAND_UNKNOWN = 'unknown'
|
|
|
|
# =============================================================================
|
|
# WIFI BAND CHANNEL MAPPINGS
|
|
# =============================================================================
|
|
|
|
# 2.4 GHz channels (1-14)
|
|
CHANNELS_2_4_GHZ = list(range(1, 15))
|
|
|
|
# 5 GHz channels (UNII-1, UNII-2A, UNII-2C, UNII-3)
|
|
CHANNELS_5_GHZ = [
|
|
# UNII-1 (5150-5250 MHz)
|
|
36, 40, 44, 48,
|
|
# UNII-2A (5250-5350 MHz) - DFS
|
|
52, 56, 60, 64,
|
|
# UNII-2C (5470-5725 MHz) - DFS
|
|
100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144,
|
|
# UNII-3 (5725-5850 MHz)
|
|
149, 153, 157, 161, 165,
|
|
]
|
|
|
|
# 6 GHz channels (Wi-Fi 6E)
|
|
CHANNELS_6_GHZ = [
|
|
1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73,
|
|
77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 129, 133, 137,
|
|
141, 145, 149, 153, 157, 161, 165, 169, 173, 177, 181, 185, 189, 193, 197,
|
|
201, 205, 209, 213, 217, 221, 225, 229, 233
|
|
]
|
|
|
|
# Non-overlapping channels for recommendations
|
|
NON_OVERLAPPING_2_4_GHZ = [1, 6, 11]
|
|
NON_OVERLAPPING_5_GHZ = [36, 40, 44, 48, 149, 153, 157, 161, 165] # Non-DFS
|
|
|
|
# Channel to frequency mappings (MHz)
|
|
CHANNEL_FREQUENCIES = {
|
|
# 2.4 GHz
|
|
1: 2412, 2: 2417, 3: 2422, 4: 2427, 5: 2432, 6: 2437, 7: 2442,
|
|
8: 2447, 9: 2452, 10: 2457, 11: 2462, 12: 2467, 13: 2472, 14: 2484,
|
|
# 5 GHz
|
|
36: 5180, 40: 5200, 44: 5220, 48: 5240,
|
|
52: 5260, 56: 5280, 60: 5300, 64: 5320,
|
|
100: 5500, 104: 5520, 108: 5540, 112: 5560, 116: 5580,
|
|
120: 5600, 124: 5620, 128: 5640, 132: 5660, 136: 5680, 140: 5700, 144: 5720,
|
|
149: 5745, 153: 5765, 157: 5785, 161: 5805, 165: 5825,
|
|
}
|
|
|
|
# Frequency to channel reverse mapping
|
|
FREQUENCY_CHANNELS = {v: k for k, v in CHANNEL_FREQUENCIES.items()}
|
|
|
|
|
|
def get_band_from_channel(channel: int) -> str:
|
|
"""Get WiFi band from channel number."""
|
|
if 1 <= channel <= 14:
|
|
return BAND_2_4_GHZ
|
|
elif channel in CHANNELS_5_GHZ:
|
|
return BAND_5_GHZ
|
|
elif channel in CHANNELS_6_GHZ:
|
|
return BAND_6_GHZ
|
|
return BAND_UNKNOWN
|
|
|
|
|
|
def get_band_from_frequency(frequency_mhz: int) -> str:
|
|
"""Get WiFi band from frequency in MHz."""
|
|
if 2400 <= frequency_mhz <= 2500:
|
|
return BAND_2_4_GHZ
|
|
elif 5150 <= frequency_mhz <= 5850:
|
|
return BAND_5_GHZ
|
|
elif 5925 <= frequency_mhz <= 7125:
|
|
return BAND_6_GHZ
|
|
return BAND_UNKNOWN
|
|
|
|
|
|
def get_channel_from_frequency(frequency_mhz: int) -> int | None:
|
|
"""Get channel number from frequency in MHz."""
|
|
return FREQUENCY_CHANNELS.get(frequency_mhz)
|
|
|
|
|
|
# =============================================================================
|
|
# SECURITY TYPES
|
|
# =============================================================================
|
|
|
|
SECURITY_OPEN = 'Open'
|
|
SECURITY_WEP = 'WEP'
|
|
SECURITY_WPA = 'WPA'
|
|
SECURITY_WPA2 = 'WPA2'
|
|
SECURITY_WPA3 = 'WPA3'
|
|
SECURITY_WPA_WPA2 = 'WPA/WPA2'
|
|
SECURITY_WPA2_WPA3 = 'WPA2/WPA3'
|
|
SECURITY_ENTERPRISE = 'Enterprise'
|
|
SECURITY_UNKNOWN = 'Unknown'
|
|
|
|
# Security type priority (higher = more secure)
|
|
SECURITY_PRIORITY = {
|
|
SECURITY_OPEN: 0,
|
|
SECURITY_WEP: 1,
|
|
SECURITY_WPA: 2,
|
|
SECURITY_WPA_WPA2: 3,
|
|
SECURITY_WPA2: 4,
|
|
SECURITY_WPA2_WPA3: 5,
|
|
SECURITY_WPA3: 6,
|
|
SECURITY_ENTERPRISE: 7,
|
|
SECURITY_UNKNOWN: -1,
|
|
}
|
|
|
|
# =============================================================================
|
|
# CIPHER TYPES
|
|
# =============================================================================
|
|
|
|
CIPHER_NONE = 'None'
|
|
CIPHER_WEP = 'WEP'
|
|
CIPHER_TKIP = 'TKIP'
|
|
CIPHER_CCMP = 'CCMP'
|
|
CIPHER_GCMP = 'GCMP'
|
|
CIPHER_UNKNOWN = 'Unknown'
|
|
|
|
# =============================================================================
|
|
# AUTHENTICATION TYPES
|
|
# =============================================================================
|
|
|
|
AUTH_OPEN = 'Open'
|
|
AUTH_PSK = 'PSK'
|
|
AUTH_SAE = 'SAE'
|
|
AUTH_EAP = 'EAP'
|
|
AUTH_OWE = 'OWE'
|
|
AUTH_UNKNOWN = 'Unknown'
|
|
|
|
# =============================================================================
|
|
# CHANNEL WIDTH
|
|
# =============================================================================
|
|
|
|
WIDTH_20_MHZ = '20MHz'
|
|
WIDTH_40_MHZ = '40MHz'
|
|
WIDTH_80_MHZ = '80MHz'
|
|
WIDTH_160_MHZ = '160MHz'
|
|
WIDTH_320_MHZ = '320MHz'
|
|
WIDTH_UNKNOWN = 'Unknown'
|
|
|
|
# =============================================================================
|
|
# SIGNAL STRENGTH BANDS (for proximity radar)
|
|
# =============================================================================
|
|
|
|
SIGNAL_STRONG = 'strong' # >= -50 dBm
|
|
SIGNAL_MEDIUM = 'medium' # -50 to -70 dBm
|
|
SIGNAL_WEAK = 'weak' # -70 to -85 dBm
|
|
SIGNAL_VERY_WEAK = 'very_weak' # < -85 dBm
|
|
SIGNAL_UNKNOWN = 'unknown'
|
|
|
|
# RSSI thresholds for signal bands
|
|
RSSI_STRONG = -50
|
|
RSSI_MEDIUM = -70
|
|
RSSI_WEAK = -85
|
|
|
|
|
|
def get_signal_band(rssi: int | None) -> str:
|
|
"""Get signal band from RSSI value."""
|
|
if rssi is None:
|
|
return SIGNAL_UNKNOWN
|
|
if rssi >= RSSI_STRONG:
|
|
return SIGNAL_STRONG
|
|
elif rssi >= RSSI_MEDIUM:
|
|
return SIGNAL_MEDIUM
|
|
elif rssi >= RSSI_WEAK:
|
|
return SIGNAL_WEAK
|
|
return SIGNAL_VERY_WEAK
|
|
|
|
|
|
# =============================================================================
|
|
# PROXIMITY BANDS (consistent with Bluetooth)
|
|
# =============================================================================
|
|
|
|
PROXIMITY_IMMEDIATE = 'immediate' # < 3m
|
|
PROXIMITY_NEAR = 'near' # 3-10m
|
|
PROXIMITY_FAR = 'far' # > 10m
|
|
PROXIMITY_UNKNOWN = 'unknown'
|
|
|
|
# RSSI thresholds for proximity band classification
|
|
PROXIMITY_RSSI_IMMEDIATE = -55 # >= -55 dBm -> immediate
|
|
PROXIMITY_RSSI_NEAR = -70 # >= -70 dBm -> near
|
|
|
|
|
|
def get_proximity_band(rssi: int | None) -> str:
|
|
"""Get proximity band from RSSI value."""
|
|
if rssi is None:
|
|
return PROXIMITY_UNKNOWN
|
|
if rssi >= PROXIMITY_RSSI_IMMEDIATE:
|
|
return PROXIMITY_IMMEDIATE
|
|
elif rssi >= PROXIMITY_RSSI_NEAR:
|
|
return PROXIMITY_NEAR
|
|
return PROXIMITY_FAR
|
|
|
|
|
|
# =============================================================================
|
|
# DISTANCE ESTIMATION (WiFi-specific)
|
|
# =============================================================================
|
|
|
|
# Path-loss exponent for indoor WiFi (typically 2.5-4.0)
|
|
WIFI_PATH_LOSS_EXPONENT = 3.0
|
|
|
|
# Reference RSSI at 1 meter (typical WiFi AP)
|
|
WIFI_RSSI_AT_1M = -40
|
|
|
|
# EMA smoothing alpha for RSSI
|
|
WIFI_EMA_ALPHA = 0.3
|
|
|
|
# =============================================================================
|
|
# SCAN MODES
|
|
# =============================================================================
|
|
|
|
SCAN_MODE_QUICK = 'quick' # Uses system tools (no monitor mode)
|
|
SCAN_MODE_DEEP = 'deep' # Uses airodump-ng (monitor mode required)
|
|
|
|
# =============================================================================
|
|
# TOOL DETECTION
|
|
# =============================================================================
|
|
|
|
# Quick scan tools (by platform priority)
|
|
QUICK_SCAN_TOOLS_LINUX = ['nmcli', 'iw', 'iwlist']
|
|
QUICK_SCAN_TOOLS_DARWIN = ['airport']
|
|
|
|
# Deep scan tools
|
|
DEEP_SCAN_TOOLS = ['airodump-ng']
|
|
|
|
# Monitor mode tools
|
|
MONITOR_MODE_TOOLS = ['airmon-ng', 'iw']
|
|
|
|
# Tool command timeouts (seconds)
|
|
TOOL_TIMEOUT_QUICK = 30.0
|
|
TOOL_TIMEOUT_DETECT = 5.0
|
|
|
|
# =============================================================================
|
|
# AIRODUMP-NG SETTINGS
|
|
# =============================================================================
|
|
|
|
AIRODUMP_OUTPUT_PREFIX = 'airodump_wifi'
|
|
AIRODUMP_POLL_INTERVAL = 1.0 # seconds between CSV reads
|
|
|
|
# =============================================================================
|
|
# HEURISTIC FLAGS
|
|
# =============================================================================
|
|
|
|
HEURISTIC_HIDDEN = 'hidden'
|
|
HEURISTIC_ROGUE_AP = 'rogue_ap'
|
|
HEURISTIC_EVIL_TWIN = 'evil_twin'
|
|
HEURISTIC_BEACON_FLOOD = 'beacon_flood'
|
|
HEURISTIC_WEAK_SECURITY = 'weak_security'
|
|
HEURISTIC_DEAUTH_DETECTED = 'deauth_detected'
|
|
HEURISTIC_NEW = 'new'
|
|
HEURISTIC_PERSISTENT = 'persistent'
|
|
HEURISTIC_STRONG_STABLE = 'strong_stable'
|
|
|
|
# Thresholds
|
|
BEACON_FLOOD_THRESHOLD = 50 # Same BSSID seen > 50 times/minute
|
|
PERSISTENT_MIN_SEEN = 10
|
|
PERSISTENT_WINDOW_SECONDS = 300
|
|
STRONG_RSSI_THRESHOLD = -50
|
|
STABLE_VARIANCE_THRESHOLD = 5.0
|
|
|
|
# =============================================================================
|
|
# COMMON VENDOR OUI PREFIXES (first 3 bytes of MAC)
|
|
# =============================================================================
|
|
|
|
VENDOR_OUIS = {
|
|
'00:00:5E': 'IANA',
|
|
'00:03:93': 'Apple',
|
|
'00:0A:95': 'Apple',
|
|
'00:0D:93': 'Apple',
|
|
'00:11:24': 'Apple',
|
|
'00:14:51': 'Apple',
|
|
'00:16:CB': 'Apple',
|
|
'00:17:F2': 'Apple',
|
|
'00:19:E3': 'Apple',
|
|
'00:1B:63': 'Apple',
|
|
'00:1C:B3': 'Apple',
|
|
'00:1D:4F': 'Apple',
|
|
'00:1E:52': 'Apple',
|
|
'00:1E:C2': 'Apple',
|
|
'00:1F:5B': 'Apple',
|
|
'00:1F:F3': 'Apple',
|
|
'00:21:E9': 'Apple',
|
|
'00:22:41': 'Apple',
|
|
'00:23:12': 'Apple',
|
|
'00:23:32': 'Apple',
|
|
'00:23:6C': 'Apple',
|
|
'00:23:DF': 'Apple',
|
|
'00:24:36': 'Apple',
|
|
'00:25:00': 'Apple',
|
|
'00:25:4B': 'Apple',
|
|
'00:25:BC': 'Apple',
|
|
'00:26:08': 'Apple',
|
|
'00:26:4A': 'Apple',
|
|
'00:26:B0': 'Apple',
|
|
'00:26:BB': 'Apple',
|
|
'00:50:F2': 'Microsoft',
|
|
'00:15:5D': 'Microsoft',
|
|
'00:17:FA': 'Microsoft',
|
|
'00:1D:D8': 'Microsoft',
|
|
'00:50:56': 'VMware',
|
|
'00:0C:29': 'VMware',
|
|
'00:05:69': 'VMware',
|
|
'08:00:27': 'VirtualBox',
|
|
'00:1C:42': 'Parallels',
|
|
'00:16:3E': 'Xen',
|
|
'DC:A6:32': 'Raspberry Pi',
|
|
'B8:27:EB': 'Raspberry Pi',
|
|
'E4:5F:01': 'Raspberry Pi',
|
|
'28:CD:C1': 'Raspberry Pi',
|
|
'00:1A:11': 'Google',
|
|
'00:1A:22': 'Google',
|
|
'3C:5A:B4': 'Google',
|
|
'54:60:09': 'Google',
|
|
'94:EB:2C': 'Google',
|
|
'F4:F5:D8': 'Google',
|
|
'00:17:C4': 'Netgear',
|
|
'00:1B:2F': 'Netgear',
|
|
'00:1E:2A': 'Netgear',
|
|
'00:22:3F': 'Netgear',
|
|
'00:24:B2': 'Netgear',
|
|
'00:26:F2': 'Netgear',
|
|
'00:18:F8': 'Cisco',
|
|
'00:1A:A1': 'Cisco',
|
|
'00:1B:0C': 'Cisco',
|
|
'00:1B:D4': 'Cisco',
|
|
'00:1C:0E': 'Cisco',
|
|
'00:1C:57': 'Cisco',
|
|
'00:40:96': 'Cisco',
|
|
'00:50:54': 'Cisco',
|
|
'00:60:5C': 'Cisco',
|
|
'E8:65:D4': 'Ubiquiti',
|
|
'FC:EC:DA': 'Ubiquiti',
|
|
'00:27:22': 'Ubiquiti',
|
|
'04:18:D6': 'Ubiquiti',
|
|
'18:E8:29': 'Ubiquiti',
|
|
'24:A4:3C': 'Ubiquiti',
|
|
'44:D9:E7': 'Ubiquiti',
|
|
'68:72:51': 'Ubiquiti',
|
|
'74:83:C2': 'Ubiquiti',
|
|
'78:8A:20': 'Ubiquiti',
|
|
'B4:FB:E4': 'Ubiquiti',
|
|
'F0:9F:C2': 'Ubiquiti',
|
|
'00:0C:F1': 'Intel',
|
|
'00:13:02': 'Intel',
|
|
'00:13:20': 'Intel',
|
|
'00:13:CE': 'Intel',
|
|
'00:13:E8': 'Intel',
|
|
'00:15:00': 'Intel',
|
|
'00:15:17': 'Intel',
|
|
'00:16:6F': 'Intel',
|
|
'00:16:76': 'Intel',
|
|
'00:16:EA': 'Intel',
|
|
'00:16:EB': 'Intel',
|
|
'00:18:DE': 'Intel',
|
|
'00:19:D1': 'Intel',
|
|
'00:19:D2': 'Intel',
|
|
'00:1B:21': 'Intel',
|
|
'00:1B:77': 'Intel',
|
|
'00:1C:BF': 'Intel',
|
|
'00:1D:E0': 'Intel',
|
|
'00:1D:E1': 'Intel',
|
|
'00:1E:64': 'Intel',
|
|
'00:1E:65': 'Intel',
|
|
'00:1E:67': 'Intel',
|
|
'00:1F:3B': 'Intel',
|
|
'00:1F:3C': 'Intel',
|
|
'00:20:E0': 'TP-Link',
|
|
'00:23:CD': 'TP-Link',
|
|
'00:25:86': 'TP-Link',
|
|
'00:27:19': 'TP-Link',
|
|
'14:CC:20': 'TP-Link',
|
|
'14:CF:92': 'TP-Link',
|
|
'18:A6:F7': 'TP-Link',
|
|
'1C:3B:F3': 'TP-Link',
|
|
'30:B5:C2': 'TP-Link',
|
|
'50:C7:BF': 'TP-Link',
|
|
'54:C8:0F': 'TP-Link',
|
|
'60:E3:27': 'TP-Link',
|
|
'64:56:01': 'TP-Link',
|
|
'64:66:B3': 'TP-Link',
|
|
'64:70:02': 'TP-Link',
|
|
}
|
|
|
|
|
|
def get_vendor_from_mac(mac: str) -> str | None:
|
|
"""Get vendor name from MAC address OUI."""
|
|
if not mac:
|
|
return None
|
|
# Normalize MAC format
|
|
mac_upper = mac.upper().replace('-', ':')
|
|
oui = mac_upper[:8]
|
|
return VENDOR_OUIS.get(oui)
|
|
|
|
|
|
# =============================================================================
|
|
# HIDDEN SSID CORRELATION
|
|
# =============================================================================
|
|
|
|
# Time window for correlating probe requests with hidden AP associations
|
|
HIDDEN_CORRELATION_WINDOW_SECONDS = 60
|
|
|
|
# Minimum confidence for hidden SSID revelation
|
|
HIDDEN_MIN_CORRELATION_CONFIDENCE = 0.7
|
|
|
|
# =============================================================================
|
|
# CHANNEL ANALYSIS
|
|
# =============================================================================
|
|
|
|
# Weights for channel utilization scoring
|
|
CHANNEL_WEIGHT_AP_COUNT = 0.6
|
|
CHANNEL_WEIGHT_CLIENT_COUNT = 0.4
|
|
|
|
# RSSI adjustment factor (stronger signals = more interference)
|
|
CHANNEL_RSSI_INTERFERENCE_FACTOR = 0.1
|