1 Introduction
Threat intelligence is only useful if it reaches the tools that can act on it. Copying IOCs from a PDF into a firewall by hand is slow, error-prone and loses all the context: who is behind the activity, which malware, how confident the source is, who may see it. Two OASIS open standards solve this problem:
STIX 2.1 — the language
Structured Threat Information Expression: a JSON format to describe threat actors, malware, techniques, indicators, observations and the relationships between them, as a graph.
TAXII 2.1 — the transport
Trusted Automated Exchange of Intelligence Information: a REST API over HTTPS to publish and retrieve STIX content from servers, organised in collections.
Analogy: STIX is the letter (with a standard structure every recipient can read), TAXII is the postal service. Both reached version 2.1 as OASIS Standards in 2021, and they are what sits behind most "TAXII feeds" offered by ISACs, CERTs, vendors and platforms such as MISP or OpenCTI.
STIX 1.x (XML) and TAXII 1.x are obsolete and incompatible with version 2. If a partner still sends XML, you will need a converter.
2 Object Model
Everything in STIX is a JSON object with a type and a unique
id of the form type--UUID,
e.g. malware--481ba5c9-2b13-47e5-87d7-c2464ade08ec.
Objects fall into three main families.
SDOs — STIX Domain Objects (the "nouns" of CTI)
STIX 2.1 defines 18 SDOs. The ones you will meet every day:
| Type | Describes | Required (besides common properties) |
|---|---|---|
| indicator | A detection pattern for suspicious activity | pattern, pattern_type, valid_from |
| malware | A malware family or a specific sample | is_family (+ name if family) |
| threat-actor | Individuals or groups acting with malicious intent | name |
| intrusion-set | A cluster of activity attributed to one (often unknown) actor, e.g. "APT29" | name |
| attack-pattern | A TTP, typically an ATT&CK technique | name |
| campaign | A set of attacks over a period with a common goal | name |
| vulnerability | A flaw, usually referenced by CVE | name |
| identity | An organization, person or sector (authors, victims) | name |
| report | A published report grouping other objects | name, published, object_refs |
The others are course-of-action, grouping, infrastructure, location, malware-analysis, note, observed-data, opinion and tool. Every SDO carries the common properties type, spec_version ("2.1"), id, created and modified.
SROs — relationships (the "verbs")
relationship links two objects with a relationship_type:
- indicator → indicates → malware
- threat-actor → uses → malware / attack-pattern
- campaign → attributed-to → threat-actor
- malware → targets → identity / vulnerability
sighting says "this indicator, malware or actor was seen here, N times, between these dates". Sightings are how defenders give feedback to producers.
SCOs — cyber-observables (the "facts")
Technical things observed on a system or network, with no judgement attached:
- file — hashes, name, size
- ipv4-addr / ipv6-addr — value
- domain-name, url, email-addr
- process, network-traffic, windows-registry-key, mutex, x509-certificate…
SCOs have no created/modified, and their IDs should be deterministic UUIDv5 computed from key properties, so the same IP always gets the same ID.
Observable vs indicator: ipv4-addr 203.0.113.45 is a fact. An indicator whose pattern matches that IP is a claim ("this IP is malicious") with a validity period, a confidence and an author. That distinction is the core of STIX.
3 Pattern Language
The pattern of an indicator is written in the STIX Patterning language ("pattern_type": "stix"). It describes which observables must be seen for the indicator to match. The building block is a comparison expression object-type:property operator value, wrapped in square brackets to form an observation expression.
# One SHA-256 hash
[file:hashes.'SHA-256' = '9c165ab24690c682be401bdaafca19fd5124eb9b9f08dd495de87d2e0c80e652']
# Domain OR IP: two observations, either one is enough
[domain-name:value = 'cdn-update.example'] OR [ipv4-addr:value = '203.0.113.45']
# Two conditions on the SAME object
[file:name = 'upd.dll' AND file:size < 300000]
# Wildcards, CIDR and regular expressions
[domain-name:value LIKE '%.cdn-update.example']
[ipv4-addr:value ISSUBSET '203.0.113.0/24']
[process:command_line MATCHES '^powershell.*-enc']
# Sequence + time window: the file, then the domain, within 5 minutes
([file:hashes.'SHA-256' = 'fb18cd2a74c578b056a81a207cfe4b93d412c707c7f6751c0d4ea32219da4bf2'] FOLLOWEDBY [domain-name:value = 'cdn-update.example']) WITHIN 300 SECONDS
# Repetition: 5 connections to the C2 within 10 minutes
[network-traffic:dst_ref.value = '203.0.113.45' AND network-traffic:dst_port = 443] REPEATS 5 TIMES WITHIN 600 SECONDS
| Element | Values |
|---|---|
| Comparison operators | = != > < >= <= IN LIKE MATCHES ISSUBSET ISSUPERSET, NOT to negate, and EXISTS (prefix: [EXISTS windows-registry-key:values]) |
| Inside brackets | AND, OR — with AND, all comparisons must match the same object |
| Between brackets | AND, OR, FOLLOWEDBY |
| Qualifiers | WITHIN n SECONDS, REPEATS n TIMES, START t'…' STOP t'…' |
4 A Full Bundle
Objects travel in a bundle: a wrapper with "type": "bundle", an ID and an objects array (the bundle itself is not a STIX object and has no spec_version). The bundle below describes a fictional phishing campaign: an actor, its malware, the technique used, two indicators, the observables, the relationships, and a sighting from a SOC. It is valid STIX 2.1 JSON.
{
"type": "bundle",
"id": "bundle--e558bf9b-eb83-400b-8375-5cc6d2c587e0",
"objects": [
{
"type": "identity",
"spec_version": "2.1",
"id": "identity--2ca7f0b5-433d-4e29-9e91-db1d737c4bcb",
"created": "2026-10-14T09:00:00.000Z",
"modified": "2026-10-14T09:00:00.000Z",
"name": "Example SOC (mlab Academy)",
"identity_class": "organization"
},
{
"type": "marking-definition",
"spec_version": "2.1",
"id": "marking-definition--bab4a63c-aed9-4cf5-a766-dfca5abac2bb",
"created": "2022-10-01T00:00:00.000Z",
"name": "TLP:GREEN",
"extensions": {
"extension-definition--60a3c5c5-0d10-413e-aab3-9e08dde9e88d": {
"extension_type": "property-extension",
"tlp_2_0": "green"
}
}
},
{
"type": "threat-actor",
"spec_version": "2.1",
"id": "threat-actor--ea786151-92a0-45df-a007-f6f8ae65fd23",
"created": "2026-10-14T09:12:00.000Z",
"modified": "2026-10-14T09:12:00.000Z",
"created_by_ref": "identity--2ca7f0b5-433d-4e29-9e91-db1d737c4bcb",
"name": "Grey Ledger",
"description": "Fictional financially motivated actor used for training.",
"threat_actor_types": ["crime-syndicate"],
"primary_motivation": "personal-gain",
"object_marking_refs": ["marking-definition--bab4a63c-aed9-4cf5-a766-dfca5abac2bb"]
},
{
"type": "malware",
"spec_version": "2.1",
"id": "malware--481ba5c9-2b13-47e5-87d7-c2464ade08ec",
"created": "2026-10-14T09:12:00.000Z",
"modified": "2026-10-14T09:12:00.000Z",
"created_by_ref": "identity--2ca7f0b5-433d-4e29-9e91-db1d737c4bcb",
"name": "LedgerLoader",
"is_family": true,
"malware_types": ["downloader", "backdoor"],
"object_marking_refs": ["marking-definition--bab4a63c-aed9-4cf5-a766-dfca5abac2bb"]
},
{
"type": "attack-pattern",
"spec_version": "2.1",
"id": "attack-pattern--a2b2da60-af37-4e61-b37b-cc0adfd6b0ea",
"created": "2026-10-14T09:12:00.000Z",
"modified": "2026-10-14T09:12:00.000Z",
"name": "Spearphishing Attachment",
"external_references": [
{
"source_name": "mitre-attack",
"external_id": "T1566.001",
"url": "https://attack.mitre.org/techniques/T1566/001/"
}
],
"kill_chain_phases": [
{ "kill_chain_name": "mitre-attack", "phase_name": "initial-access" }
]
},
{
"type": "campaign",
"spec_version": "2.1",
"id": "campaign--660a8186-7fe0-4645-b44d-2ea1ed3a806a",
"created": "2026-10-14T09:12:00.000Z",
"modified": "2026-10-14T09:12:00.000Z",
"created_by_ref": "identity--2ca7f0b5-433d-4e29-9e91-db1d737c4bcb",
"name": "Fake invoice wave - October 2026",
"first_seen": "2026-10-06T00:00:00Z",
"object_marking_refs": ["marking-definition--bab4a63c-aed9-4cf5-a766-dfca5abac2bb"]
},
{
"type": "indicator",
"spec_version": "2.1",
"id": "indicator--18a84965-183d-433e-a90f-79569c6eaf77",
"created": "2026-10-14T09:12:00.000Z",
"modified": "2026-10-14T09:12:00.000Z",
"created_by_ref": "identity--2ca7f0b5-433d-4e29-9e91-db1d737c4bcb",
"name": "Invoice_4471.docm (LedgerLoader dropper)",
"indicator_types": ["malicious-activity"],
"pattern": "[file:hashes.'SHA-256' = '9c165ab24690c682be401bdaafca19fd5124eb9b9f08dd495de87d2e0c80e652']",
"pattern_type": "stix",
"valid_from": "2026-10-14T09:12:00Z",
"object_marking_refs": ["marking-definition--bab4a63c-aed9-4cf5-a766-dfca5abac2bb"]
},
{
"type": "indicator",
"spec_version": "2.1",
"id": "indicator--11125fd5-ae7d-471a-83f7-9c397123b080",
"created": "2026-10-14T09:12:00.000Z",
"modified": "2026-10-14T09:12:00.000Z",
"created_by_ref": "identity--2ca7f0b5-433d-4e29-9e91-db1d737c4bcb",
"name": "LedgerLoader payload server",
"indicator_types": ["malicious-activity"],
"pattern": "[domain-name:value = 'cdn-update.example'] OR [ipv4-addr:value = '203.0.113.45']",
"pattern_type": "stix",
"valid_from": "2026-10-14T09:12:00Z",
"valid_until": "2026-11-14T09:12:00Z",
"object_marking_refs": ["marking-definition--bab4a63c-aed9-4cf5-a766-dfca5abac2bb"]
},
{
"type": "domain-name",
"spec_version": "2.1",
"id": "domain-name--a584802c-c4fa-5a2d-8623-483171fa04b8",
"value": "cdn-update.example",
"resolves_to_refs": ["ipv4-addr--cb9d70c5-d244-531c-9efb-bcae57adc793"]
},
{
"type": "ipv4-addr",
"spec_version": "2.1",
"id": "ipv4-addr--cb9d70c5-d244-531c-9efb-bcae57adc793",
"value": "203.0.113.45"
},
{
"type": "relationship",
"spec_version": "2.1",
"id": "relationship--830010b0-0d38-4ea0-87c8-5067c88405f1",
"created": "2026-10-14T09:12:00.000Z",
"modified": "2026-10-14T09:12:00.000Z",
"relationship_type": "indicates",
"source_ref": "indicator--18a84965-183d-433e-a90f-79569c6eaf77",
"target_ref": "malware--481ba5c9-2b13-47e5-87d7-c2464ade08ec"
},
{
"type": "relationship",
"spec_version": "2.1",
"id": "relationship--4d7b9f9f-a0b0-4729-bd87-4b8545a365ac",
"created": "2026-10-14T09:12:00.000Z",
"modified": "2026-10-14T09:12:00.000Z",
"relationship_type": "uses",
"source_ref": "threat-actor--ea786151-92a0-45df-a007-f6f8ae65fd23",
"target_ref": "malware--481ba5c9-2b13-47e5-87d7-c2464ade08ec"
},
{
"type": "relationship",
"spec_version": "2.1",
"id": "relationship--58ff3faa-2029-40f9-bf69-03e9905da6ff",
"created": "2026-10-14T09:12:00.000Z",
"modified": "2026-10-14T09:12:00.000Z",
"relationship_type": "uses",
"source_ref": "campaign--660a8186-7fe0-4645-b44d-2ea1ed3a806a",
"target_ref": "attack-pattern--a2b2da60-af37-4e61-b37b-cc0adfd6b0ea"
},
{
"type": "relationship",
"spec_version": "2.1",
"id": "relationship--11fc53ce-a4f7-47f8-91dc-0a91e714f493",
"created": "2026-10-14T09:12:00.000Z",
"modified": "2026-10-14T09:12:00.000Z",
"relationship_type": "attributed-to",
"source_ref": "campaign--660a8186-7fe0-4645-b44d-2ea1ed3a806a",
"target_ref": "threat-actor--ea786151-92a0-45df-a007-f6f8ae65fd23",
"confidence": 60
},
{
"type": "sighting",
"spec_version": "2.1",
"id": "sighting--7c3e9b1a-5d24-4f6e-9a0b-2e8f4c6d1a37",
"created": "2026-10-15T14:03:00.000Z",
"modified": "2026-10-15T14:03:00.000Z",
"created_by_ref": "identity--2ca7f0b5-433d-4e29-9e91-db1d737c4bcb",
"sighting_of_ref": "indicator--11125fd5-ae7d-471a-83f7-9c397123b080",
"where_sighted_refs": ["identity--2ca7f0b5-433d-4e29-9e91-db1d737c4bcb"],
"first_seen": "2026-10-15T13:41:07Z",
"last_seen": "2026-10-15T13:58:22Z",
"count": 3
}
]
}
What to notice
IDs: SDOs and SROs use random UUIDv4; the two SCOs (domain-name, ipv4-addr) use UUIDv5 derived from their value, so another producer describing the same domain gets the same ID.
Timestamps are UTC in RFC 3339 format ending with Z; created and modified are precise to the millisecond. To update an object, keep its ID and increase modified: that is how STIX versions objects.
ATT&CK link: the attack-pattern points to T1566.001 through external_references with source_name: "mitre-attack", the convention every tool understands.
Lifetime: the network indicator has a valid_until one month later. Infrastructure is short-lived; a hash indicator can stay valid much longer.
Confidence: the attribution relationship carries "confidence": 60 (0–100 scale). Attribution is rarely certain; say so in the data.
Don't build bundles by hand in production: libraries such as the OASIS stix2 Python package generate IDs and timestamps for you and validate required properties.
5 TAXII 2.1
A TAXII 2.1 server exposes a small, predictable REST API. Every request must send the header
Accept: application/taxii+json;version=2.1, and
authentication is usually HTTP Basic or an API key over HTTPS.
GET /taxii2/
Discovery: lists the server's API roots.
GET {api-root}/collections/
API root: an independent group of collections (e.g. one per trust group). Collection: a named set of STIX objects, with can_read/can_write permissions.
GET {api-root}/collections/{id}/objects/
Objects: returns an envelope {"more", "next", "objects"}. Filters: added_after, limit, next, match[type], match[id], match[version], match[spec_version]. A POST on the same URL publishes objects (if can_write).
GET {api-root}/collections/{id}/manifest/
Manifest: metadata only (ID, version, date added) to see what changed before downloading.
Hands-on: MITRE's public TAXII server
MITRE publishes ATT&CK itself over TAXII 2.1 without authentication, which makes it a perfect playground. Note the -g (--globoff) option: without it, curl tries to interpret the square brackets of match[type] as a pattern.
# 1. Discovery
curl -s -H 'Accept: application/taxii+json;version=2.1' \
https://attack-taxii.mitre.org/taxii2/
# 2. Collections of the default API root
curl -s -H 'Accept: application/taxii+json;version=2.1' \
https://attack-taxii.mitre.org/api/v21/collections/ | jq -r '.collections[] | "\(.id) \(.title)"'
# 3. Objects of type intrusion-set (ATT&CK groups) from the Enterprise collection
curl -g -s -D headers.txt -H 'Accept: application/taxii+json;version=2.1' \
'https://attack-taxii.mitre.org/api/v21/collections/x-mitre-collection--1f5f1533-f617-4ca8-9ab4-6a02367fa019/objects/?match[type]=intrusion-set&limit=50' \
| jq -r '.more, .next, (.objects[] | .external_references[0].external_id + " " + .name)'
A response looks like this (truncated):
{
"more": true,
"next": "1",
"objects": [
{
"type": "intrusion-set",
"spec_version": "2.1",
"id": "intrusion-set--f29b7c5e-2439-42ad-a86f-9f8984fafae3",
"created": "2018-10-17T00:14:20.652Z",
"modified": "2026-07-31T20:48:51.374Z",
"name": "HEXANE",
"aliases": [
"HEXANE",
"Lyceum",
"Siamesekitten",
"Spirlin"
],
"external_references": [
{
"source_name": "mitre-attack",
"url": "https://attack.mitre.org/groups/G1001",
"external_id": "G1001"
}
]
},
{
"type": "intrusion-set",
"spec_version": "2.1",
"id": "intrusion-set--e6db1e55-b199-4b6b-8633-989345ee45e0",
"created": "2024-08-26T17:39:06.020Z",
"modified": "2026-07-31T18:48:24.240Z",
"name": "Moonstone Sleet",
"aliases": [
"Moonstone Sleet",
"Storm-1789"
],
"external_references": [
{
"source_name": "mitre-attack",
"url": "https://attack.mitre.org/groups/G1036",
"external_id": "G1036"
}
]
}
]
}
ATT&CK groups are published as intrusion-set, not threat-actor: they are clusters of activity, not confirmed identities. When more is true, repeat the request with next=<value> to get the next page. For incremental polling, store the X-TAXII-Date-Added-Last response header and pass it as added_after next time. Against a private server, add -u "$TAXII_USER:$TAXII_PASS" and keep credentials in environment variables, never in scripts.
6 TLP Markings
The Traffic Light Protocol (TLP), maintained by FIRST, tells recipients how far they may share a piece of information. Version 2.0 (August 2022) replaced TLP:WHITE with TLP:CLEAR and added TLP:AMBER+STRICT. In STIX, a marking is a marking-definition object referenced from object_marking_refs. TLP 2.0 markings have fixed IDs published by the OASIS CTI Technical Committee:
| Label | May be shared with | STIX marking-definition ID |
|---|---|---|
| TLP:RED | Named recipients only, e.g. those present in the meeting | marking-definition--e828b379-4e03-4974-9ac4-e53a884c97c1 |
| TLP:AMBER+STRICT | The recipient's organization only | marking-definition--939a9414-2ddd-4d32-a0cd-375ea402b003 |
| TLP:AMBER | The recipient's organization and its clients, on a need-to-know basis | marking-definition--55d920b0-5e8b-4f79-9ee9-91f868d9b421 |
| TLP:GREEN | The recipient's community, but not publicly | marking-definition--bab4a63c-aed9-4cf5-a766-dfca5abac2bb |
| TLP:CLEAR | Anyone, publicly (copyright still applies) | marking-definition--94868c89-83c2-464b-929b-a1a8aa3c8487 |
The STIX 2.1 specification itself only predefines the older TLP 1.0 markings (WHITE, GREEN, AMBER, RED). TLP 2.0 is added through an extension-definition, which is why the marking in the bundle above has an extensions block. Some tools still only understand TLP 1.0: check what your platform expects.
TLP is a social contract, not a technical control: nothing in STIX stops a recipient from forwarding a TLP:RED object. Platforms enforce it by filtering what each user or sharing group can see.
Try it on mlab.sh
Before turning an observable into a STIX indicator, check its reputation: look up IPs, domains, URLs and file hashes on mlab.sh.
Start analyzing on mlab.sh7 In Practice
MISP
Open-source sharing platform, very common among CERTs and ISACs. Its native model is not STIX: information is grouped into events containing attributes (an IP, a hash), objects (groups of attributes), tags and taxonomies (including TLP) and galaxies (including ATT&CK). MISP instances synchronise with each other directly, and import/export STIX 2.1 through the misp-stix library. Conversions are good but not lossless: check the result.
OpenCTI
Open-source CTI platform developed by Filigran (initially with ANSSI and CERT-EU) whose data model is built on STIX 2.1. Everything is a graph of SDOs, SCOs and relationships. Connectors import ATT&CK, MISP events, TAXII collections, CVE data or sandbox results, and OpenCTI can itself expose TAXII collections and live streams to your SIEM or EDR.
From feed to detection
Collect
Your TIP polls TAXII collections and other threat-intel feeds on a schedule, using added_after.
Deduplicate and score
The same IOC arrives from several sources: merge it, keep the best confidence, drop expired indicators (valid_until).
Push to controls
Indicators go to the SIEM (matching), EDR (hash blocking), proxy/DNS (blocklists), respecting TLP.
Feed back
When an indicator fires, create a sighting and share it back. Sightings tell producers which indicators are actually useful.
Indicators without context or expiry
A feed of bare IPs with no valid_until, no relationship and no confidence will flood your SIEM with false positives within months (shared hosting, recycled cloud IPs).
"STIX" that isn't STIX
Missing spec_version, IDs that are not type--UUID, timestamps without Z: run a validator (e.g. the OASIS stix2-validator) before publishing.
Related Modules
What is a threat intel feed?
Threat intelligence feeds: how they work and why they are essential.
What is an IOC?
Clues of a cyberattack: learning to recognize indicators of compromise.
Understanding MITRE ATT&CK
Tactics, techniques and IDs explained, plus how to map an incident or CTI report to ATT&CK and build Navigator layers.
Source: mlab Academy — Cybersecurity Awareness Platform
URL: https://academy.mlab.sh//page/stix-taxii
Module: Sharing threat intel with STIX & TAXII — Detection Engineering
Disclaimer: This content is for awareness purposes only.