Fingerprinting SSL Servers using JARM and Python

Introducing pyJARM, the simple way to use JARM with Python

pyJARM logo by Charlie Sestito
pyJARM logo by Charlie Sestito

In early November 2020, SalesForce announced a new active TLS fingerprinting tool, inexplicably named JARM. The premise of JARM is that if a target server is scanned using a variety of different custom-crafted TCP “Client hello” packets, the resulting responses can be combined and hashed to provide a fingerprint that is indicative of a shared or similar application or server stack.

Building on this basic premise, it’s asserted that different companies, platforms, or individual products will present similar fingerprints and therefore can allow researchers and security teams to identify possible C2 servers or group hosts with similar SSL configurations.

I believe there are many interesting use-cases for JARM, and once proven there are opportunities to add JARM to a number of existing products and research initiatives. A short list of these use-cases include:

  1. Identifying malicious servers or malware C2 servers on the internet
  2. Identifying hosts owned by an organization that may not be using an approved or default configuration
  3. Grouping hosts that have similar configurations
  4. Identifying hosts that are/are not behind a firewall or WAF

JARM works by crafting 10 custom TCP “Client hello” packets which it sends to the target server and records the responses. Each response is then parsed into a three piece record, with each piece being separated by a pipe. These raw results look like c02b|0303|h2|0017-0010. All of these raw scan results are combined into a single comma-separated string and hashed.

The hash itself is a custom fuzzy hash that produces a 62 character fingerprint. The first 30 characters are made up of the cipher and TLS version chosen by the server for each of the 10 client hello’s sent (i.e. 3 characters per scan). The remaining 32 characters are a truncated SHA256 hash of the “Server hello” extensions returned by the server. A JARM fingerprint that is all 0’s indicates that the server did not respond.

When I saw the initial implementation of JARM in Python that was delivered by the Salesforce team I was happy that they’d made it available and open sourced it. However, with my developer hat on, when I looked at the code I couldn’t help but think that it could be more usable. I worked with Francesco Vigo, a colleague at Palo Alto Networks, to rewrite the original implementation into a more convenient library, known as pyJARM.

We also proceeded to add some additional features to pyJARM that we thought would be convenient for developers and researchers, such as proxy and async support. We also made pyJARM available to install via pip and open-sourced it under the ISC license.

Installation of pyJARM is simple and can be accomplished with a simple pip install pyjarm using any machine with access to pypi and Python 3.7+ installed.

pyJARM can be run either as a command line tool or utilized programatically. When running as a command line tool it can accept a single target, or a file with a list of inputs. It can output to stdout or write directly to a csv file.

You can also use pyJARM programmatically by importing the Scanner class and calling Scanner.scan with an IP/Domain and port number.

We’re eager to see wider adoption of pyJARM and JARM generally in the infosec community and within vendor tools and solutions. If you have any questions, issues, or enhancements for pyJARM, we’d encourage you to open an Issue or PR on Github.

Founder @OchronaSec | PANW, ex Expanse, ex Tenable | DevSecOps | Automation | All views are my own... and awesome