threat intelligence

Implementing STIX/TAXII Feed Integration

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.

ctifeed-integrationiocmitre-attackoasisstixtaxiithreat-intelligence
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

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

Source materials

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 requests

Authentication

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 iocs

Create 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 modified timestamp 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 response
  • next: Cursor for next page
  • added_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:

  1. Discovery: Query TAXII server discovery endpoint for available API roots
  2. Root Enumeration: List available API roots and their supported features
  3. Collection Listing: Enumerate collections with read/write permissions
  4. Incremental Polling: Fetch new objects using added_after timestamp filter
  5. STIX Parsing: Deserialize JSON into typed STIX objects
  6. IOC Extraction: Extract indicators, observables, and relationships
  7. 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:

  1. Source Collection: Gather IOCs from internal analysis, feeds, incident response
  2. Normalization: Standardize IOC formats and remove duplicates
  3. Object Creation: Create STIX Indicators, Observables, and Relationships
  4. TLP Marking: Apply appropriate TLP marking definitions
  5. Bundle Assembly: Package objects into STIX 2.1 bundles
  6. 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:

  1. Feed Registration: Configure multiple TAXII and file-based STIX sources
  2. Parallel Polling: Poll all feeds concurrently with rate limiting
  3. Deduplication: Remove duplicate objects by STIX ID and modified timestamp
  4. Correlation: Link related objects across feeds via relationships
  5. Unified Storage: Store in MemoryStore, FileSystemStore, or database-backed store
  6. Output: Generate alerts, dashboards, or exports for downstream consumers

Scripts 2

agent.py11.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/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
Display-only source. This catalog never executes bundled scripts.
#!/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()

Assets 1

template.mdtext/markdown · 1.2 KB
Keep exploring