def __init__(self, server_address='127.0.0.1', enrollment_port=1515, key_path='/etc/manager.key', cert_path='/etc/manager.cert', initial_mode='ACCEPT'): self.mitm_enrollment = ManInTheMiddle(address=(server_address, enrollment_port), family='AF_INET', connection_protocol='SSL', func=self._process_enrollment_message) self.key_path = key_path self.cert_path = cert_path self.id_count = 1 self.secret = 'TopSecret' self.controller = CertificateController() self.mode = initial_mode
def configure_mitm_environment_wazuhdb(request): """Use MITM to replace analysisd and wazuh-db sockets.""" wdb_path = getattr(request.module, 'wdb_path') # Stop wazuh-service and ensure all daemons are stopped control_service('stop') check_daemon_status(running=False) remove_logs() control_service('start', daemon='wazuh-db', debug_mode=True) check_daemon_status(running=True, daemon='wazuh-db') mitm_wdb = ManInTheMiddle(socket_path=wdb_path) wdb_queue = mitm_wdb.queue mitm_wdb.start() wdb_monitor = QueueMonitor(queue_item=wdb_queue) setattr(request.module, 'wdb_monitor', wdb_monitor) yield mitm_wdb.shutdown() for daemon in ['wazuh-db']: control_service('stop', daemon=daemon) check_daemon_status(running=False, daemon=daemon) # Delete all db delete_dbs() control_service('start')
configurations = load_wazuh_configurations(configurations_path, __name__, params=params, metadata=metadata) # Variables log_monitor_paths = [CLUSTER_LOGS_PATH] modulesd_socket_path = os.path.join(WAZUH_PATH, 'queue', 'ossec', 'krequest') cluster_socket_address = ('localhost', 1516) receiver_sockets_params = [(cluster_socket_address, 'AF_INET', 'TCP') ] # SocketController items mitm_modules = ManInTheMiddle(address=modulesd_socket_path, family='AF_UNIX', connection_protocol='UDP') # monitored_sockets_params is a List of daemons to start with optional ManInTheMiddle to monitor # List items -> (wazuh_daemon: str,( # mitm: ManInTheMiddle # daemon_first: bool)) # Example1 -> ('wazuh-clusterd', None) Only start wazuh-clusterd with no MITM # Example2 -> ('wazuh-clusterd', (my_mitm, True)) Start MITM and then wazuh-clusterd monitored_sockets_params = [('wazuh-clusterd', None, None), ('wazuh-modulesd', mitm_modules, True)] receiver_sockets, monitored_sockets, log_monitors = None, None, None # Set in the fixtures # Functions
def generate_analysisd_yaml(n_events, modify_events): def parse_events_into_yaml(requests, yaml_file): yaml_result = [] with open(yaml_file, 'a') as y_f: id_ev = 0 for req, event in requests: type_ev = event['data']['type'] stage_ev = type_ev.title() mode = None agent_id = callback_analysisd_agent_id(req) or '000' del event['data']['mode'] del event['data']['type'] if 'tags' in event['data']: del event['data']['tags'] if type_ev == 'added': mode = 'save2' output_ev = json.dumps(event['data']) elif type_ev == 'deleted': mode = 'delete' output_ev = json.dumps(event['data']['path']).replace( '"', '') elif type_ev == 'modified': mode = 'save2' for field in [ 'old_attributes', 'changed_attributes', 'content_changes' ]: if field in event['data']: del event['data'][field] output_ev = json.dumps(event['data']) yaml_result.append({ 'name': f"{stage_ev}{id_ev}", 'test_case': [{ 'input': f"{req}", 'output': f"agent {agent_id} syscheck {mode} {output_ev}", 'stage': f"{stage_ev}" }] }) id_ev += 1 y_f.write(yaml.safe_dump(yaml_result)) def remove_logs(): for root, dirs, files in os.walk(WAZUH_LOGS_PATH): for file in files: os.remove(os.path.join(root, file)) # Restart syscheckd with the new configuration truncate_file(LOG_FILE_PATH) control_service('stop') check_daemon_status(running=False) remove_logs() control_service('start', daemon='ossec-analysisd', debug_mode=True) check_daemon_status(running=True, daemon='ossec-analysisd') mitm_analysisd = ManInTheMiddle(address=analysis_path, family='AF_UNIX', connection_protocol='UDP') analysis_queue = mitm_analysisd.queue mitm_analysisd.start() control_service('start', daemon='ossec-remoted', debug_mode=True) check_daemon_status(running=True, daemon='ossec-remoted') analysis_monitor = QueueMonitor(analysis_queue) while True: try: grep = subprocess.Popen(['grep', 'deleted', alerts_json], stdout=subprocess.PIPE) wc = int( subprocess.check_output([ 'wc', '-l', ], stdin=grep.stdout).decode()) except subprocess.CalledProcessError: wc = 0 if wc >= n_events: logging.debug('All alerts received. Collecting by alert type...') break logger.debug(f'{wc} deleted events so far.') logger.debug('Waiting for alerts. Sleeping 5 seconds.') time.sleep(5) added = analysis_monitor.start(timeout=max(0.01 * n_events, 10), callback=callback_analysisd_event, accum_results=n_events).result() logger.debug('"added" alerts collected.') modified = analysis_monitor.start(timeout=max(0.01 * n_events, 10), callback=callback_analysisd_event, accum_results=modify_events).result() logger.debug('"modified" alerts collected.') deleted = analysis_monitor.start(timeout=max(0.01 * n_events, 10), callback=callback_analysisd_event, accum_results=n_events).result() logger.debug('"deleted" alerts collected.') # Truncate file with open(yaml_file, 'w') as y_f: y_f.write(f'---\n') for ev_list in [added, modified, deleted]: parse_events_into_yaml(ev_list, yaml_file) logger.debug(f'YAML done: "{yaml_file}"') return mitm_analysisd
'data') messages_path = os.path.join(test_data_path, 'integrity_messages.yaml') with open(messages_path) as f: test_cases = yaml.safe_load(f) # Variables log_monitor_paths = [LOG_FILE_PATH] wdb_path = os.path.join(os.path.join(WAZUH_PATH, 'queue', 'db', 'wdb')) analysis_path = os.path.join( os.path.join(WAZUH_PATH, 'queue', 'sockets', 'queue')) receiver_sockets_params = [(analysis_path, 'AF_UNIX', 'UDP')] mitm_wdb = ManInTheMiddle(address=wdb_path, family='AF_UNIX', connection_protocol='TCP') mitm_analysisd = ManInTheMiddle(address=analysis_path, family='AF_UNIX', connection_protocol='UDP') # monitored_sockets_params is a List of daemons to start with optional ManInTheMiddle to monitor # List items -> (wazuh_daemon: str,( # mitm: ManInTheMiddle # daemon_first: bool)) # Example1 -> ('wazuh-clusterd', None) Only start wazuh-clusterd with no MITM # Example2 -> ('wazuh-clusterd', (my_mitm, True)) Start MITM and then wazuh-clusterd monitored_sockets_params = [('wazuh-db', mitm_wdb, True), ('wazuh-analysisd', mitm_analysisd, True)] receiver_sockets, monitored_sockets, log_monitors = None, None, None # Set in the fixtures
messages_files = os.listdir(test_data_path) module_tests = list() for file in messages_files: with open(os.path.join(test_data_path, file)) as f: module_tests.append((yaml.safe_load(f), file.split("_")[0])) # Variables log_monitor_paths = [] wdb_path = os.path.join(os.path.join(WAZUH_PATH, 'queue', 'db', 'wdb')) receiver_sockets_params = [(wdb_path, 'AF_UNIX', 'TCP')] mitm_wdb = ManInTheMiddle(address=wdb_path, family='AF_UNIX', connection_protocol='TCP') # mitm_analysisd = ManInTheMiddle(address=analysis_path, family='AF_UNIX', connection_protocol='UDP') # monitored_sockets_params is a List of daemons to start with optional ManInTheMiddle to monitor # List items -> (wazuh_daemon: str,( # mitm: ManInTheMiddle # daemon_first: bool)) # Example1 -> ('wazuh-clusterd', None) Only start wazuh-clusterd with no MITM # Example2 -> ('wazuh-clusterd', (my_mitm, True)) Start MITM and then wazuh-clusterd monitored_sockets_params = [('wazuh-db', mitm_wdb, True)] receiver_sockets, monitored_sockets, log_monitors = None, None, None # Set in the fixtures # Tests
test_data_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data') configurations_path = os.path.join(test_data_path, 'cluster_conf.yaml') params = [{'FERNET_KEY': FERNET_KEY}] metadata = [{'fernet_key': FERNET_KEY}] configurations = load_wazuh_configurations(configurations_path, __name__, params=params, metadata=metadata) # Variables log_monitor_paths = [CLUSTER_LOGS_PATH] cluster_socket_path = os.path.join(os.path.join(WAZUH_PATH, 'queue', 'cluster', 'c-internal.sock')) cluster_socket_address = ('localhost', 1516) receiver_sockets_params = [(cluster_socket_path, 'AF_UNIX', 'TCP')] # SocketController items mitm_master = ManInTheMiddle(address=cluster_socket_address, family='AF_INET', connection_protocol='TCP', func=master_simulator) # monitored_sockets_params is a list of daemons to start with optional ManInTheMiddle to monitor # List items -> (wazuh_daemon: str,( # mitm: ManInTheMiddle # daemon_first: bool)) # Example1 -> ('wazuh-clusterd', None) Only start wazuh-clusterd with no MITM # Example2 -> ('wazuh-clusterd', (my_mitm, True)) Start MITM and then wazuh-clusterd monitored_sockets_params = [('wazuh-clusterd', mitm_master, False)] receiver_sockets, monitored_sockets, log_monitors = None, None, None # Set in the fixtures # Fixtures
class AuthdSimulator: """ Create an SSL server socket for simulating authd connection """ def __init__(self, server_address='127.0.0.1', enrollment_port=1515, key_path='/etc/manager.key', cert_path='/etc/manager.cert', initial_mode='ACCEPT'): self.mitm_enrollment = ManInTheMiddle(address=(server_address, enrollment_port), family='AF_INET', connection_protocol='SSL', func=self._process_enrollment_message) self.key_path = key_path self.cert_path = cert_path self.id_count = 1 self.secret = 'TopSecret' self.controller = CertificateController() self.mode = initial_mode def start(self): """ Generates certificate for the SSL server and starts server sockets """ self._generate_certificates() self.mitm_enrollment.start() self.mitm_enrollment.listener.set_ssl_configuration(connection_protocol=ssl.PROTOCOL_TLSv1_2, certificate=self.cert_path, keyfile=self.key_path) def shutdown(self): """ Shutdown sockets """ self.mitm_enrollment.shutdown() def clear(self): """ Clear sockets after each response. By default, they stop handling connections after one successful connection, and they need to be cleared afterwards """ while not self.mitm_enrollment.queue.empty(): self.mitm_enrollment.queue.get_nowait() self.mitm_enrollment.event.clear() @property def queue(self): return self.mitm_enrollment.queue @property def cert_controller(self): return self.controller @property def agent_id(self): return self.id_count @agent_id.setter def agent_id(self, value): self.id_count = value def set_mode(self, mode): """ Sets a mode: ACCEPT: Accepts connection and produces enrollment REJECT: Waits 2 seconds and anwsers with an empty message """ self.mode = mode def _process_enrollment_message(self, received): """ Reads a message received at the SSL socket, and parses to emulate a authd response Expected message: OSSEC A:'{name}' G:'{groups}' IP:'{ip}'\n Key response: OSSEC K: {id} {name} {ip} {key:64} """ if self.mode == 'REJECT': time.sleep(2) self.mitm_enrollment.event.set() return b'ERROR' agent_info = { 'id': self.id_count, 'name': None, 'ip': None } if len(received) == 0: # Empty message raise parts = received.decode().split(' ') for part in parts: if part.startswith('A:'): agent_info['name'] = part.split("'")[1] if part.startswith('IP:'): agent_info['ip'] = part.split("'")[1] if agent_info['ip'] is None: agent_info['ip'] = 'any' if agent_info['ip'] == 'src': agent_info['ip'] = self.mitm_enrollment.listener.last_address[0] self.id_count += 1 self.mitm_enrollment.event.set() return f'OSSEC K:\'{agent_info.get("id"):03d} {agent_info.get("name")} {agent_info["ip"]} {self.secret}\'\n'.encode() def _generate_certificates(self): # Generate root key and certificate self.controller.get_root_ca_cert().sign(self.controller.get_root_key(), self.controller.digest) self.controller.store_private_key(self.controller.get_root_key(), self.key_path) self.controller.store_ca_certificate(self.controller.get_root_ca_cert(), self.cert_path)
def generate_analysisd_yaml(n_events, modify_events): def parse_events_into_yaml(requests, yaml_file): yaml_result = [] with open(yaml_file, 'a') as y_f: id_ev = 0 for req, event in requests: type_ev = event['data']['type'] stage_ev = type_ev.title() mode = None agent_id = callback_analysisd_agent_id(req) or '000' del event['data']['mode'] del event['data']['type'] if 'tags' in event['data']: del event['data']['tags'] if type_ev == 'added': mode = 'save2' output_ev = json.dumps(event['data']) elif type_ev == 'deleted': mode = 'delete' output_ev = json.dumps(event['data']['path']).replace( '"', '') elif type_ev == 'modified': mode = 'save2' for field in [ 'old_attributes', 'changed_attributes', 'content_changes' ]: if field in event['data']: del event['data'][field] output_ev = json.dumps(event['data']) yaml_result.append({ 'name': f"{stage_ev}{id_ev}", 'test_case': [{ 'input': f"{req}", 'output': f"agent {agent_id} syscheck {mode} {output_ev}", 'stage': f"{stage_ev}" }] }) id_ev += 1 y_f.write(yaml.safe_dump(yaml_result)) def remove_logs(): for root, dirs, files in os.walk(WAZUH_LOGS_PATH): for file in files: os.remove(os.path.join(root, file)) file = 'regular' # Restart syscheckd with the new configuration truncate_file(LOG_FILE_PATH) file_monitor = FileMonitor(LOG_FILE_PATH) control_service('stop') check_daemon_status(running=False) remove_logs() control_service('start', daemon='wazuh-db', debug_mode=True) check_daemon_status(running=True, daemon='wazuh-db') control_service('start', daemon='wazuh-analysisd', debug_mode=True) check_daemon_status(running=True, daemon='wazuh-analysisd') mitm_analysisd = ManInTheMiddle(address=analysis_path, family='AF_UNIX', connection_protocol='UDP') analysis_queue = mitm_analysisd.queue mitm_analysisd.start() control_service('start', daemon='wazuh-syscheckd', debug_mode=True) check_daemon_status(running=True, daemon='wazuh-syscheckd') # Wait for initial scan detect_initial_scan(file_monitor) analysis_monitor = QueueMonitor(analysis_queue) for directory in directories_list: create_file(REGULAR, directory, file, content='') time.sleep(0.01) added = analysis_monitor.start( timeout=max(0.01 * n_events, 10), callback=callback_analysisd_event, accum_results=len(directories_list)).result() logger.debug('"added" alerts collected.') for directory in directories_list: modify_file(directory, file, new_content='Modified') time.sleep(0.01) modified = analysis_monitor.start(timeout=max(0.01 * n_events, 10), callback=callback_analysisd_event, accum_results=modify_events).result() logger.debug('"modified" alerts collected.') for directory in directories_list: delete_file(directory, file) time.sleep(0.01) deleted = analysis_monitor.start( timeout=max(0.01 * len(directories_list), 10), callback=callback_analysisd_event, accum_results=len(directories_list)).result() logger.debug('"deleted" alerts collected.') # Truncate file with open(yaml_file, 'w') as y_f: y_f.write(f'---\n') for ev_list in [added, modified, deleted]: parse_events_into_yaml(ev_list, yaml_file) logger.debug(f'YAML done: "{yaml_file}"') return mitm_analysisd
def configure_mitm_environment_analysisd(request): """Use MITM to replace analysisd and wazuh-db sockets.""" def remove_logs(): for root, dirs, files in os.walk(WAZUH_LOGS_PATH): for file in files: os.remove(os.path.join(root, file)) analysis_path = getattr(request.module, 'analysis_path') wdb_path = getattr(request.module, 'wdb_path') # Stop wazuh-service and ensure all daemons are stopped control_service('stop') check_daemon_status(running=False) remove_logs() control_service('start', daemon='wazuh-db', debug_mode=True) check_daemon_status(running=True, daemon='wazuh-db') mitm_wdb = ManInTheMiddle(socket_path=wdb_path) wdb_queue = mitm_wdb.queue mitm_wdb.start() control_service('start', daemon='ossec-analysisd', debug_mode=True) check_daemon_status(running=True, daemon='ossec-analysisd') mitm_analysisd = ManInTheMiddle(socket_path=analysis_path, mode='UDP') analysisd_queue = mitm_analysisd.queue mitm_analysisd.start() analysis_monitor = QueueMonitor(queue_item=analysisd_queue) wdb_monitor = QueueMonitor(queue_item=wdb_queue) setattr(request.module, 'analysis_monitor', analysis_monitor) setattr(request.module, 'wdb_monitor', wdb_monitor) yield mitm_analysisd.shutdown() mitm_wdb.shutdown() for daemon in ['wazuh-db', 'ossec-analysisd']: control_service('stop', daemon=daemon) check_daemon_status(running=False, daemon=daemon) control_service('start')