From: Steve Chew (stechew) Date: Wed, 12 Aug 2020 01:44:45 +0000 (+0000) Subject: Merge pull request #2386 in SNORT/snort3 from ~SBAIGAL/snort3:ftps to master X-Git-Tag: 3.0.2-5~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=57b6292ed3b3ae75e29db56e75fd3a79c5d99d4b;p=thirdparty%2Fsnort3.git Merge pull request #2386 in SNORT/snort3 from ~SBAIGAL/snort3:ftps to master Squashed commit of the following: commit 24e1fa41a6b82bc793fe90015a160ac6842876a7 Author: Steven Baigal (sbaigal) Date: Thu Jul 23 15:38:58 2020 -0400 ftp: add opportunistic TLS support --- diff --git a/src/service_inspectors/ftp_telnet/ftp_data.cc b/src/service_inspectors/ftp_telnet/ftp_data.cc index 95627ec21..6543ac91d 100644 --- a/src/service_inspectors/ftp_telnet/ftp_data.cc +++ b/src/service_inspectors/ftp_telnet/ftp_data.cc @@ -30,6 +30,7 @@ #include "packet_tracer/packet_tracer.h" #include "parser/parse_rule.h" #include "profiler/profiler.h" +#include "pub_sub/opportunistic_tls_event.h" #include "stream/stream.h" #include "utils/util.h" @@ -222,7 +223,17 @@ FtpDataFlowData::~FtpDataFlowData() void FtpDataFlowData::handle_expected(Packet* p) { if (!p->flow->service) + { p->flow->set_service(p, fd_svc_name); + + FtpDataFlowData* fd = + (FtpDataFlowData*)p->flow->get_flow_data(FtpDataFlowData::inspector_id); + if (fd and fd->in_tls) + { + OpportunisticTlsEvent evt(p, fd_svc_name); + DataBus::publish(OPPORTUNISTIC_TLS_EVENT, evt, p->flow); + } + } } void FtpDataFlowData::handle_eof(Packet* p) diff --git a/src/service_inspectors/ftp_telnet/ftp_module.cc b/src/service_inspectors/ftp_telnet/ftp_module.cc index 042f3b54b..212a28fe1 100644 --- a/src/service_inspectors/ftp_telnet/ftp_module.cc +++ b/src/service_inspectors/ftp_telnet/ftp_module.cc @@ -344,6 +344,9 @@ static const PegInfo ftp_pegs[] = { CountType::SUM, "total_bytes", "total number of bytes processed" }, { CountType::NOW, "concurrent_sessions", "total concurrent FTP sessions" }, { CountType::MAX, "max_concurrent_sessions", "maximum concurrent FTP sessions" }, + { CountType::SUM, "start_tls", "total STARTTLS events generated" }, + { CountType::SUM, "ssl_search_abandoned", "total SSL search abandoned" }, + { CountType::SUM, "ssl_srch_abandoned_early", "total SSL search abandoned too soon" }, { CountType::END, nullptr, nullptr } }; @@ -569,6 +572,9 @@ static int ProcessFTPDataChanCmdsList( if ( fc->flags & CMD_ENCR ) FTPCmd->encr_cmd = true; + if ( fc->flags & CMD_PROT ) + FTPCmd->prot_cmd = true; + if ( fc->flags & CMD_LOGIN ) FTPCmd->login_cmd = true; @@ -599,6 +605,7 @@ bool FtpServerModule::end(const char* fqn, int idx, SnortConfig*) if ( !idx && !strcmp(fqn, "ftp_server") ) { + cmds.emplace_back(new FtpCmd("PROT", CMD_PROT, 0)); for( auto cmd : cmds) { if ( FTPP_SUCCESS != ProcessFTPDataChanCmdsList(conf, cmd) ) diff --git a/src/service_inspectors/ftp_telnet/ftp_module.h b/src/service_inspectors/ftp_telnet/ftp_module.h index 06597c950..215a7ba70 100644 --- a/src/service_inspectors/ftp_telnet/ftp_module.h +++ b/src/service_inspectors/ftp_telnet/ftp_module.h @@ -95,6 +95,7 @@ private: #define CMD_DIR 0x0100 #define CMD_VALID 0x0200 #define CMD_REST 0x0400 +#define CMD_PROT 0x0800 struct FtpCmd { diff --git a/src/service_inspectors/ftp_telnet/ftpp_si.h b/src/service_inspectors/ftp_telnet/ftpp_si.h index a5d30f0e7..0feeebaa7 100644 --- a/src/service_inspectors/ftp_telnet/ftpp_si.h +++ b/src/service_inspectors/ftp_telnet/ftpp_si.h @@ -67,7 +67,10 @@ #define PROTO_IS_FTP_DATA(ssn) FTPP_SI_IS_PROTO(ssn, FTPP_SI_PROTO_FTP_DATA) #define PROTO_IS_TELNET(ssn) FTPP_SI_IS_PROTO(ssn, FTPP_SI_PROTO_TELNET) -#define FTP_FLG_MALWARE (1<<0) +#define FTP_FLG_MALWARE 0x01 +#define FTP_FLG_SEARCH_ABANDONED 0x02 +#define FTP_PROTP_CMD_ISSUED 0x04 +#define FTP_PROTP_CMD_ACCEPT 0x08 typedef struct s_FTP_TELNET_SESSION { @@ -237,6 +240,7 @@ public: static unsigned inspector_id; FTP_DATA_SESSION session; bool eof_handled = false; + bool in_tls = false; }; #define FTPDATA_FLG_REASSEMBLY_SET (1<<0) @@ -286,6 +290,9 @@ struct FtpStats PegCount total_bytes; PegCount concurrent_sessions; PegCount max_concurrent_sessions; + PegCount starttls; + PegCount ssl_search_abandoned; + PegCount ssl_search_abandoned_too_soon; }; struct TelnetStats diff --git a/src/service_inspectors/ftp_telnet/ftpp_ui_config.h b/src/service_inspectors/ftp_telnet/ftpp_ui_config.h index 6474a62d1..a7a5abfd8 100644 --- a/src/service_inspectors/ftp_telnet/ftpp_ui_config.h +++ b/src/service_inspectors/ftp_telnet/ftpp_ui_config.h @@ -161,6 +161,7 @@ typedef struct s_FTP_CMD_CONF bool file_get_cmd; bool encr_cmd; bool login_cmd; + bool prot_cmd; int dir_response; FTP_PARAM_FMT* param_format; diff --git a/src/service_inspectors/ftp_telnet/pp_ftp.cc b/src/service_inspectors/ftp_telnet/pp_ftp.cc index 0b8b79702..a96300af8 100644 --- a/src/service_inspectors/ftp_telnet/pp_ftp.cc +++ b/src/service_inspectors/ftp_telnet/pp_ftp.cc @@ -45,6 +45,7 @@ #include "hash/hash_key_operations.h" #include "file_api/file_service.h" #include "protocols/packet.h" +#include "pub_sub/opportunistic_tls_event.h" #include "stream/stream.h" #include "utils/util.h" @@ -1021,6 +1022,15 @@ static int do_stateful_checks(FTP_SESSION* session, Packet* p, * If we saw all the other dat for this channel * session->data_chan_state should be NO_STATE. */ } + else if (session->flags & FTP_PROTP_CMD_ISSUED) + { + if (rsp_code == 200) + { + session->flags &= ~FTP_PROTP_CMD_ISSUED; + session->flags |= FTP_PROTP_CMD_ACCEPT; + } + + } else if (session->data_chan_state & DATA_CHAN_PASV_CMD_ISSUED) { if (ftp_cmd_pipe_index == session->data_chan_index) @@ -1093,6 +1103,10 @@ static int do_stateful_checks(FTP_SESSION* session, Packet* p, if (session->flags & FTP_FLG_MALWARE) session->datassn = ftpdata; + if (p->flow->flags.data_decrypted and + (session->flags & FTP_PROTP_CMD_ACCEPT)) + fd->in_tls = true; + /* Call into Streams to mark data channel as ftp-data */ result = Stream::set_snort_protocol_id_expected( p, PktType::TCP, IpProtocol::TCP, @@ -1171,6 +1185,10 @@ static int do_stateful_checks(FTP_SESSION* session, Packet* p, if (session->flags & FTP_FLG_MALWARE) session->datassn = ftpdata; + if (p->flow->flags.data_decrypted and + (session->flags & FTP_PROTP_CMD_ACCEPT)) + fd->in_tls = true; + /* Call into Streams to mark data channel as ftp-data */ result = Stream::set_snort_protocol_id_expected( p, PktType::TCP, IpProtocol::TCP, @@ -1248,6 +1266,16 @@ static int do_stateful_checks(FTP_SESSION* session, Packet* p, } } /* if (session->server_conf->data_chan) */ + if ((session->encr_state == AUTH_TLS_CMD_ISSUED or session->encr_state == AUTH_SSL_CMD_ISSUED) + and rsp_code == 234) + { + OpportunisticTlsEvent event(p, p->flow->service); + DataBus::publish(OPPORTUNISTIC_TLS_EVENT, event, p->flow); + ++ftstats.starttls; + if (session->flags & FTP_FLG_SEARCH_ABANDONED) + ++ftstats.ssl_search_abandoned_too_soon; + } + if (session->server_conf->detect_encrypted) { switch (session->encr_state) @@ -1795,6 +1823,23 @@ int check_ftp(FTP_SESSION* ftpssn, Packet* p, int iMode) ftpssn->encr_state = AUTH_UNKNOWN_CMD_ISSUED; } } + else if (CmdConf->prot_cmd) + { + if (req->param_begin && (req->param_size > 0) && + ((req->param_begin[0] == 'P') || (req->param_begin[0] == 'p'))) + { + ftpssn->flags &= ~FTP_PROTP_CMD_ACCEPT; + ftpssn->flags |= FTP_PROTP_CMD_ISSUED; + } + } + else if (CmdConf->login_cmd and !p->flow->flags.data_decrypted and + !(ftpssn->flags & FTP_FLG_SEARCH_ABANDONED)) + { + ftpssn->flags |= FTP_FLG_SEARCH_ABANDONED; + DataBus::publish(SSL_SEARCH_ABANDONED, p); + ++ftstats.ssl_search_abandoned; + } + if (CmdConf->check_validity) { const char* next_param = nullptr; diff --git a/src/service_inspectors/smtp/smtp.cc b/src/service_inspectors/smtp/smtp.cc index f8ed70462..8382f2a6f 100644 --- a/src/service_inspectors/smtp/smtp.cc +++ b/src/service_inspectors/smtp/smtp.cc @@ -169,6 +169,9 @@ const PegInfo smtp_peg_names[] = { CountType::SUM, "sessions", "total smtp sessions" }, { CountType::NOW, "concurrent_sessions", "total concurrent smtp sessions" }, { CountType::MAX, "max_concurrent_sessions", "maximum concurrent smtp sessions" }, + { CountType::SUM, "start_tls", "total STARTTLS events generated" }, + { CountType::SUM, "ssl_search_abandoned", "total SSL search abandoned" }, + { CountType::SUM, "ssl_srch_abandoned_early", "total SSL search abandoned too soon" }, { CountType::SUM, "b64_attachments", "total base64 attachments decoded" }, { CountType::SUM, "b64_decoded_bytes", "total base64 decoded bytes" }, { CountType::SUM, "qp_attachments", "total quoted-printable attachments decoded" }, @@ -1096,6 +1099,9 @@ static void SMTP_ProcessServerPacket( { OpportunisticTlsEvent event(p, p->flow->service); DataBus::publish(OPPORTUNISTIC_TLS_EVENT, event, p->flow); + ++smtpstats.starttls; + if (smtp_ssn->state_flags & SMTP_FLAG_ABANDON_EVT) + ++smtpstats.ssl_search_abandoned_too_soon; } break; @@ -1110,6 +1116,7 @@ static void SMTP_ProcessServerPacket( { smtp_ssn->state_flags |= SMTP_FLAG_ABANDON_EVT; DataBus::publish(SSL_SEARCH_ABANDONED, p); + ++smtpstats.ssl_search_abandoned; } break; diff --git a/src/service_inspectors/smtp/smtp_config.h b/src/service_inspectors/smtp/smtp_config.h index bee0df255..aada92fe7 100644 --- a/src/service_inspectors/smtp/smtp_config.h +++ b/src/service_inspectors/smtp/smtp_config.h @@ -153,6 +153,9 @@ struct SmtpStats PegCount sessions; PegCount concurrent_sessions; PegCount max_concurrent_sessions; + PegCount starttls; + PegCount ssl_search_abandoned; + PegCount ssl_search_abandoned_too_soon; snort::MimeStats mime_stats; }; diff --git a/src/service_inspectors/ssl/ssl_inspector.cc b/src/service_inspectors/ssl/ssl_inspector.cc index 9b68088b4..07acc8abc 100644 --- a/src/service_inspectors/ssl/ssl_inspector.cc +++ b/src/service_inspectors/ssl/ssl_inspector.cc @@ -77,6 +77,7 @@ const PegInfo ssl_peg_names[] = SslFlowData::SslFlowData() : FlowData(inspector_id) { memset(&session, 0, sizeof(session)); + finalize_info = {}; sslstats.concurrent_sessions++; if(sslstats.max_concurrent_sessions < sslstats.concurrent_sessions) sslstats.max_concurrent_sessions = sslstats.concurrent_sessions; @@ -431,6 +432,10 @@ public: void handle(DataEvent&, Flow* flow) override { + SslFlowData* fd = new SslFlowData; + fd->finalize_info.orig_flag = flow->flags.trigger_finalize_event; + fd->finalize_info.switch_in = true; + flow->set_flow_data(fd); flow->flags.trigger_finalize_event = true; } }; @@ -444,10 +449,14 @@ public: { FinalizePacketEvent* fp_event = (FinalizePacketEvent*)&e; const Packet* pkt = fp_event->get_packet(); - - pkt->flow->flags.trigger_finalize_event = false; - pkt->flow->set_proxied(); - pkt->flow->set_service(const_cast(pkt), s_name); + SslFlowData* fd = (SslFlowData*)pkt->flow->get_flow_data(SslFlowData::inspector_id); + if (fd and fd->finalize_info.switch_in) + { + pkt->flow->flags.trigger_finalize_event = fd->finalize_info.orig_flag; + fd->finalize_info.switch_in = false; + pkt->flow->set_proxied(); + pkt->flow->set_service(const_cast(pkt), s_name); + } } }; diff --git a/src/service_inspectors/ssl/ssl_inspector.h b/src/service_inspectors/ssl/ssl_inspector.h index 756137411..56df1bb5c 100644 --- a/src/service_inspectors/ssl/ssl_inspector.h +++ b/src/service_inspectors/ssl/ssl_inspector.h @@ -51,6 +51,10 @@ public: public: static unsigned inspector_id; SSLData session; + struct { + bool orig_flag : 1; + bool switch_in : 1; + } finalize_info; }; //Function: API to get the ssl flow data from the packet flow. SSLData* get_ssl_session_data(snort::Flow* flow);