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

219 lines
7.8 KiB
Python

#!/usr/bin/env python3
"""MISP Threat Intelligence Sharing agent - creates events, manages attributes, searches IOCs, and validates sharing configuration via PyMISP"""
import argparse
import json
from collections import Counter
from datetime import datetime
from pathlib import Path
try:
from pymisp import PyMISP, MISPEvent
HAS_PYMISP = True
except ImportError:
HAS_PYMISP = False
MISP_ATTRIBUTE_TYPES = {
"ip-dst", "ip-src", "domain", "hostname", "url", "md5", "sha1",
"sha256", "filename", "email-src", "email-dst", "mutex", "regkey",
"user-agent", "vulnerability", "link", "text", "comment",
}
TLP_TAGS = {
"white": "tlp:white",
"green": "tlp:green",
"amber": "tlp:amber",
"amber+strict": "tlp:amber+strict",
"red": "tlp:red",
}
def load_data(path):
return json.loads(Path(path).read_text(encoding="utf-8"))
def connect_misp(url, api_key, ssl=True):
"""Initialize PyMISP connection."""
if not HAS_PYMISP:
return None, "pymisp not installed (pip install pymisp)"
misp = PyMISP(url, api_key, ssl=ssl)
return misp, "connected"
def create_event_from_data(misp, event_data):
"""Create a MISP event with attributes and tags."""
event = MISPEvent()
event.info = event_data.get("info", "Untitled Event")
event.distribution = event_data.get("distribution", 1) # 0=org, 1=community, 2=connected, 3=all
event.threat_level_id = event_data.get("threat_level", 2) # 1=high, 2=medium, 3=low, 4=undefined
event.analysis = event_data.get("analysis", 0) # 0=initial, 1=ongoing, 2=complete
for attr in event_data.get("attributes", []):
attr_type = attr.get("type", "text")
value = attr.get("value", "")
category = attr.get("category", "")
to_ids = attr.get("to_ids", True)
comment = attr.get("comment", "")
if attr_type in MISP_ATTRIBUTE_TYPES and value:
event.add_attribute(type=attr_type, value=value, category=category,
to_ids=to_ids, comment=comment)
for tag_name in event_data.get("tags", []):
event.add_tag(tag_name)
tlp = event_data.get("tlp", "").lower()
if tlp in TLP_TAGS:
event.add_tag(TLP_TAGS[tlp])
if misp:
result = misp.add_event(event)
return result
return event.to_dict()
def validate_event_quality(event_data):
"""Validate event data quality for sharing readiness."""
findings = []
eid = event_data.get("id", event_data.get("info", "unknown"))
if not event_data.get("info"):
findings.append({
"type": "missing_event_info",
"severity": "high",
"resource": str(eid),
"detail": "Event lacks descriptive info/title",
})
attrs = event_data.get("attributes", event_data.get("Attribute", []))
if not attrs:
findings.append({
"type": "no_attributes",
"severity": "high",
"resource": str(eid),
"detail": "Event has no IOC attributes",
})
attr_types = Counter(a.get("type", "unknown") for a in attrs)
if len(attr_types) == 1 and len(attrs) > 1:
findings.append({
"type": "single_attribute_type",
"severity": "low",
"resource": str(eid),
"detail": f"All {len(attrs)} attributes are type '{list(attr_types.keys())[0]}' - consider enriching",
})
tags = event_data.get("tags", event_data.get("Tag", []))
tag_names = [t.get("name", t) if isinstance(t, dict) else t for t in tags]
has_tlp = any("tlp:" in t.lower() for t in tag_names)
if not has_tlp:
findings.append({
"type": "missing_tlp_tag",
"severity": "high",
"resource": str(eid),
"detail": "Event lacks TLP classification tag",
})
has_mitre = any("mitre-attack" in t.lower() or "attack-pattern" in t.lower() for t in tag_names)
if not has_mitre and len(attrs) > 0:
findings.append({
"type": "missing_mitre_mapping",
"severity": "medium",
"resource": str(eid),
"detail": "Event lacks MITRE ATT&CK technique mapping",
})
dist = event_data.get("distribution", -1)
if dist == 3:
findings.append({
"type": "unrestricted_distribution",
"severity": "medium",
"resource": str(eid),
"detail": "Event set to 'All communities' distribution - verify this is intentional",
})
for attr in attrs:
val = attr.get("value", "")
atype = attr.get("type", "")
if atype in ("ip-dst", "ip-src") and val in ("127.0.0.1", "0.0.0.0", "10.0.0.1", "192.168.1.1"):
findings.append({
"type": "private_ip_ioc",
"severity": "high",
"resource": str(eid),
"detail": f"Private/localhost IP '{val}' used as IOC - will generate false positives",
})
if atype in ("md5", "sha1", "sha256") and len(val) < 32:
findings.append({
"type": "invalid_hash_length",
"severity": "high",
"resource": str(eid),
"detail": f"Hash attribute '{val}' is too short for type {atype}",
})
return findings
def validate_sharing_config(config):
"""Validate MISP sharing and feed configuration."""
findings = []
servers = config.get("sync_servers", [])
if not servers:
findings.append({
"type": "no_sync_servers",
"severity": "medium",
"resource": "misp_config",
"detail": "No synchronization servers configured for intelligence sharing",
})
for srv in servers:
if not srv.get("pull", False) and not srv.get("push", False):
findings.append({
"type": "inactive_sync_server",
"severity": "medium",
"resource": srv.get("name", srv.get("url", "")),
"detail": "Sync server has neither pull nor push enabled",
})
feeds = config.get("feeds", [])
enabled_feeds = [f for f in feeds if f.get("enabled", False)]
if not enabled_feeds:
findings.append({
"type": "no_active_feeds",
"severity": "medium",
"resource": "misp_config",
"detail": "No active threat intelligence feeds configured",
})
return findings
def analyze(data):
findings = []
events = data.get("events", [data] if "info" in data or "Attribute" in data else [])
if isinstance(data, list):
events = data
for evt in events:
findings.extend(validate_event_quality(evt))
if "sync_servers" in data or "feeds" in data:
findings.extend(validate_sharing_config(data))
return findings
def generate_report(input_path):
data = load_data(input_path)
findings = analyze(data)
sev = Counter(f["severity"] for f in findings)
cats = Counter(f["type"] for f in findings)
return {
"report": "misp_threat_intelligence_sharing",
"generated_at": datetime.utcnow().isoformat() + "Z",
"total_findings": len(findings),
"severity_summary": dict(sev),
"finding_categories": dict(cats),
"findings": findings,
}
def main():
ap = argparse.ArgumentParser(description="MISP Threat Intelligence Sharing Agent")
ap.add_argument("--input", required=True, help="Input JSON with MISP events or config")
ap.add_argument("--output", help="Output JSON report path")
ap.add_argument("--misp-url", help="MISP instance URL for live operations")
ap.add_argument("--api-key", help="MISP API key")
args = ap.parse_args()
report = generate_report(args.input)
out = json.dumps(report, indent=2)
if args.output:
Path(args.output).write_text(out, encoding="utf-8")
print(f"Report written to {args.output}")
else:
print(out)
if __name__ == "__main__":
main()