Files
Mark-VIII/tests/test_pineap_enterprise.py
bzuccaroandCursor 7d48b7ad06 fix: harden UI actions and daemon calls for reliable control (v1.3.2)
Retry and serialize pineapd/hak5 calls, queue virtual-pager keys, and grey out buttons until the pager finishes. Deploy now installs python3-light after factory firmware. Bump version to 1.3.2.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 15:49:10 -05:00

230 lines
9.7 KiB
Python

import json
import os
import sqlite3
import sys
import tempfile
import unittest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'payload', 'user', 'remote_access', 'pager-webui'))
import server
def setUpModule():
__import__('importlib').reload(server)
SCHEMA = '''
CREATE TABLE hostap_basic(id INTEGER PRIMARY KEY, scan INT, time INT, type TEXT,
identity TEXT, password TEXT, verified INT NOT NULL DEFAULT 0);
CREATE TABLE hostap_chalresp(id INTEGER PRIMARY KEY, scan INT, time INT, type TEXT,
username TEXT, challenge BLOB, response BLOB,
verified INT NOT NULL DEFAULT 0);
CREATE TABLE hostap_client(id INTEGER PRIMARY KEY, scan INT, hash INT, mac TEXT, ssid BLOB,
connected_time INT, disconnected_time INT);
'''
class EnterpriseApiTest(unittest.TestCase):
def setUp(self):
self._orig_rows = server._db_rows
self._orig_write = server._db_write
def tearDown(self):
server._db_rows = self._orig_rows
server._db_write = self._orig_write
def test_basic_rows(self):
server._db_rows = lambda db, sql: [{'time': 1, 'username': 'a', 'password': 'b'}]
status, payload = server.h_enterprise_data(type('C', (), {'args': ('basic',)})())
self.assertEqual(status, 200)
self.assertEqual(payload['table'], 'hostap_basic')
self.assertEqual(payload['rows'][0]['username'], 'a')
self.assertEqual(payload['rows'][0]['identity'], 'a')
def test_challenge_rows_empty(self):
server._db_rows = lambda db, sql: []
status, payload = server.h_enterprise_data(type('C', (), {'args': ('challenge',)})())
self.assertEqual(status, 200)
self.assertEqual(payload['table'], 'hostap_chalresp')
self.assertEqual(payload['rows'], [])
def test_unknown_table(self):
status, payload = server.h_enterprise_data(type('C', (), {'args': ('nope',)})())
self.assertEqual(status, 400)
def test_clear_uses_chalresp_table(self):
calls = []
server._db_write = lambda db, sql: calls.append(sql)
status, payload = server.h_enterprise_clear(type('C', (), {'body': {'table': 'challenge'}})())
self.assertEqual(status, 200)
self.assertTrue(any('hostap_chalresp' in s for s in calls))
self.assertFalse(any('hostap_challenge' in s for s in calls))
def test_clear_all(self):
calls = []
server._db_write = lambda db, sql: calls.append(sql)
status, payload = server.h_enterprise_clear(type('C', (), {'body': {'table': 'all'}})())
self.assertEqual(status, 200)
joined = ' '.join(calls)
self.assertIn('hostap_basic', joined)
self.assertIn('hostap_chalresp', joined)
def test_clear_unknown_table(self):
status, payload = server.h_enterprise_clear(type('C', (), {'body': {'table': 'nope'}})())
self.assertEqual(status, 400)
class EnterpriseHashFormatTest(unittest.TestCase):
def test_blob_to_hex_bytes_and_hex_string(self):
self.assertEqual(server._blob_to_hex(b'\x11\x22\x33\x44'), '11223344')
self.assertEqual(server._blob_to_hex('AABBCCDD'), 'aabbccdd')
self.assertEqual(server._blob_to_hex("X'AABB'"), 'aabb')
self.assertEqual(server._blob_to_hex('\\xde\\xad'), 'dead')
def test_hashcat_5500_and_john(self):
chal = '1122334455667788'
resp = '00112233445566778899aabbccddeeff0011223344556677'
self.assertEqual(
server._mschap_hashcat_5500('bob', chal, resp),
'bob::::00112233445566778899aabbccddeeff0011223344556677:1122334455667788')
self.assertEqual(
server._mschap_john('bob', chal, resp),
'bob:$NETNTLM$1122334455667788$00112233445566778899aabbccddeeff0011223344556677')
def test_format_chalresp_row_hexes_blobs_and_is_json_safe(self):
row = server._format_chalresp_row({
'time': 1700000000,
'username': 'alice',
'type': 'MSCHAPV2',
'challenge': bytes.fromhex('1122334455667788'),
'response': bytes.fromhex('00112233445566778899aabbccddeeff0011223344556677'),
'verified': 0,
})
self.assertEqual(row['challenge'], '1122334455667788')
self.assertEqual(row['response'], '00112233445566778899aabbccddeeff0011223344556677')
self.assertIn('alice::::', row['hashcat'])
self.assertIn(':$NETNTLM$', row['john'])
json.dumps(row)
class EnterpriseLogParseTest(unittest.TestCase):
def test_parse_wpe_mschapv2_and_identity(self):
log = (
"mschapv2: Wed Aug 19 21:00:00 2026\n"
" username: bob\n"
" challenge: 11:22:33:44:55:66:77:88\n"
" response: 00112233445566778899aabbccddeeff0011223344556677\n"
"hashcat NETNTLM: bob::::00112233445566778899aabbccddeeff0011223344556677:1122334455667788\n"
"EAP-Identity 'alice@corp.local'\n"
"GTC: username: carol password: hunter2\n"
)
items = server._parse_ent_log(log)
kinds = [i['kind'] for i in items]
self.assertIn('mschapv2', kinds)
self.assertIn('eap-identity', kinds)
self.assertIn('gtc', kinds)
mschap = [i for i in items if i['kind'] == 'mschapv2'][0]
self.assertEqual(mschap['username'], 'bob')
self.assertIn('bob::::', mschap['hashcat'])
gtc = [i for i in items if i['kind'] == 'gtc'][0]
self.assertEqual(gtc['password'], 'hunter2')
class EnterpriseCaptureDbTest(unittest.TestCase):
def setUp(self):
fd, self.db = tempfile.mkstemp(suffix='.db')
os.close(fd)
conn = sqlite3.connect(self.db)
conn.executescript(SCHEMA)
chal = bytes.fromhex('1122334455667788')
resp = bytes.fromhex('00112233445566778899aabbccddeeff0011223344556677')
conn.execute(
"INSERT INTO hostap_basic (id, scan, time, type, identity, password, verified) "
"VALUES (1, 1, 1700000001, 'PEAP', 'bob', '', 0)")
conn.execute(
"INSERT INTO hostap_chalresp (id, scan, time, type, username, challenge, response, verified) "
"VALUES (1, 1, 1700000002, 'MSCHAPV2', 'bob', ?, ?, 0)", (chal, resp))
conn.execute(
"INSERT INTO hostap_client (id, scan, hash, mac, ssid, connected_time, disconnected_time) "
"VALUES (1, 1, 1, 'AABBCCDDEEFF', X'436F7270', 1700000003, NULL)")
conn.commit()
conn.close()
self.old_db = server.RECON_DB
server.RECON_DB = self.db
def tearDown(self):
server.RECON_DB = self.old_db
try:
os.unlink(self.db)
except OSError:
pass
def test_challenge_endpoint_returns_hashcat(self):
status, payload = server.h_enterprise_data(type('C', (), {'args': ('challenge',)})())
self.assertEqual(status, 200)
self.assertEqual(payload['table'], 'hostap_chalresp')
row = payload['rows'][0]
self.assertEqual(row['username'], 'bob')
self.assertEqual(row['challenge'], '1122334455667788')
self.assertEqual(
row['hashcat'],
'bob::::00112233445566778899aabbccddeeff0011223344556677:1122334455667788')
json.dumps(payload, default=server._json_default)
def test_radius_payload_unifies_captures(self):
status, payload = server.h_enterprise_radius(type('C', (), {'args': ()})())
self.assertEqual(status, 200)
self.assertTrue(payload['note'])
kinds = [c['kind'] for c in payload['captures']]
self.assertIn('eap-identity', kinds)
self.assertIn('mschapv2', kinds)
self.assertEqual(payload['hashcat']['mode'], 5500)
self.assertEqual(len(payload['hashcat']['lines']), 1)
self.assertEqual(payload['clients'][0]['ssid'], 'Corp')
def test_export_hashcat_download(self):
status, payload = server.h_enterprise_export(type('C', (), {'args': ('hashcat',)})())
self.assertEqual(status, 200)
self.assertIsInstance(payload, server.Download)
self.assertIn(b'bob::::', payload.data)
self.assertTrue(payload.filename.endswith('.5500'))
def test_export_john_and_json(self):
status, payload = server.h_enterprise_export(type('C', (), {'args': ('john',)})())
self.assertEqual(status, 200)
self.assertIn(b'$NETNTLM$', payload.data)
status, payload = server.h_enterprise_export(type('C', (), {'args': ('json',)})())
self.assertEqual(status, 200)
body = json.loads(payload.data.decode('utf-8'))
self.assertEqual(body['hashcat']['mode'], 5500)
class EnterpriseHarvestTest(unittest.TestCase):
def test_harvest_reads_hostapd_file_and_skips_logread(self):
calls = []
fd, log_path = tempfile.mkstemp()
os.write(fd, b"EAP-Identity 'fromfile'\n")
os.close(fd)
cap_path = log_path + '.json'
old_log, old_cap = server.ENT_LOG, server.ENT_CAPTURES
old_run = server.device_run
server.ENT_LOG = log_path
server.ENT_CAPTURES = cap_path
server.device_run = lambda args, timeout=20, input_data=None: (
calls.append(list(args)) or (0, "identity: 'syslog-user'\n", ''))
try:
items = server._harvest_ent_log()
self.assertTrue(any(item.get('username') == 'fromfile' for item in items))
self.assertFalse(any(args and args[0] == 'logread' for args in calls))
finally:
server.ENT_LOG = old_log
server.ENT_CAPTURES = old_cap
server.device_run = old_run
os.unlink(log_path)
if os.path.exists(cap_path):
os.unlink(cap_path)
if __name__ == '__main__':
unittest.main()