Detecting and removing malware using VirusTotal integration
Wazuh uses the integrator module to connect to external APIs and alerting tools such as VirusTotal.
In this use case, you use the Wazuh File Integrity Monitoring (FIM) module to monitor a directory for changes and the VirusTotal API to scan the files in the directory. Then, configure Wazuh to trigger an active response script and remove files that VirusTotal detects as malicious. We test this use case on Ubuntu and Windows endpoints.
You need a VirusTotal API key in this use case to authenticate Wazuh to the VirusTotal API.
For more information on this integration, check the VirusTotal integration section of the documentation.
Infrastructure
Endpoint |
Description |
|---|---|
Ubuntu 22.04 |
This is the Linux endpoint where you download a malicious file. Wazuh triggers an active response script to remove the file once VirusTotal flags it as malicious. |
Windows 11 |
This is the Windows endpoint where you download a malicious file. Wazuh triggers an active response script to remove the file once VirusTotal flags it as malicious. |
Configuration for the Ubuntu endpoint
Configure your environment as follows to test the use case for the Ubuntu endpoint. These steps work for other Linux distributions as well.
Ubuntu endpoint
Perform the following steps to configure Wazuh to monitor near real-time changes in the /root directory of the Ubuntu endpoint. These steps also install the necessary packages and create the active response script that removes malicious files.
Search for the
<syscheck>block in the Wazuh agent configuration file/var/ossec/etc/ossec.conf. Make sure that<disabled>is set tono. This enables the Wazuh FIM to monitor for directory changes.Add an entry within the
<syscheck>block to configure a directory to be monitored in near real-time. In this case, you are monitoring the/rootdirectory:<directories realtime="yes">/root</directories>
Install
jq, a utility that processes JSON input from the active response script.$ sudo apt update $ sudo apt -y install jq
Create the
/var/ossec/active-response/bin/remove-threat.shactive response script to remove malicious files from the endpoint:#!/bin/bash LOCAL=`dirname $0`; cd $LOCAL cd ../ PWD=`pwd` read INPUT_JSON FILENAME=$(echo $INPUT_JSON | jq -r .parameters.alert.data.virustotal.source.file) COMMAND=$(echo $INPUT_JSON | jq -r .command) LOG_FILE="${PWD}/../logs/active-responses.log" #------------------------ Analyze command -------------------------# if [ ${COMMAND} = "add" ] then # Send control message to execd printf '{"version":1,"origin":{"name":"remove-threat","module":"active-response"},"command":"check_keys", "parameters":{"keys":[]}}\n' read RESPONSE COMMAND2=$(echo $RESPONSE | jq -r .command) if [ ${COMMAND2} != "continue" ] then echo "`date '+%Y/%m/%d %H:%M:%S'` $0: $INPUT_JSON Remove threat active response aborted" >> ${LOG_FILE} exit 0; fi fi # Removing file rm -f $FILENAME if [ $? -eq 0 ]; then echo "`date '+%Y/%m/%d %H:%M:%S'` $0: $INPUT_JSON Successfully removed threat" >> ${LOG_FILE} else echo "`date '+%Y/%m/%d %H:%M:%S'` $0: $INPUT_JSON Error removing threat" >> ${LOG_FILE} fi exit 0;
Change the
/var/ossec/active-response/bin/remove-threat.shfile ownership, and permissions:$ sudo chmod 750 /var/ossec/active-response/bin/remove-threat.sh $ sudo chown root:wazuh /var/ossec/active-response/bin/remove-threat.sh
Restart the Wazuh agent to apply the changes:
$ sudo systemctl restart wazuh-agent
Wazuh server
Perform the following steps on the Wazuh server to alert for changes in the endpoint directory and enable the VirusTotal integration. These steps also enable and trigger the active response script whenever a suspicious file is detected.
Add the following rules to the
/var/ossec/etc/rules/local_rules.xmlfile on the Wazuh server. These rules alert about changes in the/rootdirectory that are detected by FIM scans:<group name="syscheck,pci_dss_11.5,nist_800_53_SI.7,"> <!-- Rules for Linux systems --> <rule id="100200" level="7"> <if_sid>550</if_sid> <field name="file">/root</field> <description>File modified in /root directory.</description> </rule> <rule id="100201" level="7"> <if_sid>554</if_sid> <field name="file">/root</field> <description>File added to /root directory.</description> </rule> </group>
Add the following configuration to the Wazuh server
/var/ossec/etc/ossec.conffile to enable the Virustotal integration. Replace<YOUR_VIRUS_TOTAL_API_KEY>with your VirusTotal API key. This allows to trigger a VirusTotal query whenever any of the rules100200and100201are triggered:<ossec_config> <integration> <name>virustotal</name> <api_key><YOUR_VIRUS_TOTAL_API_KEY></api_key> <!-- Replace with your VirusTotal API key --> <rule_id>100200,100201</rule_id> <alert_format>json</alert_format> </integration> </ossec_config>
Note
The free VirusTotal API rate limits requests to four per minute. If you have a premium VirusTotal API key, with a high frequency of queries allowed, you can add more rules besides these two. You can also configure Wazuh to monitor more directories.
Append the following blocks to the Wazuh server
/var/ossec/etc/ossec.conffile. This enables active response and triggers theremove-threat.shscript when VirusTotal flags a file as malicious:<ossec_config> <command> <name>remove-threat</name> <executable>remove-threat.sh</executable> <timeout_allowed>no</timeout_allowed> </command> <active-response> <disabled>no</disabled> <command>remove-threat</command> <location>local</location> <rules_id>87105</rules_id> </active-response> </ossec_config>
Add the following rules to the Wazuh server
/var/ossec/etc/rules/local_rules.xmlfile to alert about the active response results:<group name="virustotal,"> <rule id="100092" level="12"> <if_sid>657</if_sid> <match>Successfully removed threat</match> <description>$(parameters.program) removed threat located at $(parameters.alert.data.virustotal.source.file)</description> </rule> <rule id="100093" level="12"> <if_sid>657</if_sid> <match>Error removing threat</match> <description>Error removing threat located at $(parameters.alert.data.virustotal.source.file)</description> </rule> </group>
Restart the Wazuh manager to apply the configuration changes:
$ sudo systemctl restart wazuh-manager
Attack emulation
Download an EICAR test file to the
/rootdirectory on the Ubuntu endpoint:$ sudo curl -Lo /root/eicar.com https://secure.eicar.org/eicar.com && sudo ls -lah /root/eicar.com
Visualize the alerts
You can visualize the alert data in the Wazuh dashboard. To do this, go to the Security events module and add the filters in the search bar to query the alerts.
Linux -
rule.id: is one of 553,100092,87105,100201
Configuration for the Windows endpoint
Windows endpoint
Perform the following steps to configure Wazuh to monitor near real-time changes in the /Downloads directory. These steps also install the necessary packages and create the active response script to remove malicious files.
Search for the
<syscheck>block in the Wazuh agentC:\Program Files (x86)\ossec-agent\ossec.conffile. Make sure that<disabled>is set tono. This enables the Wazuh FIM module to monitor for directory changes.Add an entry within the
<syscheck>block to configure a directory to be monitored in near real-time. In this use case, you configure Wazuh to monitor theC:\Users\<USER_NAME>\Downloadsdirectory. Replace the<USER_NAME>variable with the appropriate user name:<directories realtime="yes">C:\Users\<USER_NAME>\Downloads</directories>
Download the Python executable installer from the official Python website.
Run the Python installer once downloaded. Make sure to check the following boxes:
Install launcher for all usersAdd Python 3.X to PATH(This places the interpreter in the execution path)
Once Python completes the installation process, open an administrator PowerShell terminal and use
pipto install PyInstaller:> pip install pyinstaller > pyinstaller --version
You use Pyinstaller here to convert the active response Python script into an executable application that can run on a Windows endpoint.
Create an active response script
remove-threat.pyto remove a file from the Windows endpoint:Warning
This script is a proof of concept (PoC). Review and validate it to ensure it meets the operational and security requirements of your environment.
# Copyright (C) 2015-2025, Wazuh Inc. # All rights reserved. import os import sys import json import datetime import stat import tempfile import pathlib if os.name == 'nt': LOG_FILE = "C:\\Program Files (x86)\\ossec-agent\\active-response\\active-responses.log" else: LOG_FILE = "/var/ossec/logs/active-responses.log" ADD_COMMAND = 0 DELETE_COMMAND = 1 CONTINUE_COMMAND = 2 ABORT_COMMAND = 3 OS_SUCCESS = 0 OS_INVALID = -1 class message: def __init__(self): self.alert = "" self.command = 0 def write_debug_file(ar_name, msg): with open(LOG_FILE, mode="a") as log_file: log_file.write(str(datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')) + " " + ar_name + ": " + msg +"\n") def setup_and_check_message(argv): input_str = "" for line in sys.stdin: input_str = line break msg_obj = message() try: data = json.loads(input_str) except ValueError: write_debug_file(argv[0], 'Decoding JSON has failed, invalid input format') msg_obj.command = OS_INVALID return msg_obj msg_obj.alert = data command = data.get("command") if command == "add": msg_obj.command = ADD_COMMAND elif command == "delete": msg_obj.command = DELETE_COMMAND else: msg_obj.command = OS_INVALID write_debug_file(argv[0], 'Not valid command: ' + command) return msg_obj def send_keys_and_check_message(argv, keys): keys_msg = json.dumps({"version": 1,"origin":{"name": argv[0],"module":"active-response"},"command":"check_keys","parameters":{"keys":keys}}) write_debug_file(argv[0], keys_msg) print(keys_msg) sys.stdout.flush() input_str = "" while True: line = sys.stdin.readline() if line: input_str = line break try: data = json.loads(input_str) except ValueError: write_debug_file(argv[0], 'Decoding JSON has failed, invalid input format') return OS_INVALID action = data.get("command") if action == "continue": return CONTINUE_COMMAND elif action == "abort": return ABORT_COMMAND else: write_debug_file(argv[0], "Invalid value of 'command'") return OS_INVALID def secure_delete_file(filepath_str, ar_name): filepath = pathlib.Path(filepath_str) # Reject NTFS alternate data streams if '::' in filepath_str: raise Exception(f"Refusing to delete ADS or NTFS stream: {filepath_str}") # Reject symbolic links and reparse points if os.path.islink(filepath): raise Exception(f"Refusing to delete symbolic link: {filepath}") attrs = os.lstat(filepath).st_file_attributes if attrs & stat.FILE_ATTRIBUTE_REPARSE_POINT: raise Exception(f"Refusing to delete reparse point: {filepath}") resolved_filepath = filepath.resolve() # Ensure it's a regular file if not resolved_filepath.is_file(): raise Exception(f"Target is not a regular file: {resolved_filepath}") # Perform deletion os.remove(resolved_filepath) def main(argv): write_debug_file(argv[0], "Started") msg = setup_and_check_message(argv) if msg.command < 0: sys.exit(OS_INVALID) if msg.command == ADD_COMMAND: alert = msg.alert["parameters"]["alert"] keys = [alert["rule"]["id"]] action = send_keys_and_check_message(argv, keys) if action != CONTINUE_COMMAND: if action == ABORT_COMMAND: write_debug_file(argv[0], "Aborted") sys.exit(OS_SUCCESS) else: write_debug_file(argv[0], "Invalid command") sys.exit(OS_INVALID) try: file_path = alert["data"]["virustotal"]["source"]["file"] if os.path.exists(file_path): secure_delete_file(file_path, argv[0]) write_debug_file(argv[0], json.dumps(msg.alert) + " Successfully removed threat") else: write_debug_file(argv[0], f"File does not exist: {file_path}") except OSError as error: write_debug_file(argv[0], json.dumps(msg.alert) + "Error removing threat") except Exception as e: write_debug_file(argv[0], f"{json.dumps(msg.alert)}: Error removing threat: {str(e)}") else: write_debug_file(argv[0], "Invalid command") write_debug_file(argv[0], "Ended") sys.exit(OS_SUCCESS) if __name__ == "__main__": main(sys.argv)
Convert the active response Python script
remove-threat.pyto a Windows executable application. Run the following PowerShell command as an administrator to create the executable:> pyinstaller -F \path_to_remove-threat.py
Take note of the path where
pyinstallercreatedremove-threat.exe.Move the executable file
remove-threat.exeto theC:\Program Files (x86)\ossec-agent\active-response\bindirectory.Restart the Wazuh agent to apply the changes. Run the following PowerShell command as an administrator:
> Restart-Service -Name wazuh
Wazuh server
Perform the following steps on the Wazuh server to configure the VirusTotal integration. These steps also enable and trigger the active response script whenever a suspicious file is detected.
Add the following configuration to the
/var/ossec/etc/ossec.conffile on the Wazuh server to enable the VirusTotal integration. Replace<YOUR_VIRUS_TOTAL_API_KEY>with your VirusTotal API key. This allows to trigger a VirusTotal query whenever any of the rules in the FIMsyscheckgroup are triggered:<ossec_config> <integration> <name>virustotal</name> <api_key><YOUR_VIRUS_TOTAL_API_KEY></api_key> <!-- Replace with your VirusTotal API key --> <group>syscheck</group> <alert_format>json</alert_format> </integration> </ossec_config>
Note
The free VirusTotal API rate limits requests to four per minute. If you have a premium VirusTotal API key, with a high frequency of queries allowed, you can add more rules besides these two. You can configure Wazuh to monitor more directories besides
C:\Users\<USER_NAME>\Downloads.Append the following blocks to the Wazuh server
/var/ossec/etc/ossec.conffile. This enables active response and trigger theremove-threat.exeexecutable when the VirusTotal query returns positive matches for threats:<ossec_config> <command> <name>remove-threat</name> <executable>remove-threat.exe</executable> <timeout_allowed>no</timeout_allowed> </command> <active-response> <disabled>no</disabled> <command>remove-threat</command> <location>local</location> <rules_id>87105</rules_id> </active-response> </ossec_config>
Add the following rules to the Wazuh server
/var/ossec/etc/rules/local_rules.xmlfile to alert about the active response results.<group name="virustotal,"> <rule id="100092" level="12"> <if_sid>657</if_sid> <match>Successfully removed threat</match> <description>$(parameters.program) removed threat located at $(parameters.alert.data.virustotal.source.file)</description> </rule> <rule id="100093" level="12"> <if_sid>657</if_sid> <match>Error removing threat</match> <description>Error removing threat located at $(parameters.alert.data.virustotal.source.file)</description> </rule> </group>
Restart the Wazuh manager to apply the configuration changes:
$ sudo systemctl restart wazuh-manager
Attack emulation
Follow the next steps to temporarily turn off real-time Microsoft Defender antivirus protection in Windows Security:
Click on the Start menu and type
Windows Securityto search for that app.Select the Windows Security app from results, go to Virus & threat protection, and under Virus & threat protection settings select Manage settings.
Switch Real-time protection to Off.
Download an EICAR test file to the
C:\Users\<USER_NAME>\Downloadsdirectory on the Windows endpoint.> Invoke-WebRequest -Uri https://secure.eicar.org/eicar.com.txt -OutFile eicar.txt > cp .\eicar.txt C:\Users\<USER_NAME>\Downloads
This triggers a VirusTotal query and generates an alert. In addition, the active response script automatically removes the file.
Visualize the alerts
You can visualize the alert data in the Wazuh dashboard. To do this, go to the Security events module and add the filters in the search bar to query the alerts.
Windows -
rule.id: is one of 554,100092,553,87105