threat intelligence

Operationalizing MISP Threat Feeds

Run MISP, curate feeds, and auto-generate detections for Wazuh, Sigma, and Suricata.

detection-engineeringiocmisppymispsigmasuricatathreat-feedsthreat-intelligence
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Note: This skill covers a defensive threat-intelligence platform. Handle ingested intelligence according to its Traffic Light Protocol (TLP) marking and your sharing agreements. Treat ingested IOCs as potentially sensitive.

Overview

MISP (Malware Information Sharing Platform) is the de-facto open-source threat-intelligence platform for storing, correlating, and sharing structured indicators (IOCs), events, galaxies (threat-actor/technique knowledge), and objects. Running a MISP instance is only the first step; the value comes from operationalizing it — curating high-quality feeds, suppressing false positives with warninglists, and pushing the resulting IOCs into detection tooling so intelligence actually drives blocking and alerting.

A feed in MISP is a remote source (another MISP, a CSV/freetext list, or a structured collection) that you enable and optionally cache. Caching pulls the feed's IOCs into the instance's Redis-backed cache so values can be correlated and looked up in real time (e.g., a SIEM asking "have you seen this domain?") without importing every event. Curation matters: enabling every public feed produces noise and false positives, so you select reputable feeds (CIRCL OSINT, abuse.ch, Feodo Tracker, etc.), apply warninglists (known-good ranges like RFC1918, Alexa/Tranco top sites, public DNS resolvers) to flag non-actionable indicators, and use taxonomies/tags (TLP, confidence) to scope what gets exported.

The detection-engineering payoff comes from MISP's export formats and PyMISP. MISP can render matching attributes directly as Suricata and Snort rules via the REST API, and PyMISP lets you script extraction of fresh IOCs to generate Sigma rules and Wazuh CDB lists / rules on a schedule. This skill walks the full lifecycle: feed enablement and caching, warninglist-based FP reduction, PyMISP-driven search, and automated generation of Suricata, Sigma, and Wazuh detections.

When to Use

  • Standing up or maturing a MISP instance into a feed that drives detection, not just a repository.
  • Curating and caching public/commercial threat feeds with quality controls.
  • Reducing IOC false positives with warninglists before they reach the SIEM/IDS.
  • Automating generation of Suricata/Sigma/Wazuh detections from MISP attributes.
  • Integrating MISP with a SOC so DNS/IP/hash lookups can be enriched against current intel.

Prerequisites

  • A running MISP instance (the maintained container images are the fastest path):
    git clone https://github.com/MISP/misp-docker.git
    cd misp-docker && cp template.env .env
    docker compose up -d
    # Web UI on https://localhost; default admin: admin@admin.test / admin
  • A MISP Auth Key (UI: Administration -> List Auth Keys -> Add).
  • PyMISP:
    pip install pymisp
  • Target detection tooling reachable: Suricata, a Sigma toolchain (pip install sigma-cli), and/or Wazuh manager.

Objectives

  • Enable and cache curated threat feeds in MISP.
  • Apply warninglists to suppress known-good / non-actionable indicators.
  • Authenticate and query MISP with PyMISP to pull fresh, scoped IOCs.
  • Export matching attributes as Suricata/Snort rules via the REST API.
  • Generate Sigma rules and Wazuh CDB lists from MISP attributes on a schedule.
  • Validate that generated detections load and fire in the target tooling.

MITRE ATT&CK Mapping

Technique ID Technique Name Relevance
T1589 Gather Victim Identity Information Feeds capture adversary reconnaissance indicators; operationalizing them detects/contextualizes such activity.
T1071.001 Application Layer Protocol: Web Protocols C2 domain/URL IOCs from feeds become Suricata/Sigma detections for malicious HTTP(S).
T1071.004 Application Layer Protocol: DNS Malicious-domain IOCs feed DNS-based detection (Wazuh/Suricata).
T1105 Ingress Tool Transfer File-hash IOCs from feeds detect known malicious payload delivery.

Workflow

1. Add and enable a feed

Register a reputable source and turn it on.

# add_feed.py (PyMISP) — register the CIRCL OSINT feed
from pymisp import PyMISP, MISPFeed
misp = PyMISP("https://localhost", "YOUR_AUTH_KEY", ssl=False)
feed = MISPFeed()
feed.name = "CIRCL OSINT Feed"
feed.provider = "CIRCL"
feed.url = "https://www.circl.lu/doc/misp/feed-osint"
feed.source_format = "misp"
feed.input_source = "network"
feed.enabled = True
print(misp.add_feed(feed, pythonify=True))

2. Cache enabled feeds for real-time correlation

Caching loads feed IOCs into Redis so lookups are instant.

