maskit opened a new issue, #13727:
URL: https://github.com/apache/trafficserver/issues/13727

   The `ja4_fingerprint` plugin produces JA4 values that don't match the [JA4 
spec](https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md) or 
other JA4 implementations, for two reasons beyond the missing signature 
algorithms addressed in #13725. `jax_fingerprint --method JA4` isn't affected 
by either.
   
   ## 1. Cipher suites are byte-swapped in the `b` section
   
   `add_ciphers()` builds each cipher code with [`make_word(buf[i], buf[i + 
1])`](https://github.com/apache/trafficserver/blob/e32c31f81c547ce9eac481962377dcb85fc951a1/plugins/experimental/ja4_fingerprint/plugin.cc#L314),
 and 
[`make_word`](https://github.com/apache/trafficserver/blob/e32c31f81c547ce9eac481962377dcb85fc951a1/plugins/experimental/ja4_fingerprint/plugin.cc#L139)
 treats its first argument as the low byte:
   
   ```cpp
   make_word(unsigned char lowbyte, unsigned char highbyte)
   {
     return (static_cast<std::uint16_t>(highbyte) << 8) | lowbyte;
   }
   ```
   
   Cipher suites are big-endian on the wire, so every code is swapped (0x1302 
becomes 0x0213) before it's sorted and hashed. As a result, the `b` section 
never matches other JA4 tools for any client. The cipher count in the `a` 
section is still correct, and GREASE values still get filtered because they 
read the same either way, so the output looks plausible.
   
   `jax_fingerprint` reads the same bytes big-endian 
([`tls_client_hello_summary.cc`](https://github.com/apache/trafficserver/blob/e32c31f81c547ce9eac481962377dcb85fc951a1/plugins/experimental/jax_fingerprint/ja4/tls_client_hello_summary.cc#L81)),
 and its `b` matches the spec.
   
   The unit tests don't catch this because they pass cipher values to 
`add_cipher()` as integers and never go through `make_word`.
   
   ### Reproduction
   
   Load both plugins and connect once with `openssl s_client`:
   
   ```text
   ja4_fingerprint.so
   jax_fingerprint.so --standalone --method JA4 --log-filename jax_ja4
   ```
   
   For a ClientHello from OpenSSL 3.6 offering 30 cipher suites, the two 
plugins logged the same `a` and `c` but a different `b`:
   
   | Plugin | `b` |
   |---|---|
   | `ja4_fingerprint` | `0be778905493` |
   | `jax_fingerprint` | `1d37bd780c83` |
   
   Hashing that ClientHello's cipher list by hand, per the spec, gives 
`1d37bd780c83`. Hashing the list with each code byte-swapped gives 
`0be778905493`.
   
   ## 2. Empty lists are hashed instead of reported as zeros
   
   The spec says `b` is `000000000000` when there are no ciphers, and `c` is 
`000000000000` when there are no extensions left after removing SNI and ALPN. 
[`make_JA4_fingerprint()`](https://github.com/apache/trafficserver/blob/e32c31f81c547ce9eac481962377dcb85fc951a1/plugins/experimental/ja4_fingerprint/ja4.h#L156-L158)
 always hashes the raw string, so an empty list gives `e3b0c44298fc`, the 
SHA-256 of an empty string. `jax_fingerprint` outputs zeros in both cases.
   
   I found this by reading the code and haven't reproduced it with live 
traffic. A ClientHello with no extensions other than SNI/ALPN, which some TLS 
1.2 clients send, should trigger the `c` case. A ClientHello with no cipher 
suites is malformed and is probably rejected by the TLS library before the 
plugin runs.
   
   ## Impact
   
   Fingerprints from `ja4_fingerprint` can't be compared with JA4 values from 
other sources such as ja4db, threat-intel feeds, or network sensors. Rules 
built from its own output match only other `ja4_fingerprint` output. Fixing 
either bug changes the values the plugin produces, so stored fingerprints would 
need to be regenerated.
   
   ## Recommendation
   
   Use `jax_fingerprint` with `--method JA4` instead of `ja4_fingerprint`. It 
doesn't have either bug. With #13725 applied, its whole fingerprint matches the 
spec's published example and values computed by hand from live ClientHellos.
   
   The configuration below gives the same headers and a comparable log:
   
   ```text
   jax_fingerprint.so --standalone --method JA4 --header ja4 --via-header 
x-ja4-via --log-filename ja4_fingerprint
   ```
   
   - `--via-header` sets the header to `proxy.config.proxy_name`, as 
`ja4_fingerprint` does.
   - To leave `ja4` headers the client already sent in place, which is what 
`ja4_fingerprint --preserve` does, add `--mode keep`.
   - Log lines start with `Client:` instead of `Client IP:`, so parsers keyed 
on that prefix need updating.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to