feat: attacks workflow — playbook steps + auto-targeting deauth panel
Evil WPA/Open pages now show a state-aware playbook (current step highlighted, contextual hint) and the Deauth Targeting panel auto-fills the live attack SSID and refreshes clients automatically. Enterprise page gains a playbook hint card.
This commit is contained in:
@@ -1394,11 +1394,41 @@ function attackLauncher(kind, opts) {
|
||||
}
|
||||
|
||||
let deauthCard = null;
|
||||
const ssidRef = { current: '' };
|
||||
if (opts.deauth) {
|
||||
deauthCard = deauthPanel();
|
||||
deauthCard = deauthPanel(ssidRef);
|
||||
box.appendChild(deauthCard);
|
||||
}
|
||||
|
||||
if (opts.playbook) {
|
||||
const pb = h('div', { class: 'pineap-title-card' });
|
||||
pb.appendChild(h('div', { class: 'pineap-card-title' }, 'Playbook'));
|
||||
const pbBody = h('div', { style: 'font-size:13px;line-height:1.8' });
|
||||
pb.appendChild(pbBody);
|
||||
box.appendChild(pb);
|
||||
const steps = opts.playbook.steps;
|
||||
const stepEls = {};
|
||||
const stepList = h('ol', { style: 'margin:0;padding-left:20px' });
|
||||
steps.forEach((s) => {
|
||||
const li = h('li', { style: 'margin-top:4px' }, h('span', { text: s }));
|
||||
stepEls[s] = li;
|
||||
stepList.appendChild(li);
|
||||
});
|
||||
pbBody.appendChild(stepList);
|
||||
const pbHint = h('div', { class: 'muted', style: 'font-size:12px;margin-top:6px' });
|
||||
pbBody.appendChild(pbHint);
|
||||
const pbTick = (status, which) => {
|
||||
const activeStep = opts.playbook.currentStep(status, which);
|
||||
steps.forEach((s) => {
|
||||
stepEls[s].style.color = s === activeStep ? 'var(--primary)' : '';
|
||||
stepEls[s].style.fontWeight = s === activeStep ? '600' : '';
|
||||
});
|
||||
pbHint.textContent = opts.playbook.hint(status, which) || '';
|
||||
};
|
||||
pbTick(null, null);
|
||||
box.__pbTick = pbTick;
|
||||
}
|
||||
|
||||
function load() {
|
||||
PagerAPI.get('/api/attacks/status').then((r) => {
|
||||
const s = r.data || {};
|
||||
@@ -1416,6 +1446,11 @@ function attackLauncher(kind, opts) {
|
||||
text: 'Configured but interface not live yet — radio reload may still be in progress.' }));
|
||||
}
|
||||
capIface = (which && (which.band === '5' || which.band === '6')) ? 'wlan1mon' : 'wlan0mon';
|
||||
if (ssidRef) {
|
||||
ssidRef.current = which && which.ssid ? which.ssid : '';
|
||||
if (ssidRef.tick) ssidRef.tick();
|
||||
}
|
||||
if (box.__pbTick) box.__pbTick(s, which);
|
||||
if (opts.handshakes) {
|
||||
hsBox.innerHTML = '';
|
||||
hsBox.appendChild(h('div', { class: 'pineap-card-title', text: 'Handshakes Captured (' + (s.handshakes || 0) + ')' }));
|
||||
@@ -1525,7 +1560,7 @@ views.harness = (root) => {
|
||||
return { destroy: () => {} };
|
||||
};
|
||||
|
||||
function deauthPanel() {
|
||||
function deauthPanel(ssidRef) {
|
||||
const wrap = h('div', { class: 'pineap-title-card' });
|
||||
wrap.appendChild(h('div', { class: 'pineap-card-title' }, 'Deauth Targeting'));
|
||||
const body = h('div', { style: 'font-size:13px' });
|
||||
@@ -1533,46 +1568,58 @@ function deauthPanel() {
|
||||
const ssidBox = h('input', { placeholder: 'SSID to find clients for' });
|
||||
const apSel = h('select', {});
|
||||
const clTable = h('div', {});
|
||||
body.appendChild(h('div', { class: 'row' }, ssidBox,
|
||||
h('div', {}, btn('Find', () => {
|
||||
const q = ssidBox.value.trim();
|
||||
if (!q) return;
|
||||
PagerAPI.get('/api/attacks/clients?ssid=' + encodeURIComponent(q)).then((r) => {
|
||||
const d = r.data || {};
|
||||
apSel.innerHTML = '';
|
||||
(d.aps || []).forEach((a) => apSel.appendChild(h('option', {
|
||||
value: (a.bssid || '') + '|' + (a.channel || 1),
|
||||
text: (a.ssid || q) + ' — ' + (a.bssid || '?') + ' ch' + (a.channel || 1)
|
||||
let lastLookup = '';
|
||||
function lookup(q) {
|
||||
if (!q || q === lastLookup) return;
|
||||
lastLookup = q;
|
||||
PagerAPI.get('/api/attacks/clients?ssid=' + encodeURIComponent(q)).then((r) => {
|
||||
const d = r.data || {};
|
||||
apSel.innerHTML = '';
|
||||
(d.aps || []).forEach((a) => apSel.appendChild(h('option', {
|
||||
value: (a.bssid || '') + '|' + (a.channel || 1),
|
||||
text: (a.ssid || q) + ' — ' + (a.bssid || '?') + ' ch' + (a.channel || 1)
|
||||
})));
|
||||
if (!(d.aps || []).length) apSel.appendChild(h('option', { value: '|1', text: 'No APs found — check SSID' }));
|
||||
clTable.innerHTML = '';
|
||||
const cl = (d.clients || []).slice(0, 30);
|
||||
if (!cl.length) {
|
||||
clTable.appendChild(h('div', { class: 'empty', text: 'No devices in recon yet.' }));
|
||||
return;
|
||||
}
|
||||
clTable.appendChild(table(
|
||||
[{ key: 'mac', label: 'MAC' }, { key: 'freq', label: 'Freq (MHz)' },
|
||||
{ key: 'signal', label: 'Signal' }, { key: 'packets', label: 'Packets' },
|
||||
{ key: '_deauth', label: '' }],
|
||||
cl.map((c) => {
|
||||
const mac = c.mac || c.client_mac || '';
|
||||
return { mac: mac || '—', freq: c.freq || '', signal: c.signal || '',
|
||||
packets: c.packets || '', _deauth: btn('Deauth', () => {
|
||||
const parts = apSel.value.split('|');
|
||||
if (!mac || !parts[0]) { App.toast('Pick an AP and device first', 'error'); return; }
|
||||
PagerAPI.post('/api/attacks/deauth', {
|
||||
bssid: parts[0], client: mac, channel: parseInt(parts[1], 10)
|
||||
}).then(() => App.toast('Deauth frames sent'))
|
||||
.catch((e) => App.toast(e.message || 'Deauth failed', 'error'));
|
||||
}, 'danger') };
|
||||
})));
|
||||
if (!(d.aps || []).length) apSel.appendChild(h('option', { value: '|1', text: 'No APs found — check SSID' }));
|
||||
clTable.innerHTML = '';
|
||||
const cl = (d.clients || []).slice(0, 30);
|
||||
if (!cl.length) {
|
||||
clTable.appendChild(h('div', { class: 'empty', text: 'No devices in recon yet.' }));
|
||||
return;
|
||||
}
|
||||
clTable.appendChild(table(
|
||||
[{ key: 'mac', label: 'MAC' }, { key: 'freq', label: 'Freq (MHz)' },
|
||||
{ key: 'signal', label: 'Signal' }, { key: 'packets', label: 'Packets' },
|
||||
{ key: '_deauth', label: '' }],
|
||||
cl.map((c) => {
|
||||
const mac = c.mac || c.client_mac || '';
|
||||
return { mac: mac || '—', freq: c.freq || '', signal: c.signal || '',
|
||||
packets: c.packets || '', _deauth: btn('Deauth', () => {
|
||||
const parts = apSel.value.split('|');
|
||||
if (!mac || !parts[0]) { App.toast('Pick an AP and device first', 'error'); return; }
|
||||
PagerAPI.post('/api/attacks/deauth', {
|
||||
bssid: parts[0], client: mac, channel: parseInt(parts[1], 10)
|
||||
}).then(() => App.toast('Deauth frames sent'))
|
||||
.catch((e) => App.toast(e.message || 'Deauth failed', 'error'));
|
||||
}, 'danger') };
|
||||
})));
|
||||
}).catch(() => App.toast('Lookup failed', 'error'));
|
||||
}))));
|
||||
}).catch(() => App.toast('Lookup failed', 'error'));
|
||||
}
|
||||
body.appendChild(h('div', { class: 'row' }, ssidBox,
|
||||
h('div', {}, btn('Find', () => lookup(ssidBox.value.trim())))));
|
||||
body.appendChild(apSel);
|
||||
body.appendChild(clTable);
|
||||
body.appendChild(h('div', { class: 'muted', style: 'font-size:12px;margin-top:4px',
|
||||
text: 'Only deauth targets you are authorized to test.' }));
|
||||
if (ssidRef) {
|
||||
ssidRef.tick = () => {
|
||||
const liveSsid = ssidRef.current && ssidRef.current.trim();
|
||||
if (liveSsid && ssidBox.value.trim() !== liveSsid) {
|
||||
ssidBox.value = liveSsid;
|
||||
lastLookup = '';
|
||||
lookup(liveSsid);
|
||||
}
|
||||
};
|
||||
}
|
||||
return wrap;
|
||||
}
|
||||
|
||||
@@ -1583,14 +1630,42 @@ views.attacks_wpa = attackLauncher('wpa', {
|
||||
encodings: EVIL_ENC,
|
||||
handshakes: true,
|
||||
export: true,
|
||||
deauth: true
|
||||
deauth: true,
|
||||
playbook: {
|
||||
steps: ['Deploy the evil twin',
|
||||
'Wait for a client to associate',
|
||||
'Deauth the target client to force the 4-way',
|
||||
'Export .hc22000 and crack with hashcat'],
|
||||
currentStep: (s, w) => {
|
||||
if (!w || !w.enabled) return 'Deploy the evil twin';
|
||||
if (!s || !(s.handshakes > 0)) return 'Wait for a client to associate';
|
||||
return 'Export .hc22000 and crack with hashcat';
|
||||
},
|
||||
hint: (s, w) => {
|
||||
if (!w || !w.enabled) return '1. Set the target SSID and passphrase, pick a channel, Deploy. 2. When the target client is near, use Deauth Targeting below. 3. Captured handshakes appear above — Export and run the hashcat command.';
|
||||
if (s && s.handshakes > 0) return 'Handshake captured! Export .hc22000 and run hashcat -m 22000.';
|
||||
return 'AP is live on ' + (w.ssid || 'the target') + '. Watch the handshakes list — use Deauth Targeting to nudge the client.';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
views.attacks_open = attackLauncher('open', {
|
||||
title: 'Evil Open',
|
||||
subtitle: 'Open network evil twin',
|
||||
bssid: true,
|
||||
country: true
|
||||
country: true,
|
||||
playbook: {
|
||||
steps: ['Deploy the open AP',
|
||||
'Wait for clients to associate',
|
||||
'Watch connected clients under PineAP → Clients'],
|
||||
currentStep: (s, w) => {
|
||||
if (!w || !w.enabled) return 'Deploy the open AP';
|
||||
return 'Wait for clients to associate';
|
||||
},
|
||||
hint: (s, w) => !w || !w.enabled
|
||||
? 'Set the SSID (optionally spoof a BSSID), pick a channel, Deploy.'
|
||||
: 'Open AP is live on ' + (w.ssid || 'the target') + ' — clients that join appear in the Clients list.'
|
||||
}
|
||||
});
|
||||
|
||||
views.attacks_enterprise = (root) => {
|
||||
@@ -1628,6 +1703,13 @@ views.attacks_enterprise = (root) => {
|
||||
const status = attackStatusCard();
|
||||
box.appendChild(status.card);
|
||||
|
||||
const pb = h('div', { class: 'pineap-title-card' });
|
||||
pb.appendChild(h('div', { class: 'pineap-card-title' }, 'Playbook'));
|
||||
const pbHint = h('div', { style: 'font-size:13px;line-height:1.8', text:
|
||||
'1. Deploy the enterprise twin (5 GHz). 2. When a client attempts 802.1X, its identity and MSCHAPv2 challenge/response land in the tables below. 3. Crack offline: hashcat -m 5500 on the challenge data.' });
|
||||
pb.appendChild(pbHint);
|
||||
box.appendChild(pb);
|
||||
|
||||
const pApe = h('div', { class: 'pineap-title-card' });
|
||||
pApe.appendChild(h('div', { class: 'pineap-card-title' }, 'PineAPE'));
|
||||
const pApeBody = h('div', {});
|
||||
|
||||
Reference in New Issue
Block a user