diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..67c7c9ad9008a491d5e518b7c0cddf43a88587c1 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# Evidence Collector + +This project includes modules for collecting evidence regarding Wazuh and VAT. + +## Wazuh evidence collector + +Wazuh evidence collector uses [Wazuh's API](https://documentation.wazuh.com/current/user-manual/api/reference.html) to access information about manager's and agents' system informations and configurations. As an additional measure to ensure correct configuration of [ClamAV](https://www.clamav.net/) (if installed on machine) we also make use of [Elasticsearch's API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search.html) to dirrectly access collected logs - Elastic stack is one of the Wazuh's required components (usually installed on the same machine as Wazuh server, but can be stand alone as well). + +## Installation & use + +1. Set up your Wazuh development environment + +2. Clone this repository + +3. Install requirements + +``` +pip install -r requirements.txt +``` + +4. Run test script + +``` +python3 test.py +``` + +### Setting up Wazuh development environment + +Use [Security Monitoring](https://gitlab.xlab.si/medina/security-monitoring) repository to create and deploy Vagrant box with all required components. + +### API User authentication + +Current implementation has disabled SSL certificate verification & uses simple username/password verification. Production version should change this with cert verification. + +### Manual Elasticsearch API testin with cURL + +Example command for testing the API via CLI: + +``` +curl --user admin:changeme --insecure -X GET "https://192.168.33.10:9200/wazuh-alerts*/_search?pretty" -H 'Content-Type: application/json' -d' +{"query": { + "bool": { + "must": [{"match": {"predecoder.program_name": "clamd"}}, + {"match": {"rule.description": "Clamd restarted"}}, + {"match": {"agent.id": "001"}}] + } + } +}' +``` + +## Known issues + +### Python Elasticsearch library problems with ODFE + +Latest versions (`7.14.0` & `7.15.0`) of Python Elasticsearch library have problems connecting to Open Distro for Elasticsearch and produce the following error when trying to do so: + +``` +elasticsearch.exceptions.UnsupportedProductError: The client noticed that the server is not a supported distribution of Elasticsearch +``` + +To resolve this, downgrade to older package version: + +``` +pip install 'elasticsearch<7.14.0' +``` diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..d63607f0bf727dd9ca5ede5cf09e8866190c00ec --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +elasticsearch_dsl==7.4.0 +urllib3==1.25.8 +elasticsearch==7.13.4 diff --git a/test.py b/test.py new file mode 100644 index 0000000000000000000000000000000000000000..17fc4194afbaaa229f039984730af6102d4708a5 --- /dev/null +++ b/test.py @@ -0,0 +1,7 @@ +import pprint +from wazuh_evidence_collector import * + +evidences = run_full_check() + +for evidence in evidences: + pprint.pprint(evidence.__dict__) diff --git a/verifier.py b/verifier.py deleted file mode 100644 index 624a1bc1dc1a19084af8c15c7184044ff711030a..0000000000000000000000000000000000000000 --- a/verifier.py +++ /dev/null @@ -1,147 +0,0 @@ -from wazuhclient import WazuhClient -from evidence import Evidence, simple_evidence -from random import randint -from sys import maxsize -from datetime import datetime -import pprint - -wc = WazuhClient('192.168.33.10', 55000, 'wazuh-wui', 'wazuh-wui') - -# Get (temporary) ID -def get_id(reqId): - return reqId + '-' + str(randint(0, maxsize)) - -# Get timestamp (can be changed according to our preferences) -def get_timestamp(): - ts = datetime.utcnow() - - return ts.strftime('%Y-%m-%dT%H:%M:%SZ') - -# Get list of all agent ids (including manager's) -def get_agents(wc): - body = wc.req('GET', 'agents') - - agents_ids = [] - for agent in body['data']['affected_items']: - agents_ids.append(agent['id']) - - return body, agents_ids - -# Check if syscheck enabled -def check_syscheck(wc, agent_id): - body = wc.req('GET', 'agents/' + agent_id + '/config/syscheck/syscheck') - - measurement_result = ('true' if body['data']['syscheck']['disabled'] == 'no' else 'false') - - evidence = simple_evidence(get_id('05.3'), get_timestamp(), measurement_result, body) - - return evidence - -# Check if rootcheck enabled -def check_rootcheck(wc, agent_id): - body = wc.req('GET', 'agents/' + agent_id + '/config/syscheck/rootcheck') - - measurement_result = ('true' if body['data']['rootcheck']['disabled'] == 'no' else 'false') - - evidence = simple_evidence(get_id('05.3'), get_timestamp(), measurement_result, body) - - return evidence - -# Check if there's at least one valid alerting service -def check_alert_integrations(wc): - body = wc.req('GET', 'manager/configuration') - - # Check email notifications integration - try: - email_notifications = (True if body['data']['affected_items'][0]['global']['email_notification'] == 'yes' else False) - except: - email_notifications = False - - # Check Slack and PagerDuty notifications integration - try: - integrations = body['data']['affected_items'][0]['integration'] - - slack_notifications = pagerduty_notifications = False - - for integration in integrations: - if integration['name'] == 'slack': - slack_notifications = True - - if integration['name'] == 'pagerduty': - pagerduty_notifications = True - except: - slack_notifications = pagerduty_notifications = False - - measurement_result = ('true' if email_notifications or slack_notifications or pagerduty_notifications else 'false') - - evidence = simple_evidence(get_id('05.3'), get_timestamp(), measurement_result, body) - - return evidence - -# Check for VirusTotal integration -def check_virus_total_integration(wc): - body = wc.req('GET', 'manager/configuration') - - # Check VirusTotal integration - try: - integrations = body['data']['affected_items'][0]['integration'] - - measurement_result = 'false' - - for integration in integrations: - if integration['name'] == 'virustotal': - measurement_result = 'true' - break - except: - measurement_result = 'false' - - evidence = simple_evidence(get_id('05.3'), get_timestamp(), measurement_result, body) - - return evidence - -# Check last Syscheck & Rootcheck scan times -# When producing 'real' evidence, make sure to provide differentiation between Syscheck and Rootcheck outputs. -def check_last_scan_time(wc, agent_id): - body = wc.req('GET', 'syscheck/' + agent_id + '/last_scan') - - measurement_result = body['data']['affected_items'][0]['end'] - - evidence1 = simple_evidence(get_id('05.4'), get_timestamp(), measurement_result, body) - - body = wc.req('GET', 'rootcheck/' + agent_id + '/last_scan') - - measurement_result = body['data']['affected_items'][0]['end'] - - evidence2 = simple_evidence(get_id('05.4'), get_timestamp(), measurement_result, body) - - return evidence1, evidence2 - -# Check if ClamAV daemon package installed -def check_clamd_install(wc, agent_id): - body = wc.req('GET', 'syscollector/' + agent_id + '/packages') - - measurement_result = 'false' - - for package in body['data']['affected_items']: - if package['name'] == 'clamd': - measurement_result = 'true' - break - - evidence = simple_evidence(get_id('05.3'), get_timestamp(), measurement_result, body) - - return evidence - -# Check if ClamAV daemon process running -def check_clamd_process(wc, agent_id): - body = wc.req('GET', 'syscollector/' + agent_id + '/processes') - - measurement_result = 'false' - - for package in body['data']['affected_items']: - if package['name'] == 'clamd': - measurement_result = 'true' - break - - evidence = simple_evidence(get_id('05.3'), get_timestamp(), measurement_result, body) - - return evidence diff --git a/wazuhclient.py b/wazuh_client.py similarity index 100% rename from wazuhclient.py rename to wazuh_client.py diff --git a/wazuh_evidence_collector.py b/wazuh_evidence_collector.py new file mode 100644 index 0000000000000000000000000000000000000000..e9f8c774c12eda6a0930161487cb223f60d950c9 --- /dev/null +++ b/wazuh_evidence_collector.py @@ -0,0 +1,196 @@ +from wazuh_client import WazuhClient +from elasticsearch import Elasticsearch +from elasticsearch_dsl import Search +from evidence import Evidence, simple_evidence +from random import randint +from sys import maxsize +from datetime import datetime + +wc = WazuhClient('192.168.33.10', 55000, 'wazuh-wui', 'wazuh-wui') +es = Elasticsearch( + '192.168.33.10', + http_auth=('admin', 'changeme'), + scheme='https', + port=9200, + use_ssl=False, + verify_certs=False, + ssl_show_warn=False, + ) + +# Get (temporary) ID +def get_id(reqId): + return reqId + '-' + str(randint(0, maxsize)) + +# Get timestamp (can be changed according to our preferences) +def get_timestamp(): + ts = datetime.utcnow() + + return ts.strftime('%Y-%m-%dT%H:%M:%SZ') + +# Wrapepr function that runs all the checks (for every manager/agent) +def run_full_check(): + + # Get list of all agent ids (including manager's) + def get_agents(wc): + body = wc.req('GET', 'agents') + + agents_ids = [] + for agent in body['data']['affected_items']: + agents_ids.append(agent['id']) + + return body, agents_ids + + body, agents_ids = get_agents(wc) + + agent_evidences = [] + + for agent in agents_ids: + agent_evidences.append(wazuh_monitoring_enabled(wc, agent)) + agent_evidences.append(malvare_protection_enabled(wc, es, agent)) + + return agent_evidences + +# Check Wazuh's configuration +def wazuh_monitoring_enabled(wc, agent_id): + + # Check if syscheck enabled + def check_syscheck(wc, agent_id): + body = wc.req('GET', 'agents/' + agent_id + '/config/syscheck/syscheck') + + measurement_result = body['data']['syscheck']['disabled'] == 'no' + + return body, measurement_result + + # Check if rootcheck enabled + def check_rootcheck(wc, agent_id): + body = wc.req('GET', 'agents/' + agent_id + '/config/syscheck/rootcheck') + + measurement_result = body['data']['rootcheck']['disabled'] == 'no' + + return body, measurement_result + + # Check if there's at least one valid alerting service + def check_alert_integrations(wc): + body = wc.req('GET', 'manager/configuration') + + # Check email notifications integration + try: + email_notifications = (True if body['data']['affected_items'][0]['global']['email_notification'] == 'yes' else False) + except: + email_notifications = False + + # Check Slack and PagerDuty notifications integration + try: + integrations = body['data']['affected_items'][0]['integration'] + + slack_notifications = pagerduty_notifications = False + + for integration in integrations: + if integration['name'] == 'slack': + slack_notifications = True + + if integration['name'] == 'pagerduty': + pagerduty_notifications = True + except: + slack_notifications = pagerduty_notifications = False + + measurement_result = email_notifications or slack_notifications or pagerduty_notifications + + return body, measurement_result + + raw_evidence = [] + + evidence, result_syscheck = check_syscheck(wc, agent_id) + raw_evidence.append(evidence) + + evidence, result_rootcheck = check_rootcheck(wc, agent_id) + raw_evidence.append(evidence) + + evidence, result_aler_integration = check_alert_integrations(wc) + raw_evidence.append(evidence) + + if result_syscheck and result_rootcheck and result_aler_integration: + return simple_evidence(get_id('05.3'), get_timestamp(), "true", raw_evidence) + else: + return simple_evidence(get_id('05.3'), get_timestamp(), "false", raw_evidence) + +# Check if agent uses ClamAV or VirusTotal +def malvare_protection_enabled(wc, es, agent_id): + + # Check for VirusTotal integration + def check_virus_total_integration(wc): + body = wc.req('GET', 'manager/configuration') + + # Check VirusTotal integration + try: + integrations = body['data']['affected_items'][0]['integration'] + + measurement_result = False + + for integration in integrations: + if integration['name'] == 'virustotal': + measurement_result = True + break + except: + measurement_result = False + + return body, measurement_result + + # Check if ClamAV daemon process running + def check_clamd_process(wc, agent_id): + body = wc.req('GET', 'syscollector/' + agent_id + '/processes') + + measurement_result = False + + for package in body['data']['affected_items']: + if package['name'] == 'clamd': + measurement_result = True + break + + return body, measurement_result + + # Check ClamAV logs in Elasticsearch + def check_clamd_logs_elastic(es, agent_id): + s = Search(using=es, index="wazuh-alerts-*") \ + .query("match", predecoder__program_name="clamd") \ + .query("match", rule__description="Clamd restarted") \ + .query("match", agent__id=agent_id) + + body = s.execute().to_dict() + + measurement_result = len(body['hits']['hits']) > 0 + + return body, measurement_result + + raw_evidence = [] + + evidence, result_virus_total = check_virus_total_integration(wc) + raw_evidence.append(evidence) + + evidence, result_lamd_process = check_clamd_process(wc, agent_id) + raw_evidence.append(evidence) + + evidence, result_clamd_logs = check_clamd_logs_elastic(es, agent_id) + raw_evidence.append(evidence) + + if result_virus_total or (result_lamd_process and result_clamd_logs): + return simple_evidence(get_id('05.4'), get_timestamp(), "true", raw_evidence) + else: + return simple_evidence(get_id('05.4'), get_timestamp(), "false", raw_evidence) + +# Check last Syscheck & Rootcheck scan times +# When producing 'real' evidence, make sure to provide differentiation between Syscheck and Rootcheck outputs. +def check_last_scan_time(wc, agent_id): + body = wc.req('GET', 'syscheck/' + agent_id + '/last_scan') + + measurement_result = body['data']['affected_items'][0]['end'] + + evidence1 = simple_evidence(get_id('05.4'), get_timestamp(), measurement_result, body) + + body = wc.req('GET', 'rootcheck/' + agent_id + '/last_scan') + + measurement_result = body['data']['affected_items'][0]['end'] + + evidence2 = simple_evidence(get_id('05.4'), get_timestamp(), measurement_result, body) + + return evidence1, evidence2