I operationalized threat intelligence by ingesting STIX 2.1 objects over TAXII feeds into MISP and mapping each indicator to MITRE ATT&CK techniques. The structured pipeline turned raw IOCs into detection coverage measured against 18 mapped techniques.

Objective & Context

Threat intelligence is only useful when structured and actionable. This lab connects the three pillars: ATT&CK (the behavioural taxonomy), STIX (the data model), and TAXII (the transport), feeding MISP so detections map to adversary behaviour rather than ephemeral indicators.

  • T1071 Application Layer Protocol, network IOCs from feeds drive C2 detection.
  • T1566 Phishing, email indicators enrich inbound triage.

Learning Objectives

  • Pull a STIX bundle from a TAXII server with taxii2client.
  • Import indicators into MISP via its REST API.
  • Map an indicator to a MITRE ATT&CK technique.
  • Explain the roles of ATT&CK (taxonomy), STIX (data model), and TAXII (transport).
  • Keep indicators fresh with scheduled TAXII pulls.

Environment & Prerequisites

Prerequisites

  • MISP instance with feed and TAXII modules enabled.
  • Access to OSINT TAXII servers (MITRE CTI, CIRCL, Abuse.ch).
  • The ATT&CK Navigator for coverage visualization.
  • taxii2client installed (pip install taxii2-client).
  • PyMISP installed (pip install pymisp) for event creation.
  • API keys for MISP stored outside scripts.
  • MISP galaxies updated (Event Actions > List Galaxies > Update Galaxies) so ATT&CK tags resolve.

Step-by-Step Execution

1. Discover the TAXII server and pull a STIX bundle

Start from server discovery rather than hardcoding a collection URL: collection IDs change between feed versions, but discovery always finds the current ones. Then pull the Enterprise ATT&CK collection. Note the critical API detail: Collection.get_objects() returns the parsed envelope as a dict, not an HTTP response, so calling .text on it raises AttributeError.

python3 - <<'EOF' from taxii2client.v21 import Server import json server = Server("https://cti-taxii.mitre.org/taxii2/") api_root = server.api_roots[0] print("collections:") for c in api_root.collections: print(" -", c.title) collection = [c for c in api_root.collections if "enterprise-attack" in c.title.lower()][0] bundle = collection.get_objects() # parsed dict, NOT a response object assert bundle["type"] == "bundle" open("stix-bundle.json", "w").write(json.dumps(bundle)) from collections import Counter types = Counter(o["type"] for o in bundle["objects"]) print(f"saved {len(bundle['objects'])} objects:", dict(types)) print("more pages available:", bundle.get("more", False)) EOF

Expected output:

collections:
 - Enterprise ATT&CK
 - ...
saved 128 objects: {'attack-pattern': 42, 'malware': 18, 'indicator': 35, 'relationship': 33}
more pages available: False

If more is True, the collection is larger than one page: re-request get_objects() and append each page's objects until more is False, otherwise the import silently drops the tail of the feed. Common mistake: treating the bundle as a flat IOC list. STIX bundles mix indicators, malware, attack patterns, and relationships; the import step filters by type instead of assuming every object is an indicator.

What this teaches: TAXII is the transport and collections are the feeds you subscribe to; the taxii2client Python module (from pip install taxii2-client) speaks TAXII 2.1 and returns STIX bundles as parsed JSON.

2. Convert the STIX bundle into a MISP event

POST /events/add expects a MISP event JSON object, not a raw STIX bundle, so posting stix-bundle.json directly fails validation. Convert first: parse the bundle, extract indicators, and build the event with PyMISP. MISP also ships a built-in STIX importer (Event Actions > Import from ...) backed by the MISP-STIX project, which is the no-code equivalent of the script below.

python3 - <<'EOF' import json, re, os from pymisp import PyMISP, MISPEvent bundle = json.load(open("stix-bundle.json")) misp = PyMISP("https://misp", os.environ["MISP_KEY"], False) event = MISPEvent() event.info = "STIX import 2026-06-17" event.distribution = 0 # organisation only, until triaged event.threat_level_id = 2 event.analysis = 0 added = 0 for obj in bundle.get("objects", []): if obj.get("type") != "indicator": continue m = re.search(r"\[ipv4-addr:value\s*=\s*'([^']+)'\]", obj.get("pattern", "")) if m: event.add_attribute("ip-dst", m.group(1), category="Network activity") added += 1 created = misp.add_event(event) print("created event", created["Event"]["id"], "with", added, "ip-dst attributes") EOF

Expected output:

created event 142 with 31 ip-dst attributes

How to verify: confirm MISP deduplicated and correlated the import instead of just storing it: curl -sk -H "Authorization: $MISP_KEY" -H "Content-Type: application/json" -d '{"returnFormat":"json","value":"203.0.113.45"}' https://misp/attributes/restSearch should return the attribute with a RelatedAttributes section when the same value exists in another event. If related attributes never appear, correlation is disabled on the instance, not broken in your import.

What this teaches: MISP normalizes imported indicators into attributes with correlation built in.

3. Map an indicator to an ATT&CK technique

