JA4 Fingerprinting with Suricata 8.0

Hi,

I am running Suricata 8.0 in a container using jasonish/suricata:8.0. I want to play around with ja4+ hashed, especially ja4 (for clients) and ja4s for servers. However, I only found instructions on the Suricata documentation about ja4 (client).

My first requirement would be to generate the JA4 hashes then I could add a matching rule to capture them.

Step #1: enable ja4

So, I enabled ja4 in the logging section for eve.log in suricata.yaml.

   - tls:
            enabled: yes
            extended: yes     # enable this for extended logging information
            ja4: yes  # enable JA4 fingerprinting for TLS 1.3
            ja4s: yes # enable JA4S fingerprinting for TLS 1.3 server

Actually, I don’t know if ja4s exists, but it does not complain, so I just tried to enforce it.

Step #2: enable stream processing with memcaps

I headed to my stream section in suricata.yml and provided these settings:

stream:
  memcap: 256 MiB
  #memcap-policy: ignore
  checksum-validation: no     
  #midstream: false
  #midstream-policy: ignore
  inline: auto                  # auto will use inline mode in IPS mode, yes or no set it statically
  reassembly:
    urgent:
      policy: oob             
      oob-limit-policy: drop
    memcap: 512 MiB
    #memcap-policy: ignore
    depth: 1 MiB                # reassemble 1 MiB into a stream
    toserver-chunk-size: 2560
    toclient-chunk-size: 2560
    randomize-chunk-size: yes

I disabled checksum validation as per my understanding, Suricata will reject those packets with wrong checksum, and since checksumming is usually offloaded to the NIC, they are never good in the stream.

Step #3: Interface setting

I run Suricata with af-packet and before i initiate its container, I run another docker stack with a simple linux container to create a dedicated br-XXXXX interface. Then, I can set Suricata to tap on that br-XXXXX interface via the CLI. Accordingly, I set af-packet settings like this:

  - interface: default
    threads: auto
    cluster-id: 99
    cluster-type: cluster_flow
    defrag: yes
    tpacket-v3: yes
    buffer-size: 128535
    disable-promisc: no
    checksum-checks: kernel
    use-mmap: yes

Then, without adding any rule, I made a curl request from my client container to google.com and checked the eve.json.

And I found the ja4 (client) hash generated properly, however, for the reverse direction (from google back to me), there is no log generated at all.

 "timestamp": "2025-08-07T11:12:02.308270+0800",
  "flow_id": 657866726477229,
  "in_iface": "br-c975b4118201",
  "event_type": "tls",
  "src_ip": "172.23.1.2",
  "src_port": 56294,
  "dest_ip": "74.125.200.99",
  "dest_port": 443,
  "proto": "TCP",
  "ip_v": 4,
  "pkt_src": "wire/pcap",
  "tls": {
    "sni": "www.google.com",
    "version": "TLS 1.3",
    "ja4": "t13d3112h2_e8f1e7e78f70_b26ce05bbdd6",
    "client_alpns": [
      "h2",
      "http/1.1"
    ]
  }

I guess this is not just about the ja4s itself, but the reverse direction too. For DNS for instance, I have both directions logged, as it is UDP and no stream should be followed by Suricata. But since I set that up, hopefully properly, I don’t understand why am I not seeing anything from the Google’s servers in my log.

Even if just cat eve.json|jq .|grep src_ip, I only see my container IP and the DNS resolver’s IP

# cat eve.json |jq . |grep src_ip
  "src_ip": "172.23.1.2",
    "src_ip": "172.23.1.2",
  "src_ip": "172.23.1.2",
  "src_ip": "9.9.9.9",
  "src_ip": "172.23.1.2",
    "src_ip": "172.23.1.2",
  "src_ip": "172.23.1.2",
  "src_ip": "9.9.9.9",
  "src_ip": "172.23.1.2",
  "src_ip": "172.23.1.2",
  "src_ip": "172.23.1.2",
  "src_ip": "172.23.1.2",

What am I missing here?

I realized (almost all of) my issues. First of all, I needed to properly enforce JA4 fingerprinting, and I also enabled JA3/JA3s fingerprinting to see if return packets are properly captured by Suricata.Besides the eve.log settings, I needed to explicitly add ja3/4 to the app-layer section:

tls:
  enabled: yes
  detection-ports:
    dp: 443
  # Generate JA3/JA4 fingerprints from client hello. If not specified it
  # will be disabled by default, but enabled if rules require it.
  ja3-fingerprints: yes
  ja4-fingerprints: yes

The second realization was that suricata logs are flow-based :sweat_smile: So, it was already capturing the return packets, but the src_ip was always mine as that the flow-level representation..yikes. Eventually, I realized this when I saw that ja3s hashes are generated for the same log entry. First I thought ja3 is only generated for tls1.2 and below, but things used for generating is available in tls1.3 too. Anyway, i saw the logs now:

 "ts_progress": "client_hello_done",
  "tc_progress": "server_handshake_done",
  "tls": {
    "sni": "www.google.com",
    "version": "TLS 1.3",
    "ja3": {
      "hash": "0149f47eabf9a20d0893e2a44e5a6323",
      "string": "771,4866-4867-4865-49196-49200-159-52393-52392-52394-49195-49199-158-49188-49192-107-49187-49191-103-49162-49172-57-49161-49171-51-157-156-61-60-53-47-255,0-11-10-16-22-23-49-13-43-45-51-21,29-23-30-25-24-256-257-258-259-260,0-1-2"
    },
    "ja3s": {
      "hash": "907bf3ecef1c987c889946b737b43de8",
      "string": "771,4866,51-43"
    },
    "ja4": "t13d3112h2_e8f1e7e78f70_b26ce05bbdd6",
    "client_alpns": [
      "h2",
      "http/1.1"
    ]
  },
  "app_proto": "tls",
  "direction": "to_server",
  "flow": {
    "pkts_toserver": 4,
    "pkts_toclient": 3,
    "bytes_toserver": 789,
    "bytes_toclient": 1574,

I am still trying to figure out what to enable to see ja4s…

Only ja4 is implemented, the other methods are not available due to the patent policy around them. Perhaps we’ll see them as plugins in the future, but that is not available at this point.

Thanks, I just found the same reason after bumping into a forum having the JA4+ auhor sharing this:

Although I thought that, since for home use, I should be able to use ja4+, Suricata has this feature. On the other hand, if I want to use JA4+ with Suricata and I am willing to pay the FoxIO license, how do i get it working? do they have a suricata version, or i should forget using suricata for this?

We can’t ship the JA4+ part that easily, thus the mentioned plugin option might be a valid way for home users. Installing Suricata and adding the plugin in a second step from a different source.

AFAIK there is no plugin for the JA4+ part yet as Victor mentioned.

I see, thanks for the info guys, appreciate it.