Implementing STIX/TAXII Feed Integration
Overview
STIX (Structured Threat Information eXpression) and TAXII (Trusted Automated eXchange of Intelligence Information) are OASIS open standards for representing and transporting cyber threat intelligence. This skill covers implementing a STIX/TAXII 2.1 feed consumer and producer using Python, configuring TAXII server discovery, collection management, polling for new intelligence, parsing STIX 2.1 objects, and integrating feeds into SIEM and TIP platforms.
When to Use
- When deploying or configuring implementing stix taxii feed integration capabilities in your environment
- When establishing security controls aligned to compliance requirements
- When building or improving security architecture for this domain
- When conducting security assessments that require this implementation
Prerequisites
- Python 3.9+ with
taxii2-client, stix2, cti-taxii-client libraries
- Understanding of STIX 2.1 data model (SDOs, SCOs, SROs)
- Understanding of TAXII 2.1 protocol (discovery, API roots, collections)
- Network access to TAXII servers (MITRE ATT&CK TAXII, Anomali STAXX)
- Optional: medallion for running a local TAXII 2.1 server
Key Concepts
TAXII 2.1 Architecture
TAXII defines a RESTful API with three service types:
- Discovery: Returns information about available API roots
- API Root: Contains collections and serves as the main interaction point
- Collection: A logical grouping of STIX objects accessible via GET/POST
STIX 2.1 Object Model
STIX objects are categorized as:
- SDOs (STIX Domain Objects): Indicator, Malware, Threat Actor, Campaign, Attack Pattern, Tool, Infrastructure, Vulnerability, Identity, Location, Note, Opinion, Report, Grouping
- SCOs (STIX Cyber Observables): IPv4-Addr, Domain-Name, URL, File, Email-Addr, Process, Network-Traffic, Artifact
- SROs (STIX Relationship Objects): Relationship, Sighting
- Meta Objects: Marking Definition (TLP), Language Content, Extension Definition
STIX Bundle
A Bundle is a collection of STIX objects transmitted together. Bundles have a unique ID and contain an array of objects. TAXII collections serve bundles in response to GET requests.
Workflow
Step 1: TAXII Server Discovery
from taxii2client.v21 import Server, Collection, as_pages
# Connect to MITRE ATT&CK TAXII server
server = Server("https://cti-taxii.mitre.org/taxii2/", user="", password="")
print(f"Title: {server.title}")
print(f"Description: {server.description}")
# List API roots
for api_root in server.api_roots:
print(f"\nAPI Root: {api_root.title}")
print(f" URL: {api_root.url}")
# List collections
for collection in api_root.collections:
print(f" Collection: {collection.title} (ID: {collection.id})")
print(f" Can Read: {collection.can_read}")
print(f" Can Write: {collection.can_write}")
Step 2: Fetch STIX Objects from Collection
from taxii2client.v21 import Collection, as_pages
import json
# Connect to Enterprise ATT&CK collection
ENTERPRISE_ATTACK_ID = "95ecc380-afe9-11e4-9b6c-751b66dd541e"
collection = Collection(
f"https://cti-taxii.mitre.org/stix/collections/{ENTERPRISE_ATTACK_ID}/",
user="",
password="",
)
print(f"Collection: {collection.title}")
# Fetch all objects (paginated)
all_objects = []
for envelope in as_pages(collection.get_objects, per_request=50):
objects = envelope.get("objects", [])
all_objects.extend(objects)
print(f" Fetched {len(objects)} objects (total: {len(all_objects)})")
print(f"\nTotal objects retrieved: {len(all_objects)}")
# Categorize by type
type_counts = {}
for obj in all_objects:
obj_type = obj.get("type", "unknown")
type_counts[obj_type] = type_counts.get(obj_type, 0) + 1
for obj_type, count in sorted(type_counts.items()):
print(f" {obj_type}: {count}")
Step 3: Parse STIX 2.1 Objects with stix2 Library
from stix2 import parse, Filter, MemoryStore
# Load objects into a MemoryStore for querying
store = MemoryStore(stix_data=all_objects)
# Query for all indicators
indicators = store.query([Filter("type", "=", "indicator")])
print(f"Indicators: {len(indicators)}")
for ind in indicators[:5]:
print(f" {ind.name}: {ind.pattern}")
# Query for malware
malware_list = store.query([Filter("type", "=", "malware")])
print(f"\nMalware families: {len(malware_list)}")
# Query for threat actors
actors = store.query([Filter("type", "=", "intrusion-set")])
print(f"Threat actors: {len(actors)}")
# Find relationships for a specific object
def get_related(store, source_id):
relationships = store.query([
Filter("type", "=", "relationship"),
Filter("source_ref", "=", source_id),
])
return relationships
# Example: Get all techniques used by APT28
apt28 = store.query([
Filter("type", "=", "intrusion-set"),
Filter("name", "=", "APT28"),
])
if apt28:
rels = get_related(store, apt28[0].id)
for rel in rels:
target = store.get(rel.target_ref)
if target:
print(f" {rel.relationship_type} -> {target.name} ({target.type})")
Step 4: Implement Custom TAXII Consumer
from taxii2client.v21 import Collection, as_pages
from stix2 import parse, Bundle
from datetime import datetime, timedelta
import json
class TAXIIConsumer:
"""Consume STIX/TAXII 2.1 feeds and extract IOCs."""
def __init__(self, collection_url, user="", password=""):
self.collection = Collection(collection_url, user=user, password=password)
self.last_poll = None
def poll_new_objects(self, added_after=None):
"""Poll for objects added after a specific timestamp."""
if added_after is None:
added_after = (
self.last_poll or
(datetime.utcnow() - timedelta(days=1)).strftime(
"%Y-%m-%dT%H:%M:%S.000Z"
)
)
all_objects = []
kwargs = {"added_after": added_after}
for envelope in as_pages(
self.collection.get_objects, per_request=100, **kwargs
):
objects = envelope.get("objects", [])
all_objects.extend(objects)
self.last_poll = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z")
return all_objects
def extract_indicators(self, objects):
"""Extract actionable indicators from STIX objects."""
indicators = []
for obj in objects:
if obj.get("type") == "indicator":
indicators.append({
"id": obj.get("id"),
"name": obj.get("name", ""),
"pattern": obj.get("pattern", ""),
"pattern_type": obj.get("pattern_type", ""),
"valid_from": obj.get("valid_from", ""),
"valid_until": obj.get("valid_until", ""),
"indicator_types": obj.get("indicator_types", []),
"confidence": obj.get("confidence", 0),
"labels": obj.get("labels", []),
})
return indicators
def extract_observables(self, objects):
"""Extract STIX Cyber Observables."""
observables = []
observable_types = {
"ipv4-addr", "ipv6-addr", "domain-name", "url",
"file", "email-addr", "network-traffic",
}
for obj in objects:
if obj.get("type") in observable_types:
observables.append({
"type": obj["type"],
"value": obj.get("value", ""),
"id": obj.get("id"),
})
return observables
# Usage
consumer = TAXIIConsumer(
f"https://cti-taxii.mitre.org/stix/collections/{ENTERPRISE_ATTACK_ID}/"
)
new_objects = consumer.poll_new_objects()
indicators = consumer.extract_indicators(new_objects)
print(f"New indicators: {len(indicators)}")
Step 5: Set Up Local TAXII Server with Medallion
# medallion configuration (medallion.conf)
TAXII_CONFIG = {
"backend": {
"module_class": "MemoryBackend",
},
"users": {
"admin": "admin_password",
"readonly": "readonly_password",
},
"taxii": {
"max_content_length": 10485760,
},
}
# Run medallion server:
# pip install medallion
# python -m medallion --config medallion.conf --port 5000
# Add objects to local TAXII server
import requests
def push_to_taxii(server_url, collection_id, stix_bundle, user, password):
"""Push STIX bundle to a TAXII 2.1 collection."""
url = f"{server_url}/collections/{collection_id}/objects/"
headers = {
"Content-Type": "application/stix+json;version=2.1",
"Accept": "application/taxii+json;version=2.1",
}
response = requests.post(
url,
json=stix_bundle,
headers=headers,
auth=(user, password),
timeout=30,
)
return response.json()
Validation Criteria
- TAXII server discovery returns valid API roots and collections
- STIX objects fetched and parsed correctly from TAXII collections
- Indicators extracted with valid STIX patterns
- Pagination handled correctly for large collections
- Consumer tracks polling state for incremental updates
- Local TAXII server accepts and serves STIX bundles
References
1---2name: implementing-stix-taxii-feed-integration3description: Implements a STIX 2.1/TAXII 2.1 threat-intelligence feed consumer and producer in Python, covering TAXII server discovery, collection polling, parsing STIX bundles with the stix2 library, and standing up a local TAXII server with Medallion. Use when integrating a STIX/TAXII CTI feed into a SIEM or TIP, writing a TAXII client to poll for new indicators, or setting up TAXII collections for indicator exchange.4license: Apache-2.05---6# Implementing STIX/TAXII Feed Integration
7
8## Overview
9
10STIX (Structured Threat Information eXpression) and TAXII (Trusted Automated eXchange of Intelligence Information) are OASIS open standards for representing and transporting cyber threat intelligence. This skill covers implementing a STIX/TAXII 2.1 feed consumer and producer using Python, configuring TAXII server discovery, collection management, polling for new intelligence, parsing STIX 2.1 objects, and integrating feeds into SIEM and TIP platforms.
11
12
13## When to Use
14
15- When deploying or configuring implementing stix taxii feed integration capabilities in your environment
16- When establishing security controls aligned to compliance requirements
17- When building or improving security architecture for this domain
18- When conducting security assessments that require this implementation
19
20## Prerequisites
21
22- Python 3.9+ with `taxii2-client`, `stix2`, `cti-taxii-client` libraries
23- Understanding of STIX 2.1 data model (SDOs, SCOs, SROs)
24- Understanding of TAXII 2.1 protocol (discovery, API roots, collections)
25- Network access to TAXII servers (MITRE ATT&CK TAXII, Anomali STAXX)
26- Optional: medallion for running a local TAXII 2.1 server
27
28## Key Concepts
29
30### TAXII 2.1 Architecture
31
32TAXII defines a RESTful API with three service types:
33- **Discovery**: Returns information about available API roots
34- **API Root**: Contains collections and serves as the main interaction point
35- **Collection**: A logical grouping of STIX objects accessible via GET/POST
36
37### STIX 2.1 Object Model
38
39STIX objects are categorized as:
40- **SDOs (STIX Domain Objects)**: Indicator, Malware, Threat Actor, Campaign, Attack Pattern, Tool, Infrastructure, Vulnerability, Identity, Location, Note, Opinion, Report, Grouping
41- **SCOs (STIX Cyber Observables)**: IPv4-Addr, Domain-Name, URL, File, Email-Addr, Process, Network-Traffic, Artifact
42- **SROs (STIX Relationship Objects)**: Relationship, Sighting
43- **Meta Objects**: Marking Definition (TLP), Language Content, Extension Definition
44
45### STIX Bundle
46
47A Bundle is a collection of STIX objects transmitted together. Bundles have a unique ID and contain an array of objects. TAXII collections serve bundles in response to GET requests.
48
49## Workflow
50
51### Step 1: TAXII Server Discovery
52
53```python
54from taxii2client.v21 import Server, Collection, as_pages
55
56# Connect to MITRE ATT&CK TAXII server
57server = Server("https://cti-taxii.mitre.org/taxii2/", user="", password="")
58
59print(f"Title: {server.title}")
60print(f"Description: {server.description}")
61
62# List API roots
63for api_root in server.api_roots:
64 print(f"\nAPI Root: {api_root.title}")
65 print(f" URL: {api_root.url}")
66
67 # List collections
68 for collection in api_root.collections:
69 print(f" Collection: {collection.title} (ID: {collection.id})")
70 print(f" Can Read: {collection.can_read}")
71 print(f" Can Write: {collection.can_write}")
72```
73
74### Step 2: Fetch STIX Objects from Collection
75
76```python
77from taxii2client.v21 import Collection, as_pages
78import json
79
80# Connect to Enterprise ATT&CK collection
81ENTERPRISE_ATTACK_ID = "95ecc380-afe9-11e4-9b6c-751b66dd541e"
82collection = Collection(
83 f"https://cti-taxii.mitre.org/stix/collections/{ENTERPRISE_ATTACK_ID}/",
84 user="",
85 password="",
86)
87
88print(f"Collection: {collection.title}")
89
90# Fetch all objects (paginated)
91all_objects = []
92for envelope in as_pages(collection.get_objects, per_request=50):
93 objects = envelope.get("objects", [])
94 all_objects.extend(objects)
95 print(f" Fetched {len(objects)} objects (total: {len(all_objects)})")
96
97print(f"\nTotal objects retrieved: {len(all_objects)}")
98
99# Categorize by type
100type_counts = {}
101for obj in all_objects:
102 obj_type = obj.get("type", "unknown")
103 type_counts[obj_type] = type_counts.get(obj_type, 0) + 1
104
105for obj_type, count in sorted(type_counts.items()):
106 print(f" {obj_type}: {count}")
107```
108
109### Step 3: Parse STIX 2.1 Objects with stix2 Library
110
111```python
112from stix2 import parse, Filter, MemoryStore
113
114# Load objects into a MemoryStore for querying
115store = MemoryStore(stix_data=all_objects)
116
117# Query for all indicators
118indicators = store.query([Filter("type", "=", "indicator")])
119print(f"Indicators: {len(indicators)}")
120
121for ind in indicators[:5]:
122 print(f" {ind.name}: {ind.pattern}")
123
124# Query for malware
125malware_list = store.query([Filter("type", "=", "malware")])
126print(f"\nMalware families: {len(malware_list)}")
127
128# Query for threat actors
129actors = store.query([Filter("type", "=", "intrusion-set")])
130print(f"Threat actors: {len(actors)}")
131
132# Find relationships for a specific object
133def get_related(store, source_id):
134 relationships = store.query([
135 Filter("type", "=", "relationship"),
136 Filter("source_ref", "=", source_id),
137 ])
138 return relationships
139
140# Example: Get all techniques used by APT28
141apt28 = store.query([
142 Filter("type", "=", "intrusion-set"),
143 Filter("name", "=", "APT28"),
144])
145if apt28:
146 rels = get_related(store, apt28[0].id)
147 for rel in rels:
148 target = store.get(rel.target_ref)
149 if target:
150 print(f" {rel.relationship_type} -> {target.name} ({target.type})")
151```
152
153### Step 4: Implement Custom TAXII Consumer
154
155```python
156from taxii2client.v21 import Collection, as_pages
157from stix2 import parse, Bundle
158from datetime import datetime, timedelta
159import json
160
161class TAXIIConsumer:
162 """Consume STIX/TAXII 2.1 feeds and extract IOCs."""
163
164 def __init__(self, collection_url, user="", password=""):
165 self.collection = Collection(collection_url, user=user, password=password)
166 self.last_poll = None
167
168 def poll_new_objects(self, added_after=None):
169 """Poll for objects added after a specific timestamp."""
170 if added_after is None:
171 added_after = (
172 self.last_poll or
173 (datetime.utcnow() - timedelta(days=1)).strftime(
174 "%Y-%m-%dT%H:%M:%S.000Z"
175 )
176 )
177
178 all_objects = []
179 kwargs = {"added_after": added_after}
180
181 for envelope in as_pages(
182 self.collection.get_objects, per_request=100, **kwargs
183 ):
184 objects = envelope.get("objects", [])
185 all_objects.extend(objects)
186
187 self.last_poll = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z")
188 return all_objects
189
190 def extract_indicators(self, objects):
191 """Extract actionable indicators from STIX objects."""
192 indicators = []
193 for obj in objects:
194 if obj.get("type") == "indicator":
195 indicators.append({
196 "id": obj.get("id"),
197 "name": obj.get("name", ""),
198 "pattern": obj.get("pattern", ""),
199 "pattern_type": obj.get("pattern_type", ""),
200 "valid_from": obj.get("valid_from", ""),
201 "valid_until": obj.get("valid_until", ""),
202 "indicator_types": obj.get("indicator_types", []),
203 "confidence": obj.get("confidence", 0),
204 "labels": obj.get("labels", []),
205 })
206 return indicators
207
208 def extract_observables(self, objects):
209 """Extract STIX Cyber Observables."""
210 observables = []
211 observable_types = {
212 "ipv4-addr", "ipv6-addr", "domain-name", "url",
213 "file", "email-addr", "network-traffic",
214 }
215 for obj in objects:
216 if obj.get("type") in observable_types:
217 observables.append({
218 "type": obj["type"],
219 "value": obj.get("value", ""),
220 "id": obj.get("id"),
221 })
222 return observables
223
224
225# Usage
226consumer = TAXIIConsumer(
227 f"https://cti-taxii.mitre.org/stix/collections/{ENTERPRISE_ATTACK_ID}/"
228)
229new_objects = consumer.poll_new_objects()
230indicators = consumer.extract_indicators(new_objects)
231print(f"New indicators: {len(indicators)}")
232```
233
234### Step 5: Set Up Local TAXII Server with Medallion
235
236```python
237# medallion configuration (medallion.conf)
238TAXII_CONFIG = {
239 "backend": {
240 "module_class": "MemoryBackend",
241 },
242 "users": {
243 "admin": "admin_password",
244 "readonly": "readonly_password",
245 },
246 "taxii": {
247 "max_content_length": 10485760,
248 },
249}
250
251# Run medallion server:
252# pip install medallion
253# python -m medallion --config medallion.conf --port 5000
254
255# Add objects to local TAXII server
256import requests
257
258def push_to_taxii(server_url, collection_id, stix_bundle, user, password):
259 """Push STIX bundle to a TAXII 2.1 collection."""
260 url = f"{server_url}/collections/{collection_id}/objects/"
261 headers = {
262 "Content-Type": "application/stix+json;version=2.1",
263 "Accept": "application/taxii+json;version=2.1",
264 }
265 response = requests.post(
266 url,
267 json=stix_bundle,
268 headers=headers,
269 auth=(user, password),
270 timeout=30,
271 )
272 return response.json()
273```
274
275## Validation Criteria
276
277- TAXII server discovery returns valid API roots and collections
278- STIX objects fetched and parsed correctly from TAXII collections
279- Indicators extracted with valid STIX patterns
280- Pagination handled correctly for large collections
281- Consumer tracks polling state for incremental updates
282- Local TAXII server accepts and serves STIX bundles
283
284## References
285
286- [STIX 2.1 Specification](https://docs.oasis-open.org/cti/stix/v2.1/stix-v2.1.html)
287- [TAXII 2.1 Specification](https://docs.oasis-open.org/cti/taxii/v2.1/taxii-v2.1.html)
288- [taxii2-client PyPI](https://pypi.org/project/taxii2-client/)
289- [stix2 Python Library](https://stix2.readthedocs.io/)
290- [MITRE ATT&CK TAXII Server](https://cti-taxii.mitre.org/taxii2/)
291- [Medallion TAXII Server](https://github.com/oasis-open/cti-taxii-server)