docs: add Fingerprinting page covering sniffer, prober, and Caddy fp module
165
Fingerprinting.md
Normal file
165
Fingerprinting.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# Fingerprinting
|
||||||
|
|
||||||
|
DECNET builds a multi-layer fingerprint of every attacker from three
|
||||||
|
independent sources: **passive wire capture**, **active probing**, and
|
||||||
|
**inline HTTP inspection**. Each layer contributes distinct evidence;
|
||||||
|
together they let you tell a curl script from a Metasploit operator from a
|
||||||
|
nation-state implant even when the source IP changes.
|
||||||
|
|
||||||
|
All fingerprint data is stored as `bounty` rows in the DECNET database and
|
||||||
|
surfaces in the **Attacker detail** page under the *Fingerprints* tab.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 1 — Passive sniffer (network layer)
|
||||||
|
|
||||||
|
The sniffer runs fleet-wide on the host interface and reads raw packets
|
||||||
|
without touching any decky service. It fires on the first packet of each
|
||||||
|
connection, so it captures the attacker's stack signature before any
|
||||||
|
application-level exchange.
|
||||||
|
|
||||||
|
| Fingerprint | What it captures | Algorithm |
|
||||||
|
|---|---|---|
|
||||||
|
| **JA3 / JA3S** | TLS ClientHello / ServerHello cipher suite and extension order | MD5 of normalised fields per Salesforce spec |
|
||||||
|
| **JA4 / JA4S / JA4L** | TLS 1.3-aware version; JA4L adds latency timing | FoxIO JA4 spec |
|
||||||
|
| **TCP SYN OS** | MSS, window scale, TCP option order from the SYN | Mini-p0f classifier (`decnet/sniffer/p0f.py`) |
|
||||||
|
| **JA4-QUIC** | QUIC Initial ClientHello — QUIC-specific extensions and transport params | FoxIO JA4-QUIC spec |
|
||||||
|
| **Flow timing** | Round-trip latency and inter-packet timing | Raw timestamps from the sniffer |
|
||||||
|
|
||||||
|
Sniffer events land as `attacker.observed` or `attacker.fingerprinted` bus
|
||||||
|
events consumed by the correlator and ingester.
|
||||||
|
|
||||||
|
> **Limitation:** the sniffer only sees the TLS handshake — it cannot read
|
||||||
|
> HTTP headers or QUIC stream frames inside an encrypted session. Layers 2
|
||||||
|
> and 3 fill that gap.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 2 — Active prober (application layer)
|
||||||
|
|
||||||
|
After a new attacker is first observed, the prober worker reaches back
|
||||||
|
out to the attacker's IP on a set of default ports to collect
|
||||||
|
application-level fingerprints.
|
||||||
|
|
||||||
|
| Fingerprint | Protocol | Ports probed |
|
||||||
|
|---|---|---|
|
||||||
|
| **JARM** | TLS (any HTTPS-ish service) | 443, 8443, 8080, 4443, 50050, 2222, 993, 995, 8888, 9001 |
|
||||||
|
| **HASSH** | SSH server | 22, 2222, 22222, 2022 |
|
||||||
|
| **TCP fingerprint** | TCP SYN response analysis | 22, 80, 443, 8080, 8443, 445, 3389 |
|
||||||
|
|
||||||
|
Active probes are stealthy: they look like ordinary clients, carry no
|
||||||
|
DECNET-specific banner, and use the same port-rotation patterns an
|
||||||
|
informed scanner would use. See [Security-and-Stealth](Security-and-Stealth).
|
||||||
|
|
||||||
|
When a fingerprint changes between probes, a `attacker.fingerprint_rotated`
|
||||||
|
bus event fires — that is a strong signal of infrastructure churn (VPS
|
||||||
|
swap, cert rotation, banner rewrite).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layer 3 — Inline HTTP fingerprinting (Caddy fp module)
|
||||||
|
|
||||||
|
The `http` and `https` decky templates ship with a custom Caddy module
|
||||||
|
(`decnet_fp`) that intercepts connections at the byte level, before
|
||||||
|
Caddy's HTTP parser sees them. This gives wire-accurate fingerprints
|
||||||
|
that cannot be faked by HTTP-level header manipulation.
|
||||||
|
|
||||||
|
### JA4H (HTTP request header order)
|
||||||
|
|
||||||
|
The `decnet_fp` listener wrapper taps the raw TLS stream and buffers the
|
||||||
|
first request headers of each connection before replaying them to Caddy's
|
||||||
|
parser.
|
||||||
|
|
||||||
|
- **h1:** headers are split by `\r\n` in arrival order.
|
||||||
|
- **h2:** a per-connection HPACK decoder maintains the dynamic table and
|
||||||
|
emits headers in HPACK decode order — pseudo-headers
|
||||||
|
(`:method`, `:path`, `:scheme`, `:authority`) appear first, then regular
|
||||||
|
headers in the order the client encoded them.
|
||||||
|
|
||||||
|
The ordered list feeds `_compute_ja4h` in `syslog_bridge.py`, which
|
||||||
|
produces a JA4H hash per the FoxIO spec.
|
||||||
|
|
||||||
|
> Map-iteration order in Go is randomised; DECNET captures order at the
|
||||||
|
> *byte level*, not from `http.Header`, so the JA4H is reproducible and
|
||||||
|
> meaningful.
|
||||||
|
|
||||||
|
### H2 SETTINGS
|
||||||
|
|
||||||
|
During the h2 connection preface, the client sends a `SETTINGS` frame
|
||||||
|
listing its implementation parameters. The fp module parses the raw
|
||||||
|
6-byte `(id, value)` tuples in wire order and records:
|
||||||
|
|
||||||
|
- `settings` — map of setting name → value
|
||||||
|
(e.g. `HEADER_TABLE_SIZE`, `MAX_CONCURRENT_STREAMS`, `INITIAL_WINDOW_SIZE`)
|
||||||
|
- `frame_order` — setting IDs in the exact order the client sent them
|
||||||
|
|
||||||
|
Different HTTP/2 implementations (curl, Chrome, Firefox, Go net/http,
|
||||||
|
Java HttpClient) have characteristic SETTINGS maps and orderings.
|
||||||
|
|
||||||
|
### H3 SETTINGS
|
||||||
|
|
||||||
|
For HTTP/3, the QUIC server is Caddy with native h3 support. Caddy
|
||||||
|
exposes the client's h3 SETTINGS frame via the `http3.Settingser`
|
||||||
|
interface on the `ResponseWriter`. The fp module captures:
|
||||||
|
|
||||||
|
- `EnableDatagrams` — whether the client advertised H3 datagram support
|
||||||
|
- `EnableExtendedConnect` — extended CONNECT (used by WebTransport)
|
||||||
|
- `Other` — any additional settings (including GREASE entries)
|
||||||
|
|
||||||
|
### Source port as fingerprint signal
|
||||||
|
|
||||||
|
`remote_addr` in every fp record is the full `host:port` string from
|
||||||
|
Go's network layer. The collector strips the port before resolving
|
||||||
|
attacker identity (so 50 connections from the same IP do not produce 50
|
||||||
|
attackers), but preserves it as `remote_port` in the structured fields.
|
||||||
|
|
||||||
|
An attacker whose tooling consistently originates from the same source
|
||||||
|
port (or a narrow range) is a meaningful signal — some NAT devices, VPN
|
||||||
|
clients, and C2 frameworks exhibit this behaviour. `remote_port` is
|
||||||
|
stored in the `fingerprint` bounty payload and visible in the Attacker
|
||||||
|
detail page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Where fingerprints are stored
|
||||||
|
|
||||||
|
Every fingerprint event produces a `bounty` row:
|
||||||
|
|
||||||
|
| Bounty `fingerprint_type` | Source | Key discriminating fields |
|
||||||
|
|---|---|---|
|
||||||
|
| `ja3` / `ja4` / `ja4s` | Sniffer | `hash`, `tls_version`, `ciphers` |
|
||||||
|
| `ja4_quic` | Sniffer | `ja4_quic`, `sni`, `alpn` |
|
||||||
|
| `tcp_os` | Sniffer | `os_guess`, `mss`, `window_scale` |
|
||||||
|
| `jarm` | Prober | `jarm_hash`, `port` |
|
||||||
|
| `hassh` | Prober | `hassh_server`, `port` |
|
||||||
|
| `tcpfp` | Prober | `tcp_fp_hash`, `port` |
|
||||||
|
| `ja4h` | Caddy fp module | `ja4h`, `protocol`, `method`, `remote_port` |
|
||||||
|
| `http2_settings` | Caddy fp module | `settings`, `frame_order`, `remote_port` |
|
||||||
|
| `http3_settings` | Caddy fp module | `settings`, `remote_port` |
|
||||||
|
|
||||||
|
Bounties are deduplicated per `(attacker_uuid, fingerprint_type, hash)` so
|
||||||
|
repeated connections from the same attacker produce one row, not thousands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Enabling inline HTTP fingerprinting
|
||||||
|
|
||||||
|
The Caddy fp module is **built into the `http` and `https` decky templates
|
||||||
|
automatically** — no extra configuration is needed. The module activates
|
||||||
|
when the template is deployed.
|
||||||
|
|
||||||
|
For HTTP/3, ensure `http/3` is listed in the service's `http_versions`
|
||||||
|
setting. Caddy's native h3 stack handles UDP/443; the fp module hooks into
|
||||||
|
it via the `http3.Settingser` interface.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related pages
|
||||||
|
|
||||||
|
- [Identity-Resolution](Identity-Resolution) — how fingerprints are
|
||||||
|
clustered into attacker identities
|
||||||
|
- [OS-Fingerprint-Spoofing](OS-Fingerprint-Spoofing) — how DECNET spoofs
|
||||||
|
*its own* OS fingerprint to look like the target OS
|
||||||
|
- [Security-and-Stealth](Security-and-Stealth) — probe stealth measures
|
||||||
|
- [Logging-and-Syslog](Logging-and-Syslog) — how fp socket records flow
|
||||||
|
through syslog_bridge to the collector
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
- [Database-Drivers](Database-Drivers)
|
- [Database-Drivers](Database-Drivers)
|
||||||
- [Systemd-Setup](Systemd-Setup)
|
- [Systemd-Setup](Systemd-Setup)
|
||||||
- [Logging-and-Syslog](Logging-and-Syslog)
|
- [Logging-and-Syslog](Logging-and-Syslog)
|
||||||
|
- [Fingerprinting](Fingerprinting)
|
||||||
- [Service-Bus](Service-Bus)
|
- [Service-Bus](Service-Bus)
|
||||||
- [Realism](Realism)
|
- [Realism](Realism)
|
||||||
- [Web-Dashboard](Web-Dashboard)
|
- [Web-Dashboard](Web-Dashboard)
|
||||||
|
|||||||
Reference in New Issue
Block a user