Files
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

171 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Threat Intelligence Feed Integration Agent - Ingests STIX/TAXII and open-source TI feeds."""
import json
import logging
import os
import argparse
import hashlib
from datetime import datetime, timedelta
import requests
from taxii2client.v21 import Collection
from stix2 import Indicator, Bundle, parse
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
def ingest_taxii_feed(taxii_url, collection_url, username, password, hours_back=24):
"""Ingest indicators from a TAXII 2.1 server collection."""
added_after = (datetime.utcnow() - timedelta(hours=hours_back)).strftime(
"%Y-%m-%dT%H:%M:%S.000Z"
)
collection = Collection(collection_url, user=username, password=password)
response = collection.get_objects(added_after=added_after, type=["indicator"])
indicators = []
for obj in response.get("objects", []):
indicator = parse(obj)
indicators.append({
"id": str(indicator.id),
"pattern": indicator.pattern,
"valid_from": str(indicator.valid_from),
"source": "taxii",
})
logger.info("Ingested %d indicators from TAXII feed", len(indicators))
return indicators
def ingest_urlhaus_feed():
"""Ingest recent malicious URLs from Abuse.ch URLhaus."""
url = "https://urlhaus-api.abuse.ch/v1/urls/recent/limit/100/"
resp = requests.post(url, timeout=30)
data = resp.json()
indicators = []
for entry in data.get("urls", []):
indicators.append({
"type": "url",
"value": entry["url"],
"threat": entry.get("threat", "unknown"),
"status": entry.get("url_status", "unknown"),
"tags": entry.get("tags", []),
"source": "urlhaus",
})
logger.info("Ingested %d URLs from URLhaus", len(indicators))
return indicators
def ingest_feodotracker():
"""Ingest C2 IP blocklist from Abuse.ch Feodo Tracker."""
url = "https://feodotracker.abuse.ch/downloads/ipblocklist_recommended.json"
resp = requests.get(url, timeout=30)
indicators = []
for entry in resp.json():
indicators.append({
"type": "ipv4",
"value": entry["ip_address"],
"port": entry.get("port"),
"malware": entry.get("malware", "unknown"),
"first_seen": entry.get("first_seen"),
"source": "feodotracker",
})
logger.info("Ingested %d C2 IPs from Feodo Tracker", len(indicators))
return indicators
def normalize_to_stix(indicators):
"""Convert raw indicators to STIX 2.1 Indicator objects."""
pattern_map = {
"ipv4": lambda v: f"[ipv4-addr:value = '{v}']",
"domain": lambda v: f"[domain-name:value = '{v}']",
"url": lambda v: f"[url:value = '{v}']",
"sha256": lambda v: f"[file:hashes.'SHA-256' = '{v}']",
}
stix_objects = []
for ioc in indicators:
ioc_type = ioc.get("type", "url")
pattern_fn = pattern_map.get(ioc_type, pattern_map["url"])
stix_ind = Indicator(
name=f"{ioc_type}: {ioc['value']}",
pattern=pattern_fn(ioc["value"]),
pattern_type="stix",
valid_from=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
labels=[ioc.get("source", "unknown")],
)
stix_objects.append(stix_ind)
logger.info("Normalized %d indicators to STIX 2.1", len(stix_objects))
return stix_objects
def deduplicate(indicators):
"""Deduplicate indicators across feeds by type:value hash."""
seen = set()
unique = []
for ioc in indicators:
key = hashlib.sha256(f"{ioc.get('type', '')}:{ioc['value']}".encode()).hexdigest()
if key not in seen:
seen.add(key)
unique.append(ioc)
logger.info("Deduplicated: %d -> %d unique indicators", len(indicators), len(unique))
return unique
def export_stix_bundle(stix_objects, output_path):
"""Export STIX 2.1 bundle to JSON file."""
bundle = Bundle(objects=stix_objects)
with open(output_path, "w") as f:
f.write(bundle.serialize(pretty=True))
logger.info("STIX bundle exported to %s (%d indicators)", output_path, len(stix_objects))
def push_to_splunk_ti(splunk_url, session_key, indicators):
"""Push indicators to Splunk ES threat intelligence framework."""
headers = {"Authorization": f"Splunk {session_key}"}
pushed = 0
for ioc in indicators:
data = {"ip": ioc["value"], "description": f"{ioc.get('source')}: {ioc['value']}"}
resp = requests.post(
f"{splunk_url}/services/data/threat_intel/item/ip_intel",
headers=headers, data=data,
verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
timeout=30,
)
if resp.status_code == 201:
pushed += 1
logger.info("Pushed %d indicators to Splunk TI", pushed)
def main():
parser = argparse.ArgumentParser(description="Threat Intelligence Feed Integration Agent")
parser.add_argument("--taxii-url", help="TAXII 2.1 server URL")
parser.add_argument("--taxii-collection", help="TAXII collection URL")
parser.add_argument("--taxii-user", help="TAXII username")
parser.add_argument("--taxii-pass", help="TAXII password")
parser.add_argument("--urlhaus", action="store_true", help="Ingest URLhaus feed")
parser.add_argument("--feodo", action="store_true", help="Ingest Feodo Tracker feed")
parser.add_argument("--output", default="ti_bundle.json", help="STIX bundle output")
args = parser.parse_args()
all_indicators = []
if args.taxii_url and args.taxii_collection:
taxii_iocs = ingest_taxii_feed(
args.taxii_url, args.taxii_collection, args.taxii_user, args.taxii_pass
)
all_indicators.extend(taxii_iocs)
if args.urlhaus:
all_indicators.extend(ingest_urlhaus_feed())
if args.feodo:
all_indicators.extend(ingest_feodotracker())
unique = deduplicate(all_indicators)
stix_objects = normalize_to_stix(unique)
export_stix_bundle(stix_objects, args.output)
logger.info("Feed integration complete: %d total indicators", len(unique))
if __name__ == "__main__":
main()