Files
Mark-VIII/docs/specs/2026-08-10-pager-webui-design.md
2026-08-11 20:24:24 -07:00

13 KiB

Pager WebUI — Design Spec (v1)

  • Date: 2026-08-10
  • Status: Approved (pending written-spec review)
  • Owner: Hak5 WiFi Pineapple Pager expansion project

1. Goal

Build a Mark VII-style web management UI that runs on the WiFi Pineapple Pager itself, with core-parity featureset (Dashboard, PineAP, Recon, Handshakes/Loot, Payloads, Logs, Settings, and a bottom-docked terminal), packaged as a Payload-Portal-installable payload and deployed over SSH/SCP.

The UI is reached at http://172.16.52.1:8080/ (verified free on the device).

2. Verified Research Basis (live devices)

Pager (172.16.52.1)

  • MediaTek MT76x8 SoC, OpenWRT-based Pineapple Pager 24.10.1 (ramips/mt76x8, mipsel_24kc, Linux 6.6.86).
  • Full root SSH (root + device password). opkg available.
  • Persistent 4GB MMC: /root -> /mmc/root; payloads live under /root/payloads/, loot under /root/loot/.
  • Tools present: python3 (3.11.14), sqlite3, iwinfo, hostapd_cli, ubus, uci, curl, nc, wget.
  • Firmware is U-Boot signed — firmware modification is out of scope; no stock files are touched.

Hak5 daemon (/pineapple/pineapple, Go)

  • HTTP API on :1471 and an unauthenticated Unix socket /tmp/api.sock serving the same router.
  • Auth: POST /api/login ({"username":"root","password":...}) returns {"token": ...}. Requests authorize with Authorization: Bearer <token> or the AUTH_<serverid> cookie (serverid from /api/api_ping, e.g. 001337AEE050).
  • Existing API used by the stock Virtual Pager: api_ping, login, payloads/portal/{index,refresh,updates,<key>/install,<key>/remove}, loot/archive, loot/zip, files/zip/root/loot/handshakes, terminal/openWs (WS), pager/input/keys.ws (WS), pager/display/screen.ws (WS).
  • Terminal WS /api/terminal/openWs: plain-text WebSocket (keystrokes in, output out). Requires the AUTH_* cookie (verified 401 without, 101 with). Accepts cross-origin handshakes. Cookies are host-scoped and port-agnostic, and the :8080 page and :1471 host are the same site, so a cookie set for host 172.16.52.1 is sent on the WS handshake from the :8080 page. The docked terminal therefore connects directly to the daemon WS; a server-side relay is the documented fallback.

Control channel (payload interface)

  • /usr/bin/hak5cmd (C++, protobuf client to the daemon). 49 symlinks provide every PineAP operation, e.g.: PINEAPPLE_SSID_POOL_{ADD,ADD_FILE,START,STOP,LIST,DELETE,CLEAR,COLLECT_START,COLLECT_STOP}, PINEAPPLE_NETWORK_FILTER_{MODE,ADD,ADD_FILE,DELETE,LIST,CLEAR}, PINEAPPLE_DEVICE_FILTER_{MODE,ADD,ADD_FILE,DELETE,LIST,CLEAR}, PINEAPPLE_MIMIC_{ENABLE,DISABLE}, PINEAPPLE_DEAUTH_CLIENT, PINEAPPLE_EXAMINE_{BSSID,CHANNEL,RESET}, PINEAPPLE_HOPPING_{START,STOP}, PINEAPPLE_SET_BANDS, PINEAPPLE_RECON_NEW, PINEAPPLE_LOOT_ARCHIVE.
  • Verified: commands run from CLI over SSH (read-only ones confirmed).

Recon data

  • SQLite /root/recon/recon.db: tables scan, wifi_device, ssid, handshake, hostap_chalresp, hostap_basic, hostap_client, hostap_handshake.

PineAP config

  • UCI file /etc/config/pineapd (sections: pineapd, hostapd, ssidpool, ssid_filter, mac_filter, interface wlan0mon/wlan1mon/wlan2mon).
  • Reload trigger exists (/etc/init.d/pineapd reload, pineap_reload).

Precedents (payloads already on device)

  • nautilus (user/remote_access/nautilus): web UI payload with init script (nautilus.init, START=99, procd), python3 proxy, foreground/background run modes, PAYLOAD_GET_CONFIG persistence.
  • virtual_pager_enhancer: uhttpd+CGI on port 4040 (init script pattern).

