mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-17 05:05:16 +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
196 lines
7.3 KiB
Python
196 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
|
"""IOC Collection Agent - Extracts, enriches, and exports indicators of compromise."""
|
|
|
|
import json
|
|
import re
|
|
import logging
|
|
import argparse
|
|
from datetime import datetime
|
|
|
|
import requests
|
|
from stix2 import Indicator, Bundle
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
IPV4_PATTERN = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
|
|
DOMAIN_PATTERN = re.compile(r"\b(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}\b")
|
|
SHA256_PATTERN = re.compile(r"\b[a-fA-F0-9]{64}\b")
|
|
MD5_PATTERN = re.compile(r"\b[a-fA-F0-9]{32}\b")
|
|
URL_PATTERN = re.compile(r"https?://[^\s\"'<>]+")
|
|
|
|
|
|
def extract_iocs_from_text(text):
|
|
"""Extract IOCs (IPs, domains, hashes, URLs) from unstructured text."""
|
|
iocs = {
|
|
"ipv4": list(set(IPV4_PATTERN.findall(text))),
|
|
"domain": list(set(DOMAIN_PATTERN.findall(text))),
|
|
"sha256": list(set(SHA256_PATTERN.findall(text))),
|
|
"md5": list(set(MD5_PATTERN.findall(text))),
|
|
"url": list(set(URL_PATTERN.findall(text))),
|
|
}
|
|
total = sum(len(v) for v in iocs.values())
|
|
logger.info("Extracted %d IOCs from text input", total)
|
|
return iocs
|
|
|
|
|
|
def extract_iocs_from_file(file_path):
|
|
"""Extract IOCs from a file (text report, log file, etc.)."""
|
|
with open(file_path, "r", errors="ignore") as f:
|
|
content = f.read()
|
|
return extract_iocs_from_text(content)
|
|
|
|
|
|
def enrich_ip_virustotal(ip_address, api_key):
|
|
"""Enrich an IP address using VirusTotal API."""
|
|
url = f"https://www.virustotal.com/api/v3/ip_addresses/{ip_address}"
|
|
headers = {"x-apikey": api_key}
|
|
resp = requests.get(url, headers=headers, timeout=30)
|
|
if resp.status_code == 200:
|
|
data = resp.json()["data"]["attributes"]
|
|
result = {
|
|
"ip": ip_address,
|
|
"malicious": data.get("last_analysis_stats", {}).get("malicious", 0),
|
|
"country": data.get("country", "unknown"),
|
|
"asn": data.get("asn", 0),
|
|
"as_owner": data.get("as_owner", "unknown"),
|
|
}
|
|
logger.info("VT enrichment for %s: %d malicious detections", ip_address, result["malicious"])
|
|
return result
|
|
return {"ip": ip_address, "error": resp.status_code}
|
|
|
|
|
|
def enrich_hash_malwarebazaar(file_hash):
|
|
"""Enrich a file hash using MalwareBazaar API."""
|
|
url = "https://mb-api.abuse.ch/api/v1/"
|
|
resp = requests.post(url, data={"query": "get_info", "hash": file_hash}, timeout=30)
|
|
result = resp.json()
|
|
if result.get("query_status") == "ok":
|
|
sample = result["data"][0]
|
|
return {
|
|
"hash": file_hash,
|
|
"family": sample.get("signature", "unknown"),
|
|
"file_type": sample.get("file_type", "unknown"),
|
|
"tags": sample.get("tags", []),
|
|
"first_seen": sample.get("first_seen"),
|
|
}
|
|
return {"hash": file_hash, "status": "not_found"}
|
|
|
|
|
|
def enrich_domain_abuseipdb(domain, api_key):
|
|
"""Check domain reputation via AbuseIPDB."""
|
|
url = "https://api.abuseipdb.com/api/v2/check"
|
|
headers = {"Key": api_key, "Accept": "application/json"}
|
|
resp = requests.get(url, headers=headers, params={"ipAddress": domain}, timeout=30)
|
|
if resp.status_code == 200:
|
|
data = resp.json()["data"]
|
|
return {
|
|
"domain": domain,
|
|
"abuse_confidence": data.get("abuseConfidenceScore", 0),
|
|
"total_reports": data.get("totalReports", 0),
|
|
"country": data.get("countryCode", "unknown"),
|
|
}
|
|
return {"domain": domain, "error": resp.status_code}
|
|
|
|
|
|
def score_ioc(ioc_type, enrichment_data):
|
|
"""Assign confidence score (0-100) based on enrichment results."""
|
|
if ioc_type == "ipv4":
|
|
malicious_count = enrichment_data.get("malicious", 0)
|
|
if malicious_count >= 10:
|
|
return 95
|
|
elif malicious_count >= 5:
|
|
return 80
|
|
elif malicious_count >= 1:
|
|
return 60
|
|
return 30
|
|
elif ioc_type in ("sha256", "md5"):
|
|
if enrichment_data.get("family") and enrichment_data["family"] != "unknown":
|
|
return 95
|
|
return 50
|
|
return 50
|
|
|
|
|
|
def export_stix_bundle(iocs_with_scores, output_path):
|
|
"""Export IOCs as a STIX 2.1 bundle."""
|
|
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}']",
|
|
"md5": lambda v: f"[file:hashes.MD5 = '{v}']",
|
|
}
|
|
stix_indicators = []
|
|
for ioc in iocs_with_scores:
|
|
ioc_type = ioc["type"]
|
|
pattern_fn = pattern_map.get(ioc_type)
|
|
if pattern_fn:
|
|
indicator = 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"),
|
|
confidence=ioc.get("confidence", 50),
|
|
labels=[ioc_type],
|
|
)
|
|
stix_indicators.append(indicator)
|
|
|
|
bundle = Bundle(objects=stix_indicators)
|
|
with open(output_path, "w") as f:
|
|
f.write(bundle.serialize(pretty=True))
|
|
logger.info("STIX bundle exported: %d indicators to %s", len(stix_indicators), output_path)
|
|
|
|
|
|
def generate_ioc_report(iocs_with_scores):
|
|
"""Print a formatted IOC report."""
|
|
lines = [
|
|
"INDICATOR OF COMPROMISE REPORT",
|
|
"=" * 40,
|
|
f"Date: {datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')}",
|
|
f"Total IOCs: {len(iocs_with_scores)}",
|
|
"",
|
|
]
|
|
for ioc_type in ["ipv4", "domain", "sha256", "md5", "url"]:
|
|
typed = [i for i in iocs_with_scores if i["type"] == ioc_type]
|
|
if typed:
|
|
lines.append(f"\n{ioc_type.upper()} ({len(typed)}):")
|
|
for ioc in typed[:10]:
|
|
lines.append(f" {ioc['value'][:60]:60s} Confidence: {ioc.get('confidence', 'N/A')}")
|
|
print("\n".join(lines))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="IOC Collection Agent")
|
|
parser.add_argument("--input-file", help="File to extract IOCs from")
|
|
parser.add_argument("--input-text", help="Text string to extract IOCs from")
|
|
parser.add_argument("--vt-key", help="VirusTotal API key for enrichment")
|
|
parser.add_argument("--output", default="ioc_bundle.json", help="STIX bundle output")
|
|
args = parser.parse_args()
|
|
|
|
raw_iocs = {}
|
|
if args.input_file:
|
|
raw_iocs = extract_iocs_from_file(args.input_file)
|
|
elif args.input_text:
|
|
raw_iocs = extract_iocs_from_text(args.input_text)
|
|
|
|
iocs_with_scores = []
|
|
for ioc_type, values in raw_iocs.items():
|
|
for value in values:
|
|
entry = {"type": ioc_type, "value": value, "confidence": 50}
|
|
if ioc_type == "ipv4" and args.vt_key:
|
|
enrichment = enrich_ip_virustotal(value, args.vt_key)
|
|
entry["confidence"] = score_ioc("ipv4", enrichment)
|
|
entry["enrichment"] = enrichment
|
|
elif ioc_type in ("sha256", "md5"):
|
|
enrichment = enrich_hash_malwarebazaar(value)
|
|
entry["confidence"] = score_ioc(ioc_type, enrichment)
|
|
entry["enrichment"] = enrichment
|
|
iocs_with_scores.append(entry)
|
|
|
|
generate_ioc_report(iocs_with_scores)
|
|
export_stix_bundle(iocs_with_scores, args.output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|