MISP Threat Intelligence Platform: Self-Hosted IOC Sharing and Analysis
When a suspicious IP appears in your firewall logs, the first question your team asks is: has anyone else seen this? Your SIEM can correlate events, your IDS can match signatures, but neither one has an answer for "is this indicator known malicious, and what campaign is it tied to?" That answer lives in a threat intelligence platform.
MISP (Malware Information Sharing Platform and Threat Sharing) is the industry-standard open-source TIP. Developed by CIRCL (Computer Incident Response Center Luxembourg) and trusted by national CERTs, financial institutions, and MSSPs worldwide, it provides a structured data model for indicators of compromise (IOCs), attribution context, and bidirectional sharing via STIX/TAXII. Unlike commercial TIPs that cost thousands per year, MISP runs on a single VM or Docker host and connects to a global ecosystem of free threat feeds out of the box.
By the end of this project you will have a production-grade MISP instance running in Docker, five live threat feeds ingesting automatically, IOC events you can query and export, and a Python script that enriches any indicator in seconds.
Project Overview
What We're Building
┌─────────────────────────────────────────────────────────────────────┐
│ MISP Threat Intelligence Stack │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ External Feeds ──► MISP Core (PHP/Apache) ──► Analyst Web UI │
│ (CIRCL, AlienVault, │ │
│ Botvrij, etc.) │ │
│ ┌───────┴────────┐ │
│ ▼ ▼ │
│ MariaDB Redis │
│ (event storage) (background jobs) │
│ │ │
│ MISP Modules │
│ (enrichment, export) │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ STIX export PyMISP API Wazuh/SIEM │
│ (threat sharing) (automation) (IOC enrichment) │
│ │
└─────────────────────────────────────────────────────────────────────┘What You'll Be Able to Do
- Ingest live feeds: Pull IOCs from CIRCL, AlienVault OTX, Botvrij, and 30+ other MISP-format feeds automatically
- Correlate indicators: Find every event that shares an IP, domain, or hash across all imported intelligence
- Enrich alerts: Query MISP from Python to check if any indicator in a security alert is known malicious
- Export to standards: Generate STIX 2.1 bundles and TAXII 2.1 collections for tool integration
- Build a sharing community: Create organizations and sharing groups to exchange intelligence with partners
Prerequisites
- Docker 24+ and Docker Compose v2 on a Linux host
- 4 GB RAM minimum (8 GB recommended for active feeds)
- 20 GB free disk for events and feed data
- A hostname or static IP for the MISP server
- Outbound HTTPS access for feed downloads
- Python 3.10+ on your analyst workstation
Part 1: Deploy MISP with Docker
Step 1: Clone the Official MISP Docker Repository
The MISP project maintains the canonical Docker setup at MISP/misp-docker. It uses a template.env approach so you can pull updates cleanly without overwriting your configuration.
git clone https://github.com/MISP/misp-docker.git
cd misp-docker
# Copy the template — never commit your .env
cp template.env .envStep 2: Configure Your Environment
Open .env and set these critical values:
# .env — key variables to customize
# Base URL — must match how you'll access MISP (no trailing slash)
MISP_BASEURL=https://misp.lab.internal
# Database credentials
MYSQL_ROOT_PASSWORD=changeme_root_password
MYSQL_PASSWORD=changeme_misp_password
# Admin account
MISP_ADMIN_EMAIL=admin@lab.internal
MISP_ADMIN_PASSPHRASE=changeme_admin_password
# Optional: set timezone
PHP_TIMEZONE=America/Edmonton
# MISP image tag (use latest stable)
MISP_TAG=latestSecurity note: Use long random passwords here. MISP stores sensitive IOC data — a weak admin password is a significant risk.
Step 3: Review the Docker Compose File
The official docker-compose.yml wires together MISP core, MariaDB, and Redis. Inspect the defaults:
cat docker-compose.ymlKey services you'll see:
| Service | Image | Role |
|---|---|---|
misp | ghcr.io/misp/misp-docker/misp-core | PHP application + Apache |
misp-modules | ghcr.io/misp/misp-docker/misp-modules | Enrichment and export modules |
db | mysql:8.0 | Event and attribute storage |
redis | redis:7-alpine | Background job queue, caching |
If you're running behind a reverse proxy like Traefik or Nginx, comment out the port mapping for 443 and set MISP_BASEURL to your proxy's DNS name. Otherwise, expose ports 80 and 443 directly.
Step 4: Start MISP
docker compose pull
docker compose up -dFirst boot takes 3–7 minutes. MISP bootstraps the database schema, loads default taxonomies, galaxy clusters, and warning lists. Watch the initialization:
# Follow the MISP container logs
docker compose logs -f misp
# Wait until you see this line:
# "MISP is ready. Login: admin@admin.test / admin"Check that all containers are healthy:
docker compose psExpected output:
NAME STATUS PORTS
misp Up (healthy) 0.0.0.0:443->443/tcp
misp-modules Up (healthy)
db Up (healthy)
redis Up (healthy)
Part 2: Initial Configuration
Step 5: First Login and Admin Setup
Open https://misp.lab.internal (or your configured URL) in a browser. Accept the self-signed certificate warning for now.
Default credentials: admin@admin.test / admin
You'll be forced to change the password on first login. Use the password you set in .env.
Next, update your organization details:
- Navigate to Administration → My Organization
- Set your Organisation Name (e.g.,
CosmicBytez Lab) - Set a UUID — click Generate UUID if blank
- Save
Then harden the admin email to match your .env:
- Administration → List Users → click the admin user
- Change Email to your configured admin email
- Save
Step 6: Generate an API Key
Every automation script and integration uses an API key, not your password.
- Click your username in the top-right → My Profile
- Scroll to Auth keys → click Add authentication key
- Set a comment like
PyMISP-laband click Submit - Copy the key now — it will not be shown again
Save it somewhere secure:
export MISP_KEY="your_api_key_here"
export MISP_URL="https://misp.lab.internal"Step 7: Configure Server Settings
Navigate to Administration → Server Settings & Maintenance:
- MISP.baseurl → confirm it matches your
.envvalue - MISP.live → set to
true(enables feed fetching and background workers) - Plugin.Enrichment_services_enable →
true(enables MISP modules)
Click Diagnostics from the same menu to verify all workers are running green. If Redis workers show errors, restart the stack:
docker compose restart mispPart 3: Threat Feeds
Step 8: Load Default Feed Metadata
MISP ships with metadata for 30+ well-known public feeds but does not enable them by default — you choose which to pull.
- Navigate to Sync Actions → List Feeds
- Click Load default feed metadata at the top of the page
- You'll see feeds appear in the table
Enable and fetch the highest-value free feeds:
| Feed Name | Format | Source |
|---|---|---|
| CIRCL OSINT Feed | MISP | CIRCL |
| Botvrij.eu IDS Rule Feed | MISP | Botvrij |
| ESET Export | MISP | ESET |
| CyberCure.ai Feed | CSV | CyberCure |
| abuse.ch URLhaus | CSV | abuse.ch |
For each feed you want to enable:
- Click the toggle in the Enabled column to turn it on
- Click the fetch icon (cloud/download arrow) to pull it now
The initial fetch for CIRCL can take several minutes as it downloads historical events.
Step 9: Schedule Automatic Feed Updates
Manual fetching is fine for testing but production deployments need automation. MISP's built-in scheduler handles this:
- Administration → Scheduled Tasks
- Find fetchFeeds in the list
- Set Timer to
3600(every hour) or86400(daily, appropriate for most feeds) - Enable the task
Confirm feeds are caching locally:
# Check feed storage inside the container
docker exec misp-docker-misp-1 ls /var/www/MISP/app/files/feeds/Part 4: Working with Events and IOCs
Step 10: Create Your First Threat Event
A MISP Event is the core data object — it represents a threat cluster, campaign, or incident, with attributes (IOCs) attached.
- Navigate to Add Event
- Fill in the metadata:
- Distribution:
Your Organisation Only(keeps it private for now) - Threat Level:
High - Analysis:
Initial - Event Info:
Cobalt Strike C2 Infrastructure — April 2026
- Distribution:
- Click Add Event
You'll land on the event detail page. Add IOCs as attributes:
- Click Add Attribute
- Add the following:
| Category | Type | Value | IDS Flag |
|---|---|---|---|
| Network activity | ip-dst | 198.51.100.42 | ✓ |
| Network activity | domain | c2update.example.net | ✓ |
| Payload delivery | md5 | d41d8cd98f00b204e9800998ecf8427e | ✓ |
| External analysis | url | https://c2update.example.net/beacon | ✓ |
Enable the IDS flag on each — this marks the attribute for export to IDS/firewall blocklists.
- Click Publish Event (lightning bolt icon) to make it visible to other MISP users in your org
Step 11: Add Contextual Intelligence with Galaxies
Raw IOCs without context are incomplete intelligence. MISP Galaxies let you tag events with MITRE ATT&CK techniques, threat actor profiles, malware families, and sectors.
On the event detail page:
- Click Add Tag → select Galaxies tab
- Search for
cobalt strike→ select MITRE ATT&CK: Cobalt Strike from the malware galaxy - Add
T1071.001(Application Layer Protocol: Web Protocols) from the ATT&CK technique galaxy - Click Submit
Your event now carries machine-readable ATT&CK context that downstream tools can act on.
Part 5: PyMISP API Integration
Step 12: Install and Configure PyMISP
PyMISP is the official Python client for MISP. Install it in a virtual environment:
python3 -m venv misp-env
source misp-env/bin/activate
pip install pymispCreate a config file:
# keys.py — DO NOT commit to git
misp_url = "https://misp.lab.internal"
misp_key = "your_api_key_here"
misp_verifycert = False # Set True in production with valid certStep 13: Query MISP for Known IOCs
The most common use case: given an indicator from an alert, check whether MISP has intelligence on it.
#!/usr/bin/env python3
# check_ioc.py — Query MISP for a specific indicator
from pymisp import PyMISP
import keys
def check_indicator(value: str) -> None:
misp = PyMISP(keys.misp_url, keys.misp_key, keys.misp_verifycert)
# Search for the value across all attribute types
result = misp.search(value=value, pythonify=True)
if not result:
print(f"[CLEAN] {value} — no MISP hits")
return
print(f"[ALERT] {value} found in {len(result)} event(s):")
for event in result:
print(f" Event ID : {event.id}")
print(f" Info : {event.info}")
print(f" Threat Lvl: {event.threat_level_id}")
print(f" Date : {event.date}")
print()
if __name__ == "__main__":
import sys
check_indicator(sys.argv[1])Run it:
python3 check_ioc.py 198.51.100.42
# [ALERT] 198.51.100.42 found in 1 event(s):
# Event ID : 3
# Info : Cobalt Strike C2 Infrastructure — April 2026
# Threat Lvl: 1
# Date : 2026-04-15Step 14: Export IOCs as a Blocklist
Pull all IDS-flagged IPs from MISP and write them to a blocklist file for use with pfSense, OPNsense, or iptables:
#!/usr/bin/env python3
# export_blocklist.py — Export IDS-flagged IPs from MISP
from pymisp import PyMISP
import keys
misp = PyMISP(keys.misp_url, keys.misp_key, keys.misp_verifycert)
# Get all attributes of type ip-dst with IDS flag enabled
attributes = misp.search(
controller="attributes",
type_attribute="ip-dst",
to_ids=1,
pythonify=True
)
ips = sorted({attr.value for attr in attributes})
print(f"Exporting {len(ips)} IDS-flagged IPs...")
with open("blocklist.txt", "w") as f:
for ip in ips:
f.write(ip + "\n")
print("Saved to blocklist.txt")Step 15: Export to STIX 2.1
MISP can export any event as a STIX 2.1 bundle for sharing with external partners or ingesting into tools that speak STIX natively:
# Export a specific event as STIX 2.1 via the API
curl -s -k \
-H "Authorization: ${MISP_KEY}" \
-H "Accept: application/json" \
"${MISP_URL}/events/restSearch" \
-d '{"eventid": 3, "returnFormat": "stix2"}' \
| python3 -m json.tool > event3_stix.json
# Verify the bundle
python3 -c "import json; d = json.load(open('event3_stix.json')); print(f'STIX bundle with {len(d[\"objects\"])} objects')"Part 6: SIEM Integration
Step 16: Wire MISP into Wazuh
Wazuh can query MISP in real time to enrich security alerts. When Wazuh detects a DNS query or network connection, it calls the MISP API and adds IOC context to the alert.
On your Wazuh manager, create a custom active-response script:
#!/usr/bin/env python3
# /var/ossec/integrations/misp.py
# Wazuh custom integration for MISP IOC enrichment
import sys
import json
import requests
import urllib3
urllib3.disable_warnings()
MISP_URL = "https://misp.lab.internal"
MISP_KEY = "your_api_key_here"
def query_misp(indicator: str) -> dict:
headers = {
"Authorization": MISP_KEY,
"Accept": "application/json",
"Content-Type": "application/json",
}
payload = {
"value": indicator,
"returnFormat": "json",
"limit": 5,
}
response = requests.post(
f"{MISP_URL}/attributes/restSearch",
headers=headers,
json=payload,
verify=False,
timeout=10,
)
response.raise_for_status()
return response.json()
def main():
alert_file = sys.argv[1]
with open(alert_file) as f:
alert = json.load(f)
# Extract destination IP from Wazuh alert data
src_ip = alert.get("data", {}).get("srcip") or \
alert.get("data", {}).get("dest_ip")
if not src_ip:
sys.exit(0)
result = query_misp(src_ip)
hits = result.get("response", {}).get("Attribute", [])
if hits:
print(json.dumps({
"misp_hit": True,
"indicator": src_ip,
"event_count": len(hits),
"first_seen": hits[0].get("Event", {}).get("date"),
"event_info": hits[0].get("Event", {}).get("info"),
}))
if __name__ == "__main__":
main()Register the integration in /var/ossec/etc/ossec.conf:
<integration>
<name>misp</name>
<hook_url>https://misp.lab.internal</hook_url>
<api_key>your_api_key_here</api_key>
<level>5</level>
<alert_format>json</alert_format>
</integration>Part 7: Testing Your Deployment
Verification Checklist
Run through each test to confirm your MISP stack is operating correctly.
Database connectivity:
docker exec misp-docker-db-1 mysql -u misp -p"${MYSQL_PASSWORD}" misp \
-e "SELECT COUNT(*) as event_count FROM events;"Feed worker health:
# Check background workers
docker compose logs misp | grep -i "worker"
# Confirm Redis is processing jobs
docker exec misp-docker-redis-1 redis-cli info clientsAPI connectivity:
curl -sk \
-H "Authorization: ${MISP_KEY}" \
-H "Accept: application/json" \
"${MISP_URL}/users/view/me.json" | python3 -m json.tool | head -20Expected: your user profile in JSON.
Feed ingestion:
# Count attributes imported from feeds
curl -sk \
-H "Authorization: ${MISP_KEY}" \
-H "Accept: application/json" \
"${MISP_URL}/attributes/restSearch" \
-d '{"returnFormat": "count"}' | python3 -m json.toolAfter enabling CIRCL and abuse.ch feeds, expect 50,000+ attributes within 30 minutes.
STIX export:
curl -sk \
-H "Authorization: ${MISP_KEY}" \
-H "Accept: application/json" \
"${MISP_URL}/events/restSearch" \
-d '{"returnFormat": "stix2", "limit": 1}' | python3 -c \
"import json,sys; d=json.load(sys.stdin); print('STIX type:', d.get('type', 'error'))"Expected output: STIX type: bundle
Part 8: Production Hardening
Replace the Self-Signed Certificate
Use Let's Encrypt via Certbot or hook MISP behind your existing Traefik/Nginx reverse proxy:
# Example Traefik label additions in docker-compose.yml
services:
misp:
labels:
- "traefik.enable=true"
- "traefik.http.routers.misp.rule=Host(`misp.lab.internal`)"
- "traefik.http.routers.misp.entrypoints=websecure"
- "traefik.http.routers.misp.tls=true"
- "traefik.http.services.misp.loadbalancer.server.port=80"Then set MISP_BASEURL to your Traefik DNS name and remove port 443 from the MISP service's ports: block.
Enable Two-Factor Authentication
- Administration → Server Settings → search for
MISP.totp - Set
MISP.totp.enabledtotrue - Each user enables TOTP from their profile page
Persistent Volumes and Backups
The official Docker Compose already defines named volumes for the database and MISP files. Add a daily backup:
#!/bin/bash
# backup-misp.sh — run via cron daily
BACKUP_DIR="/opt/backups/misp/$(date +%Y-%m-%d)"
mkdir -p "${BACKUP_DIR}"
# Database dump
docker exec misp-docker-db-1 \
mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" misp \
> "${BACKUP_DIR}/misp-db.sql"
# MISP files (events attachments, feeds cache)
docker run --rm \
--volumes-from misp-docker-misp-1 \
-v "${BACKUP_DIR}:/backup" \
alpine tar czf /backup/misp-files.tar.gz /var/www/MISP/app/files
echo "Backup complete: ${BACKUP_DIR}"Extensions and Next Steps
With a working MISP instance you have a solid threat intelligence foundation. Here's where to take it next:
TheHive + MISP Integration
TheHive is an incident response case management platform that natively integrates with MISP. Create a case in TheHive and it automatically creates a corresponding MISP event — analysts add IOCs in TheHive and they sync back to MISP for the wider community.
Cortex Enrichment Modules
Cortex connects to MISP's module system to auto-enrich attributes using 300+ analyzers: VirusTotal, Shodan, MaxMind GeoIP, AbuseIPDB, CIRCL passive DNS, and more. Every new IOC gets a reputation score and passive DNS history without any manual lookup.
MISP Sharing Communities
Connect your MISP to the CIRCL public MISP instance to receive verified threat intelligence shared by CERTs globally:
- Administration → Sync Actions → List Servers
- Add CIRCL's server with the provided sync credentials
- Enable pull synchronization for one-way feed
Zeek IDS Rule Export
MISP can generate Zeek IDS rules directly from your events:
curl -sk \
-H "Authorization: ${MISP_KEY}" \
"${MISP_URL}/attributes/bro" > /opt/zeek/share/zeek/site/misp-iocs.zeekCombine with the Zeek + Suricata project to get automatic detection of indicators from your MISP instance on live network traffic.
Fleet-Wide Indicator Scanning with Velociraptor
Use PyMISP to pull all hash IOCs from MISP and feed them into a Velociraptor VQL hunt to scan every endpoint for malicious files simultaneously — a complete detect-and-respond loop from a single threat intelligence platform.
# Pull hashes from MISP → feed to Velociraptor
from pymisp import PyMISP
import keys
misp = PyMISP(keys.misp_url, keys.misp_key, keys.misp_verifycert)
hashes = misp.search(
controller="attributes",
type_attribute=["md5", "sha256"],
to_ids=1,
pythonify=True,
)
print("# Paste into Velociraptor VQL hunt")
print("LET BadHashes <= ", [a.value for a in hashes])MISP is one of those tools that rewards patience during setup with compounding returns over time — every feed you enable, every event you enrich, every integration you wire up makes every subsequent investigation faster. Start with the CIRCL feed, build your first private events, then gradually expand into sharing communities as your confidence grows.