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:
+45
-12
@@ -43,6 +43,8 @@ class FakeUciDevice:
|
||||
sec = a[2]
|
||||
return (0, ''.join("%s=%s\n" % (k, v) for k, v in self.state.items()
|
||||
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, '', '')
|
||||
|
||||
def uci_iface(self, name):
|
||||
@@ -76,9 +78,23 @@ class AttacksDeployTest(unittest.TestCase):
|
||||
self.tmp = tempfile.mkdtemp(prefix='pager-attacks-')
|
||||
self.old_state = server.PINEAP_STATE_FILE
|
||||
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):
|
||||
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)
|
||||
|
||||
def test_deploy_wpa_2g4_calls_daemon_and_enables_engine(self):
|
||||
@@ -99,11 +115,11 @@ class AttacksDeployTest(unittest.TestCase):
|
||||
self.assertEqual(len(engines), 2)
|
||||
|
||||
def test_deploy_wpa_2g4_stops_enterprise_ap(self):
|
||||
self.f.state['wireless.wlan0ent.disabled'] = '0'
|
||||
server.h_attacks_deploy(ctx({
|
||||
'kind': 'wpa', 'ssid': 'TargetNet', 'passphrase': 'secretpass1',
|
||||
'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):
|
||||
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]
|
||||
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({
|
||||
'kind': 'enterprise', 'ssid': 'CorpAP', 'passphrase': 'anypass',
|
||||
'enctype': 'wpa2', 'hidden': False, 'channel': 1}))
|
||||
'enctype': 'wpa2', 'hidden': False, 'channel': 36}))
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(payload['iface'], 'wlan0ent')
|
||||
self.assertEqual(self.f.state['wireless.wlan0ent.encryption'], 'wpa2')
|
||||
self.assertEqual(self.f.state['wireless.wlan0ent.ssid'], 'CorpAP')
|
||||
cfg = [s for s in self.f.sock if s[1] == '/api/settings/wifi/set_ap'][0][2]
|
||||
self.assertEqual(cfg['configs'][0]['interface'], 'wlan0ent')
|
||||
self.assertEqual(cfg['configs'][0]['enctype'], 'wpa2')
|
||||
pineape = [s for s in self.f.sock if s[1] == '/api/pineap/hostapd/set_config'][0]
|
||||
self.assertEqual(pineape[2], {'pineape_disabled': False, 'pineape_auth_pass': True})
|
||||
self.assertEqual(payload['iface'], 'wlan1ent')
|
||||
self.assertEqual(payload['band'], server.BAND_5G)
|
||||
cmds = [r[0] for r in self.f.runs]
|
||||
self.assertIn(['iw', 'phy', 'phy1', 'interface', 'add', 'wlan1ent',
|
||||
'type', 'managed'], cmds)
|
||||
self.assertIn(['iw', 'dev', 'wlan1ent', 'set', 'type', 'ap'], cmds)
|
||||
self.assertIn(['/usr/sbin/hostapd', '-B', '-P', server.ENT_PIDFILE,
|
||||
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):
|
||||
status, _ = server.h_attacks_deploy(ctx({'kind': 'wpa', 'ssid': ''}))
|
||||
|
||||
Reference in New Issue
Block a user