From 98a643abf36cb7b1d3bbc552ebf129a689e4e3e1 Mon Sep 17 00:00:00 2001 From: SsSnake Date: Tue, 24 Mar 2026 07:41:36 -0700 Subject: [PATCH] Fix WiFi scanner to use iw instead of nmcli, fix WiFi Audit link WiFi scanner was returning empty results because nmcli reports wlan0 as unavailable. Switched to iw dev scan via daemon. Fixed WiFi Audit link from /wifi-audit to /wifi/ to match blueprint url_prefix. Co-Authored-By: Claude Opus 4.6 (1M context) --- web/routes/network.py | 133 ++++++++++++++++++------------------- web/templates/network.html | 2 +- 2 files changed, 67 insertions(+), 68 deletions(-) diff --git a/web/routes/network.py b/web/routes/network.py index c973201..e50985c 100644 --- a/web/routes/network.py +++ b/web/routes/network.py @@ -1005,34 +1005,18 @@ def arp_spoof_fix(): @network_bp.route('/wifi/scan', methods=['POST']) @login_required def wifi_scan(): - """Scan for nearby WiFi networks using iwlist or nmcli.""" + """Scan for nearby WiFi networks using iw dev scan.""" results = [] try: - # Try nmcli first (most reliable) — nmcli works as normal user - ok, out = _run('nmcli -t -f SSID,BSSID,CHAN,FREQ,SIGNAL,SECURITY,MODE dev wifi list --rescan yes', timeout=20) - if ok and out.strip(): - for line in out.strip().split('\n'): - parts = _parse_nmcli_line(line) - if len(parts) >= 7: - results.append({ - 'ssid': parts[0] or '(Hidden)', - 'bssid': parts[1], - 'channel': parts[2], - 'frequency': parts[3], - 'signal': parts[4], - 'security': parts[5], - 'mode': parts[6], - }) + iface = _get_wireless_interface() + if not iface: + return jsonify({'ok': False, 'error': 'No wireless interface found'}) + + r = _run_root(['iw', 'dev', iface, 'scan'], timeout=20) + if r['ok']: + results = _parse_iw_scan(r['stdout']) else: - # Fallback to iwlist (needs root) - iface = _get_wireless_interface() - if not iface: - return jsonify({'ok': False, 'error': 'No wireless interface found'}) - r = _run_root(['iwlist', iface, 'scanning'], timeout=20) - if r['ok']: - results = _parse_iwlist(r['stdout']) - else: - return jsonify({'ok': False, 'error': r.get('stderr', 'WiFi scan failed')}) + return jsonify({'ok': False, 'error': r.get('stderr', 'WiFi scan failed')}) except Exception as e: return jsonify({'ok': False, 'error': str(e)}) @@ -1045,24 +1029,28 @@ def ssid_map(): """Build an SSID map showing all access points, their BSSIDs, channels, and signal strength. Groups by SSID to show all APs broadcasting the same network name.""" try: - ok, out = _run('nmcli -t -f SSID,BSSID,CHAN,SIGNAL,SECURITY dev wifi list --rescan yes', timeout=20) - networks = {} - if ok and out.strip(): - for line in out.strip().split('\n'): - parts = _parse_nmcli_line(line) - if len(parts) >= 5: - ssid = parts[0] or '(Hidden)' - entry = { - 'bssid': parts[1], - 'channel': parts[2], - 'signal': parts[3], - 'security': parts[4], - } - if ssid not in networks: - networks[ssid] = {'ssid': ssid, 'aps': [], 'security': entry['security']} - networks[ssid]['aps'].append(entry) + iface = _get_wireless_interface() + if not iface: + return jsonify({'ok': False, 'error': 'No wireless interface found'}) + + r = _run_root(['iw', 'dev', iface, 'scan'], timeout=20) + if not r['ok']: + return jsonify({'ok': False, 'error': r.get('stderr', 'WiFi scan failed')}) + + scan_results = _parse_iw_scan(r['stdout']) + networks = {} + for net in scan_results: + ssid = net.get('ssid', '(Hidden)') or '(Hidden)' + entry = { + 'bssid': net.get('bssid', ''), + 'channel': net.get('channel', ''), + 'signal': net.get('signal', ''), + 'security': net.get('security', ''), + } + if ssid not in networks: + networks[ssid] = {'ssid': ssid, 'aps': [], 'security': entry['security']} + networks[ssid]['aps'].append(entry) - # Sort by signal strength (strongest first) ssid_list = sorted(networks.values(), key=lambda x: max(int(a.get('signal', '0') or '0') for a in x['aps']), reverse=True) return jsonify({'ok': True, 'ssids': ssid_list, 'total_ssids': len(ssid_list), 'total_aps': sum(len(s['aps']) for s in ssid_list)}) @@ -1123,37 +1111,48 @@ def _get_wireless_interface(): return None -def _parse_iwlist(output): - """Parse iwlist scanning output into structured data.""" - import re +def _parse_iw_scan(output): + """Parse 'iw dev scan' output into structured data.""" networks = [] - current = {} + current = None for line in output.split('\n'): - line = line.strip() - if 'Cell' in line and 'Address:' in line: + line_s = line.strip() + # New BSS block + m = re.match(r'^BSS\s+([\da-fA-F:]+)', line) + if m: if current: networks.append(current) - m = re.search(r'Address:\s*([\da-fA-F:]+)', line) - current = {'bssid': m.group(1) if m else '', 'ssid': '', 'channel': '', 'signal': '', 'security': '', 'mode': ''} - elif 'ESSID:' in line: - m = re.search(r'ESSID:"(.+?)"', line) - current['ssid'] = m.group(1) if m else '(Hidden)' - elif 'Channel:' in line: - m = re.search(r'Channel:(\d+)', line) + current = {'bssid': m.group(1).upper(), 'ssid': '', 'channel': '', + 'frequency': '', 'signal': '', 'security': 'Open', 'mode': ''} + continue + if current is None: + continue + if line_s.startswith('SSID:'): + current['ssid'] = line_s[5:].strip() or '(Hidden)' + elif line_s.startswith('freq:'): + current['frequency'] = line_s[5:].strip() + elif line_s.startswith('signal:'): + m = re.search(r'(-?[\d.]+)', line_s) + if m: + current['signal'] = str(int(float(m.group(1)))) + elif line_s.startswith('DS Parameter set: channel'): + m = re.search(r'channel\s+(\d+)', line_s) if m: current['channel'] = m.group(1) - elif 'Signal level' in line: - m = re.search(r'Signal level[=:](-?\d+)', line) - if m: - current['signal'] = m.group(1) - elif 'Encryption key:' in line: - current['security'] = 'Open' if 'off' in line else 'Encrypted' - elif 'WPA' in line or 'WPA2' in line: - current['security'] = 'WPA2' if 'WPA2' in line else 'WPA' - elif 'Mode:' in line: - m = re.search(r'Mode:(\S+)', line) - if m: - current['mode'] = m.group(1) + elif 'primary channel:' in line_s: + m = re.search(r'primary channel:\s*(\d+)', line_s) + if m and not current['channel']: + current['channel'] = m.group(1) + elif 'RSN:' in line_s or 'WPA:' in line_s: + if 'RSN:' in line_s: + current['security'] = 'WPA2' + elif current['security'] == 'Open': + current['security'] = 'WPA' + elif 'BSS Load:' in line_s or 'capability:' in line_s: + if 'ESS' in line_s: + current['mode'] = 'Infra' + elif 'IBSS' in line_s: + current['mode'] = 'Ad-Hoc' if current: networks.append(current) return networks diff --git a/web/templates/network.html b/web/templates/network.html index 0785c72..b53c222 100644 --- a/web/templates/network.html +++ b/web/templates/network.html @@ -129,7 +129,7 @@ Deauth Attack Pineapple / Evil Twin MITM Proxy - WiFi Audit + WiFi Audit