From 0c16cd01200a6ae576e5fdcd53506928eda632c5 Mon Sep 17 00:00:00 2001 From: Mats Klepsland Date: Thu, 30 Nov 2017 11:04:03 +0100 Subject: [PATCH] app-layer-ssl: generate JA3 fingerprints Decode additional fields from the client hello packet and generate JA3 fingerprints. --- src/Makefile.am | 1 + src/app-layer-ssl.c | 632 +++++++++++++++++++++++++++++++++++--------- src/app-layer-ssl.h | 6 + src/util-error.c | 2 +- src/util-error.h | 4 +- src/util-ja3.c | 249 +++++++++++++++++ src/util-ja3.h | 41 +++ suricata.yaml.in | 3 + 8 files changed, 809 insertions(+), 129 deletions(-) create mode 100644 src/util-ja3.c create mode 100644 src/util-ja3.h diff --git a/src/Makefile.am b/src/Makefile.am index 0004126714..1a1ecac443 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -420,6 +420,7 @@ util-host-info.c util-host-info.h \ util-hyperscan.c util-hyperscan.h \ util-ioctl.h util-ioctl.c \ util-ip.h util-ip.c \ +util-ja3.h util-ja3.c \ util-logopenfile.h util-logopenfile.c \ util-logopenfile-tile.h util-logopenfile-tile.c \ util-log-redis.h util-log-redis.c \ diff --git a/src/app-layer-ssl.c b/src/app-layer-ssl.c index d4c36b272e..f848f61870 100644 --- a/src/app-layer-ssl.c +++ b/src/app-layer-ssl.c @@ -49,6 +49,7 @@ #include "util-print.h" #include "util-pool.h" #include "util-byte.h" +#include "util-ja3.h" #include "flow-util.h" #include "flow-private.h" @@ -83,8 +84,12 @@ SCEnumCharMap tls_decoder_event_table[ ] = { /* by default we keep tracking */ #define SSL_CONFIG_DEFAULT_NOREASSEMBLE 0 +/* JA3 fingerprints are disabled by default */ +#define SSL_CONFIG_DEFAULT_JA3 0 + typedef struct SslConfig_ { int no_reassemble; + int enable_ja3; } SslConfig; SslConfig ssl_config; @@ -134,7 +139,7 @@ SslConfig ssl_config; #define SSL_RECORD_MINIMUM_LENGTH 6 -#define HAS_SPACE(n) ((uint32_t)((*input) + (n) - (initial_input)) > (uint32_t)(input_len)) ? 0 : 1 +#define HAS_SPACE(n) ((uint32_t)((input) + (n) - (initial_input)) > (uint32_t)(input_len)) ? 0 : 1 static void SSLParserReset(SSLState *ssl_state) { @@ -250,103 +255,420 @@ static void SSLSetTxDetectFlags(void *vtx, uint8_t dir, uint64_t flags) } } -static inline int TLSDecodeHSHelloVersion(SSLState *ssl_state, uint8_t **input, - const uint32_t input_len, - const uint8_t *initial_input) +/** + * \inline + * \brief Check if value is GREASE. + * + * http://tools.ietf.org/html/draft-davidben-tls-grease-00 + * + * \param value Value to check. + * + * \retval 1 if is GREASE. + * \retval 0 if not is GREASE. + */ +static inline int TLSDecodeValueIsGREASE(const uint16_t value) { - /* Skip version */ - *input += SSLV3_CLIENT_HELLO_VERSION_LEN; - - return 0; + switch (value) + { + case 0x0a0a: + case 0x1a1a: + case 0x2a2a: + case 0x3a3a: + case 0x4a4a: + case 0x5a5a: + case 0x6a6a: + case 0x7a7a: + case 0x8a8a: + case 0x9a9a: + case 0xaaaa: + case 0xbaba: + case 0xcaca: + case 0xdada: + case 0xeaea: + case 0xfafa: + return 1; + default: + return 0; + } } -static inline int TLSDecodeHSHelloRandom(SSLState *ssl_state, uint8_t **input, - const uint32_t input_len, - const uint8_t *initial_input) +static inline int TLSDecodeHSHelloVersion(SSLState *ssl_state, + const uint8_t * const initial_input, + const uint32_t input_len) { - /* Skip random */ - *input += SSLV3_CLIENT_HELLO_RANDOM_LEN; + uint8_t *input = (uint8_t *)initial_input; - return 0; + if (!(HAS_SPACE(SSLV3_CLIENT_HELLO_VERSION_LEN))) { + SCLogDebug("TLS handshake invalid length"); + SSLSetEvent(ssl_state, + TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH); + return -1; + } + + if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) && + ssl_config.enable_ja3) { + uint16_t version = *input << 8 | *(input + 1); + + ssl_state->ja3_str = Ja3BufferInit(); + if (ssl_state->ja3_str == NULL) + return -1; + + int rc = Ja3BufferAddValue(ssl_state->ja3_str, version); + if (rc != 0) + return -1; + } + + input += SSLV3_CLIENT_HELLO_VERSION_LEN; + + return (input - initial_input); } -static inline int TLSDecodeHSHelloSessionID(SSLState *ssl_state, - uint8_t **input, - const uint32_t input_len, - const uint8_t *initial_input) +static inline int TLSDecodeHSHelloRandom(SSLState *ssl_state, + const uint8_t * const initial_input, + const uint32_t input_len) { - if (!(HAS_SPACE(1))) { + uint8_t *input = (uint8_t *)initial_input; + + if (!(HAS_SPACE(SSLV3_CLIENT_HELLO_RANDOM_LEN))) { SCLogDebug("TLS handshake invalid length"); SSLSetEvent(ssl_state, TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH); return -1; } - uint8_t session_id_length = **input; - *input += 1; + /* Skip random */ + input += SSLV3_CLIENT_HELLO_RANDOM_LEN; + + return (input - initial_input); +} + +static inline int TLSDecodeHSHelloSessionID(SSLState *ssl_state, + const uint8_t * const initial_input, + const uint32_t input_len) +{ + uint8_t *input = (uint8_t *)initial_input; + + if (!(HAS_SPACE(1))) + goto invalid_length; + + uint8_t session_id_length = *input; + input += 1; if (session_id_length != 0) { ssl_state->flags |= SSL_AL_FLAG_SSL_CLIENT_SESSION_ID; } - *input += session_id_length; + if (!(HAS_SPACE(session_id_length))) + goto invalid_length; - return 0; + input += session_id_length; + + return (input - initial_input); + +invalid_length: + SCLogDebug("TLS handshake invalid length"); + SSLSetEvent(ssl_state, + TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH); + return -1; } static inline int TLSDecodeHSHelloCipherSuites(SSLState *ssl_state, - uint8_t **input, - const uint32_t input_len, - const uint8_t *initial_input) + const uint8_t * const initial_input, + const uint32_t input_len) { - if (!(HAS_SPACE(2))) { - SCLogDebug("TLS handshake invalid length"); - SSLSetEvent(ssl_state, - TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH); - return -1; - } + uint8_t *input = (uint8_t *)initial_input; - uint16_t cipher_suites_length = **input << 8 | *(*input + 1); - *input += 2; + if (!(HAS_SPACE(2))) + goto invalid_length; - /* Skip cipher suites */ - *input += cipher_suites_length; + uint16_t cipher_suites_length = *input << 8 | *(input + 1); + input += 2; - return 0; + if (!(HAS_SPACE(cipher_suites_length))) + goto invalid_length; + + if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) && + ssl_config.enable_ja3) { + int rc; + + JA3Buffer *ja3_cipher_suites = Ja3BufferInit(); + if (ja3_cipher_suites == NULL) + return -1; + + uint16_t processed_len = 0; + while (processed_len < cipher_suites_length) + { + if (!(HAS_SPACE(2))) { + Ja3BufferFree(ja3_cipher_suites); + goto invalid_length; + } + + uint16_t cipher_suite = *input << 8 | *(input + 1); + input += 2; + + if (TLSDecodeValueIsGREASE(cipher_suite) != 1) { + rc = Ja3BufferAddValue(ja3_cipher_suites, cipher_suite); + if (rc != 0) { + Ja3BufferFree(ja3_cipher_suites); + return -1; + } + } + + processed_len += 2; + } + + rc = Ja3BufferAppendBuffer(ssl_state->ja3_str, ja3_cipher_suites); + if (rc == -1) { + return -1; + } + + } else { + /* Skip cipher suites */ + input += cipher_suites_length; + } + + return (input - initial_input); + +invalid_length: + SCLogDebug("TLS handshake invalid length"); + SSLSetEvent(ssl_state, + TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH); + return -1; } static inline int TLSDecodeHSHelloCompressionMethods(SSLState *ssl_state, - uint8_t **input, - const uint32_t input_len, - const uint8_t *initial_input) + const uint8_t * const initial_input, + const uint32_t input_len) { - if (!(HAS_SPACE(1))) { - SCLogDebug("TLS handshake invalid length"); + uint8_t *input = (uint8_t *)initial_input; + + if (!(HAS_SPACE(1))) + goto invalid_length; + + /* Skip compression methods */ + uint8_t compression_methods_length = *input; + input += 1; + + if (!(HAS_SPACE(compression_methods_length))) + goto invalid_length; + + input += compression_methods_length; + + return (input - initial_input); + +invalid_length: + SCLogDebug("TLS handshake invalid_length"); + SSLSetEvent(ssl_state, + TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH); + return -1; +} + +static inline int TLSDecodeHSHelloExtensionSni(SSLState *ssl_state, + const uint8_t * const initial_input, + const uint32_t input_len) +{ + uint8_t *input = (uint8_t *)initial_input; + + if (!(HAS_SPACE(2))) + goto invalid_length; + + /* Skip sni_list_length */ + input += 2; + + if (!(HAS_SPACE(1))) + goto invalid_length; + + uint8_t sni_type = *input; + input += 1; + + /* Currently the only type allowed is host_name + (RFC6066 section 3). */ + if (sni_type != SSL_SNI_TYPE_HOST_NAME) { + SCLogDebug("Unknown SNI type"); SSLSetEvent(ssl_state, - TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH); + TLS_DECODER_EVENT_INVALID_SNI_TYPE); return -1; } - /* Skip compression methods */ - uint8_t compression_methods_length = **input; - *input += 1; + if (!(HAS_SPACE(2))) + goto invalid_length; - *input += compression_methods_length; + uint16_t sni_len = *input << 8 | *(input + 1); + input += 2; - return 0; + if (!(HAS_SPACE(sni_len))) + goto invalid_length; + + /* There must not be more than one extension of the same + type (RFC5246 section 7.4.1.4). */ + if (ssl_state->curr_connp->sni) { + SCLogDebug("Multiple SNI extensions"); + SSLSetEvent(ssl_state, + TLS_DECODER_EVENT_MULTIPLE_SNI_EXTENSIONS); + input += sni_len; + return (input - initial_input); + } + + /* host_name contains the fully qualified domain name, + and should therefore be limited by the maximum domain + name length. */ + if (sni_len > 255) { + SCLogDebug("SNI length >255"); + SSLSetEvent(ssl_state, + TLS_DECODER_EVENT_INVALID_SNI_LENGTH); + return -1; + } + + size_t sni_strlen = sni_len + 1; + ssl_state->curr_connp->sni = SCMalloc(sni_strlen); + + if (unlikely(ssl_state->curr_connp->sni == NULL)) + return -1; + + memcpy(ssl_state->curr_connp->sni, input, sni_strlen - 1); + ssl_state->curr_connp->sni[sni_strlen-1] = 0; + + input += sni_len; + + return (input - initial_input); + +invalid_length: + SCLogDebug("TLS handshake invalid length"); + SSLSetEvent(ssl_state, + TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH); + + + return -1; +} + +static inline int TLSDecodeHSHelloExtensionEllipticCurves(SSLState *ssl_state, + const uint8_t * const initial_input, + const uint32_t input_len, + JA3Buffer *ja3_elliptic_curves) +{ + uint8_t *input = (uint8_t *)initial_input; + + if (!(HAS_SPACE(2))) + goto invalid_length; + + uint16_t elliptic_curves_len = *input << 8 | *(input + 1); + input += 2; + + if (!(HAS_SPACE(elliptic_curves_len))) + goto invalid_length; + + if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) && + ssl_config.enable_ja3) { + uint16_t ec_processed_len = 0; + while (ec_processed_len < elliptic_curves_len) + { + uint16_t elliptic_curve = *input << 8 | *(input + 1); + input += 2; + + if (TLSDecodeValueIsGREASE(elliptic_curve) != 1) { + int rc = Ja3BufferAddValue(ja3_elliptic_curves, + elliptic_curve); + if (rc != 0) + return -1; + } + + ec_processed_len += 2; + } + + } else { + /* Skip elliptic curves */ + input += elliptic_curves_len; + } + + return (input - initial_input); + +invalid_length: + SCLogDebug("TLS handshake invalid length"); + SSLSetEvent(ssl_state, + TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH); + + return -1; +} + +static inline int TLSDecodeHSHelloExtensionEllipticCurvePF(SSLState *ssl_state, + const uint8_t * const initial_input, + const uint32_t input_len, + JA3Buffer *ja3_elliptic_curves_pf) +{ + uint8_t *input = (uint8_t *)initial_input; + + if (!(HAS_SPACE(1))) + goto invalid_length; + + uint8_t ec_pf_len = *input; + input += 1; + + if (!(HAS_SPACE(ec_pf_len))) + goto invalid_length; + + if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) && + ssl_config.enable_ja3) { + uint8_t ec_pf_processed_len = 0; + while (ec_pf_processed_len < ec_pf_len) + { + uint8_t elliptic_curve_pf = *input; + input += 1; + + if (TLSDecodeValueIsGREASE(elliptic_curve_pf) != 1) { + int rc = Ja3BufferAddValue(ja3_elliptic_curves_pf, + elliptic_curve_pf); + if (rc != 0) + return -1; + } + + ec_pf_processed_len += 1; + } + + } else { + /* Skip elliptic curve point formats */ + input += ec_pf_len; + } + + return (input - initial_input); + +invalid_length: + SCLogDebug("TLS handshake invalid length"); + SSLSetEvent(ssl_state, + TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH); + + return -1; } static inline int TLSDecodeHSHelloExtensions(SSLState *ssl_state, - uint8_t **input, - const uint32_t input_len, - const uint8_t *initial_input) + const uint8_t * const initial_input, + const uint32_t input_len) { + uint8_t *input = (uint8_t *)initial_input; + + int ret; + int rc; + uint32_t parsed = 0; + + JA3Buffer *ja3_extensions = NULL; + JA3Buffer *ja3_elliptic_curves = NULL; + JA3Buffer *ja3_elliptic_curves_pf = NULL; + + if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) && + ssl_config.enable_ja3) { + ja3_extensions = Ja3BufferInit(); + ja3_elliptic_curves = Ja3BufferInit(); + ja3_elliptic_curves_pf = Ja3BufferInit(); + if (ja3_extensions == NULL || ja3_elliptic_curves == NULL || + ja3_elliptic_curves_pf == NULL) + return -1; + } + /* Extensions are optional (RFC5246 section 7.4.1.2) */ if (!(HAS_SPACE(2))) goto end; - uint16_t extensions_len = **input << 8 | *(*input + 1); - *input += 2; + uint16_t extensions_len = *input << 8 | *(input + 1); + input += 2; if (!(HAS_SPACE(extensions_len))) goto invalid_length; @@ -357,133 +679,167 @@ static inline int TLSDecodeHSHelloExtensions(SSLState *ssl_state, if (!(HAS_SPACE(2))) goto invalid_length; - uint16_t ext_type = **input << 8 | *(*input + 1); - *input += 2; + uint16_t ext_type = *input << 8 | *(input + 1); + input += 2; if (!(HAS_SPACE(2))) goto invalid_length; - uint16_t ext_len = **input << 8 | *(*input + 1); - *input += 2; + uint16_t ext_len = *input << 8 | *(input + 1); + input += 2; + + if (!(HAS_SPACE(ext_len))) + goto invalid_length; + + parsed = input - initial_input; switch (ext_type) { case SSL_EXTENSION_SNI: { - /* There must not be more than one extension of the same - type (RFC5246 section 7.4.1.4). */ - if (ssl_state->curr_connp->sni) { - SCLogDebug("Multiple SNI extensions"); - SSLSetEvent(ssl_state, - TLS_DECODER_EVENT_MULTIPLE_SNI_EXTENSIONS); - return -1; - } - - /* Skip sni_list_length */ - *input += 2; - - if (!(HAS_SPACE(1))) - goto invalid_length; - - uint8_t sni_type = **input; - *input += 1; - - /* Currently the only type allowed is host_name - (RFC6066 section 3). */ - if (sni_type != SSL_SNI_TYPE_HOST_NAME) { - SCLogDebug("Unknown SNI type"); - SSLSetEvent(ssl_state, - TLS_DECODER_EVENT_INVALID_SNI_TYPE); - return -1; - } + ret = TLSDecodeHSHelloExtensionSni(ssl_state, input, + input_len - parsed); + if (ret < 0) + goto end; - if (!(HAS_SPACE(2))) - goto invalid_length; + input += ret; - uint16_t sni_len = **input << 8 | *(*input + 1); - *input += 2; + break; + } - if (!(HAS_SPACE(sni_len))) - goto invalid_length; + case SSL_EXTENSION_ELLIPTIC_CURVES: + { + ret = TLSDecodeHSHelloExtensionEllipticCurves(ssl_state, input, + input_len - parsed, + ja3_elliptic_curves); + if (ret < 0) + goto end; - /* host_name contains the fully qualified domain name, - and should therefore be limited by the maximum domain - name length. */ - if (sni_len > 255) { - SCLogDebug("SNI length >255"); - SSLSetEvent(ssl_state, - TLS_DECODER_EVENT_INVALID_SNI_LENGTH); - return -1; - } + input += ret; - size_t sni_strlen = sni_len + 1; - ssl_state->curr_connp->sni = SCMalloc(sni_strlen); + break; + } - if (unlikely(ssl_state->curr_connp->sni == NULL)) - return -1; + case SSL_EXTENSION_EC_POINT_FORMATS: + { + ret = TLSDecodeHSHelloExtensionEllipticCurvePF(ssl_state, input, + input_len - parsed, + ja3_elliptic_curves_pf); + if (ret < 0) + goto end; - memcpy(ssl_state->curr_connp->sni, *input, sni_strlen - 1); - ssl_state->curr_connp->sni[sni_strlen-1] = 0; + input += ret; - *input += sni_len; break; } default: { - *input += ext_len; + input += ext_len; break; } } + + if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) && + ssl_config.enable_ja3) { + if (TLSDecodeValueIsGREASE(ext_type) != 1) { + rc = Ja3BufferAddValue(ja3_extensions, ext_type); + if (rc != 0) + goto error; + } + } + processed_len += ext_len + 4; } end: - return 0; + if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) && + ssl_config.enable_ja3) { + rc = Ja3BufferAppendBuffer(ssl_state->ja3_str, ja3_extensions); + if (rc == -1) + goto error; + + rc = Ja3BufferAppendBuffer(ssl_state->ja3_str, ja3_elliptic_curves); + if (rc == -1) + goto error; + + rc = Ja3BufferAppendBuffer(ssl_state->ja3_str, + ja3_elliptic_curves_pf); + if (rc == -1) + goto error; + } + + return (input - initial_input); invalid_length: SCLogDebug("TLS handshake invalid length"); SSLSetEvent(ssl_state, TLS_DECODER_EVENT_HANDSHAKE_INVALID_LENGTH); + +error: + if (ja3_extensions != NULL) + Ja3BufferFree(ja3_extensions); + if (ja3_elliptic_curves != NULL) + Ja3BufferFree(ja3_elliptic_curves); + if (ja3_elliptic_curves_pf != NULL) + Ja3BufferFree(ja3_elliptic_curves_pf); + return -1; } -static int TLSDecodeHandshakeHello(SSLState *ssl_state, uint8_t *input, - uint32_t input_len) +static int TLSDecodeHandshakeHello(SSLState *ssl_state, + const uint8_t * const input, + const uint32_t input_len) { - int rc; - uint8_t *initial_input = input; + int ret; + uint32_t parsed = 0; /* Only parse the message if it is complete */ if (input_len < ssl_state->curr_connp->message_length || input_len < 40) goto end; - rc = TLSDecodeHSHelloVersion(ssl_state, &input, input_len, initial_input); - if (rc != 0) + ret = TLSDecodeHSHelloVersion(ssl_state, input, input_len); + if (ret < 0) goto end; - rc = TLSDecodeHSHelloRandom(ssl_state, &input, input_len, initial_input); - if (rc != 0) + parsed += ret; + + ret = TLSDecodeHSHelloRandom(ssl_state, input + parsed, input_len - parsed); + if (ret < 0) goto end; - rc = TLSDecodeHSHelloSessionID(ssl_state, &input, input_len, initial_input); - if (rc != 0) + parsed += ret; + + ret = TLSDecodeHSHelloSessionID(ssl_state, input + parsed, + input_len - parsed); + if (ret < 0) goto end; - rc = TLSDecodeHSHelloCipherSuites(ssl_state, &input, input_len, - initial_input); - if (rc != 0) + parsed += ret; + + ret = TLSDecodeHSHelloCipherSuites(ssl_state, input + parsed, + input_len - parsed); + if (ret < 0) goto end; - rc = TLSDecodeHSHelloCompressionMethods(ssl_state, &input, input_len, - initial_input); - if (rc != 0) + parsed += ret; + + ret = TLSDecodeHSHelloCompressionMethods(ssl_state, input + parsed, + input_len - parsed); + if (ret < 0) goto end; - rc = TLSDecodeHSHelloExtensions(ssl_state, &input, input_len, - initial_input); - if (rc != 0) + parsed += ret; + + ret = TLSDecodeHSHelloExtensions(ssl_state, input + parsed, + input_len - parsed); + if (ret < 0) goto end; + if ((ssl_state->current_flags & SSL_AL_FLAG_STATE_CLIENT_HELLO) && + ssl_config.enable_ja3) { + ssl_state->ja3_hash = Ja3GenerateHash(ssl_state->ja3_str); + } + end: return 0; } @@ -1686,6 +2042,11 @@ static void SSLStateFree(void *p) if (ssl_state->server_connp.sni) SCFree(ssl_state->server_connp.sni); + if (ssl_state->ja3_str) + Ja3BufferFree(ssl_state->ja3_str); + if (ssl_state->ja3_hash) + SCFree(ssl_state->ja3_hash); + AppLayerDecoderEventsFreeEvents(&ssl_state->decoder_events); if (ssl_state->de_state != NULL) { @@ -1960,6 +2321,25 @@ void RegisterSSLParsers(void) if (ConfGetBool("app-layer.protocols.tls.no-reassemble", &ssl_config.no_reassemble) != 1) ssl_config.no_reassemble = SSL_CONFIG_DEFAULT_NOREASSEMBLE; } + + /* Check if we should generate JA3 fingerprints */ + if (ConfGetBool("app-layer.protocols.tls.ja3-fingerprints", + &ssl_config.enable_ja3) != 1) { + ssl_config.enable_ja3 = SSL_CONFIG_DEFAULT_JA3; + } + +#ifndef HAVE_NSS + if (ssl_config.enable_ja3) { + SCLogWarning(SC_WARN_NO_JA3_SUPPORT, + "no MD5 calculation support built in, disabling JA3"); + ssl_config.enable_ja3 = 0; + } +#else + if (RunmodeIsUnittests()) { + ssl_config.enable_ja3 = 1; + } +#endif + } else { SCLogInfo("Parsed disabled for %s protocol. Protocol detection" "still on.", proto_name); diff --git a/src/app-layer-ssl.h b/src/app-layer-ssl.h index 871d90d976..14c2c0a592 100644 --- a/src/app-layer-ssl.h +++ b/src/app-layer-ssl.h @@ -29,6 +29,7 @@ #include "app-layer-protos.h" #include "app-layer-parser.h" #include "decode-events.h" +#include "util-ja3.h" #include "queue.h" enum { @@ -107,6 +108,8 @@ enum { /* extensions */ #define SSL_EXTENSION_SNI 0x0000 +#define SSL_EXTENSION_ELLIPTIC_CURVES 0x000a +#define SSL_EXTENSION_EC_POINT_FORMATS 0x000b /* SNI types */ #define SSL_SNI_TYPE_HOST_NAME 0 @@ -202,6 +205,9 @@ typedef struct SSLState_ { uint32_t current_flags; + JA3Buffer *ja3_str; + char *ja3_hash; + SSLStateConnp *curr_connp; SSLStateConnp client_connp; diff --git a/src/util-error.c b/src/util-error.c index 354c4f80c2..c40bef0e52 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -348,7 +348,7 @@ const char * SCErrorToString(SCError err) CASE_CODE (SC_ERR_CREATE_DIRECTORY); CASE_CODE (SC_WARN_FLOWBIT); CASE_CODE (SC_ERR_SMB_CONFIG); - + CASE_CODE (SC_WARN_NO_JA3_SUPPORT); CASE_CODE (SC_ERR_MAX); } diff --git a/src/util-error.h b/src/util-error.h index d3fac31545..4cd33fd727 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -338,8 +338,8 @@ typedef enum { SC_ERR_CREATE_DIRECTORY, SC_WARN_FLOWBIT, SC_ERR_SMB_CONFIG, - - SC_ERR_MAX, + SC_WARN_NO_JA3_SUPPORT, + SC_ERR_MAX } SCError; const char *SCErrorToString(SCError); diff --git a/src/util-ja3.c b/src/util-ja3.c new file mode 100644 index 0000000000..d9b7efb67a --- /dev/null +++ b/src/util-ja3.c @@ -0,0 +1,249 @@ +/* Copyright (C) 2007-2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Mats Klepsland + * + * Functions used to generate JA3 fingerprint. + */ + +#include "suricata-common.h" +#include "util-validate.h" +#include "util-ja3.h" + +#define MD5_STRING_LENGTH 33 + +/** + * \brief Allocate new buffer. + * + * \return pointer to buffer on success. + * \return NULL on failure. + */ +JA3Buffer *Ja3BufferInit(void) +{ + JA3Buffer *buffer = SCCalloc(1, sizeof(JA3Buffer)); + if (buffer == NULL) { + return NULL; + } + + return buffer; +} + +/** + * \brief Free allocated buffer. + * + * \param buffer The buffer to free. + */ +void Ja3BufferFree(JA3Buffer *buffer) +{ + DEBUG_VALIDATE_BUG_ON(buffer == NULL); + + if (buffer->data != NULL) { + SCFree(buffer->data); + } + + SCFree(buffer); +} + +/** + * \internal + * \brief Resize buffer if it is full. + * + * \param buffer The buffer. + * \param len The length of the data that should fit into the buffer. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int Ja3BufferResizeIfFull(JA3Buffer *buffer, uint32_t len) +{ + DEBUG_VALIDATE_BUG_ON(buffer == NULL); + + if (len == 0) { + return 0; + } + + while (buffer->used + len + 2 > buffer->size) + { + buffer->size *= 2; + char *tmp = SCRealloc(buffer->data, buffer->size * sizeof(char)); + if (tmp == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, "Error resizing JA3 buffer"); + return -1; + } + buffer->data = tmp; + } + + return 0; +} + +/** + * \brief Append buffer to buffer. + * + * Append the second buffer to the first and then free it. + * + * \param buffer1 The first buffer. + * \param buffer2 The second buffer. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +int Ja3BufferAppendBuffer(JA3Buffer *buffer1, JA3Buffer *buffer2) +{ + if (buffer1 == NULL || buffer2 == NULL) { + SCLogError(SC_ERR_INVALID_ARGUMENT, "Buffers should not be NULL"); + return -1; + } + + /* If buffer1 contains no data, then we just copy the second buffer + instead of appending its data. */ + if (buffer1->data == NULL) { + buffer1->data = buffer2->data; + buffer1->used = buffer2->used; + buffer1->size = buffer2->size; + SCFree(buffer2); + return 0; + } + + int rc = Ja3BufferResizeIfFull(buffer1, buffer2->used); + if (rc != 0) { + Ja3BufferFree(buffer1); + Ja3BufferFree(buffer2); + return -1; + } + + if (buffer2->used == 0) { + buffer1->used += snprintf(buffer1->data + buffer1->used, buffer1->size - + buffer1->used, ","); + } else { + buffer1->used += snprintf(buffer1->data + buffer1->used, buffer1->size - + buffer1->used, ",%s", buffer2->data); + } + + Ja3BufferFree(buffer2); + + return 0; +} + +/** + * \internal + * \brief Return number of digits in number. + * + * \param num The number. + * + * \return digits Number of digits. + */ +static uint32_t NumberOfDigits(uint32_t num) +{ + if (num < 10) { + return 1; + } + + return 1 + NumberOfDigits(num / 10); +} + +/** + * \brief Add value to buffer. + * + * \param buffer The buffer. + * \param value The value. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +int Ja3BufferAddValue(JA3Buffer *buffer, uint32_t value) +{ + if (buffer == NULL) { + SCLogError(SC_ERR_INVALID_ARGUMENT, "Buffer should not be NULL"); + return -1; + } + + if (buffer->data == NULL) { + buffer->data = SCMalloc(JA3_BUFFER_INITIAL_SIZE * sizeof(char)); + if (buffer->data == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, + "Error allocating memory for JA3 data"); + Ja3BufferFree(buffer); + return -1; + } + buffer->size = JA3_BUFFER_INITIAL_SIZE; + } + + uint32_t value_len = NumberOfDigits(value); + + int rc = Ja3BufferResizeIfFull(buffer, value_len); + if (rc != 0) { + Ja3BufferFree(buffer); + return -1; + } + + if (buffer->used == 0) { + buffer->used += snprintf(buffer->data, buffer->size, "%d", value); + } + else { + buffer->used += snprintf(buffer->data + buffer->used, buffer->size - + buffer->used, "-%d", value); + } + + return 0; +} + +/** + * \brief Generate Ja3 hash string. + * + * \param buffer The Ja3 buffer. + * + * \retval pointer to hash string on success. + * \retval NULL on failure. + */ +char *Ja3GenerateHash(JA3Buffer *buffer) +{ + +#ifdef HAVE_NSS + if (buffer == NULL) { + SCLogError(SC_ERR_INVALID_ARGUMENT, "Buffer should not be NULL"); + return NULL; + } + + if (buffer->data == NULL) { + SCLogError(SC_ERR_INVALID_VALUE, "Buffer data should not be NULL"); + return NULL; + } + + char *ja3_hash = SCMalloc(MD5_STRING_LENGTH * sizeof(char)); + if (ja3_hash == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, + "Error allocating memory for JA3 hash"); + return NULL; + } + + unsigned char md5[MD5_LENGTH]; + HASH_HashBuf(HASH_AlgMD5, md5, (unsigned char *)buffer->data, buffer->used); + + int i, x; + for (i = 0, x = 0; x < MD5_LENGTH; x++) { + i += snprintf(ja3_hash + i, MD5_STRING_LENGTH - i, "%02x", md5[x]); + } + + return ja3_hash; +#else + return NULL; +#endif /* HAVE_NSS */ + +} diff --git a/src/util-ja3.h b/src/util-ja3.h new file mode 100644 index 0000000000..cae72167d1 --- /dev/null +++ b/src/util-ja3.h @@ -0,0 +1,41 @@ +/* Copyright (C) 2007-2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Mats Klepsland + */ + +#ifndef __UTIL_JA3_H__ +#define __UTIL_JA3_H__ + +#define JA3_BUFFER_INITIAL_SIZE 128 + +typedef struct JA3Buffer_ { + char *data; + size_t size; + size_t used; +} JA3Buffer; + +JA3Buffer *Ja3BufferInit(void); +void Ja3BufferFree(JA3Buffer *); +int Ja3BufferAppendBuffer(JA3Buffer *, JA3Buffer *); +int Ja3BufferAddValue(JA3Buffer *, uint32_t); +char *Ja3GenerateHash(JA3Buffer *); + +#endif /* __UTIL_JA3_H__ */ diff --git a/suricata.yaml.in b/suricata.yaml.in index 2f57336967..02333e9829 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -779,6 +779,9 @@ app-layer: detection-ports: dp: 443 + # Generate JA3 fingerprint from client hello + ja3-fingerprints: no + # Completely stop processing TLS/SSL session after the handshake # completed. If bypass is enabled this will also trigger flow # bypass. If disabled (the default), TLS/SSL session is still