stage-19-fix: ThreatFox + MalwareBazaar — real API shape

Live test against abuse.ch revealed two issues with the stage-19 wiring:

- ThreatFox returns `ioc` (not `ioc_value`) and `first_seen` (not
  `first_seen_utc`) — older field names from stale docs. Parser now reads
  the real names and falls back to the old aliases defensively. Also
  captures `malware_malpedia` (per-family writeup URL) and
  `threat_type_desc` for richer downstream prose.
- MalwareBazaar's API expects form-encoded bodies, unlike ThreatFox's
  JSON. Extended _http with form_body=; MB fetcher switched to it.

Verified live: 10 ThreatFox cases landed with mixed botnet/malware
classification (4/6 split from threat_type signal — first real
incident-type diversity from a single feed). 10 MalwareBazaar cases
landed with sha256+sha1 hash observables and exe/file_type metadata.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
m17hr1l
2026-05-20 22:25:56 +02:00
parent d87bd710bb
commit 85830be9fa
2 changed files with 24 additions and 10 deletions

View File

@@ -59,11 +59,11 @@ def test_feodo_record_to_case():
def test_threatfox_row_url_to_case():
row = {
"id": "1234567",
"ioc_value": "http://1.2.3.4/x.bin",
"ioc": "http://1.2.3.4/x.bin",
"ioc_type": "url",
"threat_type": "payload_delivery",
"malware_printable": "Cobalt Strike",
"first_seen_utc": "2026-05-19 10:00:00",
"first_seen": "2026-05-19 10:00:00",
"confidence_level": 100,
"tags": ["c2", "stager"],
"reporter": "anon",
@@ -81,11 +81,11 @@ def test_threatfox_row_url_to_case():
def test_threatfox_row_ip_port_to_case():
row = {
"id": "9999",
"ioc_value": "5.6.7.8:443",
"ioc": "5.6.7.8:443",
"ioc_type": "ip:port",
"threat_type": "botnet_cc",
"malware_printable": "Qakbot",
"first_seen_utc": "2026-05-18 10:00:00",
"first_seen": "2026-05-18 10:00:00",
}
case = _threatfox_row_to_case(row)
assert case is not None
@@ -93,7 +93,7 @@ def test_threatfox_row_ip_port_to_case():
def test_threatfox_row_rejects_unknown_type():
assert _threatfox_row_to_case({"id": "1", "ioc_value": "x", "ioc_type": "ja3_fp"}) is None
assert _threatfox_row_to_case({"id": "1", "ioc": "x", "ioc_type": "ja3_fp"}) is None
def test_malware_bazaar_row_to_case():