diff options
Diffstat (limited to 'src/detect-ssh-software-version.c')
-rw-r--r-- | src/detect-ssh-software-version.c | 573 |
1 files changed, 573 insertions, 0 deletions
diff --git a/src/detect-ssh-software-version.c b/src/detect-ssh-software-version.c new file mode 100644 index 0000000..5fec33a --- /dev/null +++ b/src/detect-ssh-software-version.c @@ -0,0 +1,573 @@ +/* Copyright (C) 2007-2020 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 <pablo.rincon.crespo@gmail.com> + * + * 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 in combination to other checks, so you can know + * that the risk is higher + */ + +#include "suricata-common.h" +#include "threads.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 "detect-engine-build.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-parser.h" +#include "app-layer-ssh.h" +#include "detect-ssh-software-version.h" +#include "rust.h" + +#include "stream-tcp.h" + +/** + * \brief Regex for parsing the softwareversion string + */ +#define PARSE_REGEX "^\\s*\"?\\s*?([0-9a-zA-Z\\:\\.\\-\\_\\+\\s+]+)\\s*\"?\\s*$" + +static DetectParseRegex parse_regex; + +static int DetectSshSoftwareVersionMatch (DetectEngineThreadCtx *, + Flow *, uint8_t, void *, void *, + const Signature *, const SigMatchCtx *); +static int DetectSshSoftwareVersionSetup (DetectEngineCtx *, Signature *, const char *); +#ifdef UNITTESTS +static void DetectSshSoftwareVersionRegisterTests(void); +#endif +static void DetectSshSoftwareVersionFree(DetectEngineCtx *de_ctx, void *); +static int g_ssh_banner_list_id = 0; + + +/** + * \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].desc = "match SSH software string"; + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].url = "/rules/ssh-keywords.html#ssh-softwareversion"; + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].AppLayerTxMatch = DetectSshSoftwareVersionMatch; + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].Setup = DetectSshSoftwareVersionSetup; + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].Free = DetectSshSoftwareVersionFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].RegisterTests = DetectSshSoftwareVersionRegisterTests; +#endif + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].flags = SIGMATCH_QUOTES_OPTIONAL|SIGMATCH_INFO_DEPRECATED; + sigmatch_table[DETECT_AL_SSH_SOFTWAREVERSION].alternative = DETECT_AL_SSH_SOFTWARE; + + DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); + + g_ssh_banner_list_id = DetectBufferTypeRegister("ssh_banner"); + + DetectAppLayerInspectEngineRegister2("ssh_banner", ALPROTO_SSH, SIG_FLAG_TOSERVER, + SshStateBannerDone, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister2("ssh_banner", ALPROTO_SSH, SIG_FLAG_TOCLIENT, + SshStateBannerDone, DetectEngineInspectGenericList, NULL); +} + +/** + * \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 + */ +static int DetectSshSoftwareVersionMatch (DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, + const Signature *s, const SigMatchCtx *m) +{ + SCEnter(); + + DetectSshSoftwareVersionData *ssh = (DetectSshSoftwareVersionData *)m; + if (state == NULL) { + SCLogDebug("no ssh state, no match"); + SCReturnInt(0); + } + + int ret = 0; + const uint8_t *software = NULL; + uint32_t b_len = 0; + + if (rs_ssh_tx_get_software(txv, &software, &b_len, flags) != 1) + SCReturnInt(0); + if (software == NULL || b_len == 0) + SCReturnInt(0); + if (b_len == ssh->len) { + if (memcmp(software, ssh->software_ver, ssh->len) == 0) { + ret = 1; + } + } + + SCReturnInt(ret); +} + +/** + * \brief This function is used to parse IPV4 ip_id passed via keyword: "id" + * + * \param de_ctx Pointer to the detection engine context + * \param idstr Pointer to the user provided id option + * + * \retval id_d pointer to DetectSshSoftwareVersionData on success + * \retval NULL on failure + */ +static DetectSshSoftwareVersionData *DetectSshSoftwareVersionParse (DetectEngineCtx *de_ctx, const char *str) +{ + DetectSshSoftwareVersionData *ssh = NULL; + int res = 0; + size_t pcre2_len; + + pcre2_match_data *match = NULL; + int ret = DetectParsePcreExec(&parse_regex, &match, str, 0, 0); + + if (ret < 1 || ret > 3) { + SCLogError("invalid ssh.softwareversion option"); + goto error; + } + + if (ret > 1) { + const char *str_ptr = NULL; + res = pcre2_substring_get_bynumber(match, 1, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len); + if (res < 0) { + SCLogError("pcre2_substring_get_bynumber failed"); + goto error; + } + + /* We have a correct id option */ + ssh = SCMalloc(sizeof(DetectSshSoftwareVersionData)); + if (unlikely(ssh == NULL)) { + pcre2_substring_free((PCRE2_UCHAR *)str_ptr); + goto error; + } + ssh->software_ver = (uint8_t *)SCStrdup((char *)str_ptr); + if (ssh->software_ver == NULL) { + pcre2_substring_free((PCRE2_UCHAR *)str_ptr); + goto error; + } + pcre2_substring_free((PCRE2_UCHAR *)str_ptr); + + ssh->len = (uint16_t)strlen((char *)ssh->software_ver); + + SCLogDebug("will look for ssh %s", ssh->software_ver); + } + + pcre2_match_data_free(match); + return ssh; + +error: + if (match) { + pcre2_match_data_free(match); + } + if (ssh != NULL) + DetectSshSoftwareVersionFree(de_ctx, 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, const char *str) +{ + DetectSshSoftwareVersionData *ssh = NULL; + SigMatch *sm = NULL; + + if (DetectSignatureSetAppProto(s, ALPROTO_SSH) != 0) + return -1; + + ssh = DetectSshSoftwareVersionParse(NULL, 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; + + SigMatchAppendSMToList(s, sm, g_ssh_banner_list_id); + return 0; + +error: + if (ssh != NULL) + DetectSshSoftwareVersionFree(de_ctx, ssh); + if (sm != NULL) + SCFree(sm); + return -1; + +} + +/** + * \brief this function will free memory associated with DetectSshSoftwareVersionData + * + * \param id_d pointer to DetectSshSoftwareVersionData + */ +static void DetectSshSoftwareVersionFree(DetectEngineCtx *de_ctx, void *ptr) +{ + if (ptr == NULL) + return; + + DetectSshSoftwareVersionData *ssh = (DetectSshSoftwareVersionData *)ptr; + if (ssh->software_ver != NULL) + SCFree(ssh->software_ver); + SCFree(ssh); +} + +#ifdef UNITTESTS /* UNITTESTS */ +#include "detect-engine-alert.h" + +/** + * \test DetectSshSoftwareVersionTestParse01 is a test to make sure that we parse + * a software version correctly + */ +static int DetectSshSoftwareVersionTestParse01 (void) +{ + DetectSshSoftwareVersionData *ssh = NULL; + ssh = DetectSshSoftwareVersionParse(NULL, "PuTTY_1.0"); + if (ssh != NULL && strncmp((char *) ssh->software_ver, "PuTTY_1.0", 9) == 0) { + DetectSshSoftwareVersionFree(NULL, ssh); + return 1; + } + + return 0; +} + +/** + * \test DetectSshSoftwareVersionTestParse02 is a test to make sure that we parse + * the software version correctly + */ +static int DetectSshSoftwareVersionTestParse02 (void) +{ + DetectSshSoftwareVersionData *ssh = NULL; + ssh = DetectSshSoftwareVersionParse(NULL, "\"SecureCRT-4.0\""); + if (ssh != NULL && strncmp((char *) ssh->software_ver, "SecureCRT-4.0", 13) == 0) { + DetectSshSoftwareVersionFree(NULL, 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 + */ +static int DetectSshSoftwareVersionTestParse03 (void) +{ + DetectSshSoftwareVersionData *ssh = NULL; + ssh = DetectSshSoftwareVersionParse(NULL, ""); + if (ssh != NULL) { + DetectSshSoftwareVersionFree(NULL, ssh); + return 0; + } + + return 1; +} + + +#include "stream-tcp-reassemble.h" +#include "stream-tcp-util.h" + +/** \test Send a get request in three chunks + more data. */ +static int DetectSshSoftwareVersionTestDetect01(void) +{ + TcpReassemblyThreadCtx *ra_ctx = NULL; + ThreadVars tv; + TcpSession ssn; + Flow *f = NULL; + Packet *p = NULL; + + uint8_t sshbuf1[] = "SSH-1."; + uint8_t sshbuf2[] = "10-PuTTY_2.123" ; + uint8_t sshbuf3[] = "\n"; + uint8_t sshbuf4[] = "whatever..."; + + uint8_t* sshbufs[4] = {sshbuf1, sshbuf2, sshbuf3, sshbuf4}; + uint32_t sshlens[4] = {sizeof(sshbuf1) - 1, sizeof(sshbuf2) - 1, sizeof(sshbuf3) - 1, sizeof(sshbuf4) - 1}; + + memset(&tv, 0x00, sizeof(tv)); + + StreamTcpUTInit(&ra_ctx); + StreamTcpUTInitInline(); + StreamTcpUTSetupSession(&ssn); + StreamTcpUTSetupStream(&ssn.server, 1); + StreamTcpUTSetupStream(&ssn.client, 1); + + f = UTHBuildFlow(AF_INET, "1.1.1.1", "2.2.2.2", 1234, 2222); + FAIL_IF_NULL(f); + f->protoctx = &ssn; + f->proto = IPPROTO_TCP; + f->alproto = ALPROTO_SSH; + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + FAIL_IF(unlikely(p == NULL)); + p->flow = f; + + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx = NULL; + + memset(&th_v, 0, sizeof(th_v)); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + 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;)"); + FAIL_IF_NULL(s); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + uint32_t seq = 2; + for (int i=0; i<4; i++) { + FAIL_IF(StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.server, seq, sshbufs[i], sshlens[i]) == -1); + seq += sshlens[i]; + FAIL_IF(StreamTcpReassembleAppLayer(&tv, ra_ctx, &ssn, &ssn.server, p, UPDATE_DIR_PACKET) < 0); + } + + void *ssh_state = f->alstate; + FAIL_IF_NULL(ssh_state); + + /* do detect */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + FAIL_IF(PacketAlertCheck(p, 1)); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + UTHFreePacket(p); + StreamTcpUTClearSession(&ssn); + StreamTcpUTDeinit(ra_ctx); + UTHFreeFlow(f); + PASS; +} + +/** \test Send a get request in three chunks + more data. */ +static int DetectSshSoftwareVersionTestDetect02(void) +{ + TcpReassemblyThreadCtx *ra_ctx = NULL; + ThreadVars tv; + TcpSession ssn; + Flow *f = NULL; + Packet *p = NULL; + + uint8_t sshbuf1[] = "SSH-1.99-Pu"; + uint8_t sshbuf2[] = "TTY_2.123" ; + uint8_t sshbuf3[] = "\n"; + uint8_t sshbuf4[] = "whatever..."; + + uint8_t* sshbufs[4] = {sshbuf1, sshbuf2, sshbuf3, sshbuf4}; + uint32_t sshlens[4] = {sizeof(sshbuf1) - 1, sizeof(sshbuf2) - 1, sizeof(sshbuf3) - 1, sizeof(sshbuf4) - 1}; + + memset(&tv, 0x00, sizeof(tv)); + + StreamTcpUTInit(&ra_ctx); + StreamTcpUTInitInline(); + StreamTcpUTSetupSession(&ssn); + StreamTcpUTSetupStream(&ssn.server, 1); + StreamTcpUTSetupStream(&ssn.client, 1); + + f = UTHBuildFlow(AF_INET, "1.1.1.1", "2.2.2.2", 1234, 2222); + FAIL_IF_NULL(f); + f->protoctx = &ssn; + f->proto = IPPROTO_TCP; + f->alproto = ALPROTO_SSH; + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + FAIL_IF(unlikely(p == NULL)); + p->flow = f; + + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx = NULL; + + memset(&th_v, 0, sizeof(th_v)); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + 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;)"); + FAIL_IF_NULL(s); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + uint32_t seq = 2; + for (int i=0; i<4; i++) { + FAIL_IF(StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.server, seq, sshbufs[i], sshlens[i]) == -1); + seq += sshlens[i]; + FAIL_IF(StreamTcpReassembleAppLayer(&tv, ra_ctx, &ssn, &ssn.server, p, UPDATE_DIR_PACKET) < 0); + } + + void *ssh_state = f->alstate; + FAIL_IF_NULL(ssh_state); + + /* do detect */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + FAIL_IF(PacketAlertCheck(p, 1)); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + UTHFreePacket(p); + StreamTcpUTClearSession(&ssn); + StreamTcpUTDeinit(ra_ctx); + UTHFreeFlow(f); + PASS; +} + +/** \test Send a get request in three chunks + more data. */ +static int DetectSshSoftwareVersionTestDetect03(void) +{ + TcpReassemblyThreadCtx *ra_ctx = NULL; + ThreadVars tv; + TcpSession ssn; + Flow *f = NULL; + Packet *p = NULL; + + uint8_t sshbuf1[] = "SSH-1."; + uint8_t sshbuf2[] = "7-PuTTY_2.123" ; + uint8_t sshbuf3[] = "\n"; + uint8_t sshbuf4[] = "whatever..."; + + uint8_t* sshbufs[4] = {sshbuf1, sshbuf2, sshbuf3, sshbuf4}; + uint32_t sshlens[4] = {sizeof(sshbuf1) - 1, sizeof(sshbuf2) - 1, sizeof(sshbuf3) - 1, sizeof(sshbuf4) - 1}; + + memset(&tv, 0x00, sizeof(tv)); + + StreamTcpUTInit(&ra_ctx); + StreamTcpUTInitInline(); + StreamTcpUTSetupSession(&ssn); + StreamTcpUTSetupStream(&ssn.server, 1); + StreamTcpUTSetupStream(&ssn.client, 1); + + f = UTHBuildFlow(AF_INET, "1.1.1.1", "2.2.2.2", 1234, 2222); + FAIL_IF_NULL(f); + f->protoctx = &ssn; + f->proto = IPPROTO_TCP; + f->alproto = ALPROTO_SSH; + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + FAIL_IF(unlikely(p == NULL)); + p->flow = f; + + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx = NULL; + + memset(&th_v, 0, sizeof(th_v)); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + 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;)"); + FAIL_IF_NULL(s); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + uint32_t seq = 2; + for (int i=0; i<4; i++) { + FAIL_IF(StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.server, seq, sshbufs[i], sshlens[i]) == -1); + seq += sshlens[i]; + FAIL_IF(StreamTcpReassembleAppLayer(&tv, ra_ctx, &ssn, &ssn.server, p, UPDATE_DIR_PACKET) < 0); + } + + void *ssh_state = f->alstate; + FAIL_IF_NULL(ssh_state); + + /* do detect */ + SigMatchSignatures(&th_v, de_ctx, det_ctx, p); + + FAIL_IF(PacketAlertCheck(p, 1)); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + UTHFreePacket(p); + StreamTcpUTClearSession(&ssn); + StreamTcpUTDeinit(ra_ctx); + UTHFreeFlow(f); + PASS; +} + +/** + * \brief this function registers unit tests for DetectSshSoftwareVersion + */ +static void DetectSshSoftwareVersionRegisterTests(void) +{ + UtRegisterTest("DetectSshSoftwareVersionTestParse01", + DetectSshSoftwareVersionTestParse01); + UtRegisterTest("DetectSshSoftwareVersionTestParse02", + DetectSshSoftwareVersionTestParse02); + UtRegisterTest("DetectSshSoftwareVersionTestParse03", + DetectSshSoftwareVersionTestParse03); + UtRegisterTest("DetectSshSoftwareVersionTestDetect01", + DetectSshSoftwareVersionTestDetect01); + UtRegisterTest("DetectSshSoftwareVersionTestDetect02", + DetectSshSoftwareVersionTestDetect02); + UtRegisterTest("DetectSshSoftwareVersionTestDetect03", + DetectSshSoftwareVersionTestDetect03); +} +#endif /* UNITTESTS */ |