You can run a search which uses JA3 and JA3s hashes to detect abnormal activity on critical servers which are often targeted in supply chain attacks. JA3 is an open-source methodology that allows for creating an MD5 hash of specific values found in the SSL/TLS handshake process, and JA3s is a similar methodology for calculating the JA3 hash of a server session.
In this example, Zeek is used to generate JA3 and JA3s data but you can use any other tool which can generate that data.
This search is most effectively run in the following circumstances:
- with an allow list that limits the number of perceived false positives.
- against network connectivity that is not encrypted over SSL/TLS.
- with internal hosts or netblocks that have limited outbound connectivity as a client.
- in networks without SSL/TLS interceptions or inspection.
Run the following search. You can optimize it by specifying an index and adjusting the time range.
sourcetype="bro:ssl:json" ja3="*" ja3s="*" src_ip IN (192.168.70.0/24) | stats earliest(_time) AS earliest latest(_time) AS latest by ja3, ja3s, src_ip, server_name | eval maxlatest=now() | eval isOutlier=if(earliest >= relative_time(maxlatest, "-1d@d"), 1, 0) | table ja3, ja3s, src_ip, server_name, earliest, latest, maxlatest, isOutlier | convert ctime(earliest) ctime(latest) ctime(maxlatest) | sort earliest desc
The table provides an explanation of what each part of this search achieves. You can adjust this query based on the specifics of your environment.
|sourcetype="bro:ssl:json" ja3="*" ja3s="*" src_ip IN (192.168.70.0/24)||
Search Zeek data for JA3 and JA3s hashes within the critical server defined.
This part of the search uses critical server netblock, 192.168.70.0/24. It's important that you adjust this part of the search to include your own critical servers.
|| stats earliest(_time) AS earliest latest(_time) AS latest by ja3, ja3s, src_ip, server_name||Search for the first and last times the JA3 and JA3s hashes were first seen, and also return src_IP and server_name.|
|| eval maxlatest=now()||Set maxlatest to the current time.|
|| eval isOutlier=if(earliest >= relative_time(maxlatest, "-1d@d"), 1, 0)||Identify outliers as those events that have first been seen in the past day.|
|| table ja3, ja3s, src_ip, server_name, earliest, latest, maxlatest, isOutlier||Display the results in a table with columns in the order shown.|
|| convert ctime(earliest) ctime(latest) ctime(maxlatest)||Convert epoch time for the earliest and latest visits to the domains into a more easily readable format.|
|| sort earliest desc||Sort by the earliest results in descending order.|
This search will return the first seen JA3 and JA3s hashes. In the example below, you can see four TLS sessions which look suspicious for manic.imperial-stout.org and update.lunarstiiiness.com.
The results of this search are temporal, so the results can vary widely based on the timeframe specified. If the time window is too wide or narrow, potential malicious abnormal activity may be missed or blended with legitimate traffic. In many cases, a time window of 7 days can provide the best results for finding targeted malicious activity within the top 20 results.
You can improve the accuracy by adding an allow list of the most common JA3s hashes and/or server_name to remove known entities.
Finally, you might be interested in other processes associated with the Detecting software supply chain attacks use case.