From: Pierre Chifflier Date: Mon, 10 Dec 2018 12:48:00 +0000 (+0100) Subject: SNMP: add the "snmp.version" detection keyword X-Git-Tag: suricata-5.0.0-rc1~425 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=aa608e0ca2389e7878b58c0cd213f029849fc305;p=thirdparty%2Fsuricata.git SNMP: add the "snmp.version" detection keyword --- diff --git a/doc/userguide/rules/index.rst b/doc/userguide/rules/index.rst index 391d410f53..c81440ef94 100644 --- a/doc/userguide/rules/index.rst +++ b/doc/userguide/rules/index.rst @@ -22,6 +22,7 @@ Suricata Rules enip-keyword ftp-keywords kerberos-keywords + snmp-keywords app-layer xbits thresholding diff --git a/doc/userguide/rules/snmp-keywords.rst b/doc/userguide/rules/snmp-keywords.rst new file mode 100644 index 0000000000..4b24658030 --- /dev/null +++ b/doc/userguide/rules/snmp-keywords.rst @@ -0,0 +1,22 @@ +SNMP keywords +============= + +snmp.version +------------ + +SNMP protocol version (integer). Expected values are 1, 2 (for version 2c) or 3. + +Syntax:: + + snmp.version:[op] + +The version can be matched exactly, or compared using the _op_ setting:: + + snmp.version:3 # exactly 3 + snmp.version:<3 # smaller than 3 + snmp.version:>=2 # greater or equal than 2 + +Signature example:: + + alert snmp any any -> any any (msg:"old SNMP version (<3)"; snmp.version:<3; sid:1; rev:1;) + diff --git a/rust/src/snmp/detect.rs b/rust/src/snmp/detect.rs new file mode 100644 index 0000000000..da36d584fa --- /dev/null +++ b/rust/src/snmp/detect.rs @@ -0,0 +1,32 @@ +/* Copyright (C) 2017-2019 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. + */ + +// written by Pierre Chifflier + +use libc; +use snmp::snmp::SNMPTransaction; + +#[no_mangle] +pub extern "C" fn rs_snmp_tx_get_version(tx: &mut SNMPTransaction, + version: *mut libc::uint32_t) +{ + debug_assert!(tx.version != 0, "SNMP version is 0"); + unsafe { + *version = tx.version as libc::uint32_t; + } +} + diff --git a/rust/src/snmp/mod.rs b/rust/src/snmp/mod.rs index c3ccbbcb39..3bb90ab2f3 100644 --- a/rust/src/snmp/mod.rs +++ b/rust/src/snmp/mod.rs @@ -21,3 +21,4 @@ extern crate snmp_parser; pub mod snmp; pub mod log; +pub mod detect; diff --git a/rust/src/snmp/snmp.rs b/rust/src/snmp/snmp.rs index b190351742..28508bc6dd 100644 --- a/rust/src/snmp/snmp.rs +++ b/rust/src/snmp/snmp.rs @@ -62,6 +62,9 @@ pub struct SNMPPduInfo { } pub struct SNMPTransaction { + /// PDU version + pub version: u32, + /// PDU info, if present (and cleartext) pub info: Option, @@ -202,7 +205,7 @@ impl SNMPState { fn new_tx(&mut self) -> SNMPTransaction { self.tx_id += 1; - SNMPTransaction::new(self.tx_id) + SNMPTransaction::new(self.version, self.tx_id) } fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&SNMPTransaction> { @@ -254,8 +257,9 @@ impl SNMPState { } impl SNMPTransaction { - pub fn new(id: u64) -> SNMPTransaction { + pub fn new(version: u32, id: u64) -> SNMPTransaction { SNMPTransaction { + version, info: None, community: None, usm: None, diff --git a/src/Makefile.am b/src/Makefile.am index c80b71f229..fc0322f7ac 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -233,6 +233,7 @@ detect-rpc.c detect-rpc.h \ detect-sameip.c detect-sameip.h \ detect-seq.c detect-seq.h \ detect-sid.c detect-sid.h \ +detect-snmp-version.c detect-snmp-version.h \ detect-ssh-proto.c detect-ssh-proto.h \ detect-ssh-proto-version.c detect-ssh-proto-version.h \ detect-ssh-software.c detect-ssh-software.h \ diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index e104114e69..d64f94275c 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -173,6 +173,7 @@ #include "detect-krb5-sname.h" #include "detect-target.h" #include "detect-template-rust-buffer.h" +#include "detect-snmp-version.h" #include "detect-template-buffer.h" #include "detect-bypass.h" #include "detect-ftpdata.h" @@ -526,6 +527,7 @@ void SigTableSetup(void) DetectKrb5SNameRegister(); DetectTargetRegister(); DetectTemplateRustBufferRegister(); + DetectSNMPVersionRegister(); DetectTemplateBufferRegister(); DetectBypassRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index bca55bfbe6..44a698045b 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -228,6 +228,7 @@ enum { DETECT_FTPDATA, DETECT_TARGET, DETECT_AL_TEMPLATE_RUST_BUFFER, + DETECT_AL_SNMP_VERSION, DETECT_AL_TEMPLATE_BUFFER, DETECT_BYPASS, diff --git a/src/detect-snmp-version.c b/src/detect-snmp-version.c new file mode 100644 index 0000000000..6972ca8582 --- /dev/null +++ b/src/detect-snmp-version.c @@ -0,0 +1,364 @@ +/* Copyright (C) 2015-2019 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 Pierre Chifflier + */ + +#include "suricata-common.h" +#include "conf.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-content-inspection.h" +#include "detect-snmp-version.h" +#include "app-layer-parser.h" + +#ifndef HAVE_RUST + +void DetectSNMPVersionRegister(void) +{ +} + +#else + +#include "rust-snmp-snmp-gen.h" +#include "rust-snmp-detect-gen.h" + +/** + * [snmp_version]:[<|>|<=|>=]; + */ +#define PARSE_REGEX "^\\s*(<=|>=|<|>)?\\s*([0-9]+)\\s*$" +static pcre *parse_regex; +static pcre_extra *parse_regex_study; + +enum DetectSNMPVersionMode { + PROCEDURE_EQ = 1, /* equal */ + PROCEDURE_LT, /* less than */ + PROCEDURE_LE, /* less than */ + PROCEDURE_GT, /* greater than */ + PROCEDURE_GE, /* greater than */ +}; + +typedef struct DetectSNMPVersionData_ { + uint32_t version; + enum DetectSNMPVersionMode mode; +} DetectSNMPVersionData; + +static DetectSNMPVersionData *DetectSNMPVersionParse (const char *); +static int DetectSNMPVersionSetup (DetectEngineCtx *, Signature *s, const char *str); +static void DetectSNMPVersionFree(void *); +static void DetectSNMPVersionRegisterTests(void); +static int g_snmp_version_buffer_id = 0; + +static int DetectEngineInspectSNMPRequestGeneric(ThreadVars *tv, + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const Signature *s, const SigMatchData *smd, + Flow *f, uint8_t flags, void *alstate, + void *txv, uint64_t tx_id); + +static int DetectSNMPVersionMatch (ThreadVars *, DetectEngineThreadCtx *, Flow *, + uint8_t, void *, void *, const Signature *, + const SigMatchCtx *); + +/** + * \brief Registration function for snmp_procedure keyword. + */ +void DetectSNMPVersionRegister (void) +{ + sigmatch_table[DETECT_AL_SNMP_VERSION].name = "snmp_version"; + sigmatch_table[DETECT_AL_SNMP_VERSION].desc = "match SNMP version"; + sigmatch_table[DETECT_AL_SNMP_VERSION].url = DOC_URL DOC_VERSION "/rules/snmp-keywords.html#snmp_version"; + sigmatch_table[DETECT_AL_SNMP_VERSION].Match = NULL; + sigmatch_table[DETECT_AL_SNMP_VERSION].AppLayerTxMatch = DetectSNMPVersionMatch; + sigmatch_table[DETECT_AL_SNMP_VERSION].Setup = DetectSNMPVersionSetup; + sigmatch_table[DETECT_AL_SNMP_VERSION].Free = DetectSNMPVersionFree; + sigmatch_table[DETECT_AL_SNMP_VERSION].RegisterTests = DetectSNMPVersionRegisterTests; + + + DetectSetupParseRegexes(PARSE_REGEX, &parse_regex, &parse_regex_study); + + DetectAppLayerInspectEngineRegister("snmp_version", + ALPROTO_SNMP, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectSNMPRequestGeneric); + + DetectAppLayerInspectEngineRegister("snmp_version", + ALPROTO_SNMP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectSNMPRequestGeneric); + + g_snmp_version_buffer_id = DetectBufferTypeGetByName("snmp_version"); + + SCLogDebug("g_snmp_version_buffer_id %d", g_snmp_version_buffer_id); +} + +static int DetectEngineInspectSNMPRequestGeneric(ThreadVars *tv, + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const Signature *s, const SigMatchData *smd, + Flow *f, uint8_t flags, void *alstate, + void *txv, uint64_t tx_id) +{ + return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd, + f, flags, alstate, txv, tx_id); +} + +static inline int +VersionMatch(const uint32_t version, + enum DetectSNMPVersionMode mode, uint32_t ref_version) +{ + switch (mode) { + case PROCEDURE_EQ: + if (version == ref_version) + SCReturnInt(1); + break; + case PROCEDURE_LT: + if (version < ref_version) + SCReturnInt(1); + break; + case PROCEDURE_LE: + if (version <= ref_version) + SCReturnInt(1); + break; + case PROCEDURE_GT: + if (version > ref_version) + SCReturnInt(1); + break; + case PROCEDURE_GE: + if (version >= ref_version) + SCReturnInt(1); + break; + } + SCReturnInt(0); +} + +/** + * \internal + * \brief Function to match version of a TX + * + * \param t Pointer to thread vars. + * \param det_ctx Pointer to the pattern matcher thread. + * \param f Pointer to the current flow. + * \param flags Flags. + * \param state App layer state. + * \param s Pointer to the Signature. + * \param m Pointer to the sigmatch that we will cast into + * DetectSNMPVersionData. + * + * \retval 0 no match. + * \retval 1 match. + */ +static int DetectSNMPVersionMatch (ThreadVars *t, DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, + void *txv, const Signature *s, + const SigMatchCtx *ctx) +{ + SCEnter(); + + const DetectSNMPVersionData *dd = (const DetectSNMPVersionData *)ctx; + uint32_t version; + rs_snmp_tx_get_version(txv, &version); + SCLogDebug("version %u mode %u ref_version %d", + version, dd->mode, dd->version); + if (VersionMatch(version, dd->mode, dd->version)) + SCReturnInt(1); + SCReturnInt(0); +} + +/** + * \internal + * \brief Function to parse options passed via snmp_version keywords. + * + * \param rawstr Pointer to the user provided options. + * + * \retval dd pointer to DetectSNMPVersionData on success. + * \retval NULL on failure. + */ +static DetectSNMPVersionData *DetectSNMPVersionParse (const char *rawstr) +{ + DetectSNMPVersionData *dd = NULL; +#define MAX_SUBSTRINGS 30 + int ret = 0, res = 0; + int ov[MAX_SUBSTRINGS]; + char mode[2] = ""; + char value1[20] = ""; + char *endptr = NULL; + + ret = pcre_exec(parse_regex, parse_regex_study, rawstr, strlen(rawstr), 0, + 0, ov, MAX_SUBSTRINGS); + if (ret < 3 || ret > 5) { + SCLogError(SC_ERR_PCRE_MATCH, "Parse error %s", rawstr); + goto error; + } + + res = pcre_copy_substring((char *)rawstr, ov, MAX_SUBSTRINGS, 1, mode, + sizeof(mode)); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed"); + goto error; + } + + res = pcre_copy_substring((char *)rawstr, ov, MAX_SUBSTRINGS, 2, value1, + sizeof(value1)); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed"); + goto error; + } + + dd = SCCalloc(1, sizeof(DetectSNMPVersionData)); + if (unlikely(dd == NULL)) + goto error; + + if (strlen(mode) == 1) { + if (mode[0] == '<') + dd->mode = PROCEDURE_LT; + else if (mode[0] == '>') + dd->mode = PROCEDURE_GT; + } else if (strlen(mode) == 2) { + if (strcmp(mode, "<=") == 0) + dd->mode = PROCEDURE_LE; + if (strcmp(mode, ">=") == 0) + dd->mode = PROCEDURE_GE; + } + + if (dd->mode == 0) { + dd->mode = PROCEDURE_EQ; + } + + /* set the first value */ + dd->version = strtoul(value1, &endptr, 10); + if (endptr == NULL || *endptr != '\0') { + SCLogError(SC_ERR_INVALID_SIGNATURE, "invalid character as arg " + "to snmp_version keyword"); + goto error; + } + + return dd; + +error: + if (dd) + SCFree(dd); + return NULL; +} + + + +/** + * \brief Function to add the parsed snmp version field into the current signature. + * + * \param de_ctx Pointer to the Detection Engine Context. + * \param s Pointer to the Current Signature. + * \param rawstr Pointer to the user provided flags options. + * \param type Defines if this is notBefore or notAfter. + * + * \retval 0 on Success. + * \retval -1 on Failure. + */ +static int DetectSNMPVersionSetup (DetectEngineCtx *de_ctx, Signature *s, + const char *rawstr) +{ + DetectSNMPVersionData *dd = NULL; + SigMatch *sm = NULL; + + if (DetectSignatureSetAppProto(s, ALPROTO_SNMP) != 0) + return -1; + + dd = DetectSNMPVersionParse(rawstr); + if (dd == NULL) { + SCLogError(SC_ERR_INVALID_ARGUMENT,"Parsing \'%s\' failed", rawstr); + 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_SNMP_VERSION; + sm->ctx = (void *)dd; + + SCLogDebug("snmp_version %d", dd->version); + SigMatchAppendSMToList(s, sm, g_snmp_version_buffer_id); + return 0; + +error: + DetectSNMPVersionFree(dd); + return -1; +} + +/** + * \internal + * \brief Function to free memory associated with DetectSNMPVersionData. + * + * \param de_ptr Pointer to DetectSNMPVersionData. + */ +static void DetectSNMPVersionFree(void *ptr) +{ + SCFree(ptr); +} + + +#ifdef UNITTESTS + +#include "util-unittest.h" +#include "util-unittest-helper.h" + +/** + * \test This is a test for a valid value 2. + * + * \retval 1 on success. + * \retval 0 on failure. + */ +static int SNMPValidityTestParse01 (void) +{ + DetectSNMPVersionData *dd = NULL; + dd = DetectSNMPVersionParse("2"); + FAIL_IF_NULL(dd); + FAIL_IF_NOT(dd->version == 2 && dd->mode == PROCEDURE_EQ); + DetectSNMPVersionFree(dd); + PASS; +} + +/** + * \test This is a test for a valid value >2. + * + * \retval 1 on success. + * \retval 0 on failure. + */ +static int SNMPValidityTestParse02 (void) +{ + DetectSNMPVersionData *dd = NULL; + dd = DetectSNMPVersionParse(">2"); + FAIL_IF_NULL(dd); + FAIL_IF_NOT(dd->version == 2 && dd->mode == PROCEDURE_GT); + DetectSNMPVersionFree(dd); + PASS; +} + + +#endif + +static void DetectSNMPVersionRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("SNMPValidityTestParse01", SNMPValidityTestParse01); + UtRegisterTest("SNMPValidityTestParse02", SNMPValidityTestParse02); +#endif /* UNITTESTS */ +} + +#endif diff --git a/src/detect-snmp-version.h b/src/detect-snmp-version.h new file mode 100644 index 0000000000..281ae51450 --- /dev/null +++ b/src/detect-snmp-version.h @@ -0,0 +1,31 @@ +/* Copyright (C) 2015-2019 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 Pierre Chifflier + */ + +#ifndef __DETECT_SNMP_VERSION_H__ +#define __DETECT_SNMP_VERSION_H__ + +#include "app-layer-snmp.h" + +void DetectSNMPVersionRegister(void); + +#endif /* __DETECT_SNMP_VERSION_H__ */