Mark VII blueprint

  • 113 /api/* endpoints extracted from its Angular bundle: dashboard, pineap/* (clients, kick, deauth, filters, handshakes, settings, ssids, summary), recon/* (scans, start/stop/status, tags), logging, settings/ networking, modules, device, terminal (xterm.js bottom-docked panel).

3. Repo Layout

Dev workspace: C:\Users\root\Documents\Pineapple\pager-webui\

pager-webui\
├── payload\user\general\pager-webui\
│   ├── _hak5_manifest.json
│   ├── payload.sh                      # run-mode installer (nautilus pattern)
│   ├── pagerwebui.init                 # OpenWRT init template (START=99, procd, respawn)
│   ├── server.py                       # Python3 stdlib HTTP+WS+JSON API backend (:8080)
│   └── www\                            # vanilla JS SPA + xterm.js bundle
├── scripts\
│   ├── deploy.ps1                      # build portal zip + SCP/install to Pager
│   └── dev.ps1                         # local dev server w/ proxy to Pager
├── docs\specs\                         # design specs
└── README.md

4. Packaging & Deployment

  • Payload format mirrors nautilus exactly: self-contained directory user/general/pager-webui/ with payload.sh, pagerwebui.init, server.py, www/.
  • Sideload (v1 delivery): scripts/deploy.ps1 builds a portal-format zip (payload-<b64>.zip) and installs it to /root/payloads/user/general/pager-webui/ on the Pager over SCP. The payload then appears in the on-device Payloads menu and the Virtual Pager's portal view — installable like any Payload Portal payload.
  • Future portal distribution: packaging is drop-in ready for a PR to hak5/wifipineapplepager-payloads; the build script generates the manifest time / last_hash / zip fields.

payload.sh run flow (nautilus pattern)

  1. If /etc/init.d/pagerwebui exists and running: show URL http://172.16.52.1:8080/, offer CONFIRMATION_DIALOG "Stop service?"; on confirm -> stop, disable, remove init script; exit.
  2. Read PAYLOAD_GET_CONFIG pager_webui auto_mode / run_mode. If auto_mode=true, skip prompt (background if run_mode=background, else foreground).
  3. Else CONFIRMATION_DIALOG "Run as background service?":
    • Yes -> background: cp pagerwebui.init /etc/init.d/pagerwebui, chmod +x, enable, start (procd runs python3 server.py, respawns on crash). Boot-persistent via rc.d symlinks.
    • No -> foreground: spawn python3 server.py with /tmp/pagerwebui.pid, show URL, loop WAIT_FOR_INPUT until B/Escape, trap cleanup kills the server and removes the pid file.
  4. Guard: python3 is present on current firmware; init script fails gracefully if missing.
  • Uninstall: the stop path above, plus deleting the payload directory (portal semantics).
  • Boot persistence: rc.d symlinks (overlay). Survives reboots and firmware upgrades; after a firmware upgrade (which wipes overlay) re-run the payload to re-enable — same caveat as nautilus. Foreground mode needs no persistence.

5. Backend (server.py)

Python 3.11 stdlib only (http.server, sqlite3, subprocess, json, socketserver, hand-rolled minimal RFC6455 for the live-update WS). Binds 0.0.0.0:8080. Single origin serves the SPA and the API.

Endpoint Method Implementation
/api/login POST validate via daemon :1471/api/login; on success return token and Set-Cookie: AUTH_<serverid> (host 172.16.52.1, no Domain, Path=/, HttpOnly, SameSite=Lax) so docked terminal + loot downloads on :1471 are authorized cross-port
/api/api_ping GET serverid + version (mirror daemon)
/api/status GET battery/power via sysfs, WiFi/interfaces via iwinfo + ip, firmware/daemon versions, disk via df, uptime
/api/pineap/settings GET/POST read/write UCI /etc/config/pineapd (mimic, collect probes, advertise, collect handshakes, random MAC, wigle, bands) + pineap_reload
/api/pineap/ssids GET/POST SSID pool read/write via hak5cmd
/api/pineap/ssidpool/{start,stop,collect_start,collect_stop} POST via hak5cmd
/api/pineap/filters/client GET/POST device filter mode + list via hak5cmd
/api/pineap/filters/ssid GET/POST network filter mode + list via hak5cmd
/api/pineap/clients GET associated clients via iwinfo assoclist / hostapd_cli on wlan0open/wlan0wpa/wlan0mgmt
/api/pineap/clients/kick POST deauth via hak5cmd + auto-add to deny filter (Mark VII behavior)
/api/pineap/deauth/client POST PINEAPPLE_DEAUTH_CLIENT
/api/recon/{start,stop} POST hak5cmd RECON_NEW / stop
/api/recon/scans GET scans list from recon.db
/api/recon/scans/<id> GET APs/clients/handshakes for a scan from recon.db
/api/pineap/handshakes GET/DELETE list/delete /root/loot/handshakes
/api/loot/zip GET zip download (wraps daemon :1471 with cookie)
/api/loot/archive POST wraps daemon :1471
/api/payloads/index, /api/payloads/install, /api/payloads/remove, /api/payloads/refresh GET/POST wrap daemon portal endpoints
/api/logging/system GET logread (filtered, tail)
/api/logging/pineap GET pineapd/daemon log sources
/api/device GET hostname, MACs, model
/api/settings/hostname GET/POST uci network hostname
/api/settings/password POST change the device root password via BusyBox passwd stdin
/api/settings/ntp GET/POST UCI system timeserver (enabled + servers) + restart sysntpd
/api/ws WS live push: status/clients/recon deltas every ~2s
/api/terminal/openWs WS same path on our origin; client connects direct to daemon ws://172.16.52.1:1471/api/terminal/openWs (cookie-authed). Fallback: server relays

Auth model

  • All endpoints (except /api/login) require a valid session cookie set by /api/login.
  • Token is never logged or stored server-side beyond the session check; the backend validates each request by checking the session cookie against the daemon's token (a lightweight session store in /tmp/pagerwebui.session).
  • Commands are executed with argument lists (no shell string interpolation) to prevent injection.

6. Frontend (vanilla JS SPA)

  • Static files in www/: index.html, css/app.css, js/app.js (or split modules), js/xterm.js + js/xterm-fit.js (bundled copies), assets/.
  • Mark VII-style chrome: dark theme, top bar (logo, live status, Terminal button), nav rail, hash-based routing (#/dashboard, #/pineap, #/recon, #/handshakes, #/payloads, #/logs, #/settings).
  • Dashboard: status cards + live counters (battery, clients, APs, handshakes) fed by /api/ws.
  • PineAP: settings toggles (mimic/advertise/collect/handshakes/random MAC/ wigle/bands), SSID pool CRUD + collect/start/stop, client & SSID filters (mode + list CRUD), client list with kick.
  • Recon: scan list, AP/client tables from recon.db, start/stop buttons.
  • Handshakes/Loot: list, download zip, delete, archive.
  • Payloads: portal list + install/remove/refresh.
  • Logs: system + pineap logs with tail/poll.
  • Settings: hostname, NTP, password, webUI prefs (poll interval, accent), service status (background/foreground).
  • Terminal: bottom-docked xterm panel toggled by the top-bar button (Mark VII parity). Connects ws://172.16.52.1:1471/api/terminal/openWs. Default 80x24; resize behavior validated during implementation (daemon may ignore resize; fallback fixed size with xterm fit disabled).

7. Security & Resilience

  • Auth: device password validated through the daemon; HttpOnly session cookie; no plaintext secret storage; state-changing endpoints all behind login.
  • No stock files modified; no opkg changes; reversible via payload.sh stop/uninstall; factory reset / firmware recovery remain available.
  • server.py failure modes: procd respawn in background mode; foreground mode cleans up on exit; recon.db opened read-only; subprocesses use arg lists.
  • Binds 0.0.0.0:8080 (same exposure class as stock :1471 / :7681).

8. Testing & Verification

  • Windows dev loop: scripts/dev.ps1 runs the SPA + API locally and proxies to the Pager.
  • PowerShell-driven API tests against :8080: login, status, each read/write endpoint before frontend wiring.
  • On-device smoke tests per page: status, pool CRUD, filter toggles, recon start/scan read, handshake listing, portal install/remove, terminal I/O, background vs foreground modes, reboot persistence.
  • Recovery drill: uninstall, re-install, foreground-stop, firmware-upgrade caveat documented in README.

9. Out of Scope (v1)

  • :1471 takeover/redirect.
  • Mark VII features without a Pager equivalent: Campaigns, Modules, Cloud C², Enterprise/EAP pages.
  • Physical-display screen mirror.
  • Publishing a PR to the official payloads repo (packaging ready; submission later).

10. Open Risks (mitigated during implementation)

  • Client-list source validation: iwinfo assoclist / hostapd_cli against the Pager's hostapd interfaces (wlan0open, wlan0wpa, wlan0mgmt).
  • Minimal RFC6455 WebSocket server correctness.
  • Daemon terminal resize support (fallback to fixed 80x24).