remove infra.md.example, infra.md is the source of truth
This commit is contained in:
117
ayn-antivirus/ayn_antivirus/signatures/feeds/threatfox.py
Normal file
117
ayn-antivirus/ayn_antivirus/signatures/feeds/threatfox.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user