- Home
- Integrations
- Wazuh
Setup guide
Wazuh threat intelligence as CDB lists of malicious IPs and domains, refreshed by cron
No credit card required · Free API key
On this page
On this page08
What you get
A CDB list is an on-disk database with no documented size limit. Each list below becomes one CDB list, named in its row.
| List | Entries | Rebuilt | Plans |
|---|---|---|---|
blocklist-ips-critical.txtDefault: ismalicious-ips, for srcip, dstip or Suricata’s src_ip and dest_ip. IPs listed by 6 or more threat sources, or 3 or more with a critical category. | about 89,000 | every 12 h | Basic, Pro, and Enterprise |
blocklist-domains-c2.txtismalicious-domains-c2: command-and-control domains reported by C2 trackers. | about 22,000 | every 12 h | Basic, Pro, and Enterprise |
blocklist-domains-ransomware.txtismalicious-domains-ransomware: domains in the ransomware category, whatever their level. | about 3,600 | every 12 h | Basic, Pro, and Enterprise |
Entries rounded from the build of ; each list is rebuilt every 12 hours. Today’s counts are public and need no key.
What each plan receives
- FreeFree account, or no key: the first 10% of each list, marked
X-Blocklist-Version: lite. - Basic, Pro, and EnterpriseBasic, Pro, and Enterprise: every list in full.
- Pro and EnterpriseTAXII 2.1 collections, for platforms that read STIX indicators: Pro and Enterprise.
GET https:/
Full list or 10% sample
| Field | Basic, Pro, and Enterprise | Free |
|---|---|---|
X-Blocklist-Version: | full | lite |
X-Blocklist-Percentage: | 100 | 10 |
Total entries: | <COUNT> | <COUNT> |
First lines of the file
# IsMalicious.com Blocklist - IPs (Critical)
# Format: Plain
# Generated: <BUILD_TIME>
# Total entries: <COUNT>
# Update frequency: every 12 hours
# Category: All
# Threat level: Critical
# Website: https://ismalicious.com
# © <YEAR> IsMalicious (compilation). Licensed to the downloading account under https://ismalicious.com/terms; redistribution of the compilation prohibited. Third-party entries remain under their providers' licences — see https://ismalicious.com/sources.
#Values in angle brackets are set by each build.
Prerequisites
- Wazuh manager 4.3 or later, checked against the 4.14 documentation (before 4.3 the service user was ossec). Lists reload without a restart from 4.13.
- Root on the manager, the master node in a cluster, with
curlandawk. - An isMalicious API key and secret, from Account › API access.
- Outbound HTTPS (TCP 443) over IPv4 from the Wazuh manager to
api.ismalicious.com, which has no IPv6 address.
Set it up
Copy your API key and secret
Open Account › API access and copy the API Key and the API Secret. The full lists need a Basic, Pro, or Enterprise plan; with a Free key the script receives the 10% sample, and refuses it.Store the credentials on the manager
Create/etc/ismalicious/netrcroot-only with the commands below, then write the three lines into it with an editor: typed in a shell, they would land in its history. curl reads the key and the secret from the file, so they never appear on a command line.Create the file, root-onlysh install -d -m 700 /etc/ismalicious [ -e /etc/ismalicious/netrc ] || install -m 600 /dev/null /etc/ismalicious/netrc chmod 600 /etc/ismalicious/netrc # Then write the three lines below into it with an editor, unless it holds them already./ etc/ ismalicious/ netrcnetrc machine api.ismalicious.com login <API_KEY> password <API_SECRET>Install the conversion script and run it once
- Save the script as
/, mode 700, and run it.usr/ local/ sbin/ ismalicious-wazuh-lists. sh - It refuses an error, a body that is not a list, the 10% sample and a file whose entry count differs from its header.
- Wazuh skips every line without a colon, which is every line of a plain list, so the script writes each entry as a
key:line, quotes IPv6 keys and writes each one twice, compressed and in full, since Suricata’s EVE writes addresses in full. - It writes
/, owned byvar/ ossec/ etc/ lists/ ismalicious-* wazuh:wazuh, mode 660. - A list that fails a check stops the run: it and the lists after it keep their previous copy.
/ usr/ local/ sbin/ ismalicious-wazuh-lists. shsh #!/bin/sh # Download isMalicious lists and write them as Wazuh CDB lists. A list that # fails a check stops the run: it and the lists after it keep their copy. set -eu LISTS=/var/ossec/etc/lists NETRC=/etc/ismalicious/netrc RAW=$(mktemp) NEW= trap 'rm -f "$RAW" $NEW' EXIT # fetch_list LIST OUT: download one list to OUT and check it. # On any failure OUT is removed and the function returns 1. fetch_list() { if ! code=$(curl --silent --show-error --fail --netrc-file "$NETRC" \ --proto '=https' --max-time 300 --retry 2 \ --output "$2" --write-out '%{http_code}' "https://api.ismalicious.com/blocklist/download/$1"); then rm -f "$2"; echo "$1: download failed" >&2; return 1 fi if [ "$code" != 200 ]; then rm -f "$2"; echo "$1: HTTP $code" >&2; return 1 fi if ! head -n 1 "$2" | grep -q '^[#!] IsMalicious.com Blocklist'; then rm -f "$2"; echo "$1: not an isMalicious list" >&2; return 1 fi if grep -q 'Lite Version' "$2"; then rm -f "$2"; echo "$1: lite list received, check the API key and the plan" >&2 return 1 fi # Every entry ends with a newline, and one header counts them: refuse a # cut, doubled or empty file. if [ -n "$(tail -c 1 "$2")" ]; then rm -f "$2"; echo "$1: cut short, no final newline" >&2; return 1 fi if [ "$(grep -c '^[#!] Total entries:' "$2")" != 1 ]; then rm -f "$2"; echo "$1: not one Total entries line" >&2; return 1 fi total=$(sed -n 's/^[#!] Total entries: \([0-9,]*\)$/\1/p' "$2" | tr -d ,) got=$(grep -c '^[^#!]' "$2" || true) if [ -z "$total" ] || [ "$total" = 0 ] || [ "$got" != "$total" ]; then rm -f "$2"; echo "$1: $got entries, the header says ${total:-none}" >&2 return 1 fi } # convert <ip|domain> FILE: one CDB key per entry. IPv6 keys are quoted and # written twice, compressed and in full, since Suricata's EVE writes them in full. convert() { case "$1" in ip) /var/ossec/framework/python/bin/python3 -c ' import ipaddress, sys for line in open(sys.argv[1]): f = line.split() if not f or f[0][0] in "#!": continue a = ipaddress.ip_address(f[0]) if a.version == 4: print(f"{a}:") else: print(f"\"{a.compressed}\":\n\"{a.exploded}\":") ' "$2" ;; domain) awk '!/^[#!]/ && NF { printf "%s:\n", $1 }' "$2" ;; esac } # load <isMalicious file> <CDB list name> <ip|domain> load() { fetch_list "$1" "$RAW" # .swp: not the compiler's own <list>.tmp, and never synced by the cluster. NEW=$(mktemp --suffix=.swp "$LISTS/.$2.XXXXXX") convert "$3" "$RAW" > "$NEW" chown wazuh:wazuh "$NEW" chmod 660 "$NEW" mv -f "$NEW" "$LISTS/$2" NEW= } load blocklist-ips-critical.txt ismalicious-ips ip load blocklist-domains-c2.txt ismalicious-domains-c2 domain load blocklist-domains-ransomware.txt ismalicious-domains-ransomware domain- Save the script as
Declare the lists in ossec.conf
Add onelistline per list inside therulesetblock of/var/ossec/etc/ossec.conf. In a cluster, do it on every node: the master syncsetc/lists/to the workers, but notossec.conf./ var/ ossec/ etc/ ossec. confxml <!-- Inside the existing <ruleset> block, on every node: --> <list>etc/lists/ismalicious-ips</list> <list>etc/lists/ismalicious-domains-c2</list> <list>etc/lists/ismalicious-domains-ransomware</list>Add the rules
Add the rules to/, with rule IDs free in your ruleset. IP fields need thevar/ ossec/ etc/ rules/ local_rules. xml address_match_keylookup. Rule 100200 follows Wazuh’s documented IP reputation pattern for web access logs; rules 100201 and 100202 check the destination and the source of Suricata EVE alerts. For a domain list, use thematch_keylookup on the decoded field that holds the queried name, whichwazuh-logtestshows./ var/ ossec/ etc/ rules/ local_rules. xmlxml <group name="ismalicious,"> <!-- Web access logs: Wazuh's documented IP reputation pattern. --> <rule id="100200" level="10"> <if_group>web|attack|attacks</if_group> <list field="srcip" lookup="address_match_key">etc/lists/ismalicious-ips</list> <description>isMalicious: source IP $(srcip) is on the critical IP list.</description> </rule> <!-- Suricata EVE alerts (rule 86601). Dynamic fields src_ip / dest_ip. --> <rule id="100201" level="10"> <if_sid>86601</if_sid> <list field="dest_ip" lookup="address_match_key">etc/lists/ismalicious-ips</list> <description>isMalicious: Suricata alert towards listed IP $(dest_ip).</description> </rule> <rule id="100202" level="10"> <if_sid>86601</if_sid> <list field="src_ip" lookup="address_match_key">etc/lists/ismalicious-ips</list> <description>isMalicious: Suricata alert from listed IP $(src_ip).</description> </rule> </group>Load the lists
- Restart the manager with
systemctl, on any version.restart wazuh-manager - From 4.13, the reload script loads lists without a restart: it logs in to the Wazuh API with credentials read from
/root/.wazuh-api.netrc(mode 600; the API user needsmanager:readandmanager:restart, orcluster:*), keeps the token in a mode-600 curl config, callsPUT, and exits 1 when the answer reports a failed item or a list that did not load: the endpoint answers 200 either way./ manager/ analysisd/ reload - In a cluster, replace
manager/withcluster/in the script:manager/reloads the master only, and workers on 4.14.2 ignore it (fixed in 4.14.3). - Before 4.13, restart every node once the master has synced
etc/lists/. - The
-koption accepts the API’s own certificate on localhost, as in Wazuh’s API guide.
/ root/ . wazuh-api. netrcnetrc machine localhost login <WAZUH_API_USER> password <WAZUH_API_PASSWORD>/ usr/ local/ sbin/ ismalicious-wazuh-reload. shsh #!/bin/sh # Wazuh 4.13 and later: hot reload, no restart. In a cluster, replace # manager/ with cluster/ below: manager/ reloads the master only. set -eu API=https://localhost:55000 CFG=$(mktemp) trap 'rm -f "$CFG"' EXIT TOKEN=$(curl -fsS -k --netrc-file /root/.wazuh-api.netrc -X POST \ "$API/security/user/authenticate?raw=true") # The token goes in a mode-600 curl config, not on the command line. printf 'header = "Authorization: Bearer %s"\n' "$TOKEN" > "$CFG" R=$(curl -fsS -k -K "$CFG" -X PUT "$API/manager/analysisd/reload?wait_for_complete=true") echo "$R" case "$R" in *'"total_failed_items": 0'*) ;; *) echo "Wazuh reload failed" >&2; exit 1 ;; esac case "$R" in *'(7616)'*) echo "a CDB list did not load: its rules are ignored" >&2; exit 1 ;; esac # Older than 4.13, replace the calls above with: # systemctl restart wazuh-manager- Restart the manager with
Schedule both scripts
Run the download, then the reload, every 12 hours from/: the lists are rebuilt every 12 hours. A failed download stops the run before the reload, and the previous lists stay in place.etc/ cron. d/ ismalicious-wazuh / etc/ cron. d/ ismalicious-wazuhcron 17 3,15 * * * root /usr/local/sbin/ismalicious-wazuh-lists.sh && /usr/local/sbin/ismalicious-wazuh-reload.sh
Verify it works
wc -lon a converted list is close to itscountin /blocklist/stats, andheadshowskey:lines, IPv6 keys in quotes.- After a restart or a reload, the list’s
.cdbfile carries a fresh timestamp. wazuh-logtestshows rule 100200, 100201 or 100202 in phase 3 for a log line that holds a listed IP. It re-reads the files and compiles the lists itself, so it does not prove the running engine reloaded:ossec.logdoes, withwazuh-analysisd: INFO: Ruleset.reloaded successfully - Alerts appear in the dashboard under the rule IDs or the
ismaliciousgroup, and inalerts.json. - The first lines of a direct download show
Total entrieswithoutLite Version.
# The converted list and its compiled file:
wc -l /var/ossec/etc/lists/ismalicious-ips
head -3 /var/ossec/etc/lists/ismalicious-ips
ls -l /var/ossec/etc/lists/ismalicious-ips.cdb
# The running engine took the new ruleset:
grep 'Ruleset reloaded successfully' /var/ossec/logs/ossec.log | tail -1
# Paste a log line that holds a listed IP; phase 3 shows the rule:
/var/ossec/bin/wazuh-logtest
# Alerts raised by the rules:
grep ismalicious /var/ossec/logs/alerts/alerts.json
# As root (the netrc file is root-only): the key reaches isMalicious when
# Total entries has no "Lite Version".
sudo curl -sS --netrc-file /etc/ismalicious/netrc \
https://api.ismalicious.com/blocklist/download/blocklist-ips-critical.txt | head -12Troubleshooting
The script stops with a 401
The key or the secret in the netrc file is wrong or incomplete: curl exits with code 22 and the previous lists stay in place. The response body reads “Blocklist not found or empty”; trust the status code. Regenerating the key in Account › API access invalidates the old pair.
“lite list received”
No credential reached the server, or the plan is Free or lapsed: past due, unpaid, canceled, incomplete or paused counts as Free. Check the netrc entry and the plan.
“entries, the header says”, “cut short, no final newline” or “not one Total entries line”
The file was empty, cut short or not one of the lists, as a proxy or captive portal answers. The run stops: that list and the ones after it keep their previous copy, and the reload does not run.
Timeouts or a 502
A download that takes longer than 30 seconds on our side ends in a timeout or a 502: use a tier or category list from the table.
503 with Retry-After: 600
A list is being built. The next run picks it up.
A rule never fires
The list is missing from ossec.conf, or the manager was neither restarted nor reloaded: ossec.log then shows (7616): List A (7612): Rule line means the rule IDs clash: pick free ones. Or the field name differs: check the decoded names in wazuh-logtest. IP fields need address_match_key, and matching is exact, so case and IPv6 notation must match.
Junk keys or an empty list
Never declare a raw download as a CDB list: lines without a colon are skipped, and a # line that holds one becomes a key. Use the script’s conversion.
curl exits with code 60
Update the CA bundle, check the clock, and exempt api.ismalicious.com from TLS inspection.
Workers do not match
Their own ossec.conf lacks the list lines, which the cluster does not sync, or they still hold the old lists: PUT reloads the master only. Use cluster/ in the reload script (and 4.14.3 or later), or restart them.
Limits
- Wazuh has no feed integration for isMalicious and downloads nothing itself: the update path is the cron script, then a restart or a reload.
- CDB lookups are exact. No CIDR or wildcard lists are served, and IPv4 prefix matching only applies to keys ending in a dot, which the lists never hold.
- An IPv6 entry matches only the exact strings listed: the script writes each IPv6 key compressed and in full, the forms decoders such as Suricata’s EVE use; any other notation does not match.
- A domain matches only the exact name in the event: a listed
evil.exampledoes not matchwww.evil.example. No domain rule is published here, because the field that holds a DNS query depends on the decoder. - The hash lists are not covered: their size is not published, and their download has not been checked against the api host’s 30-second window.
- The 10% sample is the first tenth of an unsorted file, not the riskiest tenth: the script refuses it.
Questions
Can Wazuh download a blocklist by itself?
No. Wazuh reads CDB lists from local files declared in ossec.conf. A cron script on the manager downloads the isMalicious lists with curl, converts them, and loads them.
Why do the lists need converting?
A CDB list needs a colon after each key, and Wazuh skips every line without one, which is every line of a plain list. The script writes key: lines, quotes IPv6 keys, and drops the header lines.
Does each update need a manager restart?
Not from Wazuh 4.13, which reloads rules, decoders and CDB lists through the analysisd reload endpoint. Before 4.13, restart wazuh-manager after each update.
How do the lists reach the workers in a cluster?
The master syncs etc/lists/ to the workers, but the cluster does not sync ossec.conf, so declare each list in ossec.conf on every node. From 4.13, reload with PUT , which reaches every node (4.14.3 or later); before, restart every node.
Related
Lists as lookups, ES threat lists, HEC push
TAXII 2.1 collections for indicator match rules
Threat IPs and domains as Suricata datasets
Threat IPs and domains as QRadar reference sets
TAXII 2.1 indicators into Sentinel
Every list, level and category
TAXII 2.1 collections for SIEMs and TIPs
Authentication, endpoints and limits
Check one address before you block it
Get Started
Ready to get started?
No credit card required · Free API key