5.6 KiB
Mark VIII — Deauth UX, Evil Portal, Monitor Capture Fixes
Date: 2026-08-23 Status: approved (user confirmed design in session)
Goals
- Make deauth obvious: "Deauth All Clients" from Recon's AP focus sidebar, "Deauth All" on the Deauth Targeting card — both gated by an engagement-scope confirmation.
- New top-level Evil Portal tab compatible with Hak5 EvilPortalNano portals (kleo/evilportals layout): import zips, serve pages to victims, capture credentials, DNS-hijack delivery.
- OpenAP gains an "Evil Portal" card: dropdown of imported templates, greyed out when none exist; activating an OpenAP with a template selected starts the portal.
- Fix Monitor Capture never appearing to work.
Non-goals
- No PHP interpreter; the backend shims only the trivial PHP patterns stock portals use.
- No TLS interception (HTTPS requests are not redirected).
.enable/.disableportal scripts are stored but not executed (divergence from stock EvilPortalNano, documented here deliberately).
1. Bulk deauth endpoint
POST /api/attacks/deauth/bulk body {targets: [{bssid, client, channel?}, ...]}
(max 32). Shared helper _deauth_one(bssid, client, channel) extracted from
h_attacks_deauth; per-target results returned {results: [...], sent, failed}.
Uses the existing band→inject-iface logic and PINEAPPLE_DEAUTH_CLIENT.
Recon sidebar
In renderFocus() (www/js/views.js), when the focused AP has confirmed clients, add a
danger button "Deauth All Clients". Clicking shows
confirm("Deauthenticate N client(s) of <SSID>? Confirm this target is IN SCOPE for your engagement."),
then posts one bulk call.
Deauth Targeting card
deauthPanel() gains a danger "Deauth All" button that bulk-deauths every device in
the current result list against the selected AP, behind the same scope confirmation.
2. Evil Portal
Storage & import
- Root:
/mmc/mk8/portals/<name>/([A-Za-z0-9._-]{1,64}names, mk8_profiles rules). POST /api/portals/import{data: <base64 zip>}(≤10 MB decoded). Zip-slip guarded (reject absolute paths and..). If every entry shares a single top-level directory, that directory becomes the portal root.index.phpmust exist at the root.- Other endpoints:
GET /api/portals,DELETE /api/portals/{name},POST /api/portals/{name}/activate|deactivate,GET /api/portals/captures,DELETE /api/portals/captures,GET /api/portals/{name}/logs(Download),GET /api/portals/{name}/download(zip of the portal folder).
Serving engine (port 80)
Second listener thread in server.py, own connection handler (no auth, no same-origin — victims are unauthenticated). Admin UI on :8080 untouched.
- GET anything → active portal:
- Path resolves inside the portal dir (
_safe_join); missing paths fall back toindex.php; asset files served raw with the standard mime map. index.phprendered through the PHP shim.
- Path resolves inside the portal dir (
- PHP shim: replaces
getClientMac($_SERVER['REMOTE_ADDR'])/getClientHostName(...)with values resolved from DHCP leases for the requester IP,$_SERVER['REMOTE_ADDR']with the requester IP,$destinationwith the request URL; strips all other<?php ... ?>/<?= ... ?>blocks. - POST anything → credential capture: parse urlencoded fields, enrich with client
ip/mac/hostname, append:
<portal>/.logsin the exact MyPortal.php text format (stock-tool compatible), and/mmc/mk8/portals/captures.jsonl(one JSON object per line) for the UI. Response: simple authorization-success HTML page.
Delivery: DNS hijack
Activation writes dhcp.@dnsmasq[0].address='/#/<lan-ip>' (UCI), commits, restarts
dnsmasq; deactivation deletes the option and restarts. LAN IP discovered via
ip -4 addr show br-lan (env-overridable PAGER_LAN_IFACE, fallback 172.16.52.1).
Active portal name persists at /mmc/mk8/portals/.active; on service start the hijack
is re-applied best-effort if a portal was left active.
Tab UI
Rail entry + route #/evilportal + views.evilportal: Active Portal status card,
Templates card (list with Activate/Stop/Delete/Download, Import via zip file input →
base64), Captured Credentials table (time, portal, fields) with Clear and Download.
3. OpenAP Evil Portal card
attackLauncher('open', {..., portal: true}): card with a template <select>
(populated from GET /api/portals), disabled with hint text when no templates exist.
Deploy body carries portal: <name>; h_attacks_deploy activates it after a successful
open-AP deploy; h_attacks_stop for kind open deactivates any active portal. Status
card shows the active binding.
4. Monitor Capture fix
Root cause: views.js:1314 polls /api/attacks/capture {action:'status'} without
iface; the backend defaults to wlan0mon, so a live wlan1mon capture flips back to
"Not capturing" within one 5 s poll.
- Backend
statuswith noiface: evaluates both pidfiles and reports whichever capture is actually running (else the default). Start failures nowmkdir -pthe pcap dir first and tee tcpdump stderr to/tmp/mk8_capture_<iface>.log, surfaced in the 502 detail. - Frontend: when a status/start response reports a running capture, adopt its iface for subsequent Stop clicks.
Testing
- New
tests/test_portals.py: import validation (bad name, zip-slip, oversize, missing index.php, nested top-dir flattening), PHP shim substitutions, capture log formats. - Extend
tests/test_attacks.py: bulk deauth validation/looping (mocked device_run), capture status dual-iface resolution. - Existing suite must stay green (run per-file as documented in README).