npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
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-clientlibraries - 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
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md4.7 KB
API Reference: STIX/TAXII Threat Intelligence Feed Integration
Libraries Used
| Library | Purpose |
|---|---|
taxii2-client |
TAXII 2.0/2.1 client for fetching CTI collections |
stix2 |
Parse and create STIX 2.1 objects (indicators, malware, etc.) |
requests |
HTTP fallback for custom TAXII endpoints |
json |
Serialize and filter STIX bundles |
Installation
pip install taxii2-client stix2 requestsAuthentication
TAXII Server with HTTP Basic Auth
from taxii2client.v21 import Server, Collection
import os
TAXII_URL = os.environ["TAXII_URL"] # e.g., "https://cti-taxii.mitre.org/taxii2/"
server = Server(
TAXII_URL,
user=os.environ.get("TAXII_USER"),
password=os.environ.get("TAXII_PASS"),
)TAXII Server with API Key
from taxii2client.v21 import Server as Server21
server = Server21(
url=TAXII_URL,
headers={"Authorization": f"Bearer {os.environ['TAXII_TOKEN']}"},
)TAXII 2.1 Endpoints
| Endpoint | Description |
|---|---|
GET /taxii2/ |
Server discovery — returns API roots |
GET /{api-root}/ |
API root information |
GET /{api-root}/collections/ |
List available collections |
GET /{api-root}/collections/{id}/ |
Get collection details |
GET /{api-root}/collections/{id}/objects/ |
Get STIX objects from collection |
GET /{api-root}/collections/{id}/manifest/ |
Object manifest (metadata only) |
POST /{api-root}/collections/{id}/objects/ |
Add objects to a collection |
GET /{api-root}/status/{id}/ |
Check status of a POST operation |
Core Operations
Discover Collections
for api_root in server.api_roots:
print(f"API Root: {api_root.title}")
for collection in api_root.collections:
print(f" Collection: {collection.title} ({collection.id})")
print(f" Can read: {collection.can_read}, Can write: {collection.can_write}")Fetch STIX Objects from a Collection
from taxii2client.v21 import Collection
collection = Collection(
f"{TAXII_URL}collections/{collection_id}/",
user=os.environ.get("TAXII_USER"),
password=os.environ.get("TAXII_PASS"),
)
# Get all objects
stix_bundle = collection.get_objects()
# Filter by STIX type
indicators = collection.get_objects(type=["indicator"])
# Filter by time range
from datetime import datetime
recent = collection.get_objects(
added_after=datetime(2025, 1, 1).strftime("%Y-%m-%dT%H:%M:%SZ")
)Parse STIX Objects
import stix2
bundle = stix2.parse(stix_bundle, allow_custom=True)
for obj in bundle.objects:
if obj.type == "indicator":
print(f"Indicator: {obj.name}")
print(f" Pattern: {obj.pattern}")
print(f" Valid: {obj.valid_from} — {getattr(obj, 'valid_until', 'N/A')}")
elif obj.type == "malware":
print(f"Malware: {obj.name} — {obj.malware_types}")
elif obj.type == "attack-pattern":
print(f"TTP: {obj.name}")Extract IOCs from STIX Indicators
import re
def extract_iocs(stix_objects):
iocs = {"ipv4": [], "domain": [], "url": [], "sha256": [], "md5": []}
for obj in stix_objects:
if obj.get("type") != "indicator":
continue
pattern = obj.get("pattern", "")
# IPv4
for ip in re.findall(r"ipv4-addr:value\s*=\s*'([^']+)'", pattern):
iocs["ipv4"].append(ip)
# Domain
for domain in re.findall(r"domain-name:value\s*=\s*'([^']+)'", pattern):
iocs["domain"].append(domain)
# SHA-256
for sha in re.findall(r"file:hashes\.'SHA-256'\s*=\s*'([^']+)'", pattern):
iocs["sha256"].append(sha)
return iocsCreate and Push STIX Objects
indicator = stix2.Indicator(
name="Malicious IP",
pattern="[ipv4-addr:value = '198.51.100.42']",
pattern_type="stix",
valid_from=datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
labels=["malicious-activity"],
)
bundle = stix2.Bundle(objects=[indicator])
collection.add_objects(bundle.serialize())Public TAXII Feeds
| Provider | URL | Content |
|---|---|---|
| MITRE ATT&CK | https://cti-taxii.mitre.org/taxii2/ |
ATT&CK Enterprise, Mobile, ICS |
| AlienVault OTX | OTX API + STIX export | Community threat intel |
| Anomali STAXX | STAXX TAXII endpoint | Curated threat feeds |
Output Format
{
"type": "bundle",
"id": "bundle--a1b2c3d4",
"objects": [
{
"type": "indicator",
"id": "indicator--e5f6a7b8",
"created": "2025-01-15T10:30:00Z",
"name": "Malicious C2 IP",
"pattern": "[ipv4-addr:value = '198.51.100.42']",
"pattern_type": "stix",
"valid_from": "2025-01-15T10:30:00Z",
"labels": ["malicious-activity"]
}
]
}standards.md2.8 KB
Standards and Frameworks Reference
STIX 2.1 Standard (OASIS)
Core Concepts
- STIX Bundle: Top-level container for STIX objects (type: "bundle")
- STIX ID Format:
type--uuid(e.g.,indicator--a1b2c3d4-...) - Versioning: Objects use
modifiedtimestamp for version tracking - Confidence: 0-100 scale for reliability assessment
STIX Domain Objects (SDOs)
| Type | Purpose | Key Properties |
|---|---|---|
| attack-pattern | ATT&CK technique | name, kill_chain_phases |
| campaign | Related intrusion activity | name, first_seen, objective |
| identity | Individuals/orgs | name, identity_class, sectors |
| indicator | Detection pattern | pattern, pattern_type, valid_from |
| infrastructure | Adversary systems | name, infrastructure_types |
| intrusion-set | Threat group | name, aliases, goals |
| malware | Malware family | name, malware_types, is_family |
| note | Analyst annotation | content, object_refs |
| report | CTI document | name, published, object_refs |
| threat-actor | Human adversary | name, threat_actor_types, roles |
| tool | Legitimate software | name, tool_types |
| vulnerability | CVE/weakness | name, external_references |
STIX Patterning Language
[file:hashes.'SHA-256' = 'abc...']
[ipv4-addr:value = '198.51.100.1']
[domain-name:value = 'malware.example.com']
[network-traffic:dst_ref.type = 'ipv4-addr' AND network-traffic:dst_port = 443]
[process:name = 'cmd.exe' AND process:command_line MATCHES '.*powershell.*']TAXII 2.1 Standard (OASIS)
Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
| /taxii2/ | GET | Server discovery |
| /{api-root}/ | GET | API root information |
| /{api-root}/collections/ | GET | List collections |
| /{api-root}/collections/{id}/ | GET | Collection details |
| /{api-root}/collections/{id}/objects/ | GET/POST | Get/add objects |
| /{api-root}/collections/{id}/manifest/ | GET | Object manifest |
| /{api-root}/status/{id}/ | GET | Status of add operation |
HTTP Headers
- Content-Type:
application/stix+json;version=2.1 - Accept:
application/taxii+json;version=2.1
Pagination Parameters
limit: Maximum number of objects per responsenext: Cursor for next pageadded_after: Filter objects by timestamp
Marking Definitions (TLP)
{"definition_type": "tlp", "definition": {"tlp": "clear"}}
{"definition_type": "tlp", "definition": {"tlp": "green"}}
{"definition_type": "tlp", "definition": {"tlp": "amber"}}
{"definition_type": "tlp", "definition": {"tlp": "amber+strict"}}
{"definition_type": "tlp", "definition": {"tlp": "red"}}References
workflows.md2.8 KB
STIX/TAXII Feed Integration Workflows
Workflow 1: TAXII Feed Consumption
[TAXII Discovery] --> [API Root Enumeration] --> [Collection Selection] --> [Object Polling]
|
v
[STIX Parsing] --> [IOC Extraction]
|
v
[SIEM/TIP Ingestion]Steps:
- Discovery: Query TAXII server discovery endpoint for available API roots
- Root Enumeration: List available API roots and their supported features
- Collection Listing: Enumerate collections with read/write permissions
- Incremental Polling: Fetch new objects using added_after timestamp filter
- STIX Parsing: Deserialize JSON into typed STIX objects
- IOC Extraction: Extract indicators, observables, and relationships
- Platform Ingestion: Push to SIEM, MISP, or OpenCTI
Workflow 2: STIX Bundle Production
[IOC Sources] --> [Normalization] --> [STIX Object Creation] --> [Bundle Assembly]
|
v
[TAXII Publication]Steps:
- Source Collection: Gather IOCs from internal analysis, feeds, incident response
- Normalization: Standardize IOC formats and remove duplicates
- Object Creation: Create STIX Indicators, Observables, and Relationships
- TLP Marking: Apply appropriate TLP marking definitions
- Bundle Assembly: Package objects into STIX 2.1 bundles
- TAXII Push: POST bundles to writable TAXII collections
Workflow 3: Multi-Feed Aggregation
[TAXII Feed A] --+
|--> [Deduplication] --> [Correlation] --> [Unified Store]
[TAXII Feed B] --+ |
| v
[STIX File C] ---+ [Dashboard/Alerts]Steps:
- Feed Registration: Configure multiple TAXII and file-based STIX sources
- Parallel Polling: Poll all feeds concurrently with rate limiting
- Deduplication: Remove duplicate objects by STIX ID and modified timestamp
- Correlation: Link related objects across feeds via relationships
- Unified Storage: Store in MemoryStore, FileSystemStore, or database-backed store
- Output: Generate alerts, dashboards, or exports for downstream consumers
Scripts 2
agent.py11.7 KB
#!/usr/bin/env python3
"""STIX/TAXII threat intelligence feed integration agent.
Connects to TAXII 2.0/2.1 servers to discover and consume threat intelligence
feeds in STIX format. Extracts indicators of compromise (IOCs), threat actors,
malware families, and attack patterns from STIX bundles.
"""
import argparse
import json
import os
import sys
from datetime import datetime, timezone
try:
from taxii2client.v20 import Server as Server20, Collection as Collection20
from taxii2client.v21 import Server as Server21, Collection as Collection21
HAS_TAXII_CLIENT = True
except ImportError:
HAS_TAXII_CLIENT = False
try:
from stix2 import parse as stix_parse
HAS_STIX2 = True
except ImportError:
HAS_STIX2 = False
try:
import requests
except ImportError:
requests = None
def connect_taxii_server(url, username=None, password=None, version="2.1"):
"""Connect to a TAXII server and return Server object."""
if not HAS_TAXII_CLIENT:
print("[!] taxii2-client required: pip install taxii2-client", file=sys.stderr)
sys.exit(1)
kwargs = {}
if username and password:
kwargs["user"] = username
kwargs["password"] = password
print(f"[*] Connecting to TAXII {version} server: {url}")
if version == "2.0":
server = Server20(url, **kwargs)
else:
server = Server21(url, **kwargs)
print(f"[+] Connected: {server.title or 'Untitled'}")
return server
def discover_collections(server, version="2.1"):
"""Discover available collections on the TAXII server."""
collections = []
for api_root in server.api_roots:
print(f"[*] API Root: {api_root.title or api_root.url}")
for col in api_root.collections:
col_info = {
"id": col.id,
"title": col.title or "Untitled",
"description": getattr(col, "description", ""),
"can_read": getattr(col, "can_read", True),
"can_write": getattr(col, "can_write", False),
"media_types": getattr(col, "media_types", []),
"api_root": api_root.title or str(api_root.url),
}
collections.append(col_info)
print(f" Collection: {col_info['title']} ({col.id})")
return collections
def fetch_collection_objects(collection, added_after=None, limit=100, obj_type=None):
"""Fetch STIX objects from a TAXII collection."""
kwargs = {}
if added_after:
kwargs["added_after"] = added_after
print(f"[*] Fetching objects from collection: {collection.title or collection.id}")
try:
envelope = collection.get_objects(**kwargs)
except Exception as e:
print(f"[!] Error fetching objects: {e}", file=sys.stderr)
return []
if isinstance(envelope, dict):
objects = envelope.get("objects", [])
elif hasattr(envelope, "objects"):
objects = envelope.objects or []
else:
objects = []
if obj_type:
objects = [o for o in objects if o.get("type") == obj_type]
if limit and len(objects) > limit:
objects = objects[:limit]
print(f"[+] Retrieved {len(objects)} object(s)")
return objects
def extract_indicators(stix_objects):
"""Extract indicators of compromise from STIX objects."""
indicators = []
for obj in stix_objects:
obj_type = obj.get("type", "")
if obj_type == "indicator":
indicators.append({
"type": "indicator",
"id": obj.get("id", ""),
"name": obj.get("name", ""),
"description": obj.get("description", "")[:200],
"pattern": obj.get("pattern", ""),
"pattern_type": obj.get("pattern_type", "stix"),
"valid_from": obj.get("valid_from", ""),
"valid_until": obj.get("valid_until", ""),
"labels": obj.get("labels", []),
"confidence": obj.get("confidence", 0),
"created": obj.get("created", ""),
})
return indicators
def extract_threat_actors(stix_objects):
"""Extract threat actor information from STIX objects."""
actors = []
for obj in stix_objects:
if obj.get("type") == "threat-actor":
actors.append({
"type": "threat-actor",
"id": obj.get("id", ""),
"name": obj.get("name", ""),
"description": obj.get("description", "")[:200],
"aliases": obj.get("aliases", []),
"roles": obj.get("roles", []),
"goals": obj.get("goals", []),
"sophistication": obj.get("sophistication", ""),
"resource_level": obj.get("resource_level", ""),
"primary_motivation": obj.get("primary_motivation", ""),
})
return actors
def extract_malware(stix_objects):
"""Extract malware family info from STIX objects."""
malware = []
for obj in stix_objects:
if obj.get("type") == "malware":
malware.append({
"type": "malware",
"id": obj.get("id", ""),
"name": obj.get("name", ""),
"description": obj.get("description", "")[:200],
"malware_types": obj.get("malware_types", []),
"is_family": obj.get("is_family", False),
"aliases": obj.get("aliases", []),
"capabilities": obj.get("capabilities", []),
})
return malware
def extract_attack_patterns(stix_objects):
"""Extract MITRE ATT&CK patterns from STIX objects."""
patterns = []
for obj in stix_objects:
if obj.get("type") == "attack-pattern":
external_refs = obj.get("external_references", [])
mitre_id = ""
for ref in external_refs:
if ref.get("source_name") in ("mitre-attack", "mitre-mobile-attack"):
mitre_id = ref.get("external_id", "")
break
patterns.append({
"type": "attack-pattern",
"id": obj.get("id", ""),
"name": obj.get("name", ""),
"mitre_id": mitre_id,
"description": obj.get("description", "")[:200],
"kill_chain_phases": obj.get("kill_chain_phases", []),
})
return patterns
def summarize_stix_objects(stix_objects):
"""Group and count STIX objects by type."""
type_counts = {}
for obj in stix_objects:
t = obj.get("type", "unknown")
type_counts[t] = type_counts.get(t, 0) + 1
return type_counts
def format_summary(type_counts, indicators, actors, malware, attack_patterns):
"""Print human-readable summary."""
print(f"\n{'='*60}")
print(f" STIX/TAXII Feed Intelligence Report")
print(f"{'='*60}")
print(f"\n Object Type Distribution:")
for obj_type, count in sorted(type_counts.items(), key=lambda x: -x[1]):
print(f" {obj_type:30s}: {count}")
if indicators:
print(f"\n Indicators ({len(indicators)}):")
for ind in indicators[:10]:
print(f" {ind['name'][:40]:40s} | {ind['pattern_type']:8s} | "
f"{ind['pattern'][:50]}")
if actors:
print(f"\n Threat Actors ({len(actors)}):")
for actor in actors[:10]:
aliases = ", ".join(actor["aliases"][:3]) if actor["aliases"] else ""
print(f" {actor['name']:30s} | {actor['sophistication']:12s} | {aliases}")
if malware:
print(f"\n Malware Families ({len(malware)}):")
for m in malware[:10]:
types = ", ".join(m["malware_types"][:3]) if m["malware_types"] else ""
print(f" {m['name']:30s} | {types}")
if attack_patterns:
print(f"\n ATT&CK Patterns ({len(attack_patterns)}):")
for ap in attack_patterns[:10]:
print(f" {ap.get('mitre_id', 'N/A'):12s} | {ap['name']}")
def main():
parser = argparse.ArgumentParser(
description="STIX/TAXII threat intelligence feed integration agent"
)
parser.add_argument("--server", required=True, help="TAXII server discovery URL")
parser.add_argument("--username", help="TAXII authentication username")
parser.add_argument("--password", help="TAXII authentication password")
parser.add_argument("--version", choices=["2.0", "2.1"], default="2.1",
help="TAXII version (default: 2.1)")
parser.add_argument("--collection-id", help="Specific collection ID to fetch")
parser.add_argument("--added-after", help="Only fetch objects added after date (ISO format)")
parser.add_argument("--type", dest="obj_type",
help="Filter by STIX object type (e.g., indicator, malware)")
parser.add_argument("--limit", type=int, default=500,
help="Max objects to retrieve (default: 500)")
parser.add_argument("--discover-only", action="store_true",
help="Only discover collections, don't fetch objects")
parser.add_argument("--output", "-o", help="Output JSON report path")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
server = connect_taxii_server(args.server, args.username, args.password, args.version)
collections = discover_collections(server, args.version)
if args.discover_only:
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "STIX/TAXII Client",
"server": args.server,
"collections": collections,
}
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"\n[+] Report saved to {args.output}")
else:
print(json.dumps(report, indent=2))
return
# Fetch objects from specified or all readable collections
all_objects = []
for col_info in collections:
if args.collection_id and col_info["id"] != args.collection_id:
continue
if not col_info.get("can_read", True):
continue
try:
if args.version == "2.0":
col = Collection20(
f"{args.server.rstrip('/')}/collections/{col_info['id']}/",
user=args.username, password=args.password
)
else:
col = Collection21(
f"{args.server.rstrip('/')}/collections/{col_info['id']}/",
user=args.username, password=args.password
)
objects = fetch_collection_objects(col, args.added_after, args.limit, args.obj_type)
all_objects.extend(objects)
except Exception as e:
print(f"[!] Error fetching {col_info['title']}: {e}")
type_counts = summarize_stix_objects(all_objects)
indicators = extract_indicators(all_objects)
actors = extract_threat_actors(all_objects)
malware_items = extract_malware(all_objects)
attack_patterns = extract_attack_patterns(all_objects)
format_summary(type_counts, indicators, actors, malware_items, attack_patterns)
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "STIX/TAXII Client",
"server": args.server,
"taxii_version": args.version,
"collections_discovered": len(collections),
"total_objects": len(all_objects),
"type_distribution": type_counts,
"indicators": indicators,
"threat_actors": actors,
"malware": malware_items,
"attack_patterns": attack_patterns,
}
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"\n[+] Report saved to {args.output}")
elif args.verbose:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
process.py10.7 KB
#!/usr/bin/env python3
"""
STIX/TAXII 2.1 Feed Integration Script
Implements a TAXII 2.1 feed consumer that:
- Discovers TAXII server API roots and collections
- Polls collections for new STIX objects
- Parses and categorizes STIX 2.1 objects
- Extracts actionable IOCs from indicators
- Exports to multiple formats (JSON, CSV, STIX bundle)
Requirements:
pip install taxii2-client stix2 requests
Usage:
python process.py --server https://cti-taxii.mitre.org/taxii2/ --discover
python process.py --server https://cti-taxii.mitre.org/taxii2/ --collection COLLECTION_ID --poll
python process.py --server https://cti-taxii.mitre.org/taxii2/ --collection COLLECTION_ID --extract-iocs
"""
import argparse
import json
import csv
import sys
import re
from datetime import datetime, timedelta
from collections import defaultdict
from typing import Optional
try:
from taxii2client.v21 import Server, Collection, as_pages
except ImportError:
print("ERROR: taxii2-client not installed. Run: pip install taxii2-client")
sys.exit(1)
try:
from stix2 import parse, MemoryStore, Filter
except ImportError:
print("ERROR: stix2 not installed. Run: pip install stix2")
sys.exit(1)
class STIXTAXIIIntegrator:
"""STIX/TAXII 2.1 feed consumer and IOC extractor."""
def __init__(self, server_url: str, user: str = "", password: str = ""):
self.server_url = server_url
self.user = user
self.password = password
self.server = None
self.stats = defaultdict(int)
def discover(self) -> dict:
"""Discover TAXII server API roots and collections."""
self.server = Server(self.server_url, user=self.user, password=self.password)
discovery = {
"title": self.server.title,
"description": getattr(self.server, "description", ""),
"api_roots": [],
}
for api_root in self.server.api_roots:
root_info = {
"title": api_root.title,
"url": api_root.url,
"collections": [],
}
for collection in api_root.collections:
root_info["collections"].append({
"id": collection.id,
"title": collection.title,
"description": getattr(collection, "description", ""),
"can_read": collection.can_read,
"can_write": collection.can_write,
})
self.stats["collections"] += 1
discovery["api_roots"].append(root_info)
self.stats["api_roots"] = len(discovery["api_roots"])
return discovery
def poll_collection(self, collection_url: str,
added_after: Optional[str] = None,
max_objects: int = 1000) -> list:
"""Poll a TAXII collection for STIX objects."""
collection = Collection(
collection_url, user=self.user, password=self.password
)
all_objects = []
kwargs = {}
if added_after:
kwargs["added_after"] = added_after
try:
for envelope in as_pages(
collection.get_objects, per_request=100, **kwargs
):
objects = envelope.get("objects", [])
all_objects.extend(objects)
self.stats["objects_fetched"] += len(objects)
if len(all_objects) >= max_objects:
all_objects = all_objects[:max_objects]
break
except Exception as e:
print(f"[-] Polling error: {e}")
# Categorize by type
for obj in all_objects:
self.stats[f"type_{obj.get('type', 'unknown')}"] += 1
return all_objects
def extract_indicators(self, objects: list) -> list:
"""Extract indicator patterns from STIX objects."""
indicators = []
for obj in objects:
if obj.get("type") != "indicator":
continue
pattern = obj.get("pattern", "")
indicator = {
"id": obj.get("id", ""),
"name": obj.get("name", ""),
"pattern": pattern,
"pattern_type": obj.get("pattern_type", ""),
"valid_from": obj.get("valid_from", ""),
"valid_until": obj.get("valid_until", ""),
"confidence": obj.get("confidence", 0),
"indicator_types": obj.get("indicator_types", []),
"labels": obj.get("labels", []),
"created": obj.get("created", ""),
"modified": obj.get("modified", ""),
}
# Parse pattern to extract observable values
parsed = self._parse_stix_pattern(pattern)
indicator["parsed_observables"] = parsed
indicators.append(indicator)
self.stats["indicators_extracted"] += 1
return indicators
def _parse_stix_pattern(self, pattern: str) -> list:
"""Parse STIX pattern to extract observable values."""
observables = []
# Match common STIX patterns
ip_pattern = re.compile(
r"ipv[46]-addr:value\s*=\s*'([^']+)'"
)
domain_pattern = re.compile(
r"domain-name:value\s*=\s*'([^']+)'"
)
url_pattern = re.compile(
r"url:value\s*=\s*'([^']+)'"
)
hash_pattern = re.compile(
r"file:hashes\.'([^']+)'\s*=\s*'([^']+)'"
)
email_pattern = re.compile(
r"email-addr:value\s*=\s*'([^']+)'"
)
for match in ip_pattern.finditer(pattern):
observables.append({"type": "ip", "value": match.group(1)})
for match in domain_pattern.finditer(pattern):
observables.append({"type": "domain", "value": match.group(1)})
for match in url_pattern.finditer(pattern):
observables.append({"type": "url", "value": match.group(1)})
for match in hash_pattern.finditer(pattern):
observables.append({
"type": f"hash-{match.group(1).lower()}",
"value": match.group(2),
})
for match in email_pattern.finditer(pattern):
observables.append({"type": "email", "value": match.group(1)})
return observables
def build_relationship_graph(self, objects: list) -> dict:
"""Build a relationship graph from STIX objects."""
store = MemoryStore(stix_data=objects)
relationships = store.query([Filter("type", "=", "relationship")])
graph = {"nodes": {}, "edges": []}
for obj in objects:
if obj.get("type") not in ("relationship", "marking-definition"):
graph["nodes"][obj.get("id")] = {
"type": obj.get("type"),
"name": obj.get("name", obj.get("value", obj.get("id"))),
}
for rel in relationships:
graph["edges"].append({
"source": rel.get("source_ref"),
"target": rel.get("target_ref"),
"type": rel.get("relationship_type"),
})
self.stats["relationships"] += 1
return graph
def export_iocs_csv(self, indicators: list, output_path: str):
"""Export parsed IOCs to CSV."""
rows = []
for ind in indicators:
for obs in ind.get("parsed_observables", []):
rows.append({
"indicator_id": ind["id"],
"indicator_name": ind["name"],
"observable_type": obs["type"],
"observable_value": obs["value"],
"confidence": ind["confidence"],
"valid_from": ind["valid_from"],
"valid_until": ind["valid_until"],
})
if rows:
with open(output_path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)
print(f"[+] Exported {len(rows)} IOCs to {output_path}")
else:
print("[-] No IOCs to export")
def print_stats(self):
"""Print integration statistics."""
print("\n=== STIX/TAXII Integration Statistics ===")
for key, value in sorted(self.stats.items()):
print(f" {key.replace('_', ' ').title()}: {value}")
print("=========================================\n")
def main():
parser = argparse.ArgumentParser(
description="STIX/TAXII 2.1 Feed Integration Tool"
)
parser.add_argument("--server", required=True, help="TAXII server URL")
parser.add_argument("--user", default="", help="TAXII username")
parser.add_argument("--password", default="", help="TAXII password")
parser.add_argument("--discover", action="store_true", help="Discover server")
parser.add_argument("--collection", help="Collection ID or URL to poll")
parser.add_argument("--poll", action="store_true", help="Poll for objects")
parser.add_argument("--extract-iocs", action="store_true", help="Extract IOCs")
parser.add_argument("--added-after", help="Filter: added after timestamp")
parser.add_argument("--max-objects", type=int, default=1000, help="Max objects")
parser.add_argument("--output", default="stix_output.json", help="Output file")
parser.add_argument("--csv-output", help="CSV output for IOCs")
args = parser.parse_args()
integrator = STIXTAXIIIntegrator(args.server, args.user, args.password)
if args.discover:
discovery = integrator.discover()
print(json.dumps(discovery, indent=2))
with open(args.output, "w") as f:
json.dump(discovery, f, indent=2)
elif args.collection and (args.poll or args.extract_iocs):
# Build collection URL
if args.collection.startswith("http"):
collection_url = args.collection
else:
collection_url = (
f"{args.server.rstrip('/')}/stix/collections/{args.collection}/"
)
objects = integrator.poll_collection(
collection_url,
added_after=args.added_after,
max_objects=args.max_objects,
)
print(f"[+] Fetched {len(objects)} objects")
if args.extract_iocs:
indicators = integrator.extract_indicators(objects)
print(f"[+] Extracted {len(indicators)} indicators")
if args.csv_output:
integrator.export_iocs_csv(indicators, args.csv_output)
with open(args.output, "w") as f:
json.dump(indicators, f, indent=2, default=str)
else:
with open(args.output, "w") as f:
json.dump(objects, f, indent=2, default=str)
integrator.print_stats()
if __name__ == "__main__":
main()