Files
T
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

323 lines
12 KiB
Python

#!/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()