From: Victor Julien Date: Tue, 4 Jan 2022 15:43:24 +0000 (+0100) Subject: telnet: initial support with frames X-Git-Tag: suricata-7.0.0-beta1~1030 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=44c9241b6a9be35f28657d03a61ba881d2b44898;p=thirdparty%2Fsuricata.git telnet: initial support with frames Bootstrapped using setup script. Basic option parsing for purpose of tagging frames. --- diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 296d02a0ca..5008021f0d 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -122,6 +122,7 @@ pub mod dhcp; pub mod sip; pub mod rfb; pub mod mqtt; +pub mod telnet; pub mod applayertemplate; pub mod rdp; pub mod x509; diff --git a/rust/src/telnet/mod.rs b/rust/src/telnet/mod.rs new file mode 100644 index 0000000000..2dfa97a2f1 --- /dev/null +++ b/rust/src/telnet/mod.rs @@ -0,0 +1,19 @@ +/* Copyright (C) 2018 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. + */ + +pub mod telnet; +mod parser; diff --git a/rust/src/telnet/parser.rs b/rust/src/telnet/parser.rs new file mode 100644 index 0000000000..e2dbfa4a6e --- /dev/null +++ b/rust/src/telnet/parser.rs @@ -0,0 +1,77 @@ +/* Copyright (C) 2021 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. + */ + +use crate::common::nom7::take_until_and_consume; +use nom7::combinator::peek; +use nom7::bytes::complete::take; +use nom7::{IResult}; +use nom7::number::streaming::le_u8; +use nom7::bytes::streaming::tag; +use nom7::bytes::streaming::{take_until}; + +pub fn peek_message_is_ctl(i: &[u8]) -> IResult<&[u8], bool> { + let (i, v) = peek(le_u8)(i)?; + Ok((i, v == b'\xff')) +} + +pub enum TelnetMessageType<'a> { + Control(&'a [u8]), + Data(&'a [u8]), +} + +pub fn parse_ctl_suboption<'a>(i: &'a[u8], full: &'a[u8]) -> IResult<&'a[u8], &'a[u8]> { + let (i, _sc) = le_u8(i)?; + let tag: &[u8] = b"\xff\xf0"; + let (i, x) = take_until(tag)(i)?; + let o = &full[..(x.len()+3)]; + Ok((i, o)) +} + +pub fn parse_ctl_message(oi: &[u8]) -> IResult<&[u8], &[u8]> { + let (i, _) = tag(b"\xff")(oi)?; + let (i, cmd) = le_u8(i)?; + let (i, d) = match cmd { + 251..=254 => take(3_usize)(oi)?, + 240..=249 => take(2_usize)(oi)?, + 250 => parse_ctl_suboption(i, oi)?, + _ => take(2_usize)(oi)?, // TODO maybe an error or some other special handling + }; + Ok((i, d)) +} + +pub fn parse_message(i: &[u8]) -> IResult<&[u8], TelnetMessageType> { + let (i, v) = peek(le_u8)(i)?; + if v == b'\xff' { + let (i, c) = parse_ctl_message(i)?; + Ok((i, TelnetMessageType::Control(c))) + } else { + let (i, t) = take_until_and_consume(b"\n")(i)?; + Ok((i, TelnetMessageType::Data(t))) + } +} + +// 'login: ', 'Password: ', possibly with leading ctls +pub fn parse_welcome_message(i: &[u8]) -> IResult<&[u8], TelnetMessageType> { + let (i, v) = peek(le_u8)(i)?; + if v == b'\xff' { + let (i, c) = parse_ctl_message(i)?; + Ok((i, TelnetMessageType::Control(c))) + } else { + let (i, t) = take_until_and_consume(b": ")(i)?; + Ok((i, TelnetMessageType::Data(t))) + } +} diff --git a/rust/src/telnet/telnet.rs b/rust/src/telnet/telnet.rs new file mode 100644 index 0000000000..4fd3542ee7 --- /dev/null +++ b/rust/src/telnet/telnet.rs @@ -0,0 +1,551 @@ +/* Copyright (C) 2021 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. + */ + +use std; +use crate::core::{ALPROTO_UNKNOWN, AppProto, Flow, IPPROTO_TCP}; +use crate::applayer::{self, *}; +use crate::frames::*; +use std::ffi::CString; +use nom; +use super::parser; + +static mut ALPROTO_TELNET: AppProto = ALPROTO_UNKNOWN; + +#[derive(AppLayerEvent)] +enum TelnetEvent {} + +#[derive(AppLayerFrameType)] +pub enum TelnetFrameType { + Pdu, + Ctl, + Data, +} + +pub struct TelnetTransaction { + tx_id: u64, + tx_data: AppLayerTxData, +} + +impl TelnetTransaction { + pub fn new() -> TelnetTransaction { + TelnetTransaction { + tx_id: 0, + tx_data: AppLayerTxData::new(), + } + } +} + +impl Transaction for TelnetTransaction { + fn id(&self) -> u64 { + self.tx_id + } +} + +pub enum TelnetProtocolState { + Idle, + LoginSent, + LoginRecv, + PasswdSent, + PasswdRecv, + AuthOk, + AuthFail, +} + +pub struct TelnetState { + tx_id: u64, + transactions: Vec, + request_gap: bool, + response_gap: bool, + + request_frame: Option, + response_frame: Option, + + /// either control or data frame + request_specific_frame: Option, + /// either control or data frame + response_specific_frame: Option, + state: TelnetProtocolState, +} + +impl State for TelnetState { + fn get_transactions(&self) -> &[TelnetTransaction] { + &self.transactions + } +} + +impl TelnetState { + pub fn new() -> Self { + Self { + tx_id: 0, + transactions: Vec::new(), + request_gap: false, + response_gap: false, + request_frame: None, + request_specific_frame: None, + response_frame: None, + response_specific_frame: None, + state: TelnetProtocolState::Idle, + } + } + + // Free a transaction by ID. + fn free_tx(&mut self, tx_id: u64) { + let len = self.transactions.len(); + let mut found = false; + let mut index = 0; + for i in 0..len { + let tx = &self.transactions[i]; + if tx.tx_id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + self.transactions.remove(index); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&TelnetTransaction> { + for tx in &mut self.transactions { + if tx.tx_id == tx_id + 1 { + return Some(tx); + } + } + return None; + } + + fn _new_tx(&mut self) -> TelnetTransaction { + let mut tx = TelnetTransaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + return tx; + } + + fn _find_request(&mut self) -> Option<&mut TelnetTransaction> { + // TODO + None + } + + fn parse_request(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8]) -> AppLayerResult { + // We're not interested in empty requests. + if input.len() == 0 { + return AppLayerResult::ok(); + } + + // If there was gap, check we can sync up again. + if self.request_gap { + if probe(input).is_err() { + // The parser now needs to decide what to do as we are not in sync. + // For this telnet, we'll just try again next time. + return AppLayerResult::ok(); + } + + // It looks like we're in sync with a message header, clear gap + // state and keep parsing. + self.request_gap = false; + } + + let mut start = input; + while start.len() > 0 { + if self.request_frame.is_none() { + self.request_frame = Frame::new_ts(flow, stream_slice, start, -1 as i64, TelnetFrameType::Pdu as u8); + } + if self.request_specific_frame.is_none() { + if let Ok((_, is_ctl)) = parser::peek_message_is_ctl(start) { + let f = if is_ctl { + Frame::new_ts(flow, stream_slice, start, -1 as i64, TelnetFrameType::Ctl as u8) + } else { + Frame::new_ts(flow, stream_slice, start, -1 as i64, TelnetFrameType::Data as u8) + }; + self.request_specific_frame = f; + } + } + match parser::parse_message(start) { + Ok((rem, request)) => { + let consumed = start.len() - rem.len(); + if rem.len() == start.len() { panic!("lockup"); } + start = rem; + + if let Some(frame) = &self.request_frame { + frame.set_len(flow, 0, consumed as i64); + self.request_frame = None; + } + if let Some(frame) = &self.request_specific_frame { + frame.set_len(flow, 0, consumed as i64); + self.request_specific_frame = None; + } + + if let parser::TelnetMessageType::Data(d) = request { + match self.state { + TelnetProtocolState::LoginSent => { + self.state = TelnetProtocolState::LoginRecv; + }, + TelnetProtocolState::PasswdSent => { + self.state = TelnetProtocolState::PasswdRecv; + }, + TelnetProtocolState::AuthOk => { + let _message = std::str::from_utf8(&d); + if let Ok(_message) = _message { + SCLogDebug!("=> {}", _message); + } + }, + _ => {}, + } + } else if let parser::TelnetMessageType::Control(_c) = request { + SCLogDebug!("request {:?}", _c); + } + }, + Err(nom7::Err::Incomplete(_)) => { + // Not enough data. This parser doesn't give us a good indication + // of how much data is missing so just ask for one more byte so the + // parse is called as soon as more data is received. + let consumed = input.len() - start.len(); + let needed = start.len() + 1; + return AppLayerResult::incomplete(consumed as u32, needed as u32); + }, + Err(_) => { + return AppLayerResult::err(); + }, + } + } + + // Input was fully consumed. + return AppLayerResult::ok(); + } + + fn parse_response(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8]) -> AppLayerResult { + // We're not interested in empty responses. + if input.len() == 0 { + return AppLayerResult::ok(); + } + + if self.response_gap { + if probe(input).is_err() { + // The parser now needs to decide what to do as we are not in sync. + // For this telnet, we'll just try again next time. + return AppLayerResult::ok(); + } + + // It looks like we're in sync with a message header, clear gap + // state and keep parsing. + self.response_gap = false; + } + let mut start = input; + while start.len() > 0 { + if self.response_frame.is_none() { + self.response_frame = Frame::new_tc(flow, stream_slice, start, -1 as i64, TelnetFrameType::Pdu as u8); + } + if self.response_specific_frame.is_none() { + if let Ok((_, is_ctl)) = parser::peek_message_is_ctl(start) { + self.response_specific_frame = if is_ctl { + Frame::new_tc(flow, stream_slice, start, -1 as i64, TelnetFrameType::Ctl as u8) + } else { + Frame::new_tc(flow, stream_slice, start, -1 as i64, TelnetFrameType::Data as u8) + }; + } + } + + let r = match self.state { + TelnetProtocolState::Idle => parser::parse_welcome_message(start), + TelnetProtocolState::AuthFail => parser::parse_welcome_message(start), + TelnetProtocolState::LoginRecv => parser::parse_welcome_message(start), + _ => parser::parse_message(start), + }; + match r { + Ok((rem, response)) => { + let consumed = start.len() - rem.len(); + start = rem; + + if let Some(frame) = &self.response_frame { + frame.set_len(flow, 1, consumed as i64); + self.response_frame = None; + } + if let Some(frame) = &self.response_specific_frame { + frame.set_len(flow, 1, consumed as i64); + self.response_specific_frame = None; + } + + if let parser::TelnetMessageType::Data(d) = response { + match self.state { + TelnetProtocolState::Idle | + TelnetProtocolState::AuthFail => { + self.state = TelnetProtocolState::LoginSent; + }, + TelnetProtocolState::LoginRecv => { + self.state = TelnetProtocolState::PasswdSent; + }, + TelnetProtocolState::PasswdRecv => { + if let Ok(message) = std::str::from_utf8(&d) { + match message { + "Login incorrect" => { + SCLogDebug!("LOGIN FAILED"); + self.state = TelnetProtocolState::AuthFail; + }, + "" => { + + }, + &_ => { + SCLogDebug!("LOGIN OK"); + self.state = TelnetProtocolState::AuthOk; + }, + } + } + }, + TelnetProtocolState::AuthOk => { + let _message = std::str::from_utf8(&d); + if let Ok(_message) = _message { + SCLogDebug!("<= {}", _message); + } + }, + _ => {}, + } + } else if let parser::TelnetMessageType::Control(_c) = response { + SCLogDebug!("response {:?}", _c); + } + } + Err(nom7::Err::Incomplete(_)) => { + let consumed = input.len() - start.len(); + let needed = start.len() + 1; + return AppLayerResult::incomplete(consumed as u32, needed as u32); + } + Err(_e) => { + SCLogDebug!("error! {}", _e); + return AppLayerResult::err(); + } + } + } + + // All input was fully consumed. + return AppLayerResult::ok(); + } + + fn on_request_gap(&mut self, _size: u32) { + self.request_gap = true; + } + + fn on_response_gap(&mut self, _size: u32) { + self.response_gap = true; + } +} + +/// Probe for a valid header. +/// +fn probe(input: &[u8]) -> nom::IResult<&[u8], ()> { + // TODO see if we can implement something here. Ctl message is easy, + // and 'login: ' is common, but we can have random text and possibly + // other output as well. So for now data on port 23 is it. + Ok((input, ())) +} + +// C exports. + +/// C entry point for a probing parser. +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_probing_parser( + _flow: *const Flow, + _direction: u8, + input: *const u8, + input_len: u32, + _rdir: *mut u8 +) -> AppProto { + // Need at least 2 bytes. + if input_len > 1 && !input.is_null() { + let slice = build_slice!(input, input_len as usize); + if probe(slice).is_ok() { + SCLogDebug!("telnet detected"); + return ALPROTO_TELNET; + } + } + return ALPROTO_UNKNOWN; +} + +#[no_mangle] +pub extern "C" fn rs_telnet_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = TelnetState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut std::os::raw::c_void; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(Box::from_raw(state as *mut TelnetState)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_state_tx_free( + state: *mut std::os::raw::c_void, + tx_id: u64, +) { + let state = cast_pointer!(state, TelnetState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_parse_request( + flow: *const Flow, + state: *mut std::os::raw::c_void, + pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void +) -> AppLayerResult { + let eof = if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0 { + true + } else { + false + }; + + if eof { + // If needed, handle EOF, or pass it into the parser. + return AppLayerResult::ok(); + } + + let state = cast_pointer!(state, TelnetState); + + if stream_slice.is_gap() { + // Here we have a gap signaled by the input being null, but a greater + // than 0 input_len which provides the size of the gap. + state.on_request_gap(stream_slice.gap_size()); + AppLayerResult::ok() + } else { + let buf = stream_slice.as_slice(); + state.parse_request(flow, &stream_slice, buf) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_parse_response( + flow: *const Flow, + state: *mut std::os::raw::c_void, + pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void +) -> AppLayerResult { + let _eof = if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0 { + true + } else { + false + }; + let state = cast_pointer!(state, TelnetState); + + if stream_slice.is_gap() { + // Here we have a gap signaled by the input being null, but a greater + // than 0 input_len which provides the size of the gap. + state.on_response_gap(stream_slice.gap_size()); + AppLayerResult::ok() + } else { + let buf = stream_slice.as_slice(); + state.parse_response(flow, &stream_slice, buf) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_state_get_tx( + state: *mut std::os::raw::c_void, + tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, TelnetState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_state_get_tx_count( + state: *mut std::os::raw::c_void, +) -> u64 { + let state = cast_pointer!(state, TelnetState); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, + _direction: u8, +) -> std::os::raw::c_int { + let _tx = cast_pointer!(tx, TelnetTransaction); + // TODO + return 0; +} + +export_tx_data_get!(rs_telnet_get_tx_data, TelnetTransaction); + +// Parser name as a C style string. +const PARSER_NAME: &'static [u8] = b"telnet\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_register_parser() { + let default_port = CString::new("[23]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_TCP, + probe_ts: Some(rs_telnet_probing_parser), + probe_tc: Some(rs_telnet_probing_parser), + min_depth: 0, + max_depth: 16, + state_new: rs_telnet_state_new, + state_free: rs_telnet_state_free, + tx_free: rs_telnet_state_tx_free, + parse_ts: rs_telnet_parse_request, + parse_tc: rs_telnet_parse_response, + get_tx_count: rs_telnet_state_get_tx_count, + get_tx: rs_telnet_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_telnet_tx_get_alstate_progress, + get_eventinfo: Some(TelnetEvent::get_event_info), + get_eventinfo_byid : Some(TelnetEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_files: None, + get_tx_iterator: Some(applayer::state_get_tx_iterator::), + get_tx_data: rs_telnet_get_tx_data, + apply_tx_config: None, + flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, + truncate: None, + get_frame_id_by_name: Some(TelnetFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(TelnetFrameType::ffi_name_from_id), + + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_TELNET = alproto; + if AppLayerParserConfParserEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust telnet parser registered."); + } else { + SCLogDebug!("Protocol detector and parser disabled for TELNET."); + } +} diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index 35f9a0e73d..e422297e0f 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -906,6 +906,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_RFB\n"); else if (pp_pe->alproto == ALPROTO_MQTT) printf(" alproto: ALPROTO_MQTT\n"); + else if (pp_pe->alproto == ALPROTO_TELNET) + printf(" alproto: ALPROTO_TELNET\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE) printf(" alproto: ALPROTO_TEMPLATE\n"); else if (pp_pe->alproto == ALPROTO_DNP3) @@ -983,6 +985,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_RFB\n"); else if (pp_pe->alproto == ALPROTO_MQTT) printf(" alproto: ALPROTO_MQTT\n"); + else if (pp_pe->alproto == ALPROTO_TELNET) + printf(" alproto: ALPROTO_TELNET\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE) printf(" alproto: ALPROTO_TEMPLATE\n"); else if (pp_pe->alproto == ALPROTO_DNP3) diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index e4fb82d2e1..a2f643e445 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -1663,6 +1663,7 @@ void AppLayerParserRegisterProtocolParsers(void) RegisterTemplateParsers(); RegisterRdpParsers(); RegisterHTTP2Parsers(); + rs_telnet_register_parser(); /** IMAP */ AppLayerProtoDetectRegisterProtocol(ALPROTO_IMAP, "imap"); diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index 5bc0bbc51a..b75a8a5616 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -108,6 +108,9 @@ const char *AppProtoToString(AppProto alproto) case ALPROTO_MQTT: proto_name = "mqtt"; break; + case ALPROTO_TELNET: + proto_name = "telnet"; + break; case ALPROTO_TEMPLATE: proto_name = "template"; break; @@ -172,6 +175,8 @@ AppProto StringToAppProto(const char *proto_name) if (strcmp(proto_name,"sip")==0) return ALPROTO_SIP; if (strcmp(proto_name,"rfb")==0) return ALPROTO_RFB; if (strcmp(proto_name,"mqtt")==0) return ALPROTO_MQTT; + if (strcmp(proto_name, "telnet") == 0) + return ALPROTO_TELNET; if (strcmp(proto_name,"template")==0) return ALPROTO_TEMPLATE; if (strcmp(proto_name,"template-rust")==0) return ALPROTO_TEMPLATE_RUST; if (strcmp(proto_name,"rdp")==0) return ALPROTO_RDP; diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index b84698a1ba..c31d5c6249 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -53,6 +53,7 @@ enum AppProtoEnum { ALPROTO_SIP, ALPROTO_RFB, ALPROTO_MQTT, + ALPROTO_TELNET, ALPROTO_TEMPLATE, ALPROTO_TEMPLATE_RUST, ALPROTO_RDP, diff --git a/suricata.yaml.in b/suricata.yaml.in index 0c49273675..1ed10d0b92 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -773,6 +773,8 @@ pcap-file: # "detection-only" enables protocol detection only (parser disabled). app-layer: protocols: + telnet: + enabled: yes rfb: enabled: yes detection-ports: