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

27 KiB
Raw Permalink Blame History

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_CLIENTPINEAPPLE_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:

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()
    rc, out, err = device_run([HAK5CMD, 'DEAUTH_CLIENT', bssid, mac, str(channel)], timeout=30)
    
  • server.py:4602h_attacks_deauth()
    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):

# 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

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:

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_textserver.py:3836-3853 (writes the eap_user_file)
  • _deploy_enterpriseserver.py:4217-4325
  • _ent_conf_textserver.py:4102
  • _start_ent_hostapdserver.py:4173-4202
  • _ensure_ent_certsserver.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.localInvalid 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 → .hc22000hashcat -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:6wlan0wpa (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/pagerwebuirunning), 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

# 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).