163 lines
9.6 KiB
Markdown
163 lines
9.6 KiB
Markdown
# Design: Mark VII PineAP Page Port for the Pager WebUI
|
|
|
|
Date: 2026-08-11
|
|
|
|
## Goal
|
|
|
|
Replace the current pager-webui PineAP page with a faithful replica of the WiFi
|
|
Pineapple Mark VII PineAP view, fully functional on the WiFi Pineapple Pager,
|
|
independent of campaign needs. Also fix the side-rail icon (location pin ->
|
|
wifi).
|
|
|
|
## Background / findings
|
|
|
|
- The current PineAP page (tabs: Open, Clients, Filtering, APs, Impersonation)
|
|
looks nothing like the Mark VII page and its backend is broken on the Pager:
|
|
- `server.py` reads Mark VII uci keys (`pineapd.pineapd.mimic`,
|
|
`collect_handshakes`, ...) that do not exist in the Pager's `pineapd`
|
|
config, so settings never persist (live `GET /api/pineap/settings` returns
|
|
only `{"bands":"2.4"}` plus raw).
|
|
- The filter-mode endpoints call `hak5cmd` commands without the required
|
|
argument, returning usage text instead of the mode.
|
|
- The Pager's daemon (`/pineapple/pineapple`, listens on `:1471`) natively
|
|
exposes a Mark VII-style PineAP REST API over a root-only unix socket at
|
|
`/tmp/api.sock` (raw HTTP/1.1, no auth - socket permission is the boundary).
|
|
pager-webui already uses this socket (`daemon_sock_call`, e.g. recon
|
|
start/stop). Confirmed live routes:
|
|
|
|
| Route | Method | Purpose |
|
|
|---|---|---|
|
|
| `/api/pineap/get_config`, `/set_config` | GET/POST | log flags, handshake path, ssidpool autocollect |
|
|
| `/api/pineap/hostapd/get_config`, `/set_config` | GET/POST | master PineAP on/off, Evil WPA (`wpa_ifaces`), Evil Enterprise (`pineape_*`) |
|
|
| `/api/pineap/hostapd/enable_pineap` | POST | master enable |
|
|
| `/api/pineap/hostapd/enable_pineape` | POST | evil enterprise enable |
|
|
| `/api/pineap/hostapd/enable_pineape_auth` | POST | enterprise auth-pass capture |
|
|
| `/api/pineap/mimic/enable`, `/disable` | POST | karma |
|
|
| `/api/pineap/examine/bssid`, `/reset` | POST | targeted handshake capture |
|
|
| `/api/pineap/ssidpool/list`, `/add`, `/clear`, `/disable`, `/disable_collect` | GET/POST | SSID pool + advertise/collect |
|
|
| `/api/pineap/ssidfilter/get_config`, `/set_config`, `/allow/*`, `/deny/*` | GET/POST | SSID filter mode + lists |
|
|
| `/api/pineap/macfilter/get_config`, `/set_config`, `/set_mode`, `/allow/*`, `/deny/*` | GET/POST | MAC/client filter mode + lists |
|
|
| `/api/pineap/interfaces/get`, `/set_interface`, `/set_interface_bands` | GET/POST | monitor interface hop/inject/bands |
|
|
| `/api/pineap/log/recon|pcap|wigle/start|stop` | POST | logging control |
|
|
| `/api/pineap/recon/new` | POST | new recon scan |
|
|
|
|
- The Pager natively supports Evil WPA (rogue AP on `wlan0wpa`: SSID, PSK,
|
|
encryption WPA2-PSK / WPA3-SAE / WPA3-OAE, hidden, enabled) and Evil
|
|
Enterprise (EAP, auth-pass capture), confirmed via the daemon binary strings
|
|
and the native (virtual) pager menu.
|
|
- The daemon lacks routes for: connected clients (use `iwinfo`), kick
|
|
(`hak5cmd PINEAPPLE_DEAUTH_CLIENT`), nearby-AP scan (`iwinfo scan`), and
|
|
handshake file listing (`/root/loot/handshakes/`). pager-webui already
|
|
implements these.
|
|
|
|
## Architecture
|
|
|
|
### Backend (`payload/user/general/pager-webui/server.py`)
|
|
|
|
1. Add a generic proxy handler `h_pineap_proxy` that forwards the browser
|
|
request (method + JSON body) verbatim to the daemon socket path
|
|
`/api/pineap/<subpath>` using the existing `daemon_sock_call`, and returns
|
|
the daemon's JSON response. Register it for the whole native tree
|
|
(`get_config`, `set_config`, `hostapd/*`, `mimic/*`, `examine/*`,
|
|
`ssidpool/*`, `ssidfilter/*`, `macfilter/*`, `interfaces/*`, `log/*`,
|
|
`recon/new`) so every native capability is reachable 1:1 through
|
|
pager-webui's authenticated `/api/pineap/*` namespace.
|
|
2. Keep the custom endpoints that have no daemon route: `clients` (iwinfo),
|
|
`clients/kick` (deauth), `aps` (iwinfo scan), `handshakes` (loot listing +
|
|
download/delete).
|
|
3. Add Enterprise data endpoints: list + clear of `hostap_basic` /
|
|
`hostap_challenge` rows from `recon.db` (via the sqlite3 CLI, the same
|
|
mechanism the recon page uses).
|
|
4. Delete the broken Mark VII-uci settings handlers (`SETTING_MAP`,
|
|
`_uci_map`, `h_pineap_settings_get/post`) and the filter handlers that
|
|
returned hak5 usage text (`h_filter_get/post`); replace with the proxy.
|
|
5. On daemon socket failure return 502 with a JSON error; never crash.
|
|
|
|
### Frontend (`www/js/app.js`, `www/js/views.js`, `www/css/app.css`)
|
|
|
|
1. Rail icon: change the PineAP rail item from `pineap` (map-marker) to the
|
|
existing `wifi` icon in `icons.js`/`app.js` rail definition.
|
|
2. Rebuild the PineAP page as 8 tabs matching the Mark VII structure (using
|
|
the existing vanilla-JS SPA design language - material-style cards,
|
|
toggles, tables, tab bar):
|
|
|
|
| Tab | Route | Content |
|
|
|---|---|---|
|
|
| PineAP | `#/pineap` | Mode badge (Passive/Active/Advanced from `pineap_disabled`+mimic+evilwpa/enterprise), description, quick toggles (Capture SSIDs to Pool, Advertise AP Impersonation Pool, Randomize Source MAC), alert-payload info note, 4 status cards (Karma / Open Network / Evil WPA / Evil Enterprise) with Configure links |
|
|
| Open AP | `#/pineap/open` | Enable PineAP (master), Karma, Logging group (handshakes/partial/pcap/wigle/recon), Capture SSIDs, Advertise Pool, Randomize MAC, AP Channel select, PineAP MAC + Target MAC (read-only) |
|
|
| Evil WPA | `#/pineap/evilwpa` | SSID, passphrase, encryption select (WPA2-PSK / WPA3-SAE / WPA3-OAE), Hidden, Enabled; handshake capture card (Examine BSSID + start/stop/reset); captured handshakes table |
|
|
| Enterprise | `#/pineap/enterprise` | Enabled, Auth Pass Capture; Basic Data + Challenge Data tables with Clear |
|
|
| Impersonation | `#/pineap/impersonation` | SSID pool textarea editor + Add + Clear; Advertise + Randomize toggles; pool start/stop/collect |
|
|
| Clients | `#/pineap/clients` | Connected clients table (MAC/interface/RSSI) + Kick, 5s auto-refresh |
|
|
| Filtering | `#/pineap/filtering` | Client Filter + SSID Filter cards: Allow/Deny mode + line-based list textareas with add/delete/clear |
|
|
| APs | `#/pineap/aps` | Kept as-is (iwinfo scan table, 10s refresh) |
|
|
|
|
3. Mark VII-only controls with no Pager equivalent (Autostart, Beacon
|
|
Responses, Beacon Intervals, enterprise cert generation) are omitted
|
|
rather than greyed out; Client Connect/Disconnect Notifications are shown
|
|
as an info note (the Pager handles these via alert payloads natively).
|
|
4. Register the new routes in the `routes` map and add the tab bar entries +
|
|
CSS.
|
|
|
|
## Data flow
|
|
|
|
Browser -> pager-webui `/api/pineap/*` (session-authenticated) ->
|
|
`daemon_sock_call` -> `/tmp/api.sock` -> Pager daemon -> `pineapd`/`hostapd`.
|
|
Mutating calls show a toast; failures surface a friendly error.
|
|
|
|
## Error handling
|
|
|
|
- Daemon socket unavailable -> HTTP 502 `{error}`; frontend shows a toast and
|
|
never renders a dead page.
|
|
- Unknown/unsupported subpath -> 404 through pager-webui.
|
|
- Enterprise tables empty -> "no data" empty states (same pattern as Recon).
|
|
|
|
## Testing
|
|
|
|
- Rewrite `tests/test_pineap_{settings,pool,clients,aps}.py` and add
|
|
`test_pineap_evilwpa.py` / `test_pineap_enterprise.py` /
|
|
`test_pineap_filtering.py` against the proxied shapes, mocking the daemon
|
|
socket call (module-level monkeypatch per the existing test conventions).
|
|
- Run the README's per-file unittest loop on Windows.
|
|
- On-device smoke test after `deploy.ps1`: walk every tab, verify toggles
|
|
persist across a reboot.
|
|
|
|
## Out of scope
|
|
|
|
- Firmware changes; the daemon API internals are used as-is.
|
|
- A PR to `hak5/wifipineapplepager-payloads` (packaging is drop-in ready).
|
|
|
|
## Open items (resolved during implementation)
|
|
|
|
- Exact `hostapd/set_config` Evil WPA field names (SSID/PSK/encryption/
|
|
hidden/enabled) - probe on the live device.
|
|
**Resolved:** Evil WPA lives in UCI `wireless.wlan0wpa`. The daemon route
|
|
`PUT /api/settings/wifi/set_ap` takes `{"configs":[{interface, ssid, enctype,
|
|
enabled, hidden, key, channel(int)}]}` (full replace; `channel` must be int).
|
|
`PUT /api/settings/wifi/get_ap` is a stub (always `{"interfaces":[]}`), so the
|
|
webui reads UCI `wireless.wlan0wpa`/`wlan0open` directly. `enctype` values:
|
|
`psk2`, `psk`, `sae`, `none` (not `wpa2`). Applying reconfigures the radio and
|
|
briefly drops the management connection.
|
|
- `ssidpool` list response shape (fallback: `hak5cmd PINEAPPLE_SSID_POOL_LIST`).
|
|
**Resolved:** `GET /api/pineap/ssidpool/list` is a stub (`{"success":true}`);
|
|
the pool is stored base64 in UCI `pineapd.@ssidpool[0].ssid`. `hak5cmd`
|
|
`PINEAPPLE_SSID_POOL_LIST/ADD/DELETE/CLEAR` all work and are used.
|
|
- Randomize-Source-MAC route (`GetPineAPRandomizeMAC` internal var) -
|
|
discover the backing route/field.
|
|
**Resolved (partial):** no readable daemon route for the pool advertise state
|
|
(`ssidpool/enable|disable` are POST; `enable` returns 500 natively because no
|
|
`wlan0open` interface exists on the Pager). The webui surfaces the daemon error.
|
|
- sqlite3 CLI availability for the Enterprise tables.
|
|
**Resolved:** sqlite3 CLI present; `hostap_basic`/`hostap_challenge` do not
|
|
exist yet in `recon.db`, so the Enterprise tab renders empty tables (webui
|
|
returns `rows: []`).
|
|
- Pineapd UCI required-field cycle: the daemon's `set_config`/`hostapd/set_config`
|
|
are full-replaces. If a write omits required fields (`pineapd.@pineapd[0].
|
|
reconpath` etc.), `get_config` then fails and later writes stay broken. The
|
|
webui merges each write over the current daemon config (or defaults when
|
|
`get_config` fails) so required fields are always preserved.
|
|
- Verified daemon methods: reads GET; `set_config`/`hostapd/set_config`/
|
|
`hostapd/enable_pineap`/`interfaces/set_interface`/`ssidpool/add`/`wifi/*` PUT;
|
|
toggles (`mimic/*`, `examine/*`, `ssidpool/enable|disable|enable_collect|
|
|
disable_collect`) POST. `ssidfilter`/`macfilter` `set_config` are PUT.
|