Detecting Encrypted C2 with JA4+ Fingerprinting in Suricata
JA3 struggles against TLS 1.3. Configure JA4 in Suricata, write a ja4.hash detection rule, and correlate with SNI to catch encrypted C2 and data exfiltration.
An outbound HTTPS flow, to a domain with an unremarkable reputation, on port 443. Nothing in the firewall logs distinguishes it from a legitimate API call. That is exactly the blind spot modern C2 exploits: encryption hides the content, not how the client negotiated the TLS session. JA4+ fingerprinting targets that blind spot, and Suricata can now exploit it natively.
Why JA3 no longer holds up against TLS 1.3
JA3 did great service for years: hash the order of ciphers, extensions, and curves in the ClientHello to produce a fingerprint of the TLS client. The problem is that TLS 1.3 and modern browsers broke both assumptions JA3 relied on.
First, GREASE: Chrome and Chromium-based browsers deliberately inject random values into the cipher and extension lists, specifically to defeat server-side fingerprinting. As a result, the same browser produces dozens of different JA3 hashes across connections. Second, TLS 1.3 shrank and reordered the default extension set, which flattens the diversity between very different tools: a Cobalt Strike beacon and a legitimate client can produce a near-identical JA3 if both sit on the same underlying TLS library. JA3 identifies the library, not the intent.
What JA4+ actually adds
JA4+ (developed by FoxIO) is a family of fingerprints, not a single hash. Three variants matter for network detection:
- JA4 (TLS client): encodes the protocol version, whether an SNI is present, the count of ciphers and extensions, the negotiated ALPN, then separately hashes the sorted cipher list and the sorted extension list. Sorting neutralizes GREASE and random reordering, which keeps the fingerprint stable for a given tool, unlike JA3.
- JA4S: the server-side equivalent, useful for spotting a command-and-control infrastructure that keeps answering with the same TLS configuration even behind different decoy domains.
- JA4H: an HTTP fingerprint based on the method, header ordering, and user-agent characteristics. It complements JA4 when malicious traffic drops back to plaintext or unencrypted HTTP/2 ahead of the TLS tunnel.
Two campaigns from the same offensive builder often share a JA4 even when their JA3 differs based on the compilation environment: JA4 tracks the tool, not the library.
Enabling JA4 in Suricata
Suricata exposes JA4 as a native field in the TLS engine in recent versions. It has to be turned on explicitly in the configuration:
# suricata.yaml
app-layer:
protocols:
tls:
enabled: yes
ja4-fingerprints: yes
outputs:
- eve-log:
enabled: yes
types:
- tls:
ja4: yes
extended: yes(Illustrative example: check the exact key against your Suricata version and the bundled fingerprinting library; the configuration principle stays the same.) Once enabled, every tls record in eve.json carries a ja4 field you can search, correlate, and, more importantly, match on directly in detection rules through the ja4.hash keyword.
Writing a rule on ja4.hash
The ja4.hash keyword behaves like any content matcher, except it applies to the TLS metadata the engine computes rather than to the packet body:
alert tls $HOME_NET any -> $EXTERNAL_NET any (msg:"JA4 matches a known C2 fingerprint"; \
ja4.hash; content:"t13d1516h2_8daaf6152771_02713d6af862"; \
classtype:trojan-activity; sid:9010001; rev:1;)
The same principle applies on the server side with ja4s.hash, useful for pinning down a control infrastructure that recycles its certificate or TLS stack across several decoy domains:
alert tls $HOME_NET any -> $EXTERNAL_NET any (msg:"JA4S matches known C2 infrastructure"; \
ja4s.hash; content:"t130200_1301_a56c5b993250"; \
classtype:command-and-control; sid:9010002; rev:1;)
These rules assume you already have a hash to watch for, sourced from an incident report, a shared intelligence feed, or your own corpus. That is the "targeted signature" use case. The other one, and often the more durable one, is the allowlist.
Building an allowlist of legitimate internal JA4s
Rather than chasing known-bad hashes, always a step behind the next variant, the approach that holds up over time flips the logic: catalog the legitimate JA4s already present in your environment, then alert on anything that falls outside that baseline.
The concrete process:
- Over a clean reference window (one to two weeks of validated traffic), pull the distribution of
ja4values observed ineve.json, broken down by network segment where possible. - Group by frequency and consistency against the associated SNI: a JA4 that consistently pairs with the same small set of domains signals likely legitimacy.
- Document the retained list as a baseline, with a periodic review, since browser or runtime updates sometimes change the JA4.
- Flip the detection logic: instead of rules listing what is bad, run a tracking view that surfaces every JA4 seen for the first time, or fewer than N times, over the period.
A rare JA4, paired with a generic SNI or a periodic connection volume, always deserves a human look before being written off as benign.
Correlating JA4, SNI, and behavior to tell a beacon from a normal client
JA4 alone proves nothing: plenty of legitimate tools (monitoring agents, internal HTTP libraries) also produce rare fingerprints. Reliable detection is built on a cluster of signals, not a single field.
Three axes worth cross-referencing every time:
- JA4 x SNI: the same JA4 appearing against different SNIs over time, with no logical link between the domains, is suspicious. A legitimate client generally stays loyal to a small, coherent set of destinations.
- Timing regularity (beaconing): C2 malware calls home at near-constant intervals with low jitter. Measuring the standard deviation of connection intervals for a given JA4/SNI pair surfaces this pattern far better than a raw volume count.
- Volume and exchange size: exfiltration often shows up as an unusual asymmetry between outbound and inbound volume, or as very consistently sized packets (systematic serialization) riding on a JA4 fingerprint never seen before.
An isolated unknown JA4 is a lead. An unknown JA4, at a regular interval, with an abnormal outbound-to-inbound ratio, is an incident to open.
Use case: the internal AI agent exfiltrating over HTTPS
Rolling out an internal AI agent (a coding assistant, a ticket automation tool, a document analysis agent) adds a source of outbound encrypted traffic that is often poorly scoped: calls to a language model API, sometimes routed through several application proxies. The security lead ends up with an HTTPS flow that is legitimate on paper, but whose actual content (prompts, document excerpts, results) is entirely opaque to classic inspection.
The JA4 method applies directly:
- Identify the JA4 specific to the agent's runtime (the HTTP client or SDK used to call the model API) and record it in the allowlist, paired with the vendor's expected SNI.
- Watch for any drift: a JA4 that changes without a known runtime update, an SNI that changes without a documented configuration change, or an outbound volume climbing with no correlation to the agent's functional activity.
- Set a reasonable volume threshold over a rolling window for that JA4/SNI pair: a well-scoped agent has a stable usage profile, and a sustained overshoot deserves a review, whether it is a data leak or the agent being repurposed by a third party.
- Stay alert to a JA4 identical to the agent's, but paired with an unexpected SNI: that is the signature of a component reusing the same client library to talk to a destination it was never meant to reach.
The point is not to block the agent, but to give it the same treatment as any other sensitive outbound service: a known network profile, documented, and monitored for drift.
False-positive traps
Three sources of noise show up consistently once JA4 goes into production:
- TLS library rotation. A runtime update (Node.js, Python, a browser) can change the JA4 without changing the application's behavior at all. A frozen allowlist quickly becomes a false-positive generator with every documented update cycle; plan a review whenever a fleet-wide version change lands.
- Shared CDNs and infrastructure. Two entirely unrelated services, hosted behind the same CDN or the same API gateway, can produce an identical server-side JA4S. JA4S alone is never enough to judge a destination's legitimacy; always cross-reference it with the SNI and, where possible, the resolved IP range.
- Corpus drift. A baseline of legitimate JA4s built once and never revisited goes stale within months, at the pace of application updates. Keeping it current takes a process, not a static file.
In summary
JA4+ is a different animal from JA3: by sorting the cipher and extension lists before hashing, it neutralizes GREASE and makes the fingerprint representative of the tool rather than the underlying TLS library. Enabled in Suricata through ja4-fingerprints, matched in rules with ja4.hash and ja4s.hash, and cross-referenced with SNI and timing behavior, it lets you spot a C2 beacon or an exfiltration channel inside HTTPS traffic that, on the surface, looks like any other API call, including one from an internal AI agent.
Building and maintaining that JA4 signature corpus, validating every fingerprint against real benign traffic to strip out false positives, and tracking TLS library drift release after release: that is exactly the ongoing work behind the ThreatClaw network detection feed.
Related articles
ET Open is free, ET Pro and Talos are paid, and curated feeds sit in between. A practical comparison of Suricata and Snort rulesets for network detection, with the criteria that matter for a SOC or MSSP.
Suricata vs Snort compared on architecture, rule compatibility, ICS/OT coverage, and migration steps, to help you pick a NIDS engine on technical merit.
Network intrusion detection NIDS: where Suricata sees what an EDR cannot, the anatomy of a rule, and why a curated rule pack beats a raw, noisy rule feed.
A practical guide to writing a Suricata rule: header structure, modern sticky buffers, a before/after CVE example, pcap testing, and the false-positive trap.