# Cache all enabled feeds (equivalent to "Enable caching" in the UI)
print(misp.cache_all_feeds())
# Or fetch a single feed's events into the instance by feed id:
print(misp.fetch_feed(1))

3. Enable warninglists to reduce false positives

Turn on known-good lists so non-actionable indicators are flagged.

# Enable the common false-positive warninglists
for wl in misp.warninglists(pythonify=True):
    if wl.name in ("List of RFC 1918 CIDR blocks",
                   "Top 1000 website from Cisco Umbrella",
                   "List of known public DNS resolvers"):
        misp.toggle_warninglist(warninglist_id=wl.id, force_enable=True)

4. Authenticate and search for fresh IOCs

Pull recently published, TLP-scoped, to-IDS attributes only.

from pymisp import PyMISP
misp = PyMISP("https://localhost", "YOUR_AUTH_KEY", ssl=False)
# Only export attributes flagged to_ids=1, published, last 7 days, IP/domain/url/hash
attrs = misp.search(
    controller="attributes",
    type_attribute=["ip-dst", "domain", "url", "md5", "sha256"],
    to_ids=True, published=True, last="7d",
    enforce_warninglist=True,   # drop warninglisted (known-good) values
    pythonify=True,
)
print(f"{len(attrs)} actionable IOCs")

5. Export Suricata/Snort rules via the REST API

MISP renders matching attributes directly as IDS rules.

# Suricata rules for all to_ids network IOCs (NIDS export)
curl -s -k -H "Authorization: YOUR_AUTH_KEY" -H "Accept: application/json" \
  "https://localhost/attributes/restSearch/returnFormat:suricata/to_ids:1/type:domain%7Cip-dst%7Curl" \
  -o misp_suricata.rules
 
# Snort equivalent
curl -s -k -H "Authorization: YOUR_AUTH_KEY" -H "Accept: application/json" \
  "https://localhost/attributes/restSearch/returnFormat:snort/to_ids:1" -o misp_snort.rules

6. Deploy the Suricata rules

Load and reload.

cp misp_suricata.rules /etc/suricata/rules/
suricata -T -c /etc/suricata/suricata.yaml   # validate config + rules
suricatasc -c reload-rules                    # hot reload

7. Generate Wazuh CDB lists from IOCs

Convert MISP domains/IPs into a Wazuh CDB lookup list referenced by a rule.

# Build a Wazuh CDB list (key:value per line) from the searched attributes
with open("misp_iocs.cdb", "w") as fh:
    for a in attrs:
        if a.type in ("domain", "ip-dst"):
            fh.write(f"{a.value}:\n")
# On the Wazuh manager: place under /var/ossec/etc/lists/, reference in ossec.conf:
#   <list>etc/lists/misp_iocs</list>
# then compile and restart:
#   /var/ossec/bin/wazuh-control restart

8. Generate Sigma rules from MISP intelligence

Emit a Sigma rule matching the exported domains.

import yaml
domains = [a.value for a in attrs if a.type == "domain"]
sigma = {
    "title": "MISP feed malicious domain contact",
    "status": "experimental",
    "logsource": {"category": "dns"},
    "detection": {"selection": {"query|contains": domains}, "condition": "selection"},
    "level": "high",
    "tags": ["attack.command_and_control", "attack.t1071.004"],
}
with open("misp_domains.yml", "w") as fh:
    yaml.safe_dump(sigma, fh, sort_keys=False)

9. Convert and deploy Sigma to your SIEM backend

Use sigma-cli to compile to the target backend (Splunk, Elastic, etc.).

sigma convert -t splunk -p splunk_windows misp_domains.yml > misp_domains.spl
sigma convert -t elasticsearch misp_domains.yml > misp_domains.eql

10. Schedule the pipeline and run the bundled helper

agent.py searches MISP and writes Suricata/Sigma/Wazuh artifacts in one pass; schedule it via cron.

python scripts/agent.py --url https://localhost --key YOUR_AUTH_KEY \
  --last 7d --outdir ./detections --insecure
# crontab: 0 * * * * /usr/bin/python /path/scripts/agent.py ... >> /var/log/misp_pipeline.log 2>&1

Tools and Resources

Tool Purpose Source
MISP Threat-intelligence platform https://www.misp-project.org/
misp-docker Maintained container deployment https://github.com/MISP/misp-docker
PyMISP Python client for the MISP REST API https://github.com/MISP/PyMISP
MISP warninglists Known-good lists for FP reduction https://github.com/MISP/misp-warninglists
MISP automation docs REST API + export formats https://www.circl.lu/doc/misp/automation/
sigma-cli Sigma rule conversion https://github.com/SigmaHQ/sigma-cli
Wazuh CDB lists IOC lookup lists for Wazuh https://documentation.wazuh.com/

Validation Criteria

  • MISP instance reachable and an Auth Key created.
  • At least one reputable feed enabled and cached.
  • Relevant warninglists enabled and enforce_warninglist applied to searches.
  • PyMISP search returns scoped, to_ids, non-warninglisted IOCs.
  • Suricata/Snort rules exported via REST and validated with suricata -T.
  • Wazuh CDB list generated and loaded by the manager.
  • Sigma rule generated and converted to the SIEM backend.
  • Generated detections confirmed to load (and fire on a test IOC).
  • Pipeline scheduled and logging successfully.
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 2

api-reference.md2.4 KB

MISP / PyMISP API Reference

PyMISP client

Install: pip install pymisp

from pymisp import PyMISP
misp = PyMISP("https://misp.example", "AUTH_KEY", ssl=True)

Feed management

Method Description
misp.feeds(pythonify=True) List configured feeds.
misp.add_feed(MISPFeed, pythonify=True) Register a new feed.
misp.enable_feed(feed_id) / misp.disable_feed(feed_id) Toggle a feed.
misp.fetch_feed(feed_id) Pull a feed's events into the instance.
misp.cache_feeds(scope) / misp.cache_all_feeds() Cache feed IOCs into Redis for correlation.

Searching attributes/events

Call Description
misp.search(controller="attributes", ...) Search attributes (IOCs).
type_attribute=[...] Filter by attribute type (ip-dst, domain, url, md5, sha256).
to_ids=True Only IDS-flagged (actionable) attributes.
published=True Only attributes in published events.
last="7d" Published within a time window.
enforce_warninglist=True Drop values matching enabled warninglists.
tags=["tlp:white"] Filter by tag/taxonomy.

Warninglists

Method Description
misp.warninglists(pythonify=True) List warninglists.
misp.toggle_warninglist(warninglist_id=ID, force_enable=True) Enable a warninglist.

REST restSearch return formats

Endpoint: POST/GET https://<misp>/attributes/restSearch/ with header Authorization: <AUTH_KEY>.

Path-style modifiers: returnFormat:<fmt>/to_ids:1/type:<a%7Cb%7Cc>/last:7d/published:1

returnFormat Output
json Native JSON.
suricata Suricata IDS rules.
snort Snort IDS rules.
csv CSV of attributes.
text Plain value list (one per line).
stix2 STIX 2.1 bundle.

Example:

curl -s -k -H "Authorization: AUTH_KEY" -H "Accept: application/json" \
  "https://misp/attributes/restSearch/returnFormat:suricata/to_ids:1/type:domain%7Cip-dst" \
  -o misp.rules

Downstream deployment

Tool Command
Suricata validate suricata -T -c /etc/suricata/suricata.yaml
Suricata reload suricatasc -c reload-rules
Wazuh restart /var/ossec/bin/wazuh-control restart
Sigma convert sigma convert -t splunk -p splunk_windows rule.yml
standards.md1.5 KB

Standards and Framework Mapping

MITRE ATT&CK

ID Name Rationale
T1589 Gather Victim Identity Information Feeds catalog adversary reconnaissance/identity indicators; operationalizing them detects and contextualizes such activity.
T1071.001 Application Layer Protocol: Web Protocols C2 domain/URL IOCs become Suricata/Sigma web detections.
T1071.004 Application Layer Protocol: DNS Malicious-domain IOCs drive DNS-based Wazuh/Suricata detection.
T1105 Ingress Tool Transfer File-hash IOCs detect known malicious payload delivery.

NIST Cybersecurity Framework 2.0

ID Name Rationale
ID.RA-02 Cyber threat intelligence is received from information sharing forums and sources MISP feed curation, caching, and operationalization is the direct implementation of receiving and applying shared cyber threat intelligence.

Supporting Standards and References

  • Traffic Light Protocol (TLP 2.0). Governs how ingested/shared intelligence may be redistributed; enforced via MISP taxonomies.
  • STIX 2.1 / TAXII 2.1. Interoperable representation/transport of CTI that MISP can import/export.
  • NIST SP 800-150 — Guide to Cyber Threat Information Sharing. Frames the feed-ingestion and sharing lifecycle this skill operationalizes.
  • SigmaHQ specification. Detection rule format generated from MISP attributes.
  • MISP automation & REST return formats: https://www.circl.lu/doc/misp/automation/
  • PyMISP documentation: https://pymisp.readthedocs.io/

Scripts 1

agent.py4.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
MISP feed operationalization helper.

Connects to a MISP instance with PyMISP, searches for fresh, actionable
(to_ids, published, warninglist-enforced) IOCs, and writes detection artifacts:
  - Suricata rules (via REST returnFormat:suricata)
  - a Wazuh CDB list (domains + IPs)
  - a Sigma rule (DNS contact to malicious domains)

Defensive threat-intelligence use. Handle exported IOCs per their TLP marking.
"""
import argparse
import json
import sys
from pathlib import Path

try:
    from pymisp import PyMISP
except ImportError:
    print("[error] pymisp not installed: pip install pymisp", file=sys.stderr)
    sys.exit(1)

import urllib.request
import ssl

IOC_TYPES = ["ip-dst", "ip-src", "domain", "hostname", "url", "md5", "sha256"]


def connect(url: str, key: str, insecure: bool) -> PyMISP:
    return PyMISP(url, key, ssl=not insecure)


def search_iocs(misp: PyMISP, last: str) -> list:
    try:
        return misp.search(
            controller="attributes",
            type_attribute=IOC_TYPES,
            to_ids=True,
            published=True,
            last=last,
            enforce_warninglist=True,
            pythonify=True,
        )
    except Exception as exc:  # noqa: BLE001
        print(f"[error] MISP search failed: {exc}", file=sys.stderr)
        return []


def export_suricata(url: str, key: str, last: str, insecure: bool, out: Path) -> bool:
    endpoint = (
        f"{url.rstrip('/')}/attributes/restSearch/returnFormat:suricata/"
        f"to_ids:1/type:domain%7Cip-dst%7Curl/last:{last}"
    )
    ctx = ssl.create_default_context()
    if insecure:
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
    req = urllib.request.Request(
        endpoint, headers={"Authorization": key, "Accept": "application/json"}
    )
    try:
        with urllib.request.urlopen(req, context=ctx, timeout=120) as resp:
            out.write_bytes(resp.read())
        return True
    except Exception as exc:  # noqa: BLE001
        print(f"[warn] suricata export failed: {exc}", file=sys.stderr)
        return False


def write_wazuh_cdb(attrs: list, out: Path) -> int:
    n = 0
    with out.open("w", encoding="utf-8") as fh:
        for a in attrs:
            if a.type in ("domain", "hostname", "ip-dst", "ip-src"):
                fh.write(f"{a.value}:\n")
                n += 1
    return n


def write_sigma(attrs: list, out: Path) -> int:
    domains = sorted({a.value for a in attrs if a.type in ("domain", "hostname")})
    if not domains:
        return 0
    rule = {
        "title": "MISP feed malicious domain contact",
        "status": "experimental",
        "description": "DNS query to a domain flagged malicious in MISP feeds",
        "logsource": {"category": "dns"},
        "detection": {"selection": {"query|contains": domains}, "condition": "selection"},
        "level": "high",
        "tags": ["attack.command_and_control", "attack.t1071.004"],
    }
    try:
        import yaml
        out.write_text("", encoding="utf-8")
        with out.open("w", encoding="utf-8") as fh:
            yaml.safe_dump(rule, fh, sort_keys=False, default_flow_style=False)
    except ImportError:
        out.write_text(json.dumps(rule, indent=2), encoding="utf-8")
        print("[warn] pyyaml missing; wrote Sigma rule as JSON", file=sys.stderr)
    return len(domains)


def main() -> int:
    ap = argparse.ArgumentParser(description="MISP feed -> detections pipeline")
    ap.add_argument("--url", required=True, help="MISP base URL")
    ap.add_argument("--key", required=True, help="MISP Auth Key")
    ap.add_argument("--last", default="7d", help="time window (e.g. 7d, 24h)")
    ap.add_argument("--outdir", default="./detections")
    ap.add_argument("--insecure", action="store_true", help="skip TLS verification (lab)")
    args = ap.parse_args()

    outdir = Path(args.outdir)
    outdir.mkdir(parents=True, exist_ok=True)

    try:
        misp = connect(args.url, args.key, args.insecure)
    except Exception as exc:  # noqa: BLE001
        print(f"[error] cannot connect to MISP: {exc}", file=sys.stderr)
        return 2

    attrs = search_iocs(misp, args.last)
    print(f"[+] {len(attrs)} actionable IOC(s) retrieved (last {args.last})")

    suricata_ok = export_suricata(
        args.url, args.key, args.last, args.insecure, outdir / "misp_suricata.rules"
    )
    if suricata_ok:
        print(f"[+] Suricata rules -> {outdir / 'misp_suricata.rules'}")

    cdb_n = write_wazuh_cdb(attrs, outdir / "misp_iocs.cdb")
    print(f"[+] Wazuh CDB list ({cdb_n} entries) -> {outdir / 'misp_iocs.cdb'}")

    sig_n = write_sigma(attrs, outdir / "misp_domains.yml")
    print(f"[+] Sigma rule ({sig_n} domains) -> {outdir / 'misp_domains.yml'}")

    return 0


if __name__ == "__main__":
    sys.exit(main())
Keep exploring