feat: merge attacks into PineAP menu; auto channel; recon fixes; richer reports

- Rail: Attacks tab removed; PineAP becomes a grouped menu (Evil WPA /
  Evil Open / Evil Enterprise / Impersonation / Clients / Filtering);
  old #/attacks* hashes redirect to their PineAP equivalents.
- PineAP tabs gain Evil Enterprise; stock Open AP / Evil WPA / Enterprise
  pages replaced by the verified one-click launchers (status, capture,
  export, deauth, playbooks).
- Channel selects gain an Auto option: deploy resolves the target SSID's
  last-seen channel from recon.db (verified unit-tested end to end).
- Harness: pi.dev prompt section removed; Copy Token inline; robot icon.
- Recon: compare checkboxes no longer hide the AP list (multi-select
  stays visible, rows highlighted, clients table no longer suppressed);
  Previous Scans buttons moved above the dropdown with a Delete All;
  encryption chips + buckets now distinguish WPA2/WPA3 PSK vs Enterprise
  (AKM suites decoded from recon bitfield bits 32-47); scan JSON carries
  GPS when a fix exists; Reports tab shows a GPS column.
- fix: restore top-level EVIL_ENC definition lost in the repo (deployed
  build had it; repo would have thrown at init).
This commit is contained in:
2026-08-18 22:35:51 -05:00
parent 72f6a68897
commit e93359fb69
8 changed files with 375 additions and 572 deletions
+49
View File
@@ -1,5 +1,6 @@
import os
import shutil
import sqlite3
import sys
import tempfile
import unittest
@@ -133,6 +134,54 @@ class AttacksDeployTest(unittest.TestCase):
self.assertEqual(payload['iface'], 'wlan1wpa')
self.assertEqual(payload['band'], server.BAND_5G)
def test_deploy_wpa_auto_channel_defaults_to_1_without_recon(self):
status, payload = server.h_attacks_deploy(ctx({
'kind': 'wpa', 'ssid': 'UnknownNet', 'passphrase': 'secretpass1',
'enctype': 'psk2', 'hidden': False}))
self.assertEqual(status, 200)
self.assertTrue(payload['auto'])
self.assertEqual(payload['channel'], 1)
self.assertEqual(payload['band'], server.BAND_2G)
cfg = [s for s in self.f.sock if s[0] == 'PUT' and s[1] == '/api/settings/wifi/set_ap'][0][2]
self.assertEqual(cfg['configs'][0]['channel'], 1)
def test_deploy_wpa_auto_channel_uses_recon_target_channel(self):
db = self._make_recon_db()
old = server.RECON_DB
server.RECON_DB = db
try:
status, payload = server.h_attacks_deploy(ctx({
'kind': 'wpa', 'ssid': 'Anderson-5', 'passphrase': 'secretpass1',
'enctype': 'psk2', 'hidden': False}))
finally:
server.RECON_DB = old
os.unlink(db)
self.assertEqual(status, 200)
self.assertEqual(payload['channel'], 149)
self.assertEqual(payload['band'], server.BAND_5G)
self.assertEqual(self.f.state['wireless.radio1.channel'], '149')
def _make_recon_db(self):
fd, db = tempfile.mkstemp(suffix='.db')
os.close(fd)
conn = sqlite3.connect(db)
conn.executescript(
'CREATE TABLE scan(id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT,'
' time INT, name TEXT);'
'CREATE TABLE wifi_device(hash INT PRIMARY KEY, scan INT, mac TEXT,'
' time INT, signal INT, freq INT, packets INT);'
'CREATE TABLE ssid(hash INT PRIMARY KEY, wifi_device INT, scan INT,'
' type INT, bssid TEXT, ssid BLOB, hidden INT, time INT, signal INT,'
' freq INT, channel INT, encryption INT);')
conn.execute("INSERT INTO scan (uuid, time, name) VALUES ('u1', 1, 'pager')")
conn.execute("INSERT INTO ssid (hash, wifi_device, scan, type, bssid, ssid, hidden,"
" time, signal, freq, channel, encryption) VALUES"
" (10, 1, 1, 8, 'C89E43648080', X'416E646572736F6E2D35', 0,"
" 1786466532, -76, 5745, 149, 0x400400108)")
conn.commit()
conn.close()
return db
def test_deploy_open_2g4_includes_bssid_and_country(self):
status, payload = server.h_attacks_deploy(ctx({
'kind': 'open', 'ssid': 'Guest', 'hidden': False,
+8 -4
View File
@@ -76,8 +76,13 @@ class DecodersTest(unittest.TestCase):
self.assertEqual(server.decode_encryption(0x04), 'WPA')
self.assertEqual(server.decode_encryption(0x08), 'WPA2')
self.assertEqual(server.decode_encryption(0x04 | 0x08), 'WPA2 WPA')
self.assertEqual(server.decode_encryption(0x400400108), 'WPA3 WPA2')
self.assertEqual(server.decode_encryption(0x20050004C), 'WPA2 WPA')
self.assertEqual(server.decode_encryption(0x400400108), 'WPA3 WPA2 PSK')
self.assertEqual(server.decode_encryption(0x400400108 | (1 << 33)), 'WPA3 WPA2 Enterprise')
self.assertEqual(server.decode_encryption(0x400400108 | (1 << 40)), 'WPA3 WPA2 SAE')
self.assertEqual(server.decode_encryption(0x400400108 | (1 << 33) | (1 << 40)), 'WPA3 WPA2 Enterprise')
self.assertEqual(server.decode_encryption(0x400400108 | (1 << 45)), 'WPA3 WPA2 OWE')
self.assertEqual(server.decode_encryption(0x400400110), 'WPA3 PSK')
self.assertEqual(server.decode_encryption(0x20050004C), 'WPA2 WPA Enterprise')
class ReconDataTest(unittest.TestCase):
@@ -111,7 +116,7 @@ class ReconDataTest(unittest.TestCase):
self.assertEqual(a['ssid'], 'Anderson-5')
self.assertEqual(a['channel'], 149)
self.assertEqual(a['signal'], -76)
self.assertEqual(a['encryption'], 'WPA3 WPA2')
self.assertEqual(a['encryption'], 'WPA3 WPA2 PSK')
self.assertFalse(a['hidden'])
hidden = aps['50:6F:9A:01:00:00']
self.assertTrue(hidden['hidden'])
@@ -1193,4 +1198,3 @@ class ReconRoutesTest(unittest.TestCase):
method = 'GET' if path.endswith(('status', 'scans', 'events')) else 'POST'
h, args = server.ROUTER.dispatch(method, path)
self.assertIsNotNone(h, path)