From 9d7baa7a9f4743e12ffd86a9d2f3be30ec5caf9e Mon Sep 17 00:00:00 2001 From: Pablo Rincon Date: Wed, 11 Aug 2010 22:19:07 +0200 Subject: [PATCH] Adding ssh app layer module with two new keywords: ssh.protoversion and ssh.softwareversion --- src/Makefile.am | 3 + src/app-layer-detect-proto.c | 2 +- src/app-layer-ftp.c | 3 +- src/app-layer-ssh.c | 1787 +++++++++++++++++++++++++++++ src/app-layer-ssh.h | 98 ++ src/detect-ssh-proto-version.c | 663 +++++++++++ src/detect-ssh-proto-version.h | 40 + src/detect-ssh-software-version.c | 629 ++++++++++ src/detect-ssh-software-version.h | 37 + src/detect.c | 4 + src/detect.h | 2 + src/stream-tcp-reassemble.c | 2 + src/suricata.c | 3 + 13 files changed, 3271 insertions(+), 2 deletions(-) create mode 100644 src/app-layer-ssh.c create mode 100644 src/app-layer-ssh.h create mode 100644 src/detect-ssh-proto-version.c create mode 100644 src/detect-ssh-proto-version.h create mode 100644 src/detect-ssh-software-version.c create mode 100644 src/detect-ssh-software-version.h diff --git a/src/Makefile.am b/src/Makefile.am index c40cbacb4f..ddaf5f07d6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -115,6 +115,8 @@ detect-http-method.c detect-http-method.h \ detect-http-header.c detect-http-header.h \ detect-http-uri.c detect-http-uri.h \ detect-tls-version.c detect-tls-version.h \ +detect-ssh-proto-version.c detect-ssh-proto-version.h \ +detect-ssh-software-version.c detect-ssh-software-version.h \ detect-icmp-id.c detect-icmp-id.h \ detect-icmp-seq.c detect-icmp-seq.h \ detect-dce-iface.c detect-dce-iface.h \ @@ -206,6 +208,7 @@ app-layer-dcerpc.c app-layer-dcerpc.h \ app-layer-dcerpc-udp.c app-layer-dcerpc-udp.h \ app-layer-ftp.c app-layer-ftp.h \ app-layer-ssl.c app-layer-ssl.h \ +app-layer-ssh.c app-layer-ssh.h \ defrag.c defrag.h \ output.c output.h \ win32-misc.c win32-misc.h \ diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index c1ae2a70c0..a5d4a2886a 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -526,7 +526,7 @@ end: if (mpm_table[dir->mpm_ctx.mpm_type].Cleanup != NULL) { mpm_table[dir->mpm_ctx.mpm_type].Cleanup(&tdir->mpm_ctx); } -#if 0 +#if 1 printf("AppLayerDetectGetProto: returning %" PRIu16 " (%s): ", proto, flags & STREAM_TOCLIENT ? "TOCLIENT" : "TOSERVER"); switch (proto) { case ALPROTO_HTTP: diff --git a/src/app-layer-ftp.c b/src/app-layer-ftp.c index 02620e511e..9c2a464448 100644 --- a/src/app-layer-ftp.c +++ b/src/app-layer-ftp.c @@ -65,7 +65,7 @@ static int FTPParseRequestCommand(void *ftp_state, uint8_t *input, char inputlower[5]; if (input_len >= 4) { - memcpy(inputlower,input,5); + memcpy(inputlower,input,4); int i = 0; for (; i < 4; i++) inputlower[i] = tolower(inputlower[i]); @@ -73,6 +73,7 @@ static int FTPParseRequestCommand(void *ftp_state, uint8_t *input, if (memcmp(inputlower, "port", 4) == 0) { fstate->command = FTP_COMMAND_PORT; } + /* else { * Add the ftp commands you need here * } diff --git a/src/app-layer-ssh.c b/src/app-layer-ssh.c new file mode 100644 index 0000000000..de565d1754 --- /dev/null +++ b/src/app-layer-ssh.c @@ -0,0 +1,1787 @@ +/* Copyright (C) 2007-2010 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 Pablo Rincon + * + * App-layer parser for SSH protocol + * + */ + +#include "suricata-common.h" +#include "debug.h" +#include "decode.h" +#include "threads.h" + +#include "util-print.h" +#include "util-pool.h" + +#include "stream-tcp-private.h" +#include "stream-tcp-reassemble.h" +#include "stream-tcp.h" +#include "stream.h" + +#include "app-layer-protos.h" +#include "app-layer-parser.h" +#include "app-layer-ssh.h" + +#include "conf.h" + +#include "util-spm.h" +#include "util-unittest.h" +#include "util-debug.h" +#include "flow-private.h" + +#include "util-byte.h" + +/** + * \brief Function to parse the SSH version string of the server + * + * \param ssh_state Pointer the state in which the value to be stored + * \param pstate Application layer tarser state for this session + * \param input Pointer the received input data + * \param input_len Length in bytes of the received data + * \param output Pointer to the list of parsed output elements + */ +static int SSHParseServerVersion(Flow *f, void *ssh_state, AppLayerParserState *pstate, + uint8_t *input, uint32_t input_len, + AppLayerParserResult *output) { + uint8_t *line_ptr = input; + uint32_t line_len = input_len; + uint32_t offset = 0; + + SshState *state = (SshState *)ssh_state; + + while (input_len > 0) { + offset = 0; + + if (pstate->store_len > 0){ + const uint8_t delim[] = { 0x0a, }; + int r = AlpParseFieldByDelimiter(output, pstate, + SSH_FIELD_SERVER_VER_STATE_LINE, delim, sizeof(delim), + input, input_len, &offset); + + if (r == 0) + SCReturnInt(0); + + /* process the result elements */ + AppLayerParserResultElmt *e = output->head; + line_ptr = NULL; + line_len = 0; + for (; e != NULL; e = e->next) { + SCLogDebug("e %p e->name_idx %" PRIu32 ", e->data_ptr %p, e->data_len " + "%" PRIu32, e, e->name_idx, + e->data_ptr, e->data_len); + + /* no parser defined for this field. */ + if (e->name_idx != SSH_FIELD_SERVER_VER_STATE_LINE) { + continue; + } + + line_ptr = e->data_ptr; + line_len = e->data_len; + } + + /* Update for the next round */ + input_len -= offset; + input += offset; + + if (line_ptr == NULL) + continue; + } else { + const uint8_t delim[] = { 0x0a, }; + int r = AlpParseFieldByDelimiter(output, pstate, + SSH_FIELD_SERVER_VER_STATE_LINE, delim, sizeof(delim), + input, input_len, &offset); + + if (r == 0) + SCReturnInt(0); + + /* Temporal pointer / len for the current line */ + line_ptr = input; + line_len = offset; + + /* Update for the next round */ + input_len -= offset; + input += offset; + } + + //printf("INPUT: \n"); + //PrintRawDataFp(stdout, line_ptr, line_len); + + if (line_len < 5) { + SCLogDebug("This is not the version line we are searching for (probably a banner or informational messages)"); + continue; + } + + /* is it the version line? */ + if (memcmp("SSH-", line_ptr, 4) == 0) { + if (line_len > 255) { + SCLogDebug("Invalid version string, it should be less than 255 characters including "); + SCReturnInt(-1); + } + + /* ok, we have found the version line/string, skip it and parse proto version */ + line_ptr += 4; + line_len -= 4; + } else { + SCLogDebug("This is not the version line we are searching for (probably a banner or informational messages)"); + continue; + } + + uint8_t *proto_end = BasicSearch(line_ptr, line_len, (uint8_t*)"-", 1); + if (proto_end == NULL) { + /* Strings starting with SSH- are not allowed + * if they are not the real version string */ + SCLogDebug("Invalid Version String for SSH (invalid usage of SSH- prefix)"); + SCReturnInt(-1); + } + + uint64_t proto_ver_len = (uint64_t)((uint64_t)proto_end - (uint64_t)line_ptr); + state->server_proto_version = SCMalloc(proto_ver_len + 1); + if (state->server_proto_version == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, "Error allocating memory"); + SCReturnInt(-1); + } + memcpy(state->server_proto_version, line_ptr, proto_ver_len); + state->server_proto_version[proto_ver_len] = '\0'; + + /* Now lets parse the software & version */ + line_ptr += proto_ver_len + 1; + line_len -= proto_ver_len + 1; + if (line_len < 1) { + SCLogDebug("No software version specified (weird)"); + state->flags |= SSH_FLAG_CLIENT_VERSION_PARSED; + /* Return the remaining length */ + SCReturnInt(input_len); + } + + uint8_t *sw_end = BasicSearch(line_ptr, line_len, (uint8_t*)" ", 1); + if (sw_end == NULL) { + sw_end = BasicSearch(line_ptr, line_len, (uint8_t*)"\r", 1); + if (sw_end == NULL) { + sw_end = line_ptr + line_len; + } + } + + uint64_t sw_ver_len = (uint64_t)((uint64_t)sw_end - (uint64_t)line_ptr); + state->server_software_version = SCMalloc(sw_ver_len + 1); + if (state->server_software_version == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, "Error allocating memory"); + SCReturnInt(-1); + } + memcpy(state->server_software_version, line_ptr, sw_ver_len); + state->server_software_version[sw_ver_len] = '\0'; + if (state->server_software_version[sw_ver_len - 1] == 0x0d) + state->server_software_version[sw_ver_len - 1] = '\0'; + + state->flags |= SSH_FLAG_SERVER_VERSION_PARSED; + /* Return the remaining length */ + SCReturnInt(input_len); + } + + SCReturnInt(0); +} + +/** + * \brief Function to parse the SSH field in packet received from the server + * + * \param ssh_state Pointer the state in which the value to be stored + * \param pstate Application layer tarser state for this session + * \param input Pointer the received input data + * \param input_len Length in bytes of the received data + * \param output Pointer to the list of parsed output elements + */ +static int SSHParseServerRecord(Flow *f, void *ssh_state, AppLayerParserState *pstate, + uint8_t *input, uint32_t input_len, + AppLayerParserResult *output) +{ + SshState *state = (SshState *)ssh_state; + if (state->flags & SSH_FLAG_PARSER_DONE) { + SCReturnInt(0); + } + + SCEnter(); + int ret = 0; + + SCLogDebug("ssh_state %p, pstate %p, input %p,input_len %" PRIu32 "", + ssh_state, pstate, input, input_len); + //PrintRawDataFp(stdout, input,input_len); + + if (pstate == NULL) + SCReturnInt(-1); + + if ( !(state->flags & SSH_FLAG_SERVER_VERSION_PARSED)) { + ret = SSHParseServerVersion(f, ssh_state, pstate, input, input_len, output); + if (ret < 0) { + if (ret <= -1) { + SCLogDebug("Invalid version string"); + SCReturnInt(-1); + } + SCLogDebug("Version string not parsed yet"); + pstate->parse_field = 0; + SCReturnInt(ret); + } else if (state->flags & SSH_FLAG_SERVER_VERSION_PARSED) { + SCLogDebug("Version string parsed"); + input += input_len - ret; + input_len -= (input_len - ret); + pstate->parse_field = 1; + ret = 1; + if (input_len == 0) + SCReturnInt(ret); + } else { + SCLogDebug("Version string not parsed yet"); + pstate->parse_field = 0; + SCReturnInt(ret); + } + } else { + SCLogDebug("Version string already parsed"); + } + + uint16_t max_fields = 4; + int16_t u = 0; + uint32_t offset = 0; + + //PrintRawDataFp(stdout, input,input_len); + + if (pstate == NULL) + SCReturnInt(-1); + + for (u = pstate->parse_field; u < max_fields; u++) { + SCLogDebug("u %" PRIu32 "", u); + + switch(u % 4) { + case 0: + { + continue; + } + case 1: /* TLS CONTENT TYPE */ + { + uint8_t *data = input + offset; + uint32_t data_len = input_len - offset; + + int r = AlpParseFieldBySize(output, pstate, + SSH_FIELD_SERVER_PKT_LENGTH, + /* single byte field */4, data, + data_len, &offset); + SCLogDebug("r = %" PRId32 "", r); + + if (r == 0) { + pstate->parse_field = 1; + SCReturnInt(0); + } else if (r == -1) { + SCLogError(SC_ERR_ALPARSER, "AlpParseFieldBySize failed, " + "r %d", r); + SCReturnInt(-1); + } + + uint32_t pkt_len = 0; + int ret = ByteExtractUint32(&pkt_len, BYTE_BIG_ENDIAN, + output->tail->data_len, output->tail->data_ptr); + if (ret != 4) { + SCReturnInt(-1); + } + state->srv_hdr.pkt_len = pkt_len; + SCLogDebug("pkt len: %"PRIu32, pkt_len); + + break; + } + case 2: /* TLS VERSION */ + { + uint8_t *data = input + offset; + uint32_t data_len = input_len - offset; + + int r = AlpParseFieldBySize(output, pstate, + SSH_FIELD_SERVER_PADDING_LENGTH, + /* 2 byte field */1, data, data_len, + &offset); + if (r == 0) { + pstate->parse_field = 2; + SCReturnInt(0); + } else if (r == -1) { + SCLogError(SC_ERR_ALPARSER, "AlpParseFieldBySize failed, " + "r %d", r); + SCReturnInt(-1); + } + uint8_t padding_len = 0; + if (output->tail->data_len == 1) { + padding_len = (uint8_t) *output->tail->data_ptr; + SCLogDebug("padding len: %"PRIu8, padding_len); + } + state->srv_hdr.padding_len = padding_len; + + break; + } + case 3: /* SSH_PAYLOAD */ + { + uint8_t *data = input + offset; + uint32_t data_len = input_len - offset; + + /* we add a -1 to the pkt len since the padding length is already parsed */ + int r = AlpParseFieldBySize(output, pstate, SSH_FIELD_SERVER_PAYLOAD, + state->srv_hdr.pkt_len - 1, data, data_len, + &offset); + SCLogDebug("AlpParseFieldBySize returned r %d, offset %"PRIu32, + r, offset); + if (r == 0) { + pstate->parse_field = 3; + SCReturnInt(0); + } else if (r == -1) { + SCLogError(SC_ERR_ALPARSER, "AlpParseFieldBySize failed, " + "r %d", r); + SCReturnInt(-1); + } + + uint8_t msg_code = 0; + if (output->tail->data_len >= 1) { + msg_code = (uint8_t) *output->tail->data_ptr; + SCLogDebug("msg code: %"PRIu8, msg_code); + } + state->srv_hdr.msg_code = msg_code; + + if (state->srv_hdr.msg_code == SSH_MSG_NEWKEYS) { + /* We are not going to inspect any packet more + * as the data is now encrypted */ + SCLogDebug("SSH parser done (the rest of the communication is encrypted)"); + state->flags |= SSH_FLAG_PARSER_DONE; + pstate->flags |= APP_LAYER_PARSER_DONE; + pstate->flags |= APP_LAYER_PARSER_NO_INSPECTION; + pstate->flags |= APP_LAYER_PARSER_NO_REASSEMBLY; + pstate->parse_field = 1; + SCReturnInt(1); + } + + pstate->parse_field = 1; + ret = 1; + + /* If we have remaining data, continue processing */ + if ((int)input_len - (int)offset > 0) { + u = 0; + } + break; + } + } + + } + + SCReturnInt(ret); +} + +/** + * \brief Function to parse the SSH version string of the client + * + * \param ssh_state Pointer the state in which the value to be stored + * \param pstate Application layer tarser state for this session + * \param input Pointer the received input data + * \param input_len Length in bytes of the received data + * \param output Pointer to the list of parsed output elements + */ +static int SSHParseClientVersion(Flow *f, void *ssh_state, AppLayerParserState *pstate, + uint8_t *input, uint32_t input_len, + AppLayerParserResult *output) { + uint8_t *line_ptr = input; + uint32_t line_len = input_len; + uint32_t offset = 0; + + SshState *state = (SshState *)ssh_state; + + while (input_len > 0) { + offset = 0; + + + if (pstate->store_len > 0){ + const uint8_t delim[] = { 0x0a, }; + int r = AlpParseFieldByDelimiter(output, pstate, + SSH_FIELD_CLIENT_VER_STATE_LINE, delim, sizeof(delim), + input, input_len, &offset); + + if (r == 0) + SCReturnInt(0); + + /* process the result elements */ + AppLayerParserResultElmt *e = output->head; + line_ptr = NULL; + line_len = 0; + for (; e != NULL; e = e->next) { + SCLogDebug("e %p e->name_idx %" PRIu32 ", e->data_ptr %p, e->data_len " + "%" PRIu32, e, e->name_idx, + e->data_ptr, e->data_len); + + /* no parser defined for this field. */ + if (e->name_idx != SSH_FIELD_CLIENT_VER_STATE_LINE) { + continue; + } + + line_ptr = e->data_ptr; + line_len = e->data_len; + } + + /* Update for the next round */ + input_len -= offset; + input += offset; + + if (line_ptr == NULL) + continue; + } else { + const uint8_t delim[] = { 0x0a, }; + int r = AlpParseFieldByDelimiter(output, pstate, + SSH_FIELD_CLIENT_VER_STATE_LINE, delim, sizeof(delim), + input, input_len, &offset); + + if (r == 0) + SCReturnInt(0); + + /* Temporal pointer / len for the current line */ + line_ptr = input; + line_len = offset; + + /* Update for the next round */ + input_len -= offset; + input += offset; + } + + //PrintRawDataFp(stdout, line_ptr, line_len); + + if (line_len < 5) { + SCLogDebug("This is not the version line we are searching for (probably a banner or informational messages)"); + continue; + } + + /* is it the version line? */ + if (memcmp("SSH-", line_ptr, 4) == 0) { + if (line_len > 255) { + SCLogDebug("Invalid version string, it should be less than 255 characters including "); + SCReturnInt(-1); + } + + /* ok, we have found the version line/string, skip it and parse proto version */ + line_ptr += 4; + line_len -= 4; + } else { + SCLogDebug("This is not the version line we are searching for (probably a banner or informational messages)"); + continue; + } + + uint8_t *proto_end = BasicSearch(line_ptr, line_len, (uint8_t*)"-", 1); + if (proto_end == NULL) { + /* Strings starting with SSH- are not allowed + * if they are not the real version string */ + SCLogDebug("Invalid Version String for SSH (invalid usage of SSH- prefix)"); + SCReturnInt(-1); + } + + uint64_t proto_ver_len = (uint64_t)((uint64_t)proto_end - (uint64_t)line_ptr); + state->client_proto_version = SCMalloc(proto_ver_len + 1); + if (state->client_proto_version == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, "Error allocating memory"); + SCReturnInt(-1); + } + memcpy(state->client_proto_version, line_ptr, proto_ver_len); + state->client_proto_version[proto_ver_len] = '\0'; + + /* Now lets parse the software & version */ + line_ptr += proto_ver_len + 1; + line_len -= proto_ver_len + 1; + if (line_len < 1) { + SCLogDebug("No software version specified (weird)"); + state->flags |= SSH_FLAG_CLIENT_VERSION_PARSED; + /* Return the remaining length */ + SCReturnInt(input_len); + } + + uint8_t *sw_end = BasicSearch(line_ptr, line_len, (uint8_t*)" ", 1); + if (sw_end == NULL) { + sw_end = BasicSearch(line_ptr, line_len, (uint8_t*)"\r", 1); + if (sw_end == NULL) { + sw_end = line_ptr + line_len; + } + } + + uint64_t sw_ver_len = (uint64_t)((uint64_t)sw_end - (uint64_t)line_ptr); + state->client_software_version = SCMalloc(sw_ver_len + 1); + if (state->client_software_version == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, "Error allocating memory"); + SCReturnInt(-1); + } + memcpy(state->client_software_version, line_ptr, sw_ver_len); + state->client_software_version[sw_ver_len] = '\0'; + if (state->client_software_version[sw_ver_len - 1] == 0x0d) + state->client_software_version[sw_ver_len - 1] = '\0'; + + state->flags |= SSH_FLAG_CLIENT_VERSION_PARSED; + /* Return the remaining length */ + SCReturnInt(input_len); + } + + SCReturnInt(0); +} + +/** + * \brief Function to parse the SSH field in packet received from the client + * + * \param ssh_state Pointer the state in which the value to be stored + * \param pstate Application layer tarser state for this session + * \param input Pointer the received input data + * \param input_len Length in bytes of the received data + * \param output Pointer to the list of parsed output elements + */ +static int SSHParseClientRecord(Flow *f, void *ssh_state, AppLayerParserState *pstate, + uint8_t *input, uint32_t input_len, + AppLayerParserResult *output) +{ + SshState *state = (SshState *)ssh_state; + if (state->flags & SSH_FLAG_PARSER_DONE) { + SCReturnInt(0); + } + + SCEnter(); + int ret = 0; + + SCLogDebug("ssh_state %p, pstate %p, input %p,input_len %" PRIu32 "", + ssh_state, pstate, input, input_len); + //PrintRawDataFp(stdout, input,input_len); + + if (pstate == NULL) + SCReturnInt(-1); + + if ( !(state->flags & SSH_FLAG_CLIENT_VERSION_PARSED)) { + ret = SSHParseClientVersion(f, ssh_state, pstate, input, input_len, output); + if (ret < 0) { + if (ret <= -1) { + SCLogDebug("Invalid version string"); + SCReturnInt(-1); + } + SCLogDebug("Version string not parsed yet"); + pstate->parse_field = 0; + SCReturnInt(0); + } else if (state->flags & SSH_FLAG_CLIENT_VERSION_PARSED) { + SCLogDebug("Version string parsed"); + input += input_len - ret; + input_len -= (input_len - ret); + pstate->parse_field = 1; + ret = 1; + } else { + SCLogDebug("Version string not parsed yet"); + pstate->parse_field = 0; + SCReturnInt(0); + } + } else { + SCLogDebug("Version string already parsed"); + } + + uint16_t max_fields = 4; + int16_t u = 0; + uint32_t offset = 0; + + //printf("INPUT: \n"); + //PrintRawDataFp(stdout, input,input_len); + + if (pstate == NULL) + SCReturnInt(-1); + + for (u = pstate->parse_field; u < max_fields; u++) { + SCLogDebug("u %" PRIu32 "", u); + + switch(u % 4) { + case 0: + { + continue; + } + case 1: /* TLS CONTENT TYPE */ + { + uint8_t *data = input + offset; + uint32_t data_len = input_len - offset; + + int r = AlpParseFieldBySize(output, pstate, + SSH_FIELD_CLIENT_PKT_LENGTH, + /* single byte field */4, data, + data_len, &offset); + SCLogDebug("r = %" PRId32 "", r); + + if (r == 0) { + pstate->parse_field = 1; + SCReturnInt(0); + } else if (r == -1) { + SCLogError(SC_ERR_ALPARSER, "AlpParseFieldBySize failed, " + "r %d", r); + SCReturnInt(-1); + } + + uint32_t pkt_len = 0; + int ret = ByteExtractUint32(&pkt_len, BYTE_BIG_ENDIAN, + output->tail->data_len, output->tail->data_ptr); + if (ret != 4) { + SCReturnInt(-1); + } + state->cli_hdr.pkt_len = pkt_len; + SCLogDebug("pkt len: %"PRIu32"\n", pkt_len); + + break; + } + case 2: /* TLS VERSION */ + { + uint8_t *data = input + offset; + uint32_t data_len = input_len - offset; + + int r = AlpParseFieldBySize(output, pstate, + SSH_FIELD_CLIENT_PADDING_LENGTH, + /* 2 byte field */1, data, data_len, + &offset); + if (r == 0) { + pstate->parse_field = 2; + SCReturnInt(0); + } else if (r == -1) { + SCLogError(SC_ERR_ALPARSER, "AlpParseFieldBySize failed, " + "r %d", r); + SCReturnInt(-1); + } + uint8_t padding_len = 0; + if (output->tail->data_len == 1) { + padding_len = (uint8_t) *output->tail->data_ptr; + SCLogDebug("padding len: %"PRIu8, padding_len); + } + state->cli_hdr.padding_len = padding_len; + + break; + } + case 3: /* SSH_PAYLOAD */ + { + uint8_t *data = input + offset; + uint32_t data_len = input_len - offset; + + /* we add a -1 to the pkt len since the padding length is already parsed */ + int r = AlpParseFieldBySize(output, pstate, SSH_FIELD_CLIENT_PAYLOAD, + /* 1 byte field */ state->cli_hdr.pkt_len - 1, data, data_len, + &offset); + SCLogDebug("AlpParseFieldBySize returned r %d, offset %"PRIu32, + r, offset); + if (r == 0) { + pstate->parse_field = 3; + SCReturnInt(0); + } else if (r == -1) { + SCLogError(SC_ERR_ALPARSER, "AlpParseFieldBySize failed, " + "r %d", r); + SCReturnInt(-1); + } + + uint8_t msg_code = 0; + if (output->tail->data_len >= 1) { + msg_code = (uint8_t) *output->tail->data_ptr; + SCLogDebug("msg code: %"PRIu8, msg_code); + } + + state->cli_hdr.msg_code = msg_code; + if (state->cli_hdr.msg_code == SSH_MSG_NEWKEYS) { + /* We are not going to inspect any packet more + * as the data is now encrypted */ + SCLogDebug("SSH parser done (the rest of the communication is encrypted)"); + state->flags |= SSH_FLAG_PARSER_DONE; + pstate->flags |= APP_LAYER_PARSER_DONE; + pstate->flags |= APP_LAYER_PARSER_NO_INSPECTION; + pstate->flags |= APP_LAYER_PARSER_NO_REASSEMBLY; + pstate->parse_field = 1; + SCReturnInt(1); + } + + pstate->parse_field = 1; + ret = 1; + + /* If we have remaining data, continue processing */ + if (input_len - offset > 0) { + u = 0; + } + + break; + } + } + + } + + SCReturnInt(ret); +} + +/** \brief Function to allocates the SSH state memory + */ +static void *SSHStateAlloc(void) +{ + void *s = SCMalloc(sizeof(SshState)); + if (s == NULL) + return NULL; + + memset(s, 0, sizeof(SshState)); + return s; +} + +/** \brief Function to free the SSH state memory + */ +static void SSHStateFree(void *state) +{ + SshState *s= (SshState *)state; + if (s->client_proto_version != NULL) + SCFree(s->client_proto_version); + if (s->client_software_version != NULL) + SCFree(s->client_software_version); + if (s->server_proto_version != NULL) + SCFree(s->server_proto_version); + if (s->server_software_version != NULL) + SCFree(s->server_software_version); + + SCFree(s); +} + +/** \brief Function to register the SSH protocol parsers and other functions + */ +void RegisterSSHParsers(void) +{ + AppLayerRegisterProto("ssh", ALPROTO_SSH, STREAM_TOCLIENT, + SSHParseServerRecord); + AppLayerRegisterProto("ssh", ALPROTO_SSH, STREAM_TOSERVER, + SSHParseClientRecord); + + AppLayerRegisterStateFuncs(ALPROTO_SSH, SSHStateAlloc, SSHStateFree); + +} + +/* UNITTESTS */ +#ifdef UNITTESTS + +/** \test Send a version string in one chunk (client version str). */ +static int SSHParserTest01(void) { + int result = 0; + Flow f; + uint8_t sshbuf[] = "SSH-2.0-MySSHClient-0.5.1\n"; + uint32_t sshlen = sizeof(sshbuf) - 1; + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER|STREAM_EOF, sshbuf, sshlen); + if (r != 0) { + printf("toclient chunk 1 returned %" PRId32 ", expected 0: ", r); + result = 0; + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + result = 0; + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_CLIENT_VERSION_PARSED)) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (ssh_state->client_software_version == NULL) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (ssh_state->client_proto_version == NULL) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (strncmp((char*)ssh_state->client_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + result = 0; + goto end; + } + + if (strncmp((char*)ssh_state->client_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + result = 0; + goto end; + } + + result = 1; + +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test Send a version string in one chunk but multiple lines and comments. + * (client version str) + */ +static int SSHParserTest02(void) { + int result = 0; + Flow f; + uint8_t sshbuf[] = "lalala\n lal al al\nSSH-2.0-MySSHClient-0.5.1 some comments...\n"; + uint32_t sshlen = sizeof(sshbuf) - 1; + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER|STREAM_EOF, sshbuf, sshlen); + if (r != 0) { + printf("toclient chunk 1 returned %" PRId32 ", expected 0: ", r); + result = 0; + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + result = 0; + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_CLIENT_VERSION_PARSED)) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (ssh_state->client_software_version == NULL) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (ssh_state->client_proto_version == NULL) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (strncmp((char*)ssh_state->client_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + result = 0; + goto end; + } + + if (strncmp((char*)ssh_state->client_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + result = 0; + goto end; + } + + result = 1; + +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test Send a invalid version string in one chunk but multiple lines and comments. + * (client version str) + */ +static int SSHParserTest03(void) { + int result = 0; + Flow f; + uint8_t sshbuf[] = "lalala\n lal al al\nSSH-2.0 some comments...\n"; + uint32_t sshlen = sizeof(sshbuf) - 1; + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER|STREAM_EOF, sshbuf, sshlen); + if (r == 0) { + printf("toclient chunk 1 returned %" PRId32 ", expected != 0: ", r); + result = 0; + goto end; + } + /* Ok, it returned an error. Let's make sure we didn't parse the string at all */ + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + result = 0; + goto end; + } + + if (ssh_state->flags & SSH_FLAG_CLIENT_VERSION_PARSED) { + printf("Client version string parsed? It's not a valid string: "); + result = 0; + goto end; + } + + if (ssh_state->client_proto_version != NULL) { + result = 0; + goto end; + } + + if (ssh_state->client_software_version != NULL) { + result = 0; + goto end; + } + + result = 1; + +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test Send a version string in one chunk (server version str). */ +static int SSHParserTest04(void) { + int result = 0; + Flow f; + uint8_t sshbuf[] = "SSH-2.0-MySSHClient-0.5.1\n"; + uint32_t sshlen = sizeof(sshbuf) - 1; + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT|STREAM_EOF, sshbuf, sshlen); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + result = 0; + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + result = 0; + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_SERVER_VERSION_PARSED)) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (ssh_state->server_software_version == NULL) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (ssh_state->server_proto_version == NULL) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (strncmp((char*)ssh_state->server_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + result = 0; + goto end; + } + + if (strncmp((char*)ssh_state->server_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + result = 0; + goto end; + } + + result = 1; + +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test Send a version string in one chunk but multiple lines and comments. + * (server version str) + */ +static int SSHParserTest05(void) { + int result = 0; + Flow f; + uint8_t sshbuf[] = "lalala\n lal al al\nSSH-2.0-MySSHClient-0.5.1 some comments...\n"; + uint32_t sshlen = sizeof(sshbuf) - 1; + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT|STREAM_EOF, sshbuf, sshlen); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + result = 0; + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + result = 0; + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_SERVER_VERSION_PARSED)) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (ssh_state->server_software_version == NULL) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (ssh_state->server_proto_version == NULL) { + printf("Client version string not parsed: "); + result = 0; + goto end; + } + + if (strncmp((char*)ssh_state->server_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + result = 0; + goto end; + } + + if (strncmp((char*)ssh_state->server_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + result = 0; + goto end; + } + + result = 1; + +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test Send a invalid version string in one chunk but multiple lines and comments. + * (server version str) + */ +static int SSHParserTest06(void) { + int result = 0; + Flow f; + uint8_t sshbuf[] = "lalala\n lal al al\nSSH-2.0 some comments...\n"; + uint32_t sshlen = sizeof(sshbuf) - 1; + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT|STREAM_EOF, sshbuf, sshlen); + if (r == 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected != 0: ", r); + result = 0; + goto end; + } + /* Ok, it returned an error. Let's make sure we didn't parse the string at all */ + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + result = 0; + goto end; + } + + if (ssh_state->flags & SSH_FLAG_SERVER_VERSION_PARSED) { + printf("Client version string parsed? It's not a valid string: "); + result = 0; + goto end; + } + + if (ssh_state->server_proto_version != NULL) { + result = 0; + goto end; + } + + if (ssh_state->server_software_version != NULL) { + result = 0; + goto end; + } + + result = 1; + +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +static int SSHParserTest07(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "SSH-2."; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = { "0-MySSHClient-0.5.1\r\n"}; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_CLIENT_VERSION_PARSED)) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->client_software_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->client_proto_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (strncmp((char*)ssh_state->client_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if (strncmp((char*)ssh_state->client_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + result = 1; + +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test Send a version banner in three chunks. */ +static int SSHParserTest08(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "Welcome to this ssh server\nSSH-"; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "2."; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = { "0-MySSHClient-0.5.1\r\n"}; + uint32_t sshlen3 = sizeof(sshbuf3) - 1; + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_CLIENT_VERSION_PARSED)) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->client_software_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->client_proto_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (strncmp((char*)ssh_state->client_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if (strncmp((char*)ssh_state->client_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + result = 1; +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +static int SSHParserTest09(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "SSH-2."; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = { "0-MySSHClient-0.5.1\r\n"}; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_SERVER_VERSION_PARSED)) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->server_software_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->server_proto_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (strncmp((char*)ssh_state->server_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if (strncmp((char*)ssh_state->server_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + result = 1; + +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test Send a version banner in three chunks. */ +static int SSHParserTest10(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "Welcome to this ssh server\nSSH-"; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "2."; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = { "0-MySSHClient-0.5.1\r\n"}; + uint32_t sshlen3 = sizeof(sshbuf3) - 1; + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_SERVER_VERSION_PARSED)) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->server_software_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->server_proto_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (strncmp((char*)ssh_state->server_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if (strncmp((char*)ssh_state->server_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + result = 1; +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test Send a banner and record in three chunks. */ +static int SSHParserTest11(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "Welcome to this ssh server\nSSH-"; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "2.0-MySSHClient-0.5.1\r\n"; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = { 0x00, 0x00, 0x00, 0x03,0x01, 21, 0x00}; + uint32_t sshlen3 = sizeof(sshbuf3); + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_CLIENT_VERSION_PARSED)) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->client_software_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->client_proto_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (strncmp((char*)ssh_state->client_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if (strncmp((char*)ssh_state->client_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_PARSER_DONE)) { + printf("Didn't detect the msg code of new keys (ciphered data starts): "); + goto end; + } + + result = 1; +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test Send a banner and 2 records record in four chunks. */ +static int SSHParserTest12(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "Welcome to this ssh server\nSSH-"; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "2.0-MySSHClient-0.5.1\r\n"; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = { 0x00, 0x00, 0x00, 0x03,0x01, 17, 0x00}; + uint32_t sshlen3 = sizeof(sshbuf3); + uint8_t sshbuf4[] = { 0x00, 0x00, 0x00, 0x03,0x01, 21, 0x00}; + uint32_t sshlen4 = sizeof(sshbuf4); + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf4, sshlen4); + if (r != 0) { + printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_CLIENT_VERSION_PARSED)) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->client_software_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->client_proto_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (strncmp((char*)ssh_state->client_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if (strncmp((char*)ssh_state->client_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_PARSER_DONE)) { + printf("Didn't detect the msg code of new keys (ciphered data starts): "); + goto end; + } + + result = 1; +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test Send toserver a banner and record in three chunks. */ +static int SSHParserTest13(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "Welcome to this ssh server\nSSH-"; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "2.0-MySSHClient-0.5.1\r\n"; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = { 0x00, 0x00, 0x00, 0x03,0x01, 21, 0x00}; + uint32_t sshlen3 = sizeof(sshbuf3); + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_SERVER_VERSION_PARSED)) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->server_software_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->server_proto_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (strncmp((char*)ssh_state->server_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if (strncmp((char*)ssh_state->server_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_PARSER_DONE)) { + printf("Didn't detect the msg code of new keys (ciphered data starts): "); + goto end; + } + + result = 1; +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +/** \test Send toserver a banner and 2 records record in four chunks. */ +static int SSHParserTest14(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "Welcome to this ssh server\nSSH-"; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "2.0-MySSHClient-0.5.1\r\n"; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = { 0x00, 0x00, 0x00, 0x03,0x01, 17, 0x00}; + uint32_t sshlen3 = sizeof(sshbuf3); + uint8_t sshbuf4[] = { 0x00, 0x00, 0x00, 0x03,0x01, 21, 0x00}; + uint32_t sshlen4 = sizeof(sshbuf4); + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + f.protoctx = (void *)&ssn; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf4, sshlen4); + if (r != 0) { + printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_SERVER_VERSION_PARSED)) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->server_software_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (ssh_state->server_proto_version == NULL) { + printf("Client version string not parsed: "); + goto end; + } + + if (strncmp((char*)ssh_state->server_software_version, "MySSHClient-0.5.1", strlen("MySSHClient-0.5.1")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if (strncmp((char*)ssh_state->server_proto_version, "2.0", strlen("2.0")) != 0) { + printf("Client version string not parsed correctly: "); + goto end; + } + + if ( !(ssh_state->flags & SSH_FLAG_PARSER_DONE)) { + printf("Didn't detect the msg code of new keys (ciphered data starts): "); + goto end; + } + + result = 1; +end: + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + return result; +} + +#endif /* UNITTESTS */ + +void SSHParserRegisterTests(void) { +#ifdef UNITTESTS + UtRegisterTest("SSHParserTest01 - ToServer", SSHParserTest01, 1); + UtRegisterTest("SSHParserTest02 - ToServer", SSHParserTest02, 1); + UtRegisterTest("SSHParserTest03 - ToServer", SSHParserTest03, 1); + UtRegisterTest("SSHParserTest04 - ToClient", SSHParserTest04, 1); + UtRegisterTest("SSHParserTest05 - ToClient", SSHParserTest05, 1); + UtRegisterTest("SSHParserTest06 - ToClient", SSHParserTest06, 1); + UtRegisterTest("SSHParserTest07 - ToServer 2 chunks", SSHParserTest07, 1); + UtRegisterTest("SSHParserTest08 - ToServer 3 chunks", SSHParserTest08, 1); + UtRegisterTest("SSHParserTest09 - ToClient 2 chunks", SSHParserTest09, 1); + UtRegisterTest("SSHParserTest10 - ToClient 3 chunks", SSHParserTest10, 1); + UtRegisterTest("SSHParserTest11 - ToClient 4 chunks", SSHParserTest11, 1); + UtRegisterTest("SSHParserTest12 - ToClient 4 chunks", SSHParserTest12, 1); + UtRegisterTest("SSHParserTest13 - ToClient 4 chunks", SSHParserTest13, 1); + UtRegisterTest("SSHParserTest14 - ToClient 4 chunks", SSHParserTest14, 1); +#endif /* UNITTESTS */ +} diff --git a/src/app-layer-ssh.h b/src/app-layer-ssh.h new file mode 100644 index 0000000000..3ce0dc084d --- /dev/null +++ b/src/app-layer-ssh.h @@ -0,0 +1,98 @@ +/* Copyright (C) 2007-2010 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 Pablo Rincon + */ + +#ifndef __APP_LAYER_SSH_H__ +#define __APP_LAYER_SSH_H__ + +#define SSH_FLAG_SERVER_CHANGE_CIPHER_SPEC 0x01 /**< Flag to indicate that + server will now on sends + encrypted msgs. */ +#define SSH_FLAG_CLIENT_CHANGE_CIPHER_SPEC 0x02 /**< Flag to indicate that + client will now on sends + encrypted msgs. */ + +#define SSH_FLAG_CLIENT_VERSION_PARSED 0x01 +#define SSH_FLAG_SERVER_VERSION_PARSED 0x02 + +/* This flags indicate that the rest of the communication + * must be ciphered, so the parsing finish here */ +#define SSH_FLAG_PARSER_DONE 0x04 + +/* MSG_CODE */ +#define SSH_MSG_NEWKEYS 21 + +enum { + SSH_FIELD_NONE = 0, + SSH_FIELD_SERVER_VER_STATE_LINE, + SSH_FIELD_CLIENT_VER_STATE_LINE, + SSH_FIELD_SERVER_PKT_LENGTH, + SSH_FIELD_CLIENT_PKT_LENGTH, + SSH_FIELD_SERVER_PADDING_LENGTH, + SSH_FIELD_CLIENT_PADDING_LENGTH, + SSH_FIELD_SERVER_PAYLOAD, + SSH_FIELD_CLIENT_PAYLOAD, + + /* must be last */ + SSH_FIELD_MAX, +}; + +/** From SSH-TRANSP rfc + + SSH Bunary packet structure: + uint32 packet_length + byte padding_length + byte[n1] payload; n1 = packet_length - padding_length - 1 + byte[n2] random padding; n2 = padding_length + byte[m] mac (Message Authentication Code - MAC); m = mac_length + + So we are going to do a header struct to store + the lenghts and msg_code (inside payload, if any) +*/ + +typedef struct SSHHeader_ { + uint32_t pkt_len; + uint8_t padding_len; + uint8_t msg_code; +} SshHeader; + +/** structure to store the SSH state values */ +typedef struct SshState_ { + uint8_t client_msg_code; /**< Client content type storage field */ + uint8_t *client_proto_version; /**< Client SSH version storage field */ + uint8_t *client_software_version; /**< Client SSH version storage field */ + + uint8_t server_msg_code; /**< Server content type storage field */ + uint8_t *server_proto_version; /**< Server SSH version storage field */ + uint8_t *server_software_version; /**< Server SSH version storage field */ + + uint8_t flags; /**< Flags to indicate the current SSH + sessoin state */ + SshHeader srv_hdr; + SshHeader cli_hdr; +} SshState; + +void RegisterSSHParsers(void); +void SSHParserRegisterTests(void); + +#endif /* __APP_LAYER_SSH_H__ */ + diff --git a/src/detect-ssh-proto-version.c b/src/detect-ssh-proto-version.c new file mode 100644 index 0000000000..eb778b2ab1 --- /dev/null +++ b/src/detect-ssh-proto-version.c @@ -0,0 +1,663 @@ +/* Copyright (C) 2007-2010 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 Pablo Rincon + * + * Implements the ssh.protoversion keyword + * You can specify a concrete version like ssh.protoversion: 1.66 + * or search for protoversion 2 compat (1.99 is considered as 2) like + * ssh.protoversion:2_compat + * or just the beginning of the string like ssh.protoversion:"1." + */ + +#include "suricata-common.h" +#include "threads.h" +#include "debug.h" +#include "decode.h" + +#include "detect.h" +#include "detect-parse.h" + +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-engine-state.h" + +#include "flow.h" +#include "flow-var.h" +#include "flow-util.h" + +#include "util-debug.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" + +#include "app-layer.h" + +#include "app-layer-ssh.h" +#include "detect-ssh-proto-version.h" + +#include "stream-tcp.h" + +/** + * \brief Regex for parsing the protoversion string + */ +#define PARSE_REGEX "^\\s*\"?\\s*([0-9]+([\\.\\-0-9]+)?|2_compat)\\s*\"?\\s*$" + +static pcre *parse_regex; +static pcre_extra *parse_regex_study; + +int DetectSshVersionMatch (ThreadVars *, DetectEngineThreadCtx *, Flow *, uint8_t, void *, Signature *, SigMatch *); +static int DetectSshVersionSetup (DetectEngineCtx *, Signature *, char *); +void DetectSshVersionRegisterTests(void); +void DetectSshVersionFree(void *); + +/** + * \brief Registration function for keyword: ssh.protoversion + */ +void DetectSshVersionRegister(void) { + sigmatch_table[DETECT_AL_SSH_PROTOVERSION].name = "ssh.protoversion"; + sigmatch_table[DETECT_AL_SSH_PROTOVERSION].Match = NULL; + sigmatch_table[DETECT_AL_SSH_PROTOVERSION].AppLayerMatch = DetectSshVersionMatch; + sigmatch_table[DETECT_AL_SSH_PROTOVERSION].alproto = ALPROTO_SSH; + sigmatch_table[DETECT_AL_SSH_PROTOVERSION].Setup = DetectSshVersionSetup; + sigmatch_table[DETECT_AL_SSH_PROTOVERSION].Free = DetectSshVersionFree; + sigmatch_table[DETECT_AL_SSH_PROTOVERSION].RegisterTests = DetectSshVersionRegisterTests; + + const char *eb; + int eo; + int opts = 0; + + SCLogDebug("registering ssh.protoversion rule option"); + + parse_regex = pcre_compile(PARSE_REGEX, opts, &eb, &eo, NULL); + if (parse_regex == NULL) { + SCLogError(SC_ERR_PCRE_COMPILE, "Compile of \"%s\" failed at offset %" PRId32 ": %s", + PARSE_REGEX, eo, eb); + goto error; + } + + parse_regex_study = pcre_study(parse_regex, 0, &eb); + if (eb != NULL) { + SCLogError(SC_ERR_PCRE_STUDY, "pcre study failed: %s", eb); + goto error; + } + return; + +error: + return; +} + +/** + * \brief match the specified version on a ssh session + * + * \param t pointer to thread vars + * \param det_ctx pointer to the pattern matcher thread + * \param p pointer to the current packet + * \param m pointer to the sigmatch that we will cast into DetectSshVersionData + * + * \retval 0 no match + * \retval 1 match + */ +int DetectSshVersionMatch (ThreadVars *t, DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state, Signature *s, SigMatch *m) +{ + SCEnter(); + + DetectSshVersionData *ssh = (DetectSshVersionData *)m->ctx; + SshState *ssh_state = (SshState *)state; + if (ssh_state == NULL) { + SCLogDebug("no ssh state, no match"); + SCReturnInt(0); + } + + int ret = 0; + SCMutexLock(&f->m); + if (flags & STREAM_TOCLIENT && ssh_state->flags & SSH_FLAG_SERVER_VERSION_PARSED) { + if (ssh->flags & SSH_FLAG_PROTOVERSION_2_COMPAT) { + SCLogDebug("looking for ssh server protoversion 2 compat"); + if (strncmp((char *) ssh_state->server_proto_version, "2", 1) == 0 || + strncmp((char *) ssh_state->server_proto_version, "2.", 2) == 0 || + strncmp((char *) ssh_state->server_proto_version, "1.99", 4) == 0) + ret = 1; + } else { + SCLogDebug("looking for ssh server protoversion %s length %"PRIu16"", ssh->ver, ssh->len); + ret = (strncmp((char *) ssh_state->server_proto_version, (char *) ssh->ver, ssh->len) == 0)? 1 : 0; + } + } else if (flags & STREAM_TOSERVER && ssh_state->flags & SSH_FLAG_CLIENT_VERSION_PARSED) { + if (ssh->flags & SSH_FLAG_PROTOVERSION_2_COMPAT) { + SCLogDebug("looking for client ssh client protoversion 2 compat"); + if (strncmp((char *) ssh_state->client_proto_version, "2", 1) == 0 || + strncmp((char *) ssh_state->client_proto_version, "2.", 2) == 0 || + strncmp((char *) ssh_state->client_proto_version, "1.99", 4) == 0) + ret = 1; + } else { + SCLogDebug("looking for ssh client protoversion %s length %"PRIu16"", ssh->ver, ssh->len); + ret = (strncmp((char *) ssh_state->client_proto_version, (char *) ssh->ver, ssh->len) == 0)? 1 : 0; + } + } + SCMutexUnlock(&f->m); + SCReturnInt(ret); +} + +/** + * \brief This function is used to parse IPV4 ip_id passed via keyword: "id" + * + * \param idstr Pointer to the user provided id option + * + * \retval id_d pointer to DetectSshVersionData on success + * \retval NULL on failure + */ +DetectSshVersionData *DetectSshVersionParse (char *str) +{ + DetectSshVersionData *ssh = NULL; + #define MAX_SUBSTRINGS 30 + int ret = 0, res = 0; + int ov[MAX_SUBSTRINGS]; + + ret = pcre_exec(parse_regex, parse_regex_study, str, strlen(str), 0, 0, + ov, MAX_SUBSTRINGS); + + if (ret < 1 || ret > 3) { + SCLogError(SC_ERR_PCRE_MATCH, "invalid ssh.protoversion option"); + goto error; + } + + if (ret > 1) { + const char *str_ptr; + res = pcre_get_substring((char *)str, ov, MAX_SUBSTRINGS, 1, &str_ptr); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + + /* We have a correct id option */ + ssh = SCMalloc(sizeof(DetectSshVersionData)); + if (ssh == NULL) + goto error; + + /* If we expect a protocol version 2 or 1.99 (considered 2, we + * will compare it with both strings) */ + if (strcmp("2_compat", str_ptr) == 0) { + ssh->flags |= SSH_FLAG_PROTOVERSION_2_COMPAT; + SCLogDebug("will look for ssh protocol version 2 (2, 2.0, 1.99 that's considered as 2"); + return ssh; + } + + ssh->ver = (uint8_t *)SCStrdup((char*)str_ptr); + if (ssh->ver == NULL) { + goto error; + } + ssh->len = strlen((char *) ssh->ver); + + SCLogDebug("will look for ssh %s", ssh->ver); + } + + return ssh; + +error: + if (ssh != NULL) + DetectSshVersionFree(ssh); + return NULL; + +} + +/** + * \brief this function is used to add the parsed "id" option + * \brief into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param idstr pointer to the user provided "id" option + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectSshVersionSetup (DetectEngineCtx *de_ctx, Signature *s, char *str) +{ + DetectSshVersionData *ssh = NULL; + SigMatch *sm = NULL; + + ssh = DetectSshVersionParse(str); + if (ssh == NULL) goto error; + + /* Okay so far so good, lets get this into a SigMatch + * and put it in the Signature. */ + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_AL_SSH_PROTOVERSION; + sm->ctx = (void *)ssh; + + SigMatchAppendAppLayer(s, sm); + + if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_SSH) { + SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords."); + goto error; + } + + s->alproto = ALPROTO_SSH; + return 0; + +error: + if (ssh != NULL) DetectSshVersionFree(ssh); + if (sm != NULL) SCFree(sm); + return -1; + +} + +/** + * \brief this function will free memory associated with DetectSshVersionData + * + * \param id_d pointer to DetectSshVersionData + */ +void DetectSshVersionFree(void *ptr) { + DetectSshVersionData *id_d = (DetectSshVersionData *)ptr; + SCFree(id_d); +} + +#ifdef UNITTESTS /* UNITTESTS */ + +/** + * \test DetectSshVersionTestParse01 is a test to make sure that we parse + * a proto version correctly + */ +int DetectSshVersionTestParse01 (void) { + DetectSshVersionData *ssh = NULL; + ssh = DetectSshVersionParse("1.0"); + if (ssh != NULL && strncmp((char *) ssh->ver, "1.0", 3) == 0) { + DetectSshVersionFree(ssh); + return 1; + } + + return 0; +} + +/** + * \test DetectSshVersionTestParse02 is a test to make sure that we parse + * the proto version (compatible with proto version 2) correctly + */ +int DetectSshVersionTestParse02 (void) { + DetectSshVersionData *ssh = NULL; + ssh = DetectSshVersionParse("2_compat"); + if (ssh->flags & SSH_FLAG_PROTOVERSION_2_COMPAT) { + DetectSshVersionFree(ssh); + return 1; + } + + return 0; +} + +/** + * \test DetectSshVersionTestParse03 is a test to make sure that we + * don't return a ssh_data with an invalid value specified + */ +int DetectSshVersionTestParse03 (void) { + DetectSshVersionData *ssh = NULL; + ssh = DetectSshVersionParse("2_com"); + if (ssh != NULL) { + DetectSshVersionFree(ssh); + return 0; + } + ssh = DetectSshVersionParse(""); + if (ssh != NULL) { + DetectSshVersionFree(ssh); + return 0; + } + ssh = DetectSshVersionParse(".1"); + if (ssh != NULL) { + DetectSshVersionFree(ssh); + return 0; + } + ssh = DetectSshVersionParse("lalala"); + if (ssh != NULL) { + DetectSshVersionFree(ssh); + return 0; + } + + return 1; +} + + +#include "stream-tcp-reassemble.h" + +/** \test Send a get request in three chunks + more data. */ +static int DetectSshVersionTestDetect01(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "SSH-1."; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "10-PuTTY_2.123" ; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = "\n"; + uint32_t sshlen3 = sizeof(sshbuf3) - 1; + uint8_t sshbuf4[] = "whatever..."; + uint32_t sshlen4 = sizeof(sshbuf4) - 1; + TcpSession ssn; + Packet *p = NULL; + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx = NULL; + + memset(&th_v, 0, sizeof(th_v)); + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.protoctx = (void *)&ssn; + p->flow = &f; + p->flowflags |= FLOW_PKT_TOSERVER; + p->flowflags |= FLOW_PKT_ESTABLISHED; + f.alproto = ALPROTO_SSH; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.protoversion:1.10; sid:1;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf4, sshlen4); + if (r != 0) { + printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + /* do detect */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + if ( !(PacketAlertCheck(p, 1))) { + printf("Error, the sig should match: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + + UTHFreePackets(&p, 1); + return result; +} + +/** \test Send a get request in three chunks + more data. */ +static int DetectSshVersionTestDetect02(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "SSH-1."; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "99-PuTTY_2.123" ; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = "\n"; + uint32_t sshlen3 = sizeof(sshbuf3) - 1; + uint8_t sshbuf4[] = "whatever..."; + uint32_t sshlen4 = sizeof(sshbuf4) - 1; + TcpSession ssn; + Packet *p = NULL; + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx = NULL; + + memset(&th_v, 0, sizeof(th_v)); + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.protoctx = (void *)&ssn; + p->flow = &f; + p->flowflags |= FLOW_PKT_TOSERVER; + p->flowflags |= FLOW_PKT_ESTABLISHED; + f.alproto = ALPROTO_SSH; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.protoversion:2_compat; sid:1;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf4, sshlen4); + if (r != 0) { + printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + /* do detect */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + if ( !(PacketAlertCheck(p, 1))) { + printf("Error, the sig should match: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + + UTHFreePackets(&p, 1); + return result; +} + +/** \test Send a get request in three chunks + more data. */ +static int DetectSshVersionTestDetect03(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "SSH-1."; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "7-PuTTY_2.123" ; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = "\n"; + uint32_t sshlen3 = sizeof(sshbuf3) - 1; + uint8_t sshbuf4[] = "whatever..."; + uint32_t sshlen4 = sizeof(sshbuf4) - 1; + TcpSession ssn; + Packet *p = NULL; + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx = NULL; + + memset(&th_v, 0, sizeof(th_v)); + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.protoctx = (void *)&ssn; + p->flow = &f; + p->flowflags |= FLOW_PKT_TOSERVER; + p->flowflags |= FLOW_PKT_ESTABLISHED; + f.alproto = ALPROTO_SSH; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.protoversion:2_compat; sid:1;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf4, sshlen4); + if (r != 0) { + printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + /* do detect */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + if (PacketAlertCheck(p, 1)) { + printf("Error, 1.7 version is not 2 compat, so the sig should not match: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + + UTHFreePackets(&p, 1); + return result; +} + +#endif /* UNITTESTS */ + +/** + * \brief this function registers unit tests for DetectSshVersion + */ +void DetectSshVersionRegisterTests(void) { +#ifdef UNITTESTS /* UNITTESTS */ + UtRegisterTest("DetectSshVersionTestParse01", DetectSshVersionTestParse01, 1); + UtRegisterTest("DetectSshVersionTestParse02", DetectSshVersionTestParse02, 1); + UtRegisterTest("DetectSshVersionTestParse03", DetectSshVersionTestParse03, 1); + UtRegisterTest("DetectSshVersionTestDetect01", DetectSshVersionTestDetect01, 1); + UtRegisterTest("DetectSshVersionTestDetect02", DetectSshVersionTestDetect02, 1); + UtRegisterTest("DetectSshVersionTestDetect03", DetectSshVersionTestDetect03, 1); +#endif /* UNITTESTS */ +} + diff --git a/src/detect-ssh-proto-version.h b/src/detect-ssh-proto-version.h new file mode 100644 index 0000000000..a4c8eddbbe --- /dev/null +++ b/src/detect-ssh-proto-version.h @@ -0,0 +1,40 @@ +/* Copyright (C) 2007-2010 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 Pablo Rincon + */ + +#ifndef __DETECT_SSH_VERSION_H__ +#define __DETECT_SSH_VERSION_H__ + +/** proto version 1.99 is considered proto version 2 */ +#define SSH_FLAG_PROTOVERSION_2_COMPAT 0x01 + +typedef struct DetectSshVersionData_ { + uint8_t *ver; /** ssh version to match */ + uint16_t len; /** ssh version length to match */ + uint8_t flags; +} DetectSshVersionData; + +/* prototypes */ +void DetectSshVersionRegister (void); + +#endif /* __DETECT_SSH_VERSION_H__ */ + diff --git a/src/detect-ssh-software-version.c b/src/detect-ssh-software-version.c new file mode 100644 index 0000000000..78e94e213e --- /dev/null +++ b/src/detect-ssh-software-version.c @@ -0,0 +1,629 @@ +/* Copyright (C) 2007-2010 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 Pablo Rincon + * + * Implements the ssh.softwareversion keyword + * You can match over the software version string of ssh, and it will + * be compared from the beginning of the string so you can say for + * example ssh.softwareversion:"PuTTY" and it can match, or you can + * also specify the version, something like + * ssh.softwareversion:"PuTTY-Release-0.55" + * I find this useful to match over a known vulnerable server/client + * software version incombination to other checks, so you can know + * that the risk is higher + */ + +#include "suricata-common.h" +#include "threads.h" +#include "debug.h" +#include "decode.h" + +#include "detect.h" +#include "detect-parse.h" + +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-engine-state.h" + +#include "flow.h" +#include "flow-var.h" +#include "flow-util.h" + +#include "util-debug.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" + +#include "app-layer.h" + +#include "app-layer-ssh.h" +#include "detect-ssh-software-version.h" + +#include "stream-tcp.h" + +/** + * \brief Regex for parsing the softwareversion string + */ +#define PARSE_REGEX "^\\s*\"?\\s*?([0-9a-zA-Z\\.\\-\\_]+)\\s*\"?\\s*$" + +static pcre *parse_regex; +static pcre_extra *parse_regex_study; + +int DetectSshSoftwareVersionMatch (ThreadVars *, DetectEngineThreadCtx *, Flow *, uint8_t, void *, Signature *, SigMatch *); +static int DetectSshSoftwareVersionSetup (DetectEngineCtx *, Signature *, char *); +void DetectSshSoftwareVersionRegisterTests(void); +void DetectSshSoftwareVersionFree(void *); +void DetectSshSoftwareVersionRegister(void); + +/** + * \brief Registration function for keyword: ssh.softwareversion + */ +void DetectSshSoftwareVersionRegister(void) { + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].name = "ssh.softwareversion"; + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].Match = NULL; + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].AppLayerMatch = DetectSshSoftwareVersionMatch; + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].alproto = ALPROTO_SSH; + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].Setup = DetectSshSoftwareVersionSetup; + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].Free = DetectSshSoftwareVersionFree; + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].RegisterTests = DetectSshSoftwareVersionRegisterTests; + + const char *eb; + int eo; + int opts = 0; + + SCLogDebug("registering ssh.softwareversion rule option"); + + parse_regex = pcre_compile(PARSE_REGEX, opts, &eb, &eo, NULL); + if (parse_regex == NULL) { + SCLogError(SC_ERR_PCRE_COMPILE, "Compile of \"%s\" failed at offset %" PRId32 ": %s", + PARSE_REGEX, eo, eb); + goto error; + } + + parse_regex_study = pcre_study(parse_regex, 0, &eb); + if (eb != NULL) { + SCLogError(SC_ERR_PCRE_STUDY, "pcre study failed: %s", eb); + goto error; + } + return; + +error: + return; +} + +/** + * \brief match the specified version on a ssh session + * + * \param t pointer to thread vars + * \param det_ctx pointer to the pattern matcher thread + * \param p pointer to the current packet + * \param m pointer to the sigmatch that we will cast into DetectSshSoftwareVersionData + * + * \retval 0 no match + * \retval 1 match + */ +int DetectSshSoftwareVersionMatch (ThreadVars *t, DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state, Signature *s, SigMatch *m) +{ + SCEnter(); + + DetectSshSoftwareVersionData *ssh = (DetectSshSoftwareVersionData *)m->ctx; + SshState *ssh_state = (SshState *)state; + if (ssh_state == NULL) { + SCLogDebug("no ssh state, no match"); + SCReturnInt(0); + } + + int ret = 0; + SCMutexLock(&f->m); + if (flags & STREAM_TOCLIENT && ssh_state->flags & SSH_FLAG_SERVER_VERSION_PARSED) { + SCLogDebug("looking for ssh server softwareversion %s length %"PRIu16" on %s", ssh->software_ver, ssh->len, ssh_state->server_software_version); + ret = (strncmp((char *) ssh_state->server_software_version, (char *) ssh->software_ver, ssh->len) == 0)? 1 : 0; + } else if (flags & STREAM_TOSERVER && ssh_state->flags & SSH_FLAG_CLIENT_VERSION_PARSED) { + SCLogDebug("looking for ssh client softwareversion %s length %"PRIu16" on %s", ssh->software_ver, ssh->len, ssh_state->client_software_version); + ret = (strncmp((char *) ssh_state->client_software_version, (char *) ssh->software_ver, ssh->len) == 0)? 1 : 0; + } + SCMutexUnlock(&f->m); + SCReturnInt(ret); +} + +/** + * \brief This function is used to parse IPV4 ip_id passed via keyword: "id" + * + * \param idstr Pointer to the user provided id option + * + * \retval id_d pointer to DetectSshSoftwareVersionData on success + * \retval NULL on failure + */ +DetectSshSoftwareVersionData *DetectSshSoftwareVersionParse (char *str) +{ + DetectSshSoftwareVersionData *ssh = NULL; + #define MAX_SUBSTRINGS 30 + int ret = 0, res = 0; + int ov[MAX_SUBSTRINGS]; + + ret = pcre_exec(parse_regex, parse_regex_study, str, strlen(str), 0, 0, + ov, MAX_SUBSTRINGS); + + if (ret < 1 || ret > 3) { + SCLogError(SC_ERR_PCRE_MATCH, "invalid ssh.softwareversion option"); + goto error; + } + + if (ret > 1) { + const char *str_ptr; + res = pcre_get_substring((char *)str, ov, MAX_SUBSTRINGS, 1, &str_ptr); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed"); + goto error; + } + + /* We have a correct id option */ + ssh = SCMalloc(sizeof(DetectSshSoftwareVersionData)); + if (ssh == NULL) + goto error; + + ssh->software_ver = (uint8_t *)SCStrdup((char*)str_ptr); + if (ssh->software_ver == NULL) { + goto error; + } + ssh->len = strlen((char *) ssh->software_ver); + + SCLogDebug("will look for ssh %s", ssh->software_ver); + } + + return ssh; + +error: + if (ssh != NULL) + DetectSshSoftwareVersionFree(ssh); + return NULL; + +} + +/** + * \brief this function is used to add the parsed "id" option + * \brief into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param idstr pointer to the user provided "id" option + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectSshSoftwareVersionSetup (DetectEngineCtx *de_ctx, Signature *s, char *str) +{ + DetectSshSoftwareVersionData *ssh = NULL; + SigMatch *sm = NULL; + + ssh = DetectSshSoftwareVersionParse(str); + if (ssh == NULL) goto error; + + /* Okay so far so good, lets get this into a SigMatch + * and put it in the Signature. */ + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_AL_SSH_SOFTWAREVERSION; + sm->ctx = (void *)ssh; + + SigMatchAppendAppLayer(s, sm); + + if (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_SSH) { + SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords."); + goto error; + } + + s->alproto = ALPROTO_SSH; + return 0; + +error: + if (ssh != NULL) DetectSshSoftwareVersionFree(ssh); + if (sm != NULL) SCFree(sm); + return -1; + +} + +/** + * \brief this function will free memory associated with DetectSshSoftwareVersionData + * + * \param id_d pointer to DetectSshSoftwareVersionData + */ +void DetectSshSoftwareVersionFree(void *ptr) { + DetectSshSoftwareVersionData *id_d = (DetectSshSoftwareVersionData *)ptr; + SCFree(id_d); +} + +#ifdef UNITTESTS /* UNITTESTS */ + +/** + * \test DetectSshSoftwareVersionTestParse01 is a test to make sure that we parse + * a software version correctly + */ +int DetectSshSoftwareVersionTestParse01 (void) { + DetectSshSoftwareVersionData *ssh = NULL; + ssh = DetectSshSoftwareVersionParse("PuTTY_1.0"); + if (ssh != NULL && strncmp((char *) ssh->software_ver, "PuTTY_1.0", 9) == 0) { + DetectSshSoftwareVersionFree(ssh); + return 1; + } + + return 0; +} + +/** + * \test DetectSshSoftwareVersionTestParse02 is a test to make sure that we parse + * the software version correctly + */ +int DetectSshSoftwareVersionTestParse02 (void) { + DetectSshSoftwareVersionData *ssh = NULL; + ssh = DetectSshSoftwareVersionParse("\"SecureCRT-4.0\""); + if (ssh != NULL && strncmp((char *) ssh->software_ver, "SecureCRT-4.0", 13) == 0) { + DetectSshSoftwareVersionFree(ssh); + return 1; + } + + return 0; +} + +/** + * \test DetectSshSoftwareVersionTestParse03 is a test to make sure that we + * don't return a ssh_data with an empty value specified + */ +int DetectSshSoftwareVersionTestParse03 (void) { + DetectSshSoftwareVersionData *ssh = NULL; + ssh = DetectSshSoftwareVersionParse(""); + if (ssh != NULL) { + DetectSshSoftwareVersionFree(ssh); + return 0; + } + + return 1; +} + + +#include "stream-tcp-reassemble.h" + +/** \test Send a get request in three chunks + more data. */ +static int DetectSshSoftwareVersionTestDetect01(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "SSH-1."; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "10-PuTTY_2.123" ; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = "\n"; + uint32_t sshlen3 = sizeof(sshbuf3) - 1; + uint8_t sshbuf4[] = "whatever..."; + uint32_t sshlen4 = sizeof(sshbuf4) - 1; + TcpSession ssn; + Packet *p = NULL; + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx = NULL; + + memset(&th_v, 0, sizeof(th_v)); + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.protoctx = (void *)&ssn; + p->flow = &f; + p->flowflags |= FLOW_PKT_TOSERVER; + p->flowflags |= FLOW_PKT_ESTABLISHED; + f.alproto = ALPROTO_SSH; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.softwareversion:PuTTY_2.123; sid:1;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf4, sshlen4); + if (r != 0) { + printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + /* do detect */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + if ( !(PacketAlertCheck(p, 1))) { + printf("Error, the sig should match: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + + UTHFreePackets(&p, 1); + return result; +} + +/** \test Send a get request in three chunks + more data. */ +static int DetectSshSoftwareVersionTestDetect02(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "SSH-1.99-Pu"; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "TTY_2.123" ; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = "\n"; + uint32_t sshlen3 = sizeof(sshbuf3) - 1; + uint8_t sshbuf4[] = "whatever..."; + uint32_t sshlen4 = sizeof(sshbuf4) - 1; + TcpSession ssn; + Packet *p = NULL; + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx = NULL; + + memset(&th_v, 0, sizeof(th_v)); + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.protoctx = (void *)&ssn; + p->flow = &f; + p->flowflags |= FLOW_PKT_TOSERVER; + p->flowflags |= FLOW_PKT_ESTABLISHED; + f.alproto = ALPROTO_SSH; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.softwareversion:PuTTY_2.123; sid:1;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf4, sshlen4); + if (r != 0) { + printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + /* do detect */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + if ( !(PacketAlertCheck(p, 1))) { + printf("Error, the sig should match: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + + UTHFreePackets(&p, 1); + return result; +} + +/** \test Send a get request in three chunks + more data. */ +static int DetectSshSoftwareVersionTestDetect03(void) { + int result = 0; + Flow f; + uint8_t sshbuf1[] = "SSH-1."; + uint32_t sshlen1 = sizeof(sshbuf1) - 1; + uint8_t sshbuf2[] = "7-PuTTY_2.123" ; + uint32_t sshlen2 = sizeof(sshbuf2) - 1; + uint8_t sshbuf3[] = "\n"; + uint32_t sshlen3 = sizeof(sshbuf3) - 1; + uint8_t sshbuf4[] = "whatever..."; + uint32_t sshlen4 = sizeof(sshbuf4) - 1; + TcpSession ssn; + Packet *p = NULL; + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx = NULL; + + memset(&th_v, 0, sizeof(th_v)); + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.protoctx = (void *)&ssn; + p->flow = &f; + p->flowflags |= FLOW_PKT_TOSERVER; + p->flowflags |= FLOW_PKT_ESTABLISHED; + f.alproto = ALPROTO_SSH; + + StreamTcpInitConfig(TRUE); + FlowL7DataPtrInit(&f); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.softwareversion:lalala-3.1.4; sid:1;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + int r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf1, sshlen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf2, sshlen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf3, sshlen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + r = AppLayerParse(&f, ALPROTO_SSH, STREAM_TOSERVER, sshbuf4, sshlen4); + if (r != 0) { + printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r); + goto end; + } + + SshState *ssh_state = f.aldata[AlpGetStateIdx(ALPROTO_SSH)]; + if (ssh_state == NULL) { + printf("no ssh state: "); + goto end; + } + + /* do detect */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + if (PacketAlertCheck(p, 1)) { + printf("Error, 1.7 version is not 2 compat, so the sig should not match: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + FlowL7DataPtrFree(&f); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + + UTHFreePackets(&p, 1); + return result; +} + +#endif /* UNITTESTS */ + +/** + * \brief this function registers unit tests for DetectSshSoftwareVersion + */ +void DetectSshSoftwareVersionRegisterTests(void) { +#ifdef UNITTESTS /* UNITTESTS */ + UtRegisterTest("DetectSshSoftwareVersionTestParse01", DetectSshSoftwareVersionTestParse01, 1); + UtRegisterTest("DetectSshSoftwareVersionTestParse02", DetectSshSoftwareVersionTestParse02, 1); + UtRegisterTest("DetectSshSoftwareVersionTestParse03", DetectSshSoftwareVersionTestParse03, 1); + UtRegisterTest("DetectSshSoftwareVersionTestDetect01", DetectSshSoftwareVersionTestDetect01, 1); + UtRegisterTest("DetectSshSoftwareVersionTestDetect02", DetectSshSoftwareVersionTestDetect02, 1); + UtRegisterTest("DetectSshSoftwareVersionTestDetect03", DetectSshSoftwareVersionTestDetect03, 1); +#endif /* UNITTESTS */ +} + diff --git a/src/detect-ssh-software-version.h b/src/detect-ssh-software-version.h new file mode 100644 index 0000000000..70c37c745f --- /dev/null +++ b/src/detect-ssh-software-version.h @@ -0,0 +1,37 @@ +/* Copyright (C) 2007-2010 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 Pablo Rincon + */ + +#ifndef __DETECT_SSH_SOFTWARE_VERSION_H__ +#define __DETECT_SSH_SOFTWARE_VERSION_H__ + +typedef struct DetectSshSoftwareVersionData_ { + uint8_t *software_ver; /** ssh version to match */ + uint16_t len; /** ssh version length to match */ +} DetectSshSoftwareVersionData; + +/* prototypes */ +void DetectSshSoftwareVersionRegister(void); +void DetectSshSoftwareVersionRegisterTests(void); + +#endif /* __DETECT_SSH_SOFTWARE_VERSION_H__ */ + diff --git a/src/detect.c b/src/detect.c index a9b508c98b..5ea725983a 100644 --- a/src/detect.c +++ b/src/detect.c @@ -119,6 +119,8 @@ #include "app-layer-protos.h" #include "app-layer-htp.h" #include "detect-tls-version.h" +#include "detect-ssh-proto-version.h" +#include "detect-ssh-software-version.h" #include "action-globals.h" #include "tm-modules.h" @@ -3356,6 +3358,8 @@ void SigTableSetup(void) { DetectHttpClientBodyRegister(); DetectHttpUriRegister(); DetectAsn1Register(); + DetectSshVersionRegister(); + DetectSshSoftwareVersionRegister(); uint8_t i = 0; for (i = 0; i < DETECT_TBLSIZE; i++) { diff --git a/src/detect.h b/src/detect.h index 1fdb5f3e46..686f2a7e23 100644 --- a/src/detect.h +++ b/src/detect.h @@ -801,6 +801,8 @@ enum { DETECT_AL_HTTP_CLIENT_BODY, DETECT_AL_HTTP_HEADER, DETECT_AL_HTTP_URI, + DETECT_AL_SSH_PROTOVERSION, + DETECT_AL_SSH_SOFTWAREVERSION, DETECT_DCE_IFACE, DETECT_DCE_OPNUM, diff --git a/src/stream-tcp-reassemble.c b/src/stream-tcp-reassemble.c index b9d1e6a759..746ec8dd36 100644 --- a/src/stream-tcp-reassemble.c +++ b/src/stream-tcp-reassemble.c @@ -1527,6 +1527,8 @@ int StreamTcpReassembleHandleSegmentUpdateACK (TcpReassemblyThreadCtx *ra_ctx, if (!(ssn->flags & STREAMTCP_FLAG_TOSERVER_REASSEMBLY_STARTED)) { SCLogDebug("toserver reassembling is not done yet, so " "skipping reassembling at the moment for to_client"); + printf("toserver reassembling is not done yet, so " + "skipping reassembling at the moment for to_client\n"); SCReturnInt(0); } } diff --git a/src/suricata.c b/src/suricata.c index 059ca69ee4..0c651aa32f 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -106,6 +106,7 @@ #include "app-layer-htp.h" #include "app-layer-ftp.h" #include "app-layer-ssl.h" +#include "app-layer-ssh.h" #include "util-radix-tree.h" #include "util-host-os-info.h" @@ -828,6 +829,7 @@ int main(int argc, char **argv) RegisterDCERPCUDPParsers(); RegisterFTPParsers(); RegisterSSLParsers(); + RegisterSSHParsers(); AppLayerParsersInitPostProcess(); #ifdef UNITTESTS @@ -864,6 +866,7 @@ int main(int argc, char **argv) DecodeVLANRegisterTests(); HTPParserRegisterTests(); TLSParserRegisterTests(); + SSHParserRegisterTests(); SMBParserRegisterTests(); DCERPCParserRegisterTests(); DCERPCUDPParserRegisterTests();