summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorToni Uhlig <matzeton@googlemail.com>2021-04-16 13:26:09 +0200
committerToni Uhlig <matzeton@googlemail.com>2021-04-16 13:30:24 +0200
commita119a72d13c564ac1274b5274ecb5d86cb98764d (patch)
tree908945bf27fadca1b643ad5c54fb252164ed2163
parenta0fa598ceeceb5496d1b837ca8ff41bdad866a2f (diff)
Added python example to check JA3 hashes against known hashes via JA3er.com
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
-rw-r--r--examples/README.md7
-rwxr-xr-xexamples/py-ja3-checker/py-ja3-checker.py101
2 files changed, 108 insertions, 0 deletions
diff --git a/examples/README.md b/examples/README.md
index 05552b008..80d4464dd 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -41,6 +41,13 @@ Captures and saves risky flows to a PCAP file.
Validate nDPId JSON strings against pre-defined JSON schema's.
See `schema/`.
+Required by `tests/run_tests.sh`
## py-semantic-validation
+
Validate nDPId JSON strings against internal event semantics.
+Required by `tests/run_tests.sh`
+
+## py-ja3-checker
+
+Captures JA3 hashes from nDPIsrvd and checks them against known hashes from [ja3er.com](https://ja3er.com).
diff --git a/examples/py-ja3-checker/py-ja3-checker.py b/examples/py-ja3-checker/py-ja3-checker.py
new file mode 100755
index 000000000..0ef4e3ff9
--- /dev/null
+++ b/examples/py-ja3-checker/py-ja3-checker.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+
+import json
+import os
+import requests
+import sys
+import time
+
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../share/nDPId')
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../usr/share/nDPId')
+try:
+ import nDPIsrvd
+ from nDPIsrvd import nDPIsrvdSocket
+except ImportError:
+ sys.path.append(os.path.dirname(sys.argv[0]) + '/../../dependencies')
+ import nDPIsrvd
+ from nDPIsrvd import nDPIsrvdSocket
+
+global ja3_fps
+ja3_fps = dict()
+# 1 hour = 3600 sec/hour = (60 minutes/hour) * (60 seconds/minute)
+JA3_FP_MAX_AGE = 60 * 60
+
+
+class JA3ER(object):
+ def __init__(self, json_dict):
+ self.json = json_dict
+ self.last_checked = time.time()
+
+ def isTooOld(self):
+ current_time = time.time()
+ if current_time - self.last_checked >= JA3_FP_MAX_AGE:
+ return True
+ return False
+
+
+def isJA3InfoTooOld(ja3_hash):
+ if ja3_hash in ja3_fps:
+ if ja3_fps[ja3_hash].isTooOld() is True:
+ print('Fingerprint {} too old, re-newing..'.format(ja3_hash))
+ return True
+ else:
+ return True
+
+ return False
+
+
+def getInfoFromJA3ER(ja3_hash):
+ response = requests.get('https://ja3er.com/search/' + ja3_hash)
+ if response.status_code == 200:
+ ja3_fps[ja3_hash] = JA3ER(json.loads(response.text, strict=True))
+ if 'error' not in ja3_fps[ja3_hash].json:
+ print('Fingerprints for JA3 {}:'.format(ja3_hash))
+ for ua in ja3_fps[ja3_hash].json:
+ if 'User-Agent' in ua:
+ print('\tUser-Agent: {}\n'
+ '\t Last seen: {}, '
+ 'Count: {}'.format(ua['User-Agent'],
+ ua['Last_seen'],
+ ua['Count']))
+ elif 'Comment' in ua:
+ print('\tComment...: {}\n'
+ '\t Reported: {}'
+ .format(ua['Comment'].replace('\r', '')
+ .replace('\n', ' '), ua['Reported']))
+ else:
+ print(ua)
+ else:
+ print('No fingerprint for JA3 {} found.'.format(ja3_hash))
+
+
+def onJsonLineRecvd(json_dict, current_flow, global_user_data):
+ if 'tls' in json_dict and 'ja3' in json_dict['tls']:
+
+ if json_dict['tls']['client_requested_server_name'] == 'ja3er.com':
+ return True
+
+ if isJA3InfoTooOld(json_dict['tls']['ja3']) is True:
+ getInfoFromJA3ER(json_dict['tls']['ja3'])
+
+ if isJA3InfoTooOld(json_dict['tls']['ja3']) is True:
+ getInfoFromJA3ER(json_dict['tls']['ja3s'])
+
+ return True
+
+
+if __name__ == '__main__':
+ argparser = nDPIsrvd.defaultArgumentParser()
+ args = argparser.parse_args()
+ address = nDPIsrvd.validateAddress(args)
+
+ sys.stderr.write('Recv buffer size: {}\n'
+ .format(nDPIsrvd.NETWORK_BUFFER_MAX_SIZE))
+ sys.stderr.write('Connecting to {} ..\n'
+ .format(address[0] + ':' +
+ str(address[1])
+ if type(address) is tuple else address))
+
+ nsock = nDPIsrvdSocket()
+ nsock.connect(address)
+ nsock.loop(onJsonLineRecvd, None)