ATT&CK galaxy tags in MISP carry the misp-galaxy: prefix; the bare mitre-attack-pattern="..." form does not resolve to the galaxy and lands as a dead custom tag. The galaxy must also be updated on the instance (see prerequisites), or the tag silently fails to attach.

curl -sk -H "Authorization: $MISP_KEY" -H "Content-Type: application/json" -d '{"tag": "misp-galaxy:mitre-attack-pattern=\"Phishing - T1566\""}' https://misp/events/addTag/142

Expected output:

{"saved":true,"success":"Tag added to event 142"}

How to verify: fetch the event and check the tag is attached with its galaxy metadata: curl -sk -H "Authorization: $MISP_KEY" https://misp/events/view/142 | python3 -c "import json,sys; e=json.load(sys.stdin)['Event']; print([t['name'] for t in e['Tag']])". Then export the technique list into an ATT&CK Navigator layer so the coverage heatmap reflects the ingested intel rather than a spreadsheet.

What this teaches: ATT&CK tags turn raw indicators into behavior-mapped intelligence analysts can act on.

4. Schedule the pull and enforce freshness

One import is a demo; a pipeline needs a schedule and a staleness alarm. Run the pull script hourly from a systemd timer or cron, and dedupe on STIX object IDs so re-pulls update the event instead of cloning it. Then monitor freshness: if the newest object's modified timestamp is older than the SLA (60 minutes here), the feed broke silently and the page's troubleshooting row for stale feeds applies.

python3 -c " import json from datetime import datetime, timezone bundle = json.load(open('stix-bundle.json')) newest = max(o.get('modified', o.get('created', '')) for o in bundle['objects']) age_min = (datetime.now(timezone.utc) - datetime.fromisoformat(newest)).total_seconds() / 60 print('newest object:', newest, '| age (min):', round(age_min, 1)) assert age_min < 60, 'FEED STALE: investigate the TAXII pull' "

Expected output:

newest object: 2026-06-17T13:42:11.000Z | age (min): 23.4

What this teaches: freshness checks turn a silent feed failure into a paged incident.

How It Works

Threat intelligence has three pillars. MITRE ATT&CK is the behavioral taxonomy: tactics (why) and techniques (how), like T1566 Phishing or T1071 Application Layer Protocol, giving every detection a common name. STIX is the data model: a JSON standard describing indicators, malware, campaigns, and their relationships. TAXII is the transport: a REST API for exchanging STIX objects between producers and consumers.

The pipeline in this lab pulls STIX bundles over TAXII from OSINT sources, imports them into MISP where deduplication and correlation happen automatically, and tags indicators with ATT&CK techniques. The result is intelligence mapped to adversary behavior rather than a pile of disconnected IPs and hashes.

Freshness is the operational challenge: indicators decay as attackers rotate infrastructure. Hourly TAXII pulls keep the feed under 60 minutes stale, and MISP's decay model can retire indicators that stop appearing. Intelligence that is not refreshed becomes a source of false positives.

Validation & Testing

Export the mapped techniques into ATT&CK Navigator and confirm the heatmap reflects ingested intel. Pass criteria: every imported IOC carries a technique tag and the coverage layer renders without orphan indicators.

Troubleshooting

SymptomLikely causeFix
TAXII connection fails with SSL errorsServer certificate not trusted by the clientVerify the server URL and install the right CA bundle; never disable verification in production
Import creates thousands of duplicatesThe same feed imported twice or overlapping collectionsEnable MISP correlation and deduplicate on value plus type before import
No ATT&CK tags on imported indicatorsThe STIX bundle lacks technique mappingsEnrich with the ATT&CK galaxy in MISP or tag manually during triage
Feed goes stale silentlyThe scheduled pull broke and nobody noticedMonitor the last-pull timestamp and alert when it exceeds the freshness SLA
MISP API returns 403The API key lacks event-add permissionCheck the key's role and regenerate with the right permissions
Advanced: Troubleshooting
  • TAXII auth fails: confirm API root and collection IDs; some feeds require basic auth.
  • Duplicate events: enable MISP correlation and deduplicate on indicator value.
  • Stale intel: set feed pull cadence and expire indicators past their sighting window.

Key Results

  • Mapped ingested intel to 18 MITRE ATT&CK techniques across 5 tactics.
  • Automated hourly TAXII pulls keeping indicators under 60 minutes stale.
  • Deduplicated 100% of imported IOCs via MISP correlation.
  • Produced a Navigator coverage heatmap exposing 3 detection gaps.

Key Takeaways

  • ATT&CK names the behavior, STIX models the data, TAXII moves it: learn all three roles.
  • Correlate and deduplicate on import; raw feeds are noisy by design.
  • Freshness is a metric: alert when pulls stop, not when analysts complain.
  • Behavior-mapped intel drives detections; bare indicators only drive blocks.

Practice Challenges

Challenge 1: Build a detection from intel

Goal: Take one imported indicator with a T1071 tag and write a Splunk detection for it.

Hint: Search proxy or firewall logs for the indicator value and tag the alert with the technique ID.

Challenge 2: Visualize coverage

Goal: Export your MISP-tagged techniques into the ATT&CK Navigator and identify the least-covered tactic.

Hint: Use the Navigator's import from MISP export and aggregate by tactic.

Expected output:

(no output on success)