npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
TAXII (Trusted Automated eXchange of Intelligence Information) is an OASIS standard protocol for exchanging cyber threat intelligence over HTTPS. OpenTAXII is an open-source TAXII server implementation by EclecticIQ that supports TAXII 1.x, while the OASIS cti-taxii-server provides a TAXII 2.1 reference implementation. This skill covers deploying a TAXII server, configuring collections for threat intelligence feeds, publishing STIX 2.1 bundles, and integrating with SIEM/SOAR platforms for automated indicator ingestion.
When to Use
- When deploying or configuring implementing taxii server with opentaxii 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
medallion,stix2,taxii2-client,opentaxii,cabbylibraries - Docker and Docker Compose for containerized deployment
- Understanding of STIX 2.1 objects (Indicator, Malware, Attack Pattern, Relationship)
- Familiarity with REST APIs and HTTPS configuration
- TLS certificates for production deployment
Key Concepts
TAXII 2.1 Architecture
TAXII 2.1 defines three services: Discovery (find available API roots), API Root (entry point for collections), and Collections (repositories of CTI objects). Collections support two access models: the Collection endpoint allows consumers to poll for objects, and the Status endpoint tracks the result of add operations. TAXII uses HTTP content negotiation with application/taxii+json;version=2.1.
Sharing Models
TAXII supports hub-and-spoke (central server distributes to consumers), peer-to-peer (bidirectional sharing between partners), and source-subscriber (producer publishes, consumers subscribe) models. Each collection can have read-only, write-only, or read-write access controls.
STIX 2.1 Content
TAXII transports STIX 2.1 bundles containing Structured Threat Information objects: Indicators (detection patterns), Observed Data, Malware, Attack Patterns, Threat Actors, Intrusion Sets, Campaigns, Relationships, and Sightings. Each object has a unique STIX ID, creation/modification timestamps, and optional TLP marking definitions.
Workflow
Step 1: Deploy TAXII 2.1 Server with Medallion
# Install medallion (OASIS reference implementation)
# pip install medallion
# medallion_config.json
import json
config = {
"backend": {
"module_class": "MemoryBackend",
"filename": "taxii_data.json"
},
"users": {
"admin": "admin_password_change_me",
"analyst": "analyst_password_change_me",
"readonly": "readonly_password_change_me"
},
"taxii": {
"max_content_length": 10485760
}
}
# Create initial data store
taxii_data = {
"discovery": {
"title": "Threat Intelligence TAXII Server",
"description": "TAXII 2.1 server for sharing CTI indicators",
"contact": "soc@organization.com",
"default": "https://taxii.organization.com/api/",
"api_roots": ["https://taxii.organization.com/api/"]
},
"api_roots": {
"api": {
"title": "Threat Intelligence API Root",
"description": "Primary API root for threat intelligence sharing",
"versions": ["application/taxii+json;version=2.1"],
"max_content_length": 10485760,
"collections": {
"malware-iocs": {
"id": "91a7b528-80eb-42ed-a74d-c6fbd5a26116",
"title": "Malware IOCs",
"description": "Indicators of compromise from malware analysis",
"can_read": True,
"can_write": True,
"media_types": ["application/stix+json;version=2.1"]
},
"apt-intelligence": {
"id": "52892447-4d7e-4f70-b94a-5460e242dd23",
"title": "APT Intelligence",
"description": "Advanced persistent threat group intelligence",
"can_read": True,
"can_write": True,
"media_types": ["application/stix+json;version=2.1"]
},
"phishing-indicators": {
"id": "64993447-4d7e-4f70-b94a-5460e242ee34",
"title": "Phishing Indicators",
"description": "Phishing URLs, domains, and email indicators",
"can_read": True,
"can_write": True,
"media_types": ["application/stix+json;version=2.1"]
}
}
}
}
}
with open("medallion_config.json", "w") as f:
json.dump(config, f, indent=2)
with open("taxii_data.json", "w") as f:
json.dump(taxii_data, f, indent=2)
print("[+] TAXII server configuration created")Step 2: Docker Deployment
# docker-compose.yml
version: '3.8'
services:
taxii-server:
image: python:3.11-slim
container_name: taxii-server
working_dir: /app
volumes:
- ./medallion_config.json:/app/medallion_config.json
- ./taxii_data.json:/app/taxii_data.json
- ./certs:/app/certs
ports:
- "6100:6100"
command: >
bash -c "pip install medallion &&
medallion --host 0.0.0.0 --port 6100
--config /app/medallion_config.json"
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:6100/taxii2/"]
interval: 30s
timeout: 10s
retries: 3Step 3: Publish STIX 2.1 Objects to Collections
from stix2 import Indicator, Malware, Relationship, Bundle, TLP_WHITE
from taxii2client.v21 import Server, Collection, as_pages
import json
from datetime import datetime
class TAXIIPublisher:
def __init__(self, server_url, username, password):
self.server = Server(
server_url,
user=username,
password=password,
)
def list_collections(self):
"""List all available collections."""
api_root = self.server.api_roots[0]
for collection in api_root.collections:
print(f" [{collection.id}] {collection.title} "
f"(read={collection.can_read}, write={collection.can_write})")
return api_root.collections
def publish_indicators(self, collection_id, indicators):
"""Publish STIX indicators to a TAXII collection."""
api_root = self.server.api_roots[0]
collection = Collection(
f"{api_root.url}collections/{collection_id}/",
user=self.server._user,
password=self.server._password,
)
bundle = Bundle(objects=indicators)
response = collection.add_objects(bundle.serialize())
print(f"[+] Published {len(indicators)} objects to {collection_id}")
print(f" Status: {response.status}")
return response
def create_malware_indicators(self):
"""Create sample STIX malware indicators."""
malware = Malware(
name="SUNBURST",
description="Backdoor used in SolarWinds supply chain attack (2020). "
"Trojanized SolarWinds.Orion.Core.BusinessLayer.dll module.",
malware_types=["backdoor", "trojan"],
is_family=True,
object_marking_refs=[TLP_WHITE],
)
indicator_hash = Indicator(
name="SUNBURST SHA-256 Hash",
description="SHA-256 hash of trojanized SolarWinds Orion DLL",
pattern="[file:hashes.'SHA-256' = "
"'32519b85c0b422e4656de6e6c41878e95fd95026267daab4215ee59c107d6c77']",
pattern_type="stix",
valid_from=datetime(2020, 12, 13),
indicator_types=["malicious-activity"],
object_marking_refs=[TLP_WHITE],
)
indicator_domain = Indicator(
name="SUNBURST C2 Domain Pattern",
description="DGA domain pattern used by SUNBURST for C2",
pattern="[domain-name:value MATCHES "
"'^[a-z0-9]{4,}\\.appsync-api\\..*\\.avsvmcloud\\.com$']",
pattern_type="stix",
valid_from=datetime(2020, 12, 13),
indicator_types=["malicious-activity"],
object_marking_refs=[TLP_WHITE],
)
rel = Relationship(
relationship_type="indicates",
source_ref=indicator_hash.id,
target_ref=malware.id,
)
return [malware, indicator_hash, indicator_domain, rel]
publisher = TAXIIPublisher(
"https://taxii.organization.com/taxii2/",
"admin", "admin_password_change_me"
)
collections = publisher.list_collections()
indicators = publisher.create_malware_indicators()
publisher.publish_indicators("91a7b528-80eb-42ed-a74d-c6fbd5a26116", indicators)Step 4: Consume Intelligence from TAXII Collections
from taxii2client.v21 import Server, Collection, as_pages
import json
class TAXIIConsumer:
def __init__(self, server_url, username, password):
self.server = Server(server_url, user=username, password=password)
def poll_collection(self, collection_id, added_after=None):
"""Poll a collection for new STIX objects."""
api_root = self.server.api_roots[0]
collection = Collection(
f"{api_root.url}collections/{collection_id}/",
user=self.server._user,
password=self.server._password,
)
kwargs = {}
if added_after:
kwargs["added_after"] = added_after
all_objects = []
for bundle in as_pages(collection.get_objects, per_request=50, **kwargs):
objects = json.loads(bundle).get("objects", [])
all_objects.extend(objects)
indicators = [o for o in all_objects if o.get("type") == "indicator"]
malware = [o for o in all_objects if o.get("type") == "malware"]
relationships = [o for o in all_objects if o.get("type") == "relationship"]
print(f"[+] Polled {len(all_objects)} objects: "
f"{len(indicators)} indicators, {len(malware)} malware, "
f"{len(relationships)} relationships")
return all_objects
def extract_iocs_for_siem(self, stix_objects):
"""Extract IOCs from STIX objects for SIEM ingestion."""
iocs = []
for obj in stix_objects:
if obj.get("type") == "indicator":
pattern = obj.get("pattern", "")
iocs.append({
"id": obj.get("id"),
"name": obj.get("name", ""),
"pattern": pattern,
"valid_from": obj.get("valid_from", ""),
"indicator_types": obj.get("indicator_types", []),
"confidence": obj.get("confidence", 0),
})
return iocs
consumer = TAXIIConsumer(
"https://taxii.organization.com/taxii2/",
"analyst", "analyst_password_change_me"
)
objects = consumer.poll_collection("91a7b528-80eb-42ed-a74d-c6fbd5a26116")
iocs = consumer.extract_iocs_for_siem(objects)Step 5: Integrate with SIEM/SOAR
import requests
def push_to_splunk(iocs, splunk_url, hec_token):
"""Push extracted IOCs to Splunk via HEC."""
headers = {"Authorization": f"Splunk {hec_token}"}
for ioc in iocs:
event = {
"event": ioc,
"sourcetype": "stix:indicator",
"source": "taxii-server",
"index": "threat_intel",
}
resp = requests.post(
f"{splunk_url}/services/collector/event",
headers=headers,
json=event,
verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
)
if resp.status_code != 200:
print(f"[-] Splunk HEC error: {resp.text}")
print(f"[+] Pushed {len(iocs)} IOCs to Splunk")
def push_to_elasticsearch(iocs, es_url, index="threat-intel"):
"""Push IOCs to Elasticsearch."""
for ioc in iocs:
resp = requests.post(
f"{es_url}/{index}/_doc",
json=ioc,
headers={"Content-Type": "application/json"},
)
if resp.status_code not in (200, 201):
print(f"[-] ES error: {resp.text}")
print(f"[+] Indexed {len(iocs)} IOCs in Elasticsearch")Validation Criteria
- TAXII 2.1 server deployed and accessible via HTTPS
- Collections created with appropriate read/write permissions
- STIX 2.1 bundles published successfully to collections
- Consumer can poll and retrieve objects with filtering
- IOCs extracted and forwarded to SIEM platform
- Authentication and authorization enforced correctly
References
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md4.2 KB
API Reference: OpenTAXII Server
Libraries Used
| Library | Purpose |
|---|---|
opentaxii |
TAXII 1.x and 2.x server implementation |
taxii2-client |
TAXII 2.1 client for testing and integration |
stix2 |
Create and parse STIX 2.1 objects |
requests |
HTTP client for direct API testing |
Installation
# Server
pip install opentaxii
# Client and testing
pip install taxii2-client stix2 requestsServer Configuration
opentaxii.yml
---
persistence_api:
class: opentaxii.persistence.sqldb.SQLDatabaseAPI
parameters:
db_connection: sqlite:////tmp/opentaxii.db
create_tables: true
auth_api:
class: opentaxii.auth.sqldb.SQLDatabaseAuth
parameters:
db_connection: sqlite:////tmp/opentaxii.db
create_tables: true
secret: "change-this-secret-in-production"
taxii1:
save_raw_inbox_messages: false
logging:
opentaxii: info
root: infoStart the Server
# Set config path
export OPENTAXII_CONFIG=/path/to/opentaxii.yml
# Run the server
opentaxii-run-dev --host 0.0.0.0 --port 9000
# Production (with gunicorn)
gunicorn opentaxii.http:app --bind 0.0.0.0:9000TAXII 2.1 API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /taxii2/ |
Server discovery |
| GET | /{api-root}/ |
API root information |
| GET | /{api-root}/collections/ |
List collections |
| GET | /{api-root}/collections/{id}/ |
Get collection details |
| GET | /{api-root}/collections/{id}/objects/ |
Get STIX objects |
| POST | /{api-root}/collections/{id}/objects/ |
Add STIX objects |
| GET | /{api-root}/collections/{id}/manifest/ |
Object manifest |
| GET | /{api-root}/status/{id}/ |
Async operation status |
Server Administration
Create Collections via CLI
opentaxii-create-services -c services.yml
opentaxii-create-collections -c collections.yml
opentaxii-create-account --username admin --password admin123collections.yml
---
- name: "threat-indicators"
id: "collection-001"
description: "Threat intelligence indicators"
type: "DATA_FEED"
accept_all_content: true
can_read: true
can_write: true
- name: "malware-samples"
id: "collection-002"
description: "Malware sample hashes and metadata"
type: "DATA_SET"
can_read: true
can_write: falseClient Operations
Discover Server and Collections
from taxii2client.v21 import Server
import os
server = Server(
os.environ.get("OPENTAXII_URL", "http://localhost:9000/taxii2/"),
user=os.environ.get("TAXII_USER", "admin"),
password=os.environ.get("TAXII_PASS", "admin123"),
)
for api_root in server.api_roots:
print(f"API Root: {api_root.title}")
for coll in api_root.collections:
print(f" {coll.title} (ID: {coll.id})")
print(f" Read: {coll.can_read} | Write: {coll.can_write}")Push STIX Objects to a Collection
import stix2
from taxii2client.v21 import Collection
collection = Collection(
f"http://localhost:9000/collections/collection-001/",
user="admin",
password="admin123",
)
indicator = stix2.Indicator(
name="Malicious C2 Domain",
pattern="[domain-name:value = 'evil.example.com']",
pattern_type="stix",
valid_from="2025-01-15T00:00:00Z",
labels=["malicious-activity"],
)
bundle = stix2.Bundle(objects=[indicator])
collection.add_objects(bundle.serialize())Fetch Objects from a Collection
objects = collection.get_objects()
for obj in objects.get("objects", []):
print(f" {obj['type']}: {obj.get('name', obj['id'])}")Health Check
import requests
resp = requests.get(
"http://localhost:9000/taxii2/",
auth=("admin", "admin123"),
timeout=10,
)
if resp.status_code == 200:
discovery = resp.json()
print(f"Server title: {discovery.get('title')}")
print(f"API roots: {discovery.get('api_roots', [])}")Output Format
{
"title": "OpenTAXII TAXII 2.1 Server",
"description": "Threat intelligence sharing server",
"api_roots": ["http://localhost:9000/api/"],
"collections": [
{
"id": "collection-001",
"title": "threat-indicators",
"can_read": true,
"can_write": true,
"media_types": ["application/stix+json;version=2.1"]
}
]
}Scripts 1
agent.py8.5 KB
#!/usr/bin/env python3
"""OpenTAXII server configuration and health audit agent.
Audits an OpenTAXII server instance by checking service discovery,
collection availability, content block statistics, and API health.
Supports both TAXII 1.1 and 2.0/2.1 endpoints.
"""
import argparse
import json
import os
import sys
from datetime import datetime, timezone
try:
import requests
except ImportError:
print("[!] 'requests' required: pip install requests", file=sys.stderr)
sys.exit(1)
try:
from taxii2client.v20 import Server as Server20
from taxii2client.v21 import Server as Server21
HAS_TAXII_CLIENT = True
except ImportError:
HAS_TAXII_CLIENT = False
def check_taxii1_discovery(base_url, username=None, password=None):
"""Check TAXII 1.1 discovery service."""
findings = []
discovery_url = f"{base_url}/services/discovery"
print(f"[*] Checking TAXII 1.1 discovery: {discovery_url}")
headers = {"Content-Type": "application/xml",
"X-TAXII-Content-Type": "urn:taxii.mitre.org:message:xml:1.1",
"X-TAXII-Protocol": "urn:taxii.mitre.org:protocol:http:1.0"}
discovery_xml = (
'<Discovery_Request xmlns="http://taxii.mitre.org/messages/taxii_xml_binding-1.1" '
'message_id="1"/>'
)
auth = (username, password) if username else None
try:
resp = requests.post(discovery_url, data=discovery_xml, headers=headers,
auth=auth, timeout=15)
if resp.status_code == 200:
findings.append({
"check": "TAXII 1.1 Discovery",
"status": "PASS",
"severity": "INFO",
"detail": f"Discovery service responding ({len(resp.content)} bytes)",
})
else:
findings.append({
"check": "TAXII 1.1 Discovery",
"status": "FAIL",
"severity": "HIGH",
"detail": f"HTTP {resp.status_code}",
})
except requests.RequestException as e:
findings.append({
"check": "TAXII 1.1 Discovery",
"status": "FAIL",
"severity": "HIGH",
"detail": str(e)[:100],
})
return findings
def check_taxii2_discovery(base_url, username=None, password=None, version="2.1"):
"""Check TAXII 2.0/2.1 discovery and collections."""
findings = []
print(f"[*] Checking TAXII {version} discovery: {base_url}")
if HAS_TAXII_CLIENT:
try:
kwargs = {}
if username and password:
kwargs["user"] = username
kwargs["password"] = password
if version == "2.0":
server = Server20(base_url, **kwargs)
else:
server = Server21(base_url, **kwargs)
findings.append({
"check": f"TAXII {version} Discovery",
"status": "PASS",
"severity": "INFO",
"detail": f"Server: {server.title or 'Untitled'}",
})
for api_root in server.api_roots:
collections = list(api_root.collections)
findings.append({
"check": f"API Root: {api_root.title or api_root.url}",
"status": "PASS",
"severity": "INFO",
"detail": f"{len(collections)} collections",
"collections": [{
"id": c.id,
"title": c.title,
"can_read": getattr(c, "can_read", True),
"can_write": getattr(c, "can_write", False),
} for c in collections],
})
except Exception as e:
findings.append({
"check": f"TAXII {version} Discovery",
"status": "FAIL",
"severity": "HIGH",
"detail": str(e)[:100],
})
else:
# Fallback to raw HTTP
auth = (username, password) if username else None
headers = {"Accept": "application/taxii+json;version=2.1"}
try:
resp = requests.get(base_url, headers=headers, auth=auth, timeout=15)
if resp.status_code == 200:
data = resp.json()
findings.append({
"check": f"TAXII {version} Discovery",
"status": "PASS",
"severity": "INFO",
"detail": f"Title: {data.get('title', 'N/A')}, "
f"API Roots: {len(data.get('api_roots', []))}",
})
else:
findings.append({
"check": f"TAXII {version} Discovery",
"status": "FAIL",
"severity": "HIGH",
"detail": f"HTTP {resp.status_code}",
})
except requests.RequestException as e:
findings.append({
"check": f"TAXII {version} Discovery",
"status": "FAIL",
"severity": "HIGH",
"detail": str(e)[:100],
})
return findings
def check_server_health(base_url):
"""Check basic server health."""
findings = []
print(f"[*] Checking server health: {base_url}")
# Check TLS
if base_url.startswith("https://"):
findings.append({
"check": "TLS enabled",
"status": "PASS",
"severity": "INFO",
})
else:
findings.append({
"check": "TLS enabled",
"status": "FAIL",
"severity": "HIGH",
"detail": "TAXII server not using HTTPS",
})
# Check response time
try:
resp = requests.get(base_url, timeout=10)
response_time = resp.elapsed.total_seconds()
if response_time > 5:
findings.append({
"check": "Response time",
"status": "WARN",
"severity": "MEDIUM",
"detail": f"{response_time:.2f}s (slow)",
})
else:
findings.append({
"check": "Response time",
"status": "PASS",
"severity": "INFO",
"detail": f"{response_time:.2f}s",
})
except requests.RequestException:
pass
return findings
def format_summary(all_findings, base_url):
"""Print audit summary."""
print(f"\n{'='*60}")
print(f" OpenTAXII Server Audit Report")
print(f"{'='*60}")
print(f" Server : {base_url}")
print(f" Findings : {len(all_findings)}")
pass_count = sum(1 for f in all_findings if f["status"] == "PASS")
fail_count = sum(1 for f in all_findings if f["status"] == "FAIL")
print(f" Passed : {pass_count}")
print(f" Failed : {fail_count}")
for f in all_findings:
icon = "OK" if f["status"] == "PASS" else "!!" if f["status"] == "FAIL" else "~~"
print(f" [{icon}] {f['check']}: {f.get('detail', '')[:50]}")
severity_counts = {}
for f in all_findings:
sev = f.get("severity", "INFO")
severity_counts[sev] = severity_counts.get(sev, 0) + 1
return severity_counts
def main():
parser = argparse.ArgumentParser(description="OpenTAXII server audit agent")
parser.add_argument("--url", required=True, help="TAXII server base URL")
parser.add_argument("--username", help="Authentication username")
parser.add_argument("--password", help="Authentication password")
parser.add_argument("--version", choices=["1.1", "2.0", "2.1"], default="2.1")
parser.add_argument("--output", "-o", help="Output JSON report")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
all_findings = []
all_findings.extend(check_server_health(args.url))
if args.version == "1.1":
all_findings.extend(check_taxii1_discovery(args.url, args.username, args.password))
else:
all_findings.extend(check_taxii2_discovery(args.url, args.username, args.password, args.version))
severity_counts = format_summary(all_findings, args.url)
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "OpenTAXII Audit",
"server": args.url,
"version": args.version,
"findings": all_findings,
"severity_counts": severity_counts,
}
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()