From 5fcb1cf372238f069eeacafe189d0e51a9221dec Mon Sep 17 00:00:00 2001 From: Niall Date: Wed, 25 Sep 2024 19:44:35 +0000 Subject: [PATCH 1/4] adding in JA4FingerprintSummary code, which summarises JA4/Server fingerprints --- src/core/operations/JA4FingerprintSummary.mjs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/core/operations/JA4FingerprintSummary.mjs diff --git a/src/core/operations/JA4FingerprintSummary.mjs b/src/core/operations/JA4FingerprintSummary.mjs new file mode 100644 index 0000000000..000c62c6dc --- /dev/null +++ b/src/core/operations/JA4FingerprintSummary.mjs @@ -0,0 +1,117 @@ +/** + * @author 0xff1ce [github.com/0xff1ce] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * JA4 Fingerprint Summary operation + */ +class JA4FingerprintSummary extends Operation { + + /** + * JA4FingerprintSummary constructor + */ + constructor() { + super(); + + this.name = "JA4 Fingerprint Summary"; + this.module = "Crypto"; + this.description = "Generates a JA4 fingerprint summary to explain the hash output of the JA4 Fingerprint recipe.

Input: A standard JA4/JA4Server Fingerprint"; + this.infoURL = "https://blog.foxio.io/ja4%2B-network-fingerprinting"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Fingerprint format", + type: "option", + value: ["JA4", "JA4Server"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [inputFormat] = args; + let retString = "Fingerprint summary of: " + input + "\n+------------------------------------------------------------+" + if (inputFormat === "JA4") { + const protocolMap = { t: "TCP", q: "QUIC" }; + const tlsMap = { + "13": "1.3", "12": "1.2", "11": "1.1", "10": "1.0", + "s3": "SSL 3.0", "s2": "SSL 2.0", "s1": "SSL 1.0" + }; + const sniMap = { d: "Yes (to domain)", i: "No (to IP)" }; + const alpnMap = { + "00": "No ALPN Chosen", "dt": "DNS-over-TLS", + "h1": "HTTP/1.1", "h2": "HTTP/2" + }; + + // Protocol + retString += `\nProtocol: ${protocolMap[input[0]] || "Unknown"}`; + + // TLS Version + retString += `\nTLS Version: ${tlsMap[input.slice(1, 3)] || input.slice(1, 3)}`; + + // SNI Extension + retString += `\nSNI Extension Present: ${sniMap[input[3]] || "Invalid symbol: " + input[3]}`; + + // Number of Cipher Suites and Extensions + retString += `\nNumber of Cipher Suites: ${input.slice(4, 6)}`; + retString += `\nNumber of Extensions: ${input.slice(6, 8)}`; + + // ALPN + retString += `\nALPN Chosen: ${alpnMap[input.slice(8, 10)] || input.slice(8, 10)}`; + + // Truncated SHA256 hashes + retString += `\nTruncated SHA256 hash of the Cipher Suites, sorted: ${input.slice(11, 23)}`; + retString += `\nTruncated SHA256 hash of the Extensions, sorted + Signature Algorithms, in the order they appear: ${input.slice(24)}`; + } + + + if (inputFormat == "JA4Server"){ + const protocolMap = { t: "TCP", q: "QUIC" }; + const tlsMap = { + "13": "1.3", "12": "1.2", "11": "1.1", "10": "1.0", + "s3": "SSL 3.0", "s2": "SSL 2.0", "s1": "SSL 1.0" + }; + const alpnMap = { + "00": "No ALPN Chosen", "dt": "DNS-over-TLS", + "h1": "HTTP/1.1", "h2": "HTTP/2" + }; + + // Protocol + retString += `\nProtocol: ${protocolMap[input[0]] || "Unknown"}`; + + // TLS Version + retString += `\nTLS Version: ${tlsMap[input.slice(1, 3)] || input.slice(1, 3)}`; + + // Number of Extensions + retString += `\nNumber of Extensions: ${input.slice(3, 5)}`; + + // ALPN + retString += `\nALPN Chosen: ${alpnMap[input.slice(5, 7)] || input.slice(5, 7)}`; + + // Cipher Suite + retString += `\nCipher Suite Chosen: ${input.slice(8,12)}`; + + // Truncated SHA256 hashes + retString += `\nTruncated SH256 hash of the Extensions, in the order they appear: ${input.slice(13)}`; + } + + return retString + } + +} + +export default JA4FingerprintSummary; +/** + * Truncated SHA256 hash of the Cipher Suites, sorted + * Truncated SHA256 hash of the Extensions, sorted + Signature Algorithms, in the order they appear + */ \ No newline at end of file From 7dab6e303949783b8680de081230baf47e688182 Mon Sep 17 00:00:00 2001 From: Niall Date: Wed, 25 Sep 2024 20:05:26 +0000 Subject: [PATCH 2/4] Refactor and formatting --- src/core/operations/JA4FingerprintSummary.mjs | 95 +++++++------------ 1 file changed, 33 insertions(+), 62 deletions(-) diff --git a/src/core/operations/JA4FingerprintSummary.mjs b/src/core/operations/JA4FingerprintSummary.mjs index 000c62c6dc..28182f0a34 100644 --- a/src/core/operations/JA4FingerprintSummary.mjs +++ b/src/core/operations/JA4FingerprintSummary.mjs @@ -40,78 +40,49 @@ class JA4FingerprintSummary extends Operation { */ run(input, args) { const [inputFormat] = args; - let retString = "Fingerprint summary of: " + input + "\n+------------------------------------------------------------+" + let retString = `Fingerprint summary of: ${input}\n+------------------------------------------------------------+`; + // Define format validation logic + const isValidJA4 = (input) => /[a-z-0-9]{10}_[a-z-0-9]{12}_[a-z-0-9]{12}/.test(input); + const isValidJA4Server = (input) => /[a-z-0-9]{7}_[a-z-0-9]{4}_[a-z-0-9]{12}/.test(input); + // Validate the input based on inputFormat + if (inputFormat === "JA4" && !isValidJA4(input)) { + return `Error: The input does not match the expected JA4 format. Please check your input: ${input}`; + } + if (inputFormat === "JA4Server" && !isValidJA4Server(input)) { + return `Error: The input does not match the expected JA4Server format. Please check your input: ${input}`; + } + const protocolMap = { t: "TCP", q: "QUIC" }; + const tlsMap = { + "13": "1.3", "12": "1.2", "11": "1.1", "10": "1.0", + "s3": "SSL 3.0", "s2": "SSL 2.0", "s1": "SSL 1.0" + }; + const alpnMap = { + "00": "No ALPN Chosen", "dt": "DNS-over-TLS", + "h1": "HTTP/1.1", "h2": "HTTP/2" + }; + const getProtocol = (input) => `\nProtocol: ${protocolMap[input[0]] || "Unknown"}`; + const getTLSVersion = (input, start = 1, end = 3) => `\nTLS Version: ${tlsMap[input.slice(start, end)] || input.slice(start, end)}`; + const getALPN = (input, start, end) => `\nALPN Chosen: ${alpnMap[input.slice(start, end)] || input.slice(start, end)}`; if (inputFormat === "JA4") { - const protocolMap = { t: "TCP", q: "QUIC" }; - const tlsMap = { - "13": "1.3", "12": "1.2", "11": "1.1", "10": "1.0", - "s3": "SSL 3.0", "s2": "SSL 2.0", "s1": "SSL 1.0" - }; const sniMap = { d: "Yes (to domain)", i: "No (to IP)" }; - const alpnMap = { - "00": "No ALPN Chosen", "dt": "DNS-over-TLS", - "h1": "HTTP/1.1", "h2": "HTTP/2" - }; - - // Protocol - retString += `\nProtocol: ${protocolMap[input[0]] || "Unknown"}`; - - // TLS Version - retString += `\nTLS Version: ${tlsMap[input.slice(1, 3)] || input.slice(1, 3)}`; - - // SNI Extension + retString += getProtocol(input); + retString += getTLSVersion(input); retString += `\nSNI Extension Present: ${sniMap[input[3]] || "Invalid symbol: " + input[3]}`; - - // Number of Cipher Suites and Extensions retString += `\nNumber of Cipher Suites: ${input.slice(4, 6)}`; retString += `\nNumber of Extensions: ${input.slice(6, 8)}`; - - // ALPN - retString += `\nALPN Chosen: ${alpnMap[input.slice(8, 10)] || input.slice(8, 10)}`; - - // Truncated SHA256 hashes + retString += getALPN(input, 8, 10); retString += `\nTruncated SHA256 hash of the Cipher Suites, sorted: ${input.slice(11, 23)}`; retString += `\nTruncated SHA256 hash of the Extensions, sorted + Signature Algorithms, in the order they appear: ${input.slice(24)}`; - } - - - if (inputFormat == "JA4Server"){ - const protocolMap = { t: "TCP", q: "QUIC" }; - const tlsMap = { - "13": "1.3", "12": "1.2", "11": "1.1", "10": "1.0", - "s3": "SSL 3.0", "s2": "SSL 2.0", "s1": "SSL 1.0" - }; - const alpnMap = { - "00": "No ALPN Chosen", "dt": "DNS-over-TLS", - "h1": "HTTP/1.1", "h2": "HTTP/2" - }; - - // Protocol - retString += `\nProtocol: ${protocolMap[input[0]] || "Unknown"}`; - - // TLS Version - retString += `\nTLS Version: ${tlsMap[input.slice(1, 3)] || input.slice(1, 3)}`; - - // Number of Extensions + } else if (inputFormat === "JA4Server") { + retString += getProtocol(input); + retString += getTLSVersion(input); retString += `\nNumber of Extensions: ${input.slice(3, 5)}`; - - // ALPN - retString += `\nALPN Chosen: ${alpnMap[input.slice(5, 7)] || input.slice(5, 7)}`; - - // Cipher Suite - retString += `\nCipher Suite Chosen: ${input.slice(8,12)}`; - - // Truncated SHA256 hashes - retString += `\nTruncated SH256 hash of the Extensions, in the order they appear: ${input.slice(13)}`; + retString += getALPN(input, 5, 7); + retString += `\nCipher Suite Chosen: ${input.slice(8, 12)}`; + retString += `\nTruncated SHA256 hash of the Extensions, in the order they appear: ${input.slice(13)}`; } - - return retString + return retString; } - } export default JA4FingerprintSummary; -/** - * Truncated SHA256 hash of the Cipher Suites, sorted - * Truncated SHA256 hash of the Extensions, sorted + Signature Algorithms, in the order they appear - */ \ No newline at end of file From 4c7214ca131e4fd2508370c6daed0a45e7671c12 Mon Sep 17 00:00:00 2001 From: Niall Date: Wed, 25 Sep 2024 20:06:31 +0000 Subject: [PATCH 3/4] Adding to categories --- src/core/config/Categories.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index bebdd6a5e2..692df2e3c6 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -248,6 +248,7 @@ "JA3S Fingerprint", "JA4 Fingerprint", "JA4Server Fingerprint", + "JA4 Fingerprint Summary", "HASSH Client Fingerprint", "HASSH Server Fingerprint", "Format MAC addresses", From 2fb331fc2d2bee6f1779122095d7cb02bf6d087a Mon Sep 17 00:00:00 2001 From: Niall Date: Wed, 25 Sep 2024 20:13:29 +0000 Subject: [PATCH 4/4] fxing unused import and string formatting --- src/core/operations/JA4FingerprintSummary.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/operations/JA4FingerprintSummary.mjs b/src/core/operations/JA4FingerprintSummary.mjs index 28182f0a34..f7f2cfa976 100644 --- a/src/core/operations/JA4FingerprintSummary.mjs +++ b/src/core/operations/JA4FingerprintSummary.mjs @@ -5,7 +5,6 @@ */ import Operation from "../Operation.mjs"; -import Utils from "../Utils.mjs"; /** * JA4 Fingerprint Summary operation @@ -67,7 +66,7 @@ class JA4FingerprintSummary extends Operation { const sniMap = { d: "Yes (to domain)", i: "No (to IP)" }; retString += getProtocol(input); retString += getTLSVersion(input); - retString += `\nSNI Extension Present: ${sniMap[input[3]] || "Invalid symbol: " + input[3]}`; + retString += `\nSNI Extension Present: ${sniMap[input[3]] || input[3] + " (Invalid Symbol)"}`; retString += `\nNumber of Cipher Suites: ${input.slice(4, 6)}`; retString += `\nNumber of Extensions: ${input.slice(6, 8)}`; retString += getALPN(input, 8, 10);