feat(prober-cert): schema for active TLS cert capture

Adds storage for TLS certificate details collected from attacker-run
servers by the active prober (sibling to the existing JARM probe).

- AttackerIdentity.tls_cert_sha256 / Campaign.tls_cert_sha256:
  JSON list[str] columns mirroring ja3_hashes / hassh_hashes for
  federation gossip.
- ingester clause 9b: emits a 'tls_certificate' fingerprint bounty
  when a prober event carries subject_cn (disjoint from the existing
  sniffer-gated clause).
- Prober-side capture (ssl.wrap_socket follow-up after JARM) and
  profiler rollup land in sibling commits.
This commit is contained in:
2026-04-28 11:09:25 -04:00
parent e986e81421
commit 4749c972e5
4 changed files with 148 additions and 0 deletions

View File

@@ -499,6 +499,30 @@ async def _extract_bounty(
},
})
# 9b. TLS certificate from active prober (sibling of the JARM probe;
# captured by a follow-up ssl.wrap_socket() against attacker-run TLS
# servers). Disjoint from clause 8 above which is the sniffer path.
_prober_subject_cn = _fields.get("subject_cn")
if _prober_subject_cn and log_data.get("service") == "prober":
await repo.add_bounty({
"decky": log_data.get("decky"),
"service": "prober",
"attacker_ip": _fields.get("target_ip", "Unknown"),
"bounty_type": "fingerprint",
"payload": {
"fingerprint_type": "tls_certificate",
"subject_cn": _prober_subject_cn,
"issuer": _fields.get("issuer"),
"self_signed": _fields.get("self_signed"),
"not_before": _fields.get("not_before"),
"not_after": _fields.get("not_after"),
"sans": _fields.get("sans"),
"cert_sha256": _fields.get("cert_sha256"),
"target_ip": _fields.get("target_ip"),
"target_port": _fields.get("target_port"),
},
})
# 10. HASSHServer fingerprint from active prober
_hassh = _fields.get("hassh_server_hash")
if _hassh and log_data.get("service") == "prober":