
Collecting Threat Intelligence With Misp
- 146 installs
- 27.3k repo stars
- Updated August 2, 2026
- mukul975/anthropic-cybersecurity-skills
Helps with ai & agent building tasks.
About
collecting-threat-intelligence-with-misp is a Claude Code skill in the AI & Agent Building category.
- collecting-threat-intelligence-with-misp
- AI & Agent Building
- AI-coding skill
Collecting Threat Intelligence With Misp by the numbers
- 146 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #3,425 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill collecting-threat-intelligence-with-mispAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 146 |
|---|---|
| repo stars | ★ 27.3k |
| Last updated | August 2, 2026 |
| Repository | mukul975/anthropic-cybersecurity-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Collecting Threat Intelligence with MISP
Overview
MISP (Malware Information Sharing Platform) is an open-source threat intelligence platform for gathering, sharing, storing, and correlating Indicators of Compromise (IOCs) of targeted attacks, threat intelligence, financial fraud information, vulnerability information, or counter-terrorism information. This skill covers deploying MISP, configuring threat feeds, using the PyMISP API for programmatic access, and building automated collection pipelines that aggregate IOCs from multiple community and commercial sources.
When to Use
- When managing security operations that require collecting threat intelligence with misp
- When improving security program maturity and operational processes
- When establishing standardized procedures for security team workflows
- When integrating threat intelligence or vulnerability data into operations
Prerequisites
- Python 3.9+ with
pymisplibrary installed - Docker and Docker Compose for MISP deployment
- Understanding of STIX 2.1 and TAXII 2.1 protocols
- Familiarity with IOC types: hashes, IP addresses, domains, URLs, email addresses
- Network access to MISP community feeds (circl.lu, botvrij.eu)
Key Concepts
MISP Architecture
MISP operates on an event-based model where threat intelligence is organized into events containing attributes (IOCs), objects (structured groupings of attributes), galaxies (threat actor/malware clusters linked to MITRE ATT&CK), and tags for classification. Synchronization between MISP instances uses a pull/push model over HTTPS with API key authentication.
Feed Types
- MISP Feeds: Native JSON/CSV feeds from MISP community (CIRCL OSINT, botvrij.eu)
- Freetext Feeds: Unstructured text feeds parsed for IOCs (abuse.ch, Feodo Tracker)
- TAXII Feeds: STIX/TAXII 2.1 compatible feeds from commercial and government sources
- CSV Feeds: Structured CSV feeds with configurable column mapping
PyMISP API
PyMISP is the official Python library to access MISP platforms via their REST API. It supports fetching events, adding/updating events and attributes, uploading samples, and searching across the entire MISP dataset. Authentication uses an API key passed in the Authorization header.
Workflow
Step 1: Deploy MISP with Docker
git clone https://github.com/MISP/misp-docker.git
cd misp-docker
cp template.env .env
# Edit .env to set MISP_BASEURL, MISP_ADMIN_EMAIL, MISP_ADMIN_PASSPHRASE
docker compose up -dStep 2: Configure Default Feeds
Enable built-in MISP feeds via the web UI or API:
from pymisp import PyMISP
misp = PyMISP('https://misp.local', 'YOUR_API_KEY', ssl=False)
# List available feeds
feeds = misp.feeds()
for feed in feeds:
print(f"{feed['Feed']['id']}: {feed['Feed']['name']} - Enabled: {feed['Feed']['enabled']}")
# Enable CIRCL OSINT Feed
misp.enable_feed(feed_id=1)
misp.cache_feed(feed_id=1)
misp.fetch_feed(feed_id=1)Step 3: Add Custom Threat Feeds
# Add abuse.ch URLhaus feed
feed_data = {
'name': 'URLhaus Recent URLs',
'provider': 'abuse.ch',
'url': 'https://urlhaus.abuse.ch/downloads/csv_recent/',
'source_format': 'csv',
'input_source': 'network',
'publish': False,
'enabled': True,
'headers': '',
'distribution': 0,
'sharing_group_id': 0,
'tag_id': 0,
'default': False,
'lookup_visible': True
}
result = misp.add_feed(feed_data)
print(f"Feed added: {result}")Step 4: Programmatic Event Search and Retrieval
from pymisp import PyMISP, MISPEvent
from datetime import datetime, timedelta
misp = PyMISP('https://misp.local', 'YOUR_API_KEY', ssl=False)
# Search for events from the last 7 days
result = misp.search(
controller='events',
date_from=(datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d'),
type_attribute='ip-dst',
to_ids=True,
pythonify=True
)
for event in result:
print(f"Event {event.id}: {event.info}")
for attr in event.attributes:
if attr.type == 'ip-dst' and attr.to_ids:
print(f" IOC: {attr.value} (category: {attr.category})")Step 5: Export IOCs for Downstream Tools
# Export as STIX 2.1 bundle
stix_output = misp.search(
controller='events',
return_format='stix2',
tags=['tlp:white'],
published=True
)
# Export IDS-flagged attributes as Suricata rules
suricata_rules = misp.search(
controller='attributes',
return_format='suricata',
to_ids=True,
type_attribute=['ip-dst', 'domain', 'url']
)
# Export as CSV for SIEM ingestion
csv_output = misp.search(
controller='attributes',
return_format='csv',
type_attribute='ip-dst',
to_ids=True
)Validation Criteria
- MISP instance is deployed and accessible via HTTPS
- At least 3 community feeds are enabled and fetching data successfully
- PyMISP script can authenticate, search events, and retrieve IOCs
- Events contain properly tagged and categorized attributes
- Export to STIX 2.1 produces valid STIX bundles
- Automated feed fetch runs on schedule (cron or MISP scheduler)
References
MISP Intelligence Collection Report Template
Report Metadata
| Field | Value |
|---|---|
| Report ID | MISP-COL-YYYY-NNNN |
| Date Generated | YYYY-MM-DD HH:MM UTC |
| MISP Instance | https://misp.example.com |
| Collection Period | YYYY-MM-DD to YYYY-MM-DD |
| Classification | TLP:AMBER |
| Analyst | [Analyst Name] |
Executive Summary
Brief overview of threat intelligence collected during the reporting period, including total events processed, notable threat campaigns identified, and key IOCs requiring immediate action.
Collection Statistics
| Metric | Count |
|---|---|
| Total Events Processed | |
| New Events Created | |
| Attributes Collected | |
| IDS-Flagged Indicators | |
| Warninglist Filtered | |
| Feeds Active | |
| Correlations Found |
Feed Status
| Feed Name | Provider | Last Fetch | Status | Events Generated |
|---|---|---|---|---|
| CIRCL OSINT | CIRCL | Active/Error | ||
| Botvrij.eu | Botvrij | Active/Error | ||
| URLhaus | abuse.ch | Active/Error | ||
| PhishTank | OpenDNS | Active/Error |
Top IOC Categories
Network Indicators
| Type | Count | Sample Values |
|---|---|---|
| IP Addresses (dst) | ||
| IP Addresses (src) | ||
| Domains | ||
| URLs | ||
| Hostnames |
File Indicators
| Type | Count | Sample Values |
|---|---|---|
| MD5 Hashes | ||
| SHA-1 Hashes | ||
| SHA-256 Hashes | ||
| Filenames |
Email Indicators
| Type | Count | Sample Values |
|---|---|---|
| Email Addresses | ||
| Email Subjects | ||
| Attachment Names |
Notable Campaigns
Campaign 1: [Campaign Name]
- Threat Actor: [Actor Name/Group]
- MITRE ATT&CK Techniques: T1566, T1059, T1071
- IOC Count: N indicators
- First Seen: YYYY-MM-DD
- TLP: AMBER
- Key Indicators:
- IP: x.x.x.x (C2 Server)
- Domain: malicious-domain.com
- SHA256: [hash]
Correlation Highlights
Events sharing common indicators across multiple campaigns or threat actors:
| Indicator | Events Linked | Threat Actors | Confidence |
|---|---|---|---|
| High/Medium/Low |
Export Summary
| Format | Destination | Record Count | Timestamp |
|---|---|---|---|
| STIX 2.1 | OpenCTI | ||
| Suricata Rules | IDS/IPS | ||
| CSV | SIEM (Splunk) | ||
| JSON | Threat Hunting |
Recommendations
1. Immediate Actions: Block high-confidence IOCs in firewall/proxy 2. Monitoring: Add medium-confidence IOCs to watchlists 3. Investigation: Review events tagged with threat level "high" 4. Feed Maintenance: Review and update feed configurations 5. Sharing: Publish sanitized events to community instances
Appendix: IOC Export
Full IOC list exported to:
misp_iocs_export.csv- CSV format for SIEM ingestionmisp_stix_bundle.json- STIX 2.1 bundle for CTI platformsmisp_suricata.rules- Suricata IDS rules for network detection
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
API Reference: Collecting Threat Intelligence with MISP
PyMISP Installation
pip install pymispClient Initialization
from pymisp import PyMISP
misp = PyMISP(
url="https://misp.example.org",
key=os.environ["MISP_API_KEY"],
ssl=True
)Event Search
# By tags
events = misp.search("events", tags=["tlp:white", "type:OSINT"], pythonify=True)
# By date range
events = misp.search("events", date_from="2025-01-01", date_to="2025-01-31", pythonify=True)
# Published only
events = misp.search("events", published=True, limit=100, pythonify=True)Attribute Search
# By type
attrs = misp.search("attributes", type_attribute="ip-dst", to_ids=True, pythonify=True)
# By event
attrs = misp.search("attributes", eventid=42, pythonify=True)
# By value
attrs = misp.search("attributes", value="198.51.100.42", pythonify=True)REST API (curl)
# Search events
curl -X POST "https://misp/events/restSearch" \
-H "Authorization: $KEY" \
-H "Content-Type: application/json" \
-d '{"tags":["tlp:white"],"limit":50}'
# Get event
curl -H "Authorization: $KEY" "https://misp/events/view/42"
# STIX 2 export
curl -H "Authorization: $KEY" "https://misp/events/restSearch/stix2"Common Attribute Types
| Type | Category | Example |
|---|---|---|
| ip-dst | Network activity | 198.51.100.42 |
| domain | Network activity | evil.example.com |
| url | Network activity | https://evil.com/mal |
| sha256 | Payload delivery | a1b2c3... |
| md5 | Payload delivery | d41d8c... |
| email-src | Payload delivery | attacker@evil.com |
| filename | Payload delivery | malware.exe |
Feed Management
# List feeds
feeds = misp.feeds()
# Enable feed
misp.enable_feed(feed_id=1)
# Fetch and cache
misp.fetch_feed(feed_id=1)
misp.cache_feeds()Standards and Frameworks Reference
MISP Standards
MISP Core Format
- MISP JSON Format: Native event format used for synchronization between instances
- MISP Galaxy: Cluster-based knowledge base linked to MITRE ATT&CK, threat actors, tools
- MISP Taxonomies: Machine-readable tagging schemes (TLP, PAP, admiralty-scale, OSINT)
- MISP Warninglists: Lists of well-known indicators to reduce false positives (Alexa Top 1M, Office 365 IPs)
STIX 2.1 (Structured Threat Information Expression)
- Standard language for representing cyber threat intelligence
- MISP supports import/export of STIX 2.1 bundles
- Object types: Indicator, Malware, Threat Actor, Attack Pattern, Campaign, Observed Data
- Relationship types: uses, targets, attributed-to, indicates, mitigates
TAXII 2.1 (Trusted Automated Exchange of Intelligence Information)
- Transport protocol for sharing CTI over HTTPS
- MISP can consume TAXII feeds and serve as a TAXII server
- Collection-based model: discovery, API root, collections, objects
- Supports pagination and filtering by added_after, type, version
MITRE ATT&CK Integration
- MISP Galaxy clusters map directly to ATT&CK techniques (T-codes)
- Events can be tagged with ATT&CK tactics: Initial Access, Execution, Persistence, etc.
- ATT&CK Navigator integration for visualizing technique coverage
- Sub-technique support (e.g., T1566.001 - Spearphishing Attachment)
Traffic Light Protocol (TLP)
- TLP:CLEAR (formerly TLP:WHITE): Unlimited disclosure
- TLP:GREEN: Limited disclosure within community
- TLP:AMBER: Limited disclosure within organization
- TLP:AMBER+STRICT: Restricted to organization only
- TLP:RED: Restricted to specific recipients only
Permissible Actions Protocol (PAP)
- PAP:RED: Only passive actions (no external lookups)
- PAP:AMBER: Active actions allowed but not against infrastructure
- PAP:GREEN: Active actions allowed
- PAP:CLEAR: Unlimited use
References
MISP Threat Intelligence Collection Workflows
Workflow 1: Automated Feed Collection Pipeline
[Community Feeds] --> [MISP Feed Manager] --> [Event Creation] --> [Correlation Engine]
| | | |
v v v v
- CIRCL OSINT - Schedule fetch - Auto-tag with - Deduplicate
- Botvrij.eu - Parse formats TLP/PAP - Cross-reference
- abuse.ch - Validate IOCs - Set distribution - Cluster similar
- PhishTank - Filter warninglists - Publish/unpublish eventsSteps:
1. Feed Registration: Add feeds via UI or PyMISP API with source_format, URL, and headers 2. Scheduled Fetch: Configure cron job or MISP scheduler to pull feeds at intervals 3. Parsing and Validation: MISP parses feed content, validates IOC formats, checks against warninglists 4. Event Generation: Each feed pull creates or updates events with parsed attributes 5. Correlation: MISP correlates new attributes against existing data, identifying overlaps 6. Distribution: Events are distributed based on TLP and sharing group configurations
Workflow 2: Manual Intelligence Collection
[Analyst Report] --> [Manual Event Creation] --> [Attribute Addition] --> [Enrichment]
|
v
[Galaxy Tagging]
|
v
[Publication]Steps:
1. Event Creation: Create event with descriptive info, date, distribution, TLP tag 2. IOC Entry: Add attributes (IP, domain, hash, URL) with correct category and type 3. Object Construction: Group related attributes into MISP objects (file, domain-ip, email) 4. Galaxy Linking: Link event to MITRE ATT&CK techniques, threat actor clusters, malware families 5. Enrichment: Use MISP modules (VirusTotal, Shodan, CIRCL PassiveDNS) to enrich attributes 6. Review and Publish: Analyst reviews, sets to_ids flags, publishes for community sharing
Workflow 3: TAXII Feed Integration
[TAXII Server] --> [TAXII Client] --> [STIX Parser] --> [MISP Import] --> [Correlation]Steps:
1. Discovery: Query TAXII server discovery endpoint for available API roots 2. Collection Enumeration: List available collections and their metadata 3. Object Retrieval: Fetch STIX 2.1 objects from collections with pagination 4. STIX-to-MISP Mapping: Map STIX Indicator, Malware, Threat Actor to MISP event/attributes 5. Import: Create MISP events from STIX bundles 6. Correlation: Run correlation against existing MISP data
Workflow 4: Instance Synchronization
[MISP Instance A] <--sync--> [MISP Instance B] <--sync--> [MISP Instance C]
| | |
v v v
[Org A Events] [Shared Events] [Org C Events]Steps:
1. Server Registration: Register remote MISP instance with URL, API key, organization 2. Sync Configuration: Set sync direction (push/pull), filter rules, preview mode 3. Pull Sync: Pull events from remote instance matching filter criteria 4. Push Sync: Push local events to remote instance based on distribution level 5. Conflict Resolution: Handle attribute conflicts with priority rules 6. Audit Logging: Log all sync activities for compliance and troubleshooting
Workflow 5: IOC Export for Defensive Tools
[MISP Events] --> [Export Module] --> [Format Conversion] --> [Defensive Tool]
|
+--------+--------+
| | |
v v v
[Suricata] [Bro/Zeek] [SIEM]
Rules Intel CSV/JSONSteps:
1. Filter Selection: Select events by tag, date range, threat level, to_ids flag 2. Format Selection: Choose output format (Suricata, Snort, Bro/Zeek, CSV, STIX, OpenIOC) 3. Rule Generation: Generate IDS/IPS rules from network IOCs 4. SIEM Export: Export to CSV/JSON for SIEM ingestion (Splunk, Elastic, QRadar) 5. Automation: Set up ZMQ/Kafka publishing for real-time IOC distribution 6. Feedback Loop: Track hit counts on exported IOCs, feed back to MISP for scoring
#!/usr/bin/env python3
"""Threat intelligence collection agent using MISP/PyMISP.
Connects to MISP instances to collect, filter, and export threat intelligence
including events, attributes, and feeds via the PyMISP REST API client.
"""
import json
import os
import datetime
try:
from pymisp import PyMISP
HAS_PYMISP = True
except ImportError:
HAS_PYMISP = False
def init_misp(url=None, key=None):
"""Initialize PyMISP client."""
url = url or os.environ.get("MISP_URL", "https://misp.example.org")
key = key or os.environ.get("MISP_API_KEY", "")
if not HAS_PYMISP:
return None
return PyMISP(url, key, ssl=True)
def search_events(misp, tags=None, date_from=None, published=True, limit=50):
"""Search MISP events by tags and date."""
if not misp:
return {"error": "PyMISP not available"}
kwargs = {"limit": limit, "published": published, "pythonify": True}
if tags:
kwargs["tags"] = tags
if date_from:
kwargs["date_from"] = date_from
try:
events = misp.search("events", **kwargs)
return [
{
"id": e.id,
"uuid": e.uuid,
"info": e.info,
"date": str(e.date),
"threat_level": {1: "High", 2: "Medium", 3: "Low", 4: "Undefined"}.get(e.threat_level_id, "?"),
"analysis": {0: "Initial", 1: "Ongoing", 2: "Complete"}.get(e.analysis, "?"),
"attribute_count": e.attribute_count,
"org": e.Orgc.name if hasattr(e, "Orgc") and e.Orgc else "",
"tags": [t.name for t in (e.tags or [])],
}
for e in events
]
except Exception as e:
return {"error": str(e)}
def extract_attributes(misp, event_id, attr_type=None):
"""Extract attributes from a MISP event."""
if not misp:
return {"error": "PyMISP not available"}
try:
kwargs = {"eventid": event_id, "pythonify": True}
if attr_type:
kwargs["type_attribute"] = attr_type
attrs = misp.search("attributes", **kwargs)
return [
{
"type": a.type,
"value": a.value,
"category": a.category,
"to_ids": a.to_ids,
"comment": a.comment or "",
"timestamp": str(datetime.datetime.fromtimestamp(int(a.timestamp))),
}
for a in attrs
]
except Exception as e:
return {"error": str(e)}
def collect_iocs_by_type(misp, ioc_types, date_from=None, limit=500):
"""Collect IOCs filtered by attribute type."""
if not misp:
return {"error": "PyMISP not available"}
results = {}
for ioc_type in ioc_types:
try:
kwargs = {"type_attribute": ioc_type, "to_ids": True, "pythonify": True, "limit": limit}
if date_from:
kwargs["date_from"] = date_from
attrs = misp.search("attributes", **kwargs)
results[ioc_type] = [
{"value": a.value, "event_id": a.event_id, "comment": a.comment or ""}
for a in attrs
]
except Exception as e:
results[ioc_type] = {"error": str(e)}
return results
def list_feeds(misp):
"""List configured MISP feeds."""
if not misp:
return {"error": "PyMISP not available"}
try:
feeds = misp.feeds()
return [
{
"id": f["Feed"]["id"],
"name": f["Feed"]["name"],
"provider": f["Feed"]["provider"],
"url": f["Feed"]["url"],
"enabled": f["Feed"]["enabled"],
"source_format": f["Feed"]["source_format"],
}
for f in feeds
]
except Exception as e:
return {"error": str(e)}
def export_stix2(misp, event_id):
"""Export MISP event as STIX 2.1 bundle."""
if not misp:
return {"error": "PyMISP not available"}
try:
stix_data = misp.get_stix_event(event_id)
return stix_data
except Exception as e:
return {"error": str(e)}
COMMON_IOC_TYPES = [
"ip-dst", "ip-src", "domain", "hostname", "url",
"md5", "sha1", "sha256", "email-src", "filename",
]
if __name__ == "__main__":
print("=" * 60)
print("Threat Intelligence Collection with MISP")
print("PyMISP REST client, event search, attribute extraction, feeds")
print("=" * 60)
print(" PyMISP available: {}".format(HAS_PYMISP))
misp = init_misp() if HAS_PYMISP else None
if not misp:
print("\n[DEMO] No MISP connection. Showing IOC types and feed structure.")
print("\n--- Common IOC Types ---")
for t in COMMON_IOC_TYPES:
print(" - {}".format(t))
print("\n--- Usage ---")
print(" Set MISP_URL and MISP_API_KEY environment variables")
print(" python agent.py")
else:
print("\n[*] Searching recent events...")
events = search_events(misp, date_from="7d")
if isinstance(events, list):
print(" Found {} events".format(len(events)))
for e in events[:5]:
print(" [{}] {} ({} attrs)".format(e["id"], e["info"][:60], e["attribute_count"]))
else:
print(" Error: {}".format(events))
feeds = list_feeds(misp)
if isinstance(feeds, list):
print("\n--- Feeds ({}) ---".format(len(feeds)))
for f in feeds[:10]:
status = "enabled" if f["enabled"] else "disabled"
print(" [{}] {} ({})".format(f["id"], f["name"], status))
print("\n" + json.dumps({"pymisp_available": HAS_PYMISP}, indent=2))
#!/usr/bin/env python3
"""
MISP Threat Intelligence Collection Script
Automates IOC collection from MISP instance including:
- Feed management and scheduled fetching
- Event search and attribute extraction
- IOC export in multiple formats (STIX, CSV, Suricata)
- Warninglist filtering to reduce false positives
- Correlation summary generation
Requirements:
pip install pymisp requests stix2
Usage:
python process.py --url https://misp.local --key YOUR_API_KEY --action collect
python process.py --url https://misp.local --key YOUR_API_KEY --action export --format stix2
python process.py --url https://misp.local --key YOUR_API_KEY --action feeds --enable-defaults
"""
import argparse
import json
import csv
import sys
import os
from datetime import datetime, timedelta
from typing import Optional
try:
from pymisp import PyMISP, MISPEvent, MISPAttribute
except ImportError:
print("ERROR: pymisp not installed. Run: pip install pymisp")
sys.exit(1)
class MISPCollector:
"""Automated threat intelligence collector for MISP."""
def __init__(self, url: str, api_key: str, ssl_verify: bool = True):
self.misp = PyMISP(url, api_key, ssl=ssl_verify)
self.url = url
self.stats = {
"events_processed": 0,
"attributes_collected": 0,
"iocs_exported": 0,
"feeds_enabled": 0,
"warninglist_filtered": 0,
}
def enable_default_feeds(self) -> dict:
"""Enable and fetch default MISP community feeds."""
default_feeds = [
"CIRCL OSINT Feed",
"Botvrij.eu",
"abuse.ch URLhaus",
"The Botnet Channel",
"Phishtank online valid phishing",
]
feeds = self.misp.feeds()
enabled = []
for feed in feeds:
feed_name = feed.get("Feed", {}).get("name", "")
feed_id = feed.get("Feed", {}).get("id")
if any(default in feed_name for default in default_feeds):
try:
self.misp.enable_feed(feed_id)
self.misp.fetch_feed(feed_id)
enabled.append(feed_name)
self.stats["feeds_enabled"] += 1
print(f"[+] Enabled feed: {feed_name}")
except Exception as e:
print(f"[-] Failed to enable {feed_name}: {e}")
return {"enabled_feeds": enabled, "count": len(enabled)}
def add_custom_feed(self, name: str, url: str, provider: str,
source_format: str = "csv") -> dict:
"""Add a custom threat intelligence feed."""
feed_config = {
"name": name,
"provider": provider,
"url": url,
"source_format": source_format,
"input_source": "network",
"publish": False,
"enabled": True,
"distribution": 0,
"default": False,
"lookup_visible": True,
}
result = self.misp.add_feed(feed_config)
print(f"[+] Added custom feed: {name} from {provider}")
return result
def collect_recent_iocs(self, days: int = 7,
ioc_types: Optional[list] = None) -> list:
"""Collect IOCs from recent events."""
if ioc_types is None:
ioc_types = [
"ip-dst", "ip-src", "domain", "hostname",
"url", "md5", "sha1", "sha256", "email-src",
]
date_from = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
all_iocs = []
for ioc_type in ioc_types:
try:
results = self.misp.search(
controller="attributes",
type_attribute=ioc_type,
date_from=date_from,
to_ids=True,
pythonify=True,
)
for attr in results:
ioc_entry = {
"type": attr.type,
"value": attr.value,
"category": attr.category,
"event_id": attr.event_id,
"timestamp": str(attr.timestamp),
"to_ids": attr.to_ids,
"comment": attr.comment or "",
}
all_iocs.append(ioc_entry)
self.stats["attributes_collected"] += 1
print(f"[+] Collected {len(results)} {ioc_type} IOCs")
except Exception as e:
print(f"[-] Error collecting {ioc_type}: {e}")
return all_iocs
def collect_events_by_tag(self, tags: list, limit: int = 100) -> list:
"""Collect events matching specific tags."""
events = self.misp.search(
controller="events",
tags=tags,
limit=limit,
pythonify=True,
)
collected = []
for event in events:
event_data = {
"id": event.id,
"info": event.info,
"date": str(event.date),
"threat_level": event.threat_level_id,
"analysis": event.analysis,
"attribute_count": len(event.attributes),
"tags": [tag.name for tag in event.tags] if event.tags else [],
"attributes": [],
}
for attr in event.attributes:
event_data["attributes"].append({
"type": attr.type,
"value": attr.value,
"category": attr.category,
"to_ids": attr.to_ids,
})
collected.append(event_data)
self.stats["events_processed"] += 1
print(f"[+] Collected {len(collected)} events with tags: {tags}")
return collected
def filter_warninglists(self, iocs: list) -> list:
"""Filter IOCs against MISP warninglists to remove known-good indicators."""
filtered = []
for ioc in iocs:
result = self.misp.values_in_warninglist([ioc["value"]])
if not result or not result.get(ioc["value"]):
filtered.append(ioc)
else:
self.stats["warninglist_filtered"] += 1
print(f"[!] Filtered (warninglist): {ioc['value']}")
print(f"[+] Filtered {self.stats['warninglist_filtered']} IOCs via warninglists")
return filtered
def export_stix2(self, event_ids: Optional[list] = None,
tags: Optional[list] = None) -> dict:
"""Export events as STIX 2.1 bundles."""
search_params = {
"controller": "events",
"return_format": "stix2",
}
if event_ids:
search_params["eventid"] = event_ids
if tags:
search_params["tags"] = tags
stix_bundle = self.misp.search(**search_params)
self.stats["iocs_exported"] += len(
stix_bundle.get("objects", []) if isinstance(stix_bundle, dict) else []
)
print(f"[+] Exported STIX 2.1 bundle")
return stix_bundle
def export_csv(self, iocs: list, output_path: str) -> str:
"""Export IOCs to CSV file."""
if not iocs:
print("[-] No IOCs to export")
return ""
fieldnames = ["type", "value", "category", "event_id", "timestamp",
"to_ids", "comment"]
with open(output_path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for ioc in iocs:
writer.writerow({k: ioc.get(k, "") for k in fieldnames})
self.stats["iocs_exported"] = len(iocs)
print(f"[+] Exported {len(iocs)} IOCs to {output_path}")
return output_path
def export_suricata(self, days: int = 7) -> str:
"""Export network IOCs as Suricata rules."""
rules = self.misp.search(
controller="attributes",
return_format="suricata",
to_ids=True,
type_attribute=["ip-dst", "ip-src", "domain", "url"],
date_from=(datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d"),
)
print(f"[+] Generated Suricata rules")
return rules
def get_correlation_summary(self, event_id: int) -> dict:
"""Get correlation summary for a specific event."""
event = self.misp.get_event(event_id, pythonify=True)
correlations = {}
for attr in event.attributes:
if hasattr(attr, "RelatedAttribute") and attr.RelatedAttribute:
correlations[attr.value] = {
"type": attr.type,
"related_events": [
rel["Event"]["id"] for rel in attr.RelatedAttribute
],
}
return {
"event_id": event_id,
"event_info": event.info,
"total_attributes": len(event.attributes),
"correlated_attributes": len(correlations),
"correlations": correlations,
}
def print_stats(self):
"""Print collection statistics."""
print("\n=== MISP Collection Statistics ===")
for key, value in self.stats.items():
print(f" {key.replace('_', ' ').title()}: {value}")
print("=================================\n")
def main():
parser = argparse.ArgumentParser(
description="MISP Threat Intelligence Collection Tool"
)
parser.add_argument("--url", required=True, help="MISP instance URL")
parser.add_argument("--key", required=True, help="MISP API key")
parser.add_argument("--no-ssl", action="store_true", help="Disable SSL verification")
parser.add_argument(
"--action",
choices=["collect", "export", "feeds", "correlate"],
required=True,
help="Action to perform",
)
parser.add_argument("--days", type=int, default=7, help="Lookback period in days")
parser.add_argument(
"--format",
choices=["csv", "stix2", "suricata"],
default="csv",
help="Export format",
)
parser.add_argument("--output", default="misp_iocs_export.csv", help="Output file path")
parser.add_argument("--tags", nargs="+", help="Filter by tags")
parser.add_argument(
"--enable-defaults",
action="store_true",
help="Enable default community feeds",
)
parser.add_argument("--event-id", type=int, help="Event ID for correlation")
args = parser.parse_args()
collector = MISPCollector(args.url, args.key, ssl_verify=not args.no_ssl)
if args.action == "feeds":
if args.enable_defaults:
result = collector.enable_default_feeds()
print(json.dumps(result, indent=2))
elif args.action == "collect":
iocs = collector.collect_recent_iocs(days=args.days)
if args.tags:
events = collector.collect_events_by_tag(args.tags)
print(json.dumps(events[:5], indent=2, default=str))
filtered = collector.filter_warninglists(iocs)
collector.export_csv(filtered, args.output)
elif args.action == "export":
if args.format == "stix2":
bundle = collector.export_stix2(tags=args.tags)
with open(args.output.replace(".csv", ".json"), "w") as f:
json.dump(bundle, f, indent=2, default=str)
elif args.format == "suricata":
rules = collector.export_suricata(days=args.days)
with open(args.output.replace(".csv", ".rules"), "w") as f:
f.write(str(rules))
else:
iocs = collector.collect_recent_iocs(days=args.days)
collector.export_csv(iocs, args.output)
elif args.action == "correlate":
if args.event_id:
summary = collector.get_correlation_summary(args.event_id)
print(json.dumps(summary, indent=2, default=str))
else:
print("[-] --event-id required for correlation action")
collector.print_stats()
if __name__ == "__main__":
main()