|
| 1 | +--- |
| 2 | +sidebar_position: 8 |
| 3 | +--- |
| 4 | + |
| 5 | +:::info |
| 6 | +**Document Creation:** 3 September 2025. **Last Edited:** 3 September 2025. **Authors:** Syed Mahmood Aleem Huzaifa. |
| 7 | +**Effective Date:** 3 September 2025. **Expiry Date:** 3 September2026. |
| 8 | +::: |
| 9 | + |
| 10 | +### Overview |
| 11 | + |
| 12 | +Phase 1 focuses on establishing the integration between Wazuh and TheHive so that security alerts generated by Wazuh are automatically pushed into TheHive as cases. The main goal here is to eliminate the manual step of copying alerts from Wazuh into TheHive, ensuring that analysts have a single central platform to investigate and manage incidents. This means that whenever Wazuh detects suspicious activity, it will trigger the creation of a case in TheHive, where the details of the alert can be examined and escalated. |
| 13 | + |
| 14 | +**To make this possible, both systems must already be properly set up: the Wazuh Manager should be actively running on the Ubuntu server where it was installed, TheHive should be online and accessible via its web interface, and you should have administrative access to both servers to configure API connectivity and permissions. This setup ensures a seamless pipeline where Wazuh handles detection and TheHive manages investigation, streamlining the incident response workflow.** |
| 15 | + |
| 16 | +### Step 1: Preparing TheHive |
| 17 | +Before Wazuh can send alerts to TheHive, we need to set up an organisation and create a user who can receive alerts through TheHive’s API. |
| 18 | +1. Create an Organisation |
| 19 | + 1. Log into TheHive web interface using the admin account. |
| 20 | + 2. Go to Administration → Organisations. |
| 21 | + 3. Create a new organisation: Example - Redback. |
| 22 | +2. Create a User for API Access |
| 23 | + 1. Inside your organisation, create a new user. |
| 24 | + o Role: **Analyst** (can receive alerts and create cases, but not change system settings). |
| 25 | + 2. Set a password for this user. |
| 26 | +3. Generate the API Key |
| 27 | + 1. Edit the new user profile. |
| 28 | + 2. Click **"Reveal"** under API Key. |
| 29 | + 3. Copy the key somewhere safe — you’ll need it in the Wazuh configuration. |
| 30 | + |
| 31 | +### Step 2: Install TheHive Python Module on Wazuh |
| 32 | +TheHive integration uses a Python library called thehive4py to send alerts. We need to install it inside Wazuh’s built-in Python environment. |
| 33 | +Run this command on your Wazuh Manager: |
| 34 | +``` |
| 35 | +sudo /var/ossec/framework/python/bin/pip3 install thehive4py==1.8.1 |
| 36 | +``` |
| 37 | +### Step 3: Create the Custom Integration Script |
| 38 | +Wazuh needs a special script to format alerts and send them to TheHive. |
| 39 | +1. Creating the Python Script |
| 40 | +``` |
| 41 | +sudo nano /var/ossec/integrations/custom-w2thive.py |
| 42 | +``` |
| 43 | +Paste the below script into the python file |
| 44 | +``` |
| 45 | +#!/var/ossec/framework/python/bin/python3 |
| 46 | +import json |
| 47 | +import sys |
| 48 | +import os |
| 49 | +import re |
| 50 | +import logging |
| 51 | +import uuid |
| 52 | +from thehive4py.api import TheHiveApi |
| 53 | +from thehive4py.models import Alert, AlertArtifact |
| 54 | +
|
| 55 | +#start user config |
| 56 | +
|
| 57 | +# Global vars |
| 58 | +
|
| 59 | +#threshold for wazuh rules level |
| 60 | +lvl_threshold=0 |
| 61 | +#threshold for suricata rules level |
| 62 | +suricata_lvl_threshold=3 |
| 63 | +
|
| 64 | +debug_enabled = False |
| 65 | +#info about created alert |
| 66 | +info_enabled = True |
| 67 | +
|
| 68 | +#end user config |
| 69 | +
|
| 70 | +# Set paths |
| 71 | +pwd = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) |
| 72 | +log_file = '{0}/logs/integrations.log'.format(pwd) |
| 73 | +logger = logging.getLogger(__name__) |
| 74 | +#set logging level |
| 75 | +logger.setLevel(logging.WARNING) |
| 76 | +if info_enabled: |
| 77 | + logger.setLevel(logging.INFO) |
| 78 | +if debug_enabled: |
| 79 | + logger.setLevel(logging.DEBUG) |
| 80 | +# create the logging file handler |
| 81 | +fh = logging.FileHandler(log_file) |
| 82 | +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') |
| 83 | +fh.setFormatter(formatter) |
| 84 | +logger.addHandler(fh) |
| 85 | +
|
| 86 | +def main(args): |
| 87 | + logger.debug('#start main') |
| 88 | + logger.debug('#get alert file location') |
| 89 | + alert_file_location = args[1] |
| 90 | + logger.debug('#get TheHive url') |
| 91 | + thive = args[3] |
| 92 | + logger.debug('#get TheHive api key') |
| 93 | + thive_api_key = args[2] |
| 94 | + thive_api = TheHiveApi(thive, thive_api_key ) |
| 95 | + logger.debug('#open alert file') |
| 96 | + w_alert = json.load(open(alert_file_location)) |
| 97 | + logger.debug('#alert data') |
| 98 | + logger.debug(str(w_alert)) |
| 99 | + logger.debug('#gen json to dot-key-text') |
| 100 | + alt = pr(w_alert,'',[]) |
| 101 | + logger.debug('#formatting description') |
| 102 | + format_alt = md_format(alt) |
| 103 | + logger.debug('#search artifacts') |
| 104 | + artifacts_dict = artifact_detect(format_alt) |
| 105 | + alert = generate_alert(format_alt, artifacts_dict, w_alert) |
| 106 | + logger.debug('#threshold filtering') |
| 107 | + if w_alert['rule']['groups']==['ids','suricata']: |
| 108 | + #checking the existence of the data.alert.severity field |
| 109 | + if 'data' in w_alert.keys(): |
| 110 | + if 'alert' in w_alert['data']: |
| 111 | + #checking the level of the source event |
| 112 | + if int(w_alert['data']['alert']['severity'])<=suricata_lvl_threshold: |
| 113 | + send_alert(alert, thive_api) |
| 114 | + elif int(w_alert['rule']['level'])>=lvl_threshold: |
| 115 | + #if the event is different from suricata AND suricata-event-type: alert check lvl_threshold |
| 116 | + send_alert(alert, thive_api) |
| 117 | +
|
| 118 | +def pr(data,prefix, alt): |
| 119 | + for key,value in data.items(): |
| 120 | + if hasattr(value,'keys'): |
| 121 | + pr(value,prefix+'.'+str(key),alt=alt) |
| 122 | + else: |
| 123 | + alt.append((prefix+'.'+str(key)+'|||'+str(value))) |
| 124 | + return alt |
| 125 | +
|
| 126 | +def md_format(alt,format_alt=''): |
| 127 | + md_title_dict = {} |
| 128 | + #sorted with first key |
| 129 | + for now in alt: |
| 130 | + now = now[1:] |
| 131 | + #fix first key last symbol |
| 132 | + dot = now.split('|||')[0].find('.') |
| 133 | + if dot==-1: |
| 134 | + md_title_dict[now.split('|||')[0]] =[now] |
| 135 | + else: |
| 136 | + if now[0:dot] in md_title_dict.keys(): |
| 137 | + (md_title_dict[now[0:dot]]).append(now) |
| 138 | + else: |
| 139 | + md_title_dict[now[0:dot]]=[now] |
| 140 | + for now in md_title_dict.keys(): |
| 141 | + format_alt+='### '+now.capitalize()+'\n'+'| key | val |\n| ------ | ------ |\n' |
| 142 | + for let in md_title_dict[now]: |
| 143 | + key,val = let.split('|||')[0],let.split('|||')[1] |
| 144 | + format_alt+='| **' + key + '** | ' + val + ' |\n' |
| 145 | + return format_alt |
| 146 | +
|
| 147 | +
|
| 148 | +def artifact_detect(format_alt): |
| 149 | + artifacts_dict = {} |
| 150 | + artifacts_dict['ip'] = re.findall(r'\d+\.\d+\.\d+\.\d+',format_alt) |
| 151 | + artifacts_dict['url'] = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+',format_alt) |
| 152 | + artifacts_dict['domain'] = [] |
| 153 | + for now in artifacts_dict['url']: artifacts_dict['domain'].append(now.split('//')[1].split('/')[0]) |
| 154 | + return artifacts_dict |
| 155 | +
|
| 156 | +def generate_alert(format_alt, artifacts_dict,w_alert): |
| 157 | + #generate alert sourceRef |
| 158 | + sourceRef = str(uuid.uuid4())[0:6] |
| 159 | + artifacts = [] |
| 160 | + if 'agent' in w_alert.keys(): |
| 161 | + if 'ip' not in w_alert['agent'].keys(): |
| 162 | + w_alert['agent']['ip']='no agent ip' |
| 163 | + else: |
| 164 | + w_alert['agent'] = {'id':'no agent id', 'name':'no agent name'} |
| 165 | +
|
| 166 | + for key,value in artifacts_dict.items(): |
| 167 | + for val in value: |
| 168 | + artifacts.append(AlertArtifact(dataType=key, data=val)) |
| 169 | + alert = Alert(title=w_alert['rule']['description'], |
| 170 | + tlp=2, |
| 171 | + tags=['wazuh', |
| 172 | + 'rule='+w_alert['rule']['id'], |
| 173 | + 'agent_name='+w_alert['agent']['name'], |
| 174 | + 'agent_id='+w_alert['agent']['id'], |
| 175 | + 'agent_ip='+w_alert['agent']['ip'],], |
| 176 | + description=format_alt , |
| 177 | + type='wazuh_alert', |
| 178 | + source='wazuh', |
| 179 | + sourceRef=sourceRef, |
| 180 | + artifacts=artifacts,) |
| 181 | + return alert |
| 182 | +
|
| 183 | +def send_alert(alert, thive_api): |
| 184 | + response = thive_api.create_alert(alert) |
| 185 | + if response.status_code == 201: |
| 186 | + logger.info('Create TheHive alert: '+ str(response.json()['id'])) |
| 187 | + else: |
| 188 | + logger.error('Error create TheHive alert: {}/{}'.format(response.status_code, response.text)) |
| 189 | +
|
| 190 | +if __name__ == "__main__": |
| 191 | +
|
| 192 | + try: |
| 193 | + logger.debug('debug mode') # if debug enabled |
| 194 | + # Main function |
| 195 | + main(sys.argv) |
| 196 | +
|
| 197 | + except Exception: |
| 198 | + logger.exception('EGOR') |
| 199 | +
|
| 200 | +``` |
| 201 | +This Python script acts as a connector between Wazuh and TheHive by automatically converting Wazuh alerts into TheHive alerts (cases) for investigation. When Wazuh generates an alert, the script parses the JSON alert file, extracts key information, and formats it into a readable description. It also detects potential observables like IPs, URLs, and domains, which are attached as artifacts. Based on severity thresholds, it decides whether the alert should be forwarded to TheHive. If it passes the filter, the script sends the alert to TheHive using its API, where it becomes available for analysts to investigate further. Logging is enabled to track each step and confirm whether alerts were successfully created. |
| 202 | + |
| 203 | +2. Create the Bash Wrapper Script |
| 204 | +Wazuh runs integration scripts through small shell wrappers. |
| 205 | + |
| 206 | + Create a file: |
| 207 | + ``` |
| 208 | + sudo nano /var/ossec/integrations/custom-w2thive |
| 209 | + ``` |
| 210 | + The script that goes into this is below |
| 211 | + ``` |
| 212 | + #!/bin/sh |
| 213 | + WPYTHON_BIN="framework/python/bin/python3" |
| 214 | + SCRIPT_PATH_NAME="$0" |
| 215 | + DIR_NAME="$(cd $(dirname ${SCRIPT_PATH_NAME}); pwd -P)" |
| 216 | + SCRIPT_NAME="$(basename ${SCRIPT_PATH_NAME})" |
| 217 | + |
| 218 | + case ${DIR_NAME} in |
| 219 | + */active-response/bin | */wodles*) |
| 220 | + if [ -z "${WAZUH_PATH}" ]; then |
| 221 | + WAZUH_PATH="$(cd ${DIR_NAME}/../..; pwd)" |
| 222 | + fi |
| 223 | + PYTHON_SCRIPT="${DIR_NAME}/${SCRIPT_NAME}.py" |
| 224 | + ;; |
| 225 | + */bin) |
| 226 | + if [ -z "${WAZUH_PATH}" ]; then |
| 227 | + WAZUH_PATH="$(cd ${DIR_NAME}/..; pwd)" |
| 228 | + fi |
| 229 | + PYTHON_SCRIPT="${WAZUH_PATH}/framework/scripts/${SCRIPT_NAME}.py" |
| 230 | + ;; |
| 231 | + */integrations) |
| 232 | + if [ -z "${WAZUH_PATH}" ]; then |
| 233 | + WAZUH_PATH="$(cd ${DIR_NAME}/..; pwd)" |
| 234 | + fi |
| 235 | + PYTHON_SCRIPT="${DIR_NAME}/${SCRIPT_NAME}.py" |
| 236 | + ;; |
| 237 | + esac |
| 238 | + |
| 239 | + ${WAZUH_PATH}/${WPYTHON_BIN} ${PYTHON_SCRIPT} $@ |
| 240 | + |
| 241 | + ``` |
| 242 | +This small shell wrapper is what allows Wazuh to execute your custom Python integration reliably. Since Wazuh can call integrations from different directories, the wrapper first checks its own location, then calculates the correct base path to Wazuh and to the Python script it needs to run. It then builds the path to your custom-w2thive.py script and finally uses Wazuh’s bundled Python interpreter (framework/python/bin/python3) to execute it, passing along any arguments that Wazuh supplies. In simple terms, the wrapper acts as a bridge: Wazuh calls the wrapper, the wrapper resolves the right paths, and then your Python script runs to forward alerts into TheHive. This ensures the integration works consistently regardless of where Wazuh launches it from. |
| 243 | + |
| 244 | +### Step 4: Set Permissions |
| 245 | +These scripts must be executable and owned by the correct group (ossec in Wazuh). |
| 246 | + |
| 247 | +``` |
| 248 | +sudo chmod 755 /var/ossec/integrations/custom-w2thive.py |
| 249 | +sudo chmod 755 /var/ossec/integrations/custom-w2thive |
| 250 | +sudo chown root:ossec /var/ossec/integrations/custom-w2thive.py |
| 251 | +sudo chown root:ossec /var/ossec/integrations/custom-w2thive |
| 252 | +``` |
| 253 | +These commands are making sure that both your wrapper (custom-w2thive) and the Python script (custom-w2thive.py) have the right permissions and ownership so Wazuh can execute them safely. By setting the mode to 755, you’re allowing the owner (root) to read, write, and execute, while members of the group and others can only read and execute. The ownership is set to root:ossec, which means the root user owns the files, but the ossec group also has access. This is important because Wazuh services run under the ossec group, so without this step, Wazuh would not be able to call your integration scripts. |
| 254 | + |
| 255 | +### Step 5: Configure Wazuh to Use the Integration |
| 256 | +Edit the Wazuh Manager config file: |
| 257 | +``` |
| 258 | +sudo nano /var/ossec/etc/ossec.conf |
| 259 | +``` |
| 260 | +Add this inside "ossec_config": |
| 261 | +``` |
| 262 | +<integration> |
| 263 | + <name>custom-w2thive</name> |
| 264 | + <hook_url>http://THEHIVE_SERVER_IP:9000</hook_url> |
| 265 | + <api_key>YOUR_API_KEY</api_key> |
| 266 | + <alert_format>json</alert_format> |
| 267 | +</integration> |
| 268 | +``` |
| 269 | +*THEHIVE_SERVER_IP = IP address of your TheHive server (e.g., 127.0.0.1 if on the same machine). |
| 270 | +YOUR_API_KEY = the key you copied earlier* |
| 271 | + |
| 272 | +### Step 6: Restart Wazuh |
| 273 | +``` |
| 274 | +sudo systemctl restart wazuh-manager |
| 275 | +``` |
| 276 | +### Step 7: Test the Integration |
| 277 | + |
| 278 | +We’ll create fake alerts to see if they appear in TheHive. |
| 279 | +Example: Trigger a Syscheck alert by creating a file in /etc: |
| 280 | +``` |
| 281 | +sudo touch /etc/wazuh_test.txt |
| 282 | +echo "test" | sudo tee -a /etc/wazuh_test.txt |
| 283 | +``` |
| 284 | + |
| 285 | +Example: Trigger an authentication failure alert: |
| 286 | +```ssh nouser@localhost |
| 287 | +``` |
| 288 | +(Type any password — it will fail and create an alert.) |
| 289 | + |
| 290 | +If everything works, you’ll see messages in: |
| 291 | +``` |
| 292 | +sudo tail -n 50 /var/ossec/logs/integrations.log |
| 293 | +``` |
| 294 | + |
| 295 | + |
| 296 | +Automatically, the cases are created as shown below |
| 297 | + |
| 298 | + |
| 299 | +### Conclusion |
| 300 | +This phase represents a major milestone in building an automated security operations pipeline, as it bridges the gap between detection and investigation. With the Wazuh ↔ TheHive integration in place, alerts no longer remain siloed within Wazuh but are automatically forwarded into TheHive, where they are transformed into actionable cases. This ensures that analysts can immediately work on verified incidents within a centralized platform rather than wasting time manually transferring data. In practice, this reduces friction, accelerates the response process, and makes sure that no critical alerts are overlooked due to human error or delays. |
| 301 | + |
| 302 | +Beyond convenience, the integration adds structure and scalability to incident management. By feeding enriched alerts into TheHive, security teams can leverage case workflows, collaborative features, and task assignments that are built into TheHive’s design. This allows multiple analysts to coordinate effectively on the same incident, track progress, and document findings in one place. It also improves accountability, since every case in TheHive is linked to its originating Wazuh alert, creating an auditable chain of events. |
| 303 | + |
| 304 | +From a strategic perspective, this integration lays the groundwork for more advanced automation in later phases, such as linking with Cortex for automated analysis, enriching cases with external threat intelligence, or triggering response actions directly from TheHive. What begins here as a simple alert forwarding mechanism evolves into the foundation of a full Security Orchestration, Automation, and Response (SOAR) pipeline. In other words, Phase 7 does not just close the loop between detection and investigation — it transforms the security workflow into a proactive, scalable, and collaborative system that strengthens the organization’s overall security posture. |
0 commit comments