mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-14 06:54:57 +03:00
c47eed6a64
- 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
323 lines
12 KiB
Python
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()
|