From 263d56fd11e58cebca535a6ad1168378db1df181 Mon Sep 17 00:00:00 2001 From: Jeff Lucovsky Date: Thu, 17 Apr 2025 08:30:50 -0400 Subject: [PATCH] detect/ftp: Add ftp.received_reply Issue: 7506 Add a (non-sticky buffer) keyword for ftp.reply_received. This is not a sticky buffer as the keyword relates to protocol state and not bytes from the actual protocol exchange. ftp.reply_received: yes|on|no|off --- rust/src/ftp/ftp.rs | 34 ++++++++ src/Makefile.am | 2 + src/detect-engine-register.c | 2 + src/detect-engine-register.h | 1 + src/detect-ftp-reply-received.c | 133 ++++++++++++++++++++++++++++++++ src/detect-ftp-reply-received.h | 29 +++++++ 6 files changed, 201 insertions(+) create mode 100644 src/detect-ftp-reply-received.c create mode 100644 src/detect-ftp-reply-received.h diff --git a/rust/src/ftp/ftp.rs b/rust/src/ftp/ftp.rs index e739aabeb0..1efa85bbae 100644 --- a/rust/src/ftp/ftp.rs +++ b/rust/src/ftp/ftp.rs @@ -30,6 +30,11 @@ pub struct DetectFtpModeData { pub active: bool, } +#[repr(C)] +pub struct DetectFtpReplyReceivedData { + pub received: bool, +} + /// cbindgen:ignore #[repr(C)] pub struct FtpCommand { @@ -224,6 +229,35 @@ pub unsafe extern "C" fn SCFTPGetConfigValues( } } +#[no_mangle] +pub unsafe extern "C" fn SCFTPParseReplyReceived(c_str: *const c_char) -> *mut DetectFtpReplyReceivedData { + if c_str.is_null() { + return ptr::null_mut(); + } + + // Convert C string to Rust string slice + let Ok(input_str) = CStr::from_ptr(c_str).to_str() else { + return ptr::null_mut(); + }; + + // Check for case-insensitive match + let received_val = match input_str.trim().to_ascii_lowercase().as_str() { + "true" | "1" | "yes" | "on" => true, + "false" | "0" | "no" | "off"=> false, + _ => return ptr::null_mut(), // invalid input + }; + + // Return a pointer to a heap-allocated struct + let boxed = Box::new(DetectFtpReplyReceivedData { received: received_val }); + Box::into_raw(boxed) +} + +#[no_mangle] +pub unsafe extern "C" fn SCFTPFreeReplyReceivedData(ptr: *mut DetectFtpReplyReceivedData) { + if !ptr.is_null() { + drop(Box::from_raw(ptr)); + } +} #[no_mangle] pub unsafe extern "C" fn SCFTPParseMode(c_str: *const c_char) -> *mut DetectFtpModeData { if c_str.is_null() { diff --git a/src/Makefile.am b/src/Makefile.am index d5c302bd3c..f502190573 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -181,6 +181,7 @@ noinst_HEADERS = \ detect-ftpbounce.h \ detect-ftpdata.h \ detect-ftp-mode.h \ + detect-ftp-reply-received.h \ detect-geoip.h \ detect-gid.h \ detect-hostbits.h \ @@ -771,6 +772,7 @@ libsuricata_c_a_SOURCES = \ detect-ftpbounce.c \ detect-ftpdata.c \ detect-ftp-mode.c \ + detect-ftp-reply-received.c \ detect-geoip.c \ detect-gid.c \ detect-hostbits.c \ diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index b60555d590..c8a096308f 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -214,6 +214,7 @@ #include "detect-ftp-command-data.h" #include "detect-ftp-reply.h" #include "detect-ftp-mode.h" +#include "detect-ftp-reply-received.h" #include "detect-bypass.h" #include "detect-ftpdata.h" @@ -728,6 +729,7 @@ void SigTableSetup(void) DetectFtpCommandDataRegister(); DetectFtpReplyRegister(); DetectFtpModeRegister(); + DetectFtpReplyReceivedRegister(); DetectBypassRegister(); DetectConfigRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 5f9053e777..11f033635b 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -332,6 +332,7 @@ enum DetectKeywordId { DETECT_FTP_COMMAND_DATA, DETECT_FTP_REPLY, DETECT_FTP_MODE, + DETECT_FTP_REPLY_RECEIVED, DETECT_VLAN_ID, DETECT_VLAN_LAYERS, diff --git a/src/detect-ftp-reply-received.c b/src/detect-ftp-reply-received.c new file mode 100644 index 0000000000..8a51730a8a --- /dev/null +++ b/src/detect-ftp-reply-received.c @@ -0,0 +1,133 @@ +/* Copyright (C) 2025 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 Jeff Lucovsky + * + * Match on FTP reply received. + */ + +#include "suricata-common.h" + +#include "detect-parse.h" +#include "detect-engine.h" + +#include "app-layer-ftp.h" + +#include "detect-ftp-reply-received.h" + +static void DetectFtpReplyReceivedFree(DetectEngineCtx *, void *); +static int g_ftp_reply_received_buffer_id = 0; + +static int DetectFtpReplyReceivedMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *m) +{ + FTPTransaction *tx = (FTPTransaction *)txv; + if (tx->command_descriptor.command_code == FTP_COMMAND_UNKNOWN) { + return 0; + } + + const DetectFtpReplyReceivedData *ftprrd = (const DetectFtpReplyReceivedData *)m; + if (ftprrd->received == tx->done) + return 1; + + return 0; +} + +/** + * \brief This function is used to parse ftp.reply_received options passed via ftp.reply_received + * keyword + * + * \param str Pointer to the user provided ftp.reply_received options + * + * \retval pointer to DetectFtpReplyReceivedData on success + * \retval NULL on failure + */ +static DetectFtpReplyReceivedData *DetectFtpdataParse(const char *optstr) +{ + DetectFtpReplyReceivedData *frrd = SCFTPParseReplyReceived(optstr); + if (unlikely(frrd == NULL)) { + SCLogError("invalid value; specify yes or no"); + return NULL; + } + + return frrd; +} + +/** + * \brief parse the options from the 'ftp.reply_received' keyword in the rule into + * the Signature data structure. + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param str pointer to the user provided ftp.reply_received options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectFtpReplyReceivedSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + DetectFtpReplyReceivedData *frrd = DetectFtpdataParse(str); + if (frrd == NULL) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_FTP) != 0) { + DetectFtpReplyReceivedFree(de_ctx, frrd); + return -1; + } + + if (SigMatchAppendSMToList(de_ctx, s, DETECT_FTP_REPLY_RECEIVED, (SigMatchCtx *)frrd, + g_ftp_reply_received_buffer_id) == NULL) { + DetectFtpReplyReceivedFree(de_ctx, frrd); + return -1; + } + return 0; +} + +/** + * \brief this function will free memory associated with DetectFtpReplyReceivedData + * + * \param ptr pointer to DetectFtpReplyReceivedData + */ +static void DetectFtpReplyReceivedFree(DetectEngineCtx *de_ctx, void *ptr) +{ + if (ptr) { + DetectFtpReplyReceivedData *frrd = (DetectFtpReplyReceivedData *)ptr; + SCFTPFreeReplyReceivedData(frrd); + } +} + +/** + * \brief Registration function for ftp.reply_received: keyword + * + * This function is called once in the 'lifetime' of the engine. + */ +void DetectFtpReplyReceivedRegister(void) +{ + sigmatch_table[DETECT_FTP_REPLY_RECEIVED].name = "ftp.reply_received"; + sigmatch_table[DETECT_FTP_REPLY_RECEIVED].desc = "match on FTP whether a reply was received"; + sigmatch_table[DETECT_FTP_REPLY_RECEIVED].url = "/rules/ftp-keywords.html#ftp.reply_received"; + sigmatch_table[DETECT_FTP_REPLY_RECEIVED].AppLayerTxMatch = DetectFtpReplyReceivedMatch; + sigmatch_table[DETECT_FTP_REPLY_RECEIVED].Setup = DetectFtpReplyReceivedSetup; + sigmatch_table[DETECT_FTP_REPLY_RECEIVED].Free = DetectFtpReplyReceivedFree; + + DetectAppLayerInspectEngineRegister("ftp.reply_received", ALPROTO_FTP, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectGenericList, NULL); + g_ftp_reply_received_buffer_id = DetectBufferTypeGetByName("ftp.reply_received"); +} diff --git a/src/detect-ftp-reply-received.h b/src/detect-ftp-reply-received.h new file mode 100644 index 0000000000..40cc6874ea --- /dev/null +++ b/src/detect-ftp-reply-received.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2025 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 Jeff Lucovsky + */ + +#ifndef SURICATA_DETECT_FTP_REPLY_RECEIVED_H +#define SURICATA_DETECT_FTP_REPLY_RECEIVED_H + +void DetectFtpReplyReceivedRegister(void); + +#endif /* SURICATA_DETECT_FTP_REPLY_RECEIVED_H */ -- 2.47.2