/* * ssh.c * * Copyright (C) 2011-22 - ntop.org * Copyright (C) 2009-11 - ipoque GmbH * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nDPI 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with nDPI. If not, see . * */ #include "ndpi_protocol_ids.h" #define NDPI_CURRENT_PROTO NDPI_PROTOCOL_SSH #include "ndpi_api.h" #include "ndpi_private.h" #include "ndpi_md5.h" #include /* HASSH - https://github.com/salesforce/hassh https://github.com/salesforce/hassh/blob/master/python/hassh.py [server] skex = packet.ssh.kex_algorithms seastc = packet.ssh.encryption_algorithms_server_to_client smastc = packet.ssh.mac_algorithms_server_to_client scastc = packet.ssh.compression_algorithms_server_to_client hasshs_str = ';'.join([skex, seastc, smastc, scastc]) [client] ckex = packet.ssh.kex_algorithms ceacts = packet.ssh.encryption_algorithms_client_to_server cmacts = packet.ssh.mac_algorithms_client_to_server ccacts = packet.ssh.compression_algorithms_client_to_server hassh_str = ';'.join([ckex, ceacts, cmacts, ccacts]) NOTE THe ECDSA key fingerprint is SHA256 -> ssh.kex.h_sig (wireshark) is in the Message Code: Diffie-Hellman Key Exchange Reply (31) that usually is packet 14 */ // #define SSH_DEBUG 1 static void ndpi_search_ssh_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); typedef struct { const char *signature; u_int16_t major, minor, patch; } ssh_pattern; /* ************************************************************************ */ static void ssh_analyze_signature_version(struct ndpi_flow_struct *flow, char *str_to_check, u_int8_t is_client_signature) { u_int i; u_int8_t obsolete_ssh_version = 0; const ssh_pattern ssh_servers_strings[] = { { (const char*)"SSH-%*f-OpenSSH_%d.%d.%d", 7, 0, 0 }, /* OpenSSH */ { (const char*)"SSH-%*f-APACHE-SSHD-%d.%d.%d", 2, 5, 1 }, /* Apache MINA SSHD */ { (const char*)"SSH-%*f-FileZilla_%d.%d.%d", 3, 40, 0 }, /* FileZilla SSH*/ { (const char*)"SSH-%*f-paramiko_%d.%d.%d", 2, 4, 0 }, /* Paramiko SSH */ { (const char*)"SSH-%*f-dropbear_%d.%d", 2020, 0, 0 }, /* Dropbear SSH */ { NULL, 0, 0, 0 } }; for(i = 0; ssh_servers_strings[i].signature != NULL; i++) { int matches; int major = 0; int minor = 0; int patch = 0; matches = sscanf(str_to_check, ssh_servers_strings[i].signature, &major, &minor, &patch); if(matches == 3 || matches == 2) { /* checking if is an old version */ if(major < ssh_servers_strings[i].major) obsolete_ssh_version = 1; else if(major == ssh_servers_strings[i].major) { if(minor < ssh_servers_strings[i].minor) obsolete_ssh_version = 1; else if(minor == ssh_servers_strings[i].minor) if(patch < ssh_servers_strings[i].patch) obsolete_ssh_version = 1; } #ifdef SSH_DEBUG printf("[SSH] [SSH Version: %d.%d.%d]\n", major, minor, patch); #endif break; } } if(obsolete_ssh_version) ndpi_set_risk(flow, (is_client_signature ? NDPI_SSH_OBSOLETE_CLIENT_VERSION_OR_CIPHER : NDPI_SSH_OBSOLETE_SERVER_VERSION_OR_CIPHER), NULL); } /* ************************************************************************ */ static void ssh_analyse_cipher(struct ndpi_flow_struct *flow, char *ciphers, u_int cipher_len, u_int8_t is_client_signature) { char *rem; char *cipher; u_int found_obsolete_cipher = 0; char *cipher_copy; /* List of obsolete ciphers can be found at https://www.linuxminion.com/deprecated-ssh-cryptographic-settings/ */ const char *obsolete_ciphers[] = { "arcfour256", "arcfour128", "3des-cbc", "blowfish-cbc", "cast128-cbc", "arcfour", NULL, }; if((cipher_copy = (char*)ndpi_malloc(cipher_len+1)) == NULL) { #ifdef SSH_DEBUG printf("[SSH] Nout enough memory\n"); #endif return; } strncpy(cipher_copy, ciphers, cipher_len); cipher_copy[cipher_len] = '\0'; cipher = strtok_r(cipher_copy, ",", &rem); while(cipher && !found_obsolete_cipher) { u_int i; for(i = 0; obsolete_ciphers[i]; i++) { if(strcmp(cipher, obsolete_ciphers[i]) == 0) { found_obsolete_cipher = i; #ifdef SSH_DEBUG printf("[SSH] [SSH obsolete %s cipher][%s]\n", is_client_signature ? "client" : "server", obsolete_ciphers[i]); #endif break; } } cipher = strtok_r(NULL, ",", &rem); } if(found_obsolete_cipher) { char str[64]; snprintf(str, sizeof(str), "Found cipher %s", obsolete_ciphers[found_obsolete_cipher]); ndpi_set_risk(flow, (is_client_signature ? NDPI_SSH_OBSOLETE_CLIENT_VERSION_OR_CIPHER : NDPI_SSH_OBSOLETE_SERVER_VERSION_OR_CIPHER), str); } ndpi_free(cipher_copy); } /* ************************************************************************ */ static int search_ssh_again(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { ndpi_search_ssh_tcp(ndpi_struct, flow); if((flow->protos.ssh.hassh_client[0] != '\0') && (flow->protos.ssh.hassh_server[0] != '\0')) { /* stop extra processing */ flow->extra_packets_func = NULL; /* We're good now */ return(0); } /* Possibly more processing */ return(1); } /* ************************************************************************ */ static void ndpi_int_ssh_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { if(flow->extra_packets_func != NULL) return; flow->max_extra_packets_to_check = 12; flow->extra_packets_func = search_ssh_again; ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SSH, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI); } /* ************************************************************************ */ static u_int16_t concat_hash_string(struct ndpi_flow_struct *flow, struct ndpi_packet_struct *packet, char *buf, u_int8_t client_hash) { u_int32_t offset = 22, len, buf_out_len = 0, max_payload_len = packet->payload_packet_len-sizeof(u_int32_t); const u_int32_t len_max = 65565; if(offset >= max_payload_len) goto invalid_payload; len = ntohl(*(u_int32_t*)&packet->payload[offset]); offset += 4; /* -1 for ';' */ if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; /* ssh.kex_algorithms [C/S] */ strncpy(buf, (const char *)&packet->payload[offset], buf_out_len = len); buf[buf_out_len++] = ';'; offset += len; if(offset >= max_payload_len) goto invalid_payload; /* ssh.server_host_key_algorithms [None] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); if(len > len_max) goto invalid_payload; offset += 4 + len; if(offset >= max_payload_len) goto invalid_payload; /* ssh.encryption_algorithms_client_to_server [C] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); offset += 4; if(client_hash) { if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); ssh_analyse_cipher(flow, (char*)&packet->payload[offset], len, 1 /* client */); buf_out_len += len; buf[buf_out_len++] = ';'; } if(len > len_max) goto invalid_payload; offset += len; if(offset >= max_payload_len) goto invalid_payload; /* ssh.encryption_algorithms_server_to_client [S] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); offset += 4; if(!client_hash) { if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); ssh_analyse_cipher(flow, (char*)&packet->payload[offset], len, 0 /* server */); buf_out_len += len; buf[buf_out_len++] = ';'; } if(len > len_max) goto invalid_payload; offset += len; if(offset >= max_payload_len) goto invalid_payload; /* ssh.mac_algorithms_client_to_server [C] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); offset += 4; if(client_hash) { if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); buf_out_len += len; buf[buf_out_len++] = ';'; } if(len > len_max) goto invalid_payload; offset += len; if(offset >= max_payload_len) goto invalid_payload; /* ssh.mac_algorithms_server_to_client [S] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); offset += 4; if(!client_hash) { if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); buf_out_len += len; buf[buf_out_len++] = ';'; } if(len > len_max) goto invalid_payload; offset += len; /* ssh.compression_algorithms_client_to_server [C] */ if(offset >= max_payload_len) goto invalid_payload; len = ntohl(*(u_int32_t*)&packet->payload[offset]); offset += 4; if(client_hash) { if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); buf_out_len += len; } if(len > len_max) goto invalid_payload; offset += len; if(offset >= max_payload_len) goto invalid_payload; /* ssh.compression_algorithms_server_to_client [S] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); offset += 4; if(!client_hash) { if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); buf_out_len += len; } if(len > len_max) goto invalid_payload; offset += len; /* ssh.languages_client_to_server [None] */ /* ssh.languages_server_to_client [None] */ #ifdef SSH_DEBUG printf("[SSH] %s\n", buf); #endif return(buf_out_len); invalid_payload: #ifdef SSH_DEBUG printf("[SSH] Invalid packet payload\n"); #endif return(0); } /* ************************************************************************ */ static void ndpi_ssh_zap_cr(char *str, int len) { len--; while(len > 0) { if((str[len] == '\n') || (str[len] == '\r')) { str[len] = '\0'; len--; } else break; } } /* ************************************************************************ */ static void ndpi_search_ssh_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &ndpi_struct->packet; #ifdef SSH_DEBUG printf("[SSH] %s()\n", __FUNCTION__); #endif if(flow->l4.tcp.ssh_stage == 0) { if(packet->payload_packet_len > 7 && memcmp(packet->payload, "SSH-", 4) == 0) { int len = ndpi_min(sizeof(flow->protos.ssh.client_signature)-1, packet->payload_packet_len); strncpy(flow->protos.ssh.client_signature, (const char *)packet->payload, len); flow->protos.ssh.client_signature[len] = '\0'; ndpi_ssh_zap_cr(flow->protos.ssh.client_signature, len); ssh_analyze_signature_version(flow, flow->protos.ssh.client_signature, 1); #ifdef SSH_DEBUG printf("[SSH] [client_signature: %s]\n", flow->protos.ssh.client_signature); #endif NDPI_LOG_DBG2(ndpi_struct, "ssh stage 0 passed\n"); flow->l4.tcp.ssh_stage = 1 + packet->packet_direction; ndpi_int_ssh_add_connection(ndpi_struct, flow); return; } } else if(flow->l4.tcp.ssh_stage == (2 - packet->packet_direction)) { if(packet->payload_packet_len > 7 && packet->payload_packet_len < 500 && memcmp(packet->payload, "SSH-", 4) == 0) { int len = ndpi_min(sizeof(flow->protos.ssh.server_signature)-1, packet->payload_packet_len); strncpy(flow->protos.ssh.server_signature, (const char *)packet->payload, len); flow->protos.ssh.server_signature[len] = '\0'; ndpi_ssh_zap_cr(flow->protos.ssh.server_signature, len); ssh_analyze_signature_version(flow, flow->protos.ssh.server_signature, 0); #ifdef SSH_DEBUG printf("[SSH] [server_signature: %s]\n", flow->protos.ssh.server_signature); #endif NDPI_LOG_DBG2(ndpi_struct, "ssh stage 1 passed\n"); flow->guessed_protocol_id = NDPI_PROTOCOL_SSH; #ifdef SSH_DEBUG printf("[SSH] [completed stage: %u]\n", flow->l4.tcp.ssh_stage); #endif flow->l4.tcp.ssh_stage = 3; return; } } else if(packet->payload_packet_len > 5) { u_int8_t msgcode = *(packet->payload + 5); ndpi_MD5_CTX ctx; if(msgcode == 20 /* key exchange init */) { char *hassh_buf = ndpi_calloc(packet->payload_packet_len, sizeof(char)); u_int i, len; #ifdef SSH_DEBUG printf("[SSH] [stage: %u][msg: %u][direction: %u][key exchange init]\n", flow->l4.tcp.ssh_stage, msgcode, packet->packet_direction); #endif if(hassh_buf) { if(packet->packet_direction == 0 /* client */) { u_char fingerprint_client[16]; len = concat_hash_string(flow, packet, hassh_buf, 1 /* client */); ndpi_MD5Init(&ctx); ndpi_MD5Update(&ctx, (const unsigned char *)hassh_buf, len); ndpi_MD5Final(fingerprint_client, &ctx); #ifdef SSH_DEBUG { printf("[SSH] [client][%s][", hassh_buf); for(i=0; i<16; i++) printf("%02X", fingerprint_client[i]); printf("]\n"); } #endif for(i=0; i<16; i++) snprintf(&flow->protos.ssh.hassh_client[i*2], sizeof(flow->protos.ssh.hassh_client) - (i*2), "%02X", fingerprint_client[i] & 0xFF); flow->protos.ssh.hassh_client[32] = '\0'; } else { u_char fingerprint_server[16]; len = concat_hash_string(flow, packet, hassh_buf, 0 /* server */); ndpi_MD5Init(&ctx); ndpi_MD5Update(&ctx, (const unsigned char *)hassh_buf, len); ndpi_MD5Final(fingerprint_server, &ctx); #ifdef SSH_DEBUG { printf("[SSH] [server][%s][", hassh_buf); for(i=0; i<16; i++) printf("%02X", fingerprint_server[i]); printf("]\n"); } #endif for(i=0; i<16; i++) snprintf(&flow->protos.ssh.hassh_server[i*2], sizeof(flow->protos.ssh.hassh_server) - (i*2), "%02X", fingerprint_server[i] & 0xFF); flow->protos.ssh.hassh_server[32] = '\0'; } ndpi_free(hassh_buf); } ndpi_int_ssh_add_connection(ndpi_struct, flow); } if((flow->protos.ssh.hassh_client[0] != '\0') && (flow->protos.ssh.hassh_server[0] != '\0')) { #ifdef SSH_DEBUG printf("[SSH] Dissection completed\n"); #endif flow->extra_packets_func = NULL; /* We're good now */ } return; } #ifdef SSH_DEBUG printf("[SSH] Excluding SSH"); #endif NDPI_LOG_DBG(ndpi_struct, "excluding ssh at stage %d\n", flow->l4.tcp.ssh_stage); NDPI_ADD_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, NDPI_PROTOCOL_SSH); } /* ************************************************************************ */ void init_ssh_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id) { ndpi_set_bitmask_protocol_detection("SSH", ndpi_struct, *id, NDPI_PROTOCOL_SSH, ndpi_search_ssh_tcp, NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK); *id += 1; }