"""ThreatFox feed for AYN Antivirus. Fetches IOCs (IPs, domains, URLs, hashes) from the abuse.ch ThreatFox CSV export (free, no API key required). CSV export: https://threatfox.abuse.ch/export/ """ from __future__ import annotations import csv import io import logging from typing import Any, Dict, List import requests from ayn_antivirus.signatures.feeds.base_feed import BaseFeed logger = logging.getLogger(__name__) _CSV_RECENT_URL = "https://threatfox.abuse.ch/export/csv/recent/" _CSV_FULL_URL = "https://threatfox.abuse.ch/export/csv/full/" _TIMEOUT = 60 class ThreatFoxFeed(BaseFeed): """Fetch IOCs from ThreatFox CSV export.""" def get_name(self) -> str: return "threatfox" def fetch(self) -> List[Dict[str, Any]]: return self.fetch_recent() def fetch_recent(self, days: int = 7) -> List[Dict[str, Any]]: """Fetch recent IOCs from CSV export.""" self._rate_limit_wait() self._log("Fetching IOCs from CSV export") try: resp = requests.get(_CSV_RECENT_URL, timeout=_TIMEOUT) resp.raise_for_status() except requests.RequestException as exc: self._error("CSV download failed: %s", exc) return [] results: List[Dict[str, Any]] = [] lines = [l for l in resp.text.splitlines() if l.strip() and not l.startswith("#")] reader = csv.reader(io.StringIO("\n".join(lines))) for row in reader: if len(row) < 6: continue # CSV: 0:first_seen, 1:ioc_id, 2:ioc_value, 3:ioc_type, # 4:threat_type, 5:malware, 6:malware_alias, # 7:malware_printable, 8:last_seen, 9:confidence, # 10:reference, 11:tags, 12:reporter ioc_value = row[2].strip().strip('"') ioc_type_raw = row[3].strip().strip('"').lower() threat_type = row[4].strip().strip('"') if len(row) > 4 else "" malware = row[5].strip().strip('"') if len(row) > 5 else "" malware_printable = row[7].strip().strip('"') if len(row) > 7 else "" confidence = row[9].strip().strip('"') if len(row) > 9 else "0" if not ioc_value: continue # Classify IOC type ioc_type = _classify_ioc(ioc_type_raw, ioc_value) threat_name = malware_printable or malware or "Unknown" # Hash IOCs go into hash DB if ioc_type == "hash": results.append({ "hash": ioc_value.lower(), "threat_name": threat_name, "threat_type": "MALWARE", "severity": "HIGH", "source": "threatfox", "details": f"threat={threat_type}, confidence={confidence}", }) else: clean_value = ioc_value if ioc_type == "ip" and ":" in ioc_value: clean_value = ioc_value.rsplit(":", 1)[0] results.append({ "ioc_type": ioc_type, "value": clean_value, "threat_name": threat_name, "type": threat_type or "C2", "source": "threatfox", "confidence": int(confidence) if confidence.isdigit() else 0, }) self._log("Fetched %d IOC(s)", len(results)) self._mark_updated() return results def _classify_ioc(raw_type: str, value: str) -> str: if "ip" in raw_type: return "ip" if "domain" in raw_type: return "domain" if "url" in raw_type: return "url" if "hash" in raw_type or "sha256" in raw_type or "md5" in raw_type: return "hash" if value.startswith("http://") or value.startswith("https://"): return "url" if len(value) == 64 and all(c in "0123456789abcdef" for c in value.lower()): return "hash" if ":" in value and value.replace(".", "").replace(":", "").isdigit(): return "ip" return "domain"