├── requirements.txt ├── .gitignore ├── custom-w2thive ├── README.md └── custom-w2thive.py /requirements.txt: -------------------------------------------------------------------------------- 1 | thehive4py==1.6.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/* 2 | logs 3 | *.alert 4 | venv 5 | venv/* 6 | *.bak 7 | .log 8 | .idea -------------------------------------------------------------------------------- /custom-w2thive: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2015-2020, Wazuh Inc. 3 | # Created by Wazuh, Inc. . 4 | # This program is free software; you can redistribute it and/or modify it under the terms of GPLv2 5 | 6 | WPYTHON_BIN="framework/python/bin/python3" 7 | 8 | SCRIPT_PATH_NAME="$0" 9 | 10 | DIR_NAME="$(cd $(dirname ${SCRIPT_PATH_NAME}); pwd -P)" 11 | SCRIPT_NAME="$(basename ${SCRIPT_PATH_NAME})" 12 | 13 | case ${DIR_NAME} in 14 | */active-response/bin | */wodles*) 15 | if [ -z "${WAZUH_PATH}" ]; then 16 | WAZUH_PATH="$(cd ${DIR_NAME}/../..; pwd)" 17 | fi 18 | 19 | PYTHON_SCRIPT="${DIR_NAME}/${SCRIPT_NAME}.py" 20 | ;; 21 | */bin) 22 | if [ -z "${WAZUH_PATH}" ]; then 23 | WAZUH_PATH="$(cd ${DIR_NAME}/..; pwd)" 24 | fi 25 | 26 | PYTHON_SCRIPT="${WAZUH_PATH}/framework/scripts/${SCRIPT_NAME}.py" 27 | ;; 28 | */integrations) 29 | if [ -z "${WAZUH_PATH}" ]; then 30 | WAZUH_PATH="$(cd ${DIR_NAME}/..; pwd)" 31 | fi 32 | 33 | PYTHON_SCRIPT="${DIR_NAME}/${SCRIPT_NAME}.py" 34 | ;; 35 | esac 36 | 37 | 38 | ${WAZUH_PATH}/${WPYTHON_BIN} ${PYTHON_SCRIPT} $@ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [article by Awwal Ishiaku](https://wazuh.com/blog/using-wazuh-and-thehive-for-threat-protection-and-incident-response/) 2 | 3 | _en_ 4 | ## Wazuh and TheHive integration 5 | This project integrates SIEM Wazuh and TheHive. Use the following instructions to configure: 6 | 7 | ```sh 8 | $ cd /opt/ 9 | $ sudo git clone https://github.com/crow1011/wazuh2thehive.git 10 | $ sudo /var/ossec/framework/python/bin/pip3 install -r /opt/wazuh2thehive/requirements.txt 11 | $ sudo cp /opt/wazuh2thehive/custom-w2thive.py /var/ossec/integrations/custom-w2thive.py 12 | $ sudo cp /opt/wazuh2thehive/custom-w2thive /var/ossec/integrations/custom-w2thive 13 | $ sudo chmod 755 /var/ossec/integrations/custom-w2thive.py 14 | $ sudo chmod 755 /var/ossec/integrations/custom-w2thive 15 | $ sudo chown root:ossec /var/ossec/integrations/custom-w2thive.py 16 | $ sudo chown root:ossec /var/ossec/integrations/custom-w2thive 17 | $ sudo nano /var/ossec/etc/ossec.conf 18 | ``` 19 | insert the following snippet into the ossec_config block: 20 | ```xml 21 | 22 | custom-w2thive 23 | http://localhost:9000 24 | 123456790 25 | json 26 | 27 | ``` 28 | lines description: 29 | 30 | **name** - integration name(no need to change) 31 | 32 | **hook_url** - TheHive host 33 | 34 | **api\_key** - TheHive user's API key. You can generate the key on the user management page by logging in as administrator. For security, allow the api-user to create only an alert. 35 | 36 | **alert\_format** - format that wazuh sends alert to the integrator(no need to change) 37 | 38 | after configuration, apply the changes with this command: 39 | ```sh 40 | /var/ossec/bin/ossec-control restart 41 | ``` 42 | Finally, check the /var/ossec/log/integrations.log file for errors. If there is not enough information from the errors, you can enable debug_mode by changing the line in the file custom-w2thive.py 43 | ```python 44 | debug_enabled = False 45 | ``` 46 | to 47 | ```python 48 | debug_enabled = True 49 | ``` 50 | If you receive too many events, you can set a severity threshold for events that will be send to TheHive. Set the value of the lvl_threshold variable in the file /var/ossec/integrations/custom-w2thive.py 51 | ```python 52 | lvl_threshold = 0 53 | ``` 54 | Events with a severity level equal to or greater will be sent to TheHive. You can read more about event classification in Wazuh here: [wazuh-rules-classification](https://documentation.wazuh.com/3.12/user-manual/ruleset/rules-classification.html) 55 | 56 | Vadim M. 57 | 58 | _ru_ 59 | ## Wazuh and TheHive integration 60 | Этот проект интегрирует SIEM Wazuh и TheHive. Для настройки воспользуйтесь следующими инструкциями: 61 | 62 | ```sh 63 | $ cd /opt/ 64 | $ sudo git clone https://github.com/crow1011/wazuh2thehive.git 65 | $ sudo /var/ossec/bin/python/pip3 install -r /opt/wazuh2thehive/requirements.txt 66 | $ sudo cp /opt/wazuh2thehive/custom-w2thive.py /var/ossec/integration/custom-w2thive.py 67 | $ sudo cp /opt/wazuh2thehive/custom-w2thive /var/ossec/integration/custom-w2thive 68 | $ sudo chmod 755 /var/ossec/integration/custom-w2thive.py 69 | $ sudo chmod 755 /var/ossec/integration/custom-w2thive 70 | $ sudo chown root:ossec /var/ossec/integration/custom-w2thive.py 71 | $ sudo chown root:ossec /var/ossec/integration/custom-w2thive 72 | $ sudo nano /var/ossec/etc/ossec.conf 73 | ``` 74 | вставьте в блок ossec_config следующий фрагмент: 75 | ```xml 76 | 77 | custom-w2thive 78 | http://localhost:9000 79 | 123456790 80 | json 81 | 82 | ``` 83 | где: 84 | 85 | **name** - название интегратора(не нужно изменять) 86 | 87 | **hook_url** - адрес TheHive 88 | 89 | **api\_key** - API ключ TheHive пользователя. Сгенериоровать ключ можно на странице управления пользователями, авторизовавшись от администратора. Для безопасности разрешите api-пользователю только создание alert. 90 | 91 | **alert\_format** - формат, в котором wazuh передает в интегратор alert(не нужно изменять) 92 | 93 | после настройки примените изменения командой: 94 | ```sh 95 | /var/ossec/bin/ossec_control restart 96 | ``` 97 | В конце проверьте файл /var/ossec/log/integrations.log на присутствие ошибок. Если информации из ошибки недостаточно, вы можете включить debug_mode, поменяв в файле custom-w2thive.py строчку 98 | ```python 99 | debug_enabled = False 100 | ``` 101 | на 102 | ```python 103 | debug_enabled = True 104 | ``` 105 | Если вы получаете слишком много событий, вы можете задать порог критичности отправляемых в TheHive событий. Для того чтобы его установить этот порог, задайте значение переменной lvl_threshold в файле /var/ossec/integrations/custom-w2thive.py 106 | ```python 107 | lvl_threshold = 0 108 | ``` 109 | В TheHive будут отправлены события с уровнем критичности равным или большим. Подробнее про классификацию событий в Wazuh можно прочитать здесь: [wazuh-rules-classification](https://documentation.wazuh.com/3.12/user-manual/ruleset/rules-classification.html) 110 | Vadim M. 111 | -------------------------------------------------------------------------------- /custom-w2thive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import json 3 | import sys 4 | import os 5 | import re 6 | import logging 7 | import uuid 8 | from thehive4py.api import TheHiveApi 9 | from thehive4py.models import Alert, AlertArtifact 10 | 11 | # ossec.conf configuration: 12 | # 13 | # custom-w2thive 14 | # http://localhost:9000 15 | # 123456790 16 | # json 17 | # 18 | 19 | 20 | #start user config 21 | 22 | # Global vars 23 | 24 | #threshold for wazuh rules level 25 | lvl_threshold=0 26 | #threshold for suricata rules level 27 | suricata_lvl_threshold=3 28 | 29 | debug_enabled = False 30 | #info about created alert 31 | info_enabled = True 32 | 33 | #end user config 34 | 35 | # Set paths 36 | pwd = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 37 | log_file = '{0}/logs/integrations.log'.format(pwd) 38 | logger = logging.getLogger(__name__) 39 | #set logging level 40 | logger.setLevel(logging.WARNING) 41 | if info_enabled: 42 | logger.setLevel(logging.INFO) 43 | if debug_enabled: 44 | logger.setLevel(logging.DEBUG) 45 | # create the logging file handler 46 | fh = logging.FileHandler(log_file) 47 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 48 | fh.setFormatter(formatter) 49 | logger.addHandler(fh) 50 | 51 | 52 | 53 | def main(args): 54 | logger.debug('#start main') 55 | logger.debug('#get alert file location') 56 | alert_file_location = args[1] 57 | logger.debug('#get TheHive url') 58 | thive = args[3] 59 | logger.debug('#get TheHive api key') 60 | thive_api_key = args[2] 61 | thive_api = TheHiveApi(thive, thive_api_key ) 62 | logger.debug('#open alert file') 63 | w_alert = json.load(open(alert_file_location)) 64 | logger.debug('#alert data') 65 | logger.debug(str(w_alert)) 66 | logger.debug('#gen json to dot-key-text') 67 | alt = pr(w_alert,'',[]) 68 | logger.debug('#formatting description') 69 | format_alt = md_format(alt) 70 | logger.debug('#search artifacts') 71 | artifacts_dict = artifact_detect(format_alt) 72 | alert = generate_alert(format_alt, artifacts_dict, w_alert) 73 | logger.debug('#threshold filtering') 74 | if w_alert['rule']['groups']==['ids','suricata']: 75 | #checking the existence of the data.alert.severity field 76 | if 'data' in w_alert.keys(): 77 | if 'alert' in w_alert['data']: 78 | #checking the level of the source event 79 | if int(w_alert['data']['alert']['severity'])<=suricata_lvl_threshold: 80 | send_alert(alert, thive_api) 81 | elif int(w_alert['rule']['level'])>=lvl_threshold: 82 | #if the event is different from suricata AND suricata-event-type: alert check lvl_threshold 83 | send_alert(alert, thive_api) 84 | 85 | 86 | def pr(data,prefix, alt): 87 | for key,value in data.items(): 88 | if hasattr(value,'keys'): 89 | pr(value,prefix+'.'+str(key),alt=alt) 90 | else: 91 | alt.append((prefix+'.'+str(key)+'|||'+str(value))) 92 | return alt 93 | 94 | 95 | 96 | def md_format(alt,format_alt=''): 97 | md_title_dict = {} 98 | #sorted with first key 99 | for now in alt: 100 | now = now[1:] 101 | #fix first key last symbol 102 | dot = now.split('|||')[0].find('.') 103 | if dot==-1: 104 | md_title_dict[now.split('|||')[0]] =[now] 105 | else: 106 | if now[0:dot] in md_title_dict.keys(): 107 | (md_title_dict[now[0:dot]]).append(now) 108 | else: 109 | md_title_dict[now[0:dot]]=[now] 110 | for now in md_title_dict.keys(): 111 | format_alt+='### '+now.capitalize()+'\n'+'| key | val |\n| ------ | ------ |\n' 112 | for let in md_title_dict[now]: 113 | key,val = let.split('|||')[0],let.split('|||')[1] 114 | format_alt+='| **' + key + '** | ' + val + ' |\n' 115 | return format_alt 116 | 117 | 118 | def artifact_detect(format_alt): 119 | artifacts_dict = {} 120 | artifacts_dict['ip'] = re.findall(r'\d+\.\d+\.\d+\.\d+',format_alt) 121 | artifacts_dict['url'] = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+',format_alt) 122 | artifacts_dict['domain'] = [] 123 | for now in artifacts_dict['url']: artifacts_dict['domain'].append(now.split('//')[1].split('/')[0]) 124 | return artifacts_dict 125 | 126 | 127 | def generate_alert(format_alt, artifacts_dict,w_alert): 128 | #generate alert sourceRef 129 | sourceRef = str(uuid.uuid4())[0:6] 130 | artifacts = [] 131 | if 'agent' in w_alert.keys(): 132 | if 'ip' not in w_alert['agent'].keys(): 133 | w_alert['agent']['ip']='no agent ip' 134 | else: 135 | w_alert['agent'] = {'id':'no agent id', 'name':'no agent name'} 136 | 137 | for key,value in artifacts_dict.items(): 138 | for val in value: 139 | artifacts.append(AlertArtifact(dataType=key, data=val)) 140 | alert = Alert(title=w_alert['rule']['description'], 141 | tlp=2, 142 | tags=['wazuh', 143 | 'rule='+w_alert['rule']['id'], 144 | 'agent_name='+w_alert['agent']['name'], 145 | 'agent_id='+w_alert['agent']['id'], 146 | 'agent_ip='+w_alert['agent']['ip'],], 147 | description=format_alt , 148 | type='wazuh_alert', 149 | source='wazuh', 150 | sourceRef=sourceRef, 151 | artifacts=artifacts,) 152 | return alert 153 | 154 | 155 | 156 | 157 | def send_alert(alert, thive_api): 158 | response = thive_api.create_alert(alert) 159 | if response.status_code == 201: 160 | logger.info('Create TheHive alert: '+ str(response.json()['id'])) 161 | else: 162 | logger.error('Error create TheHive alert: {}/{}'.format(response.status_code, response.text)) 163 | 164 | 165 | 166 | if __name__ == "__main__": 167 | 168 | try: 169 | logger.debug('debug mode') # if debug enabled 170 | # Main function 171 | main(sys.argv) 172 | 173 | except Exception: 174 | logger.exception('EGOR') 175 | --------------------------------------------------------------------------------