From: Mike Stepanek (mstepane) Date: Tue, 7 Apr 2020 20:54:41 +0000 (+0000) Subject: Merge pull request #2126 in SNORT/snort3 from ~KATHARVE/snort3:connect_pt1 to master X-Git-Tag: 3.0.1-2~32 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b6e4713ca64cdfdd77e5a9f349a081d546ca7132;p=thirdparty%2Fsnort3.git Merge pull request #2126 in SNORT/snort3 from ~KATHARVE/snort3:connect_pt1 to master Squashed commit of the following: commit e76efdd1ed2708467d1ffe895f238e986d5414b9 Author: Katura Harvey Date: Mon Mar 30 14:58:36 2020 -0400 http_inspect: enhance processing of connect messages --- diff --git a/src/service_inspectors/http_inspect/http_enum.h b/src/service_inspectors/http_inspect/http_enum.h index 3fc8a0106..56b6df7b8 100644 --- a/src/service_inspectors/http_inspect/http_enum.h +++ b/src/service_inspectors/http_inspect/http_enum.h @@ -230,6 +230,11 @@ enum Infraction INF_H2_NON_IDENTITY_TE, INF_H2_DATA_OVERRUNS_CL, INF_H2_DATA_UNDERRUNS_CL, + INF_CONNECT_REQUEST_BODY, + INF_EARLY_C2S_TRAFFIC_AFTER_CONNECT, + INF_200_CONNECT_RESP_WITH_CL, + INF_200_CONNECT_RESP_WITH_TE, + INF_100_CONNECT_RESP, INF__MAX_VALUE }; @@ -346,11 +351,16 @@ enum EventSid EVENT_206_WITHOUT_RANGE, EVENT_VERSION_NOT_UPPERCASE, EVENT_BAD_HEADER_WHITESPACE, - EVENT_GZIP_EARLY_END, // 248 + EVENT_GZIP_EARLY_END, EVENT_EXCESS_REPEAT_PARAMS, - EVENT_H2_NON_IDENTITY_TE, + EVENT_H2_NON_IDENTITY_TE, // 250 EVENT_H2_DATA_OVERRUNS_CL, EVENT_H2_DATA_UNDERRUNS_CL, + EVENT_CONNECT_REQUEST_BODY, + EVENT_EARLY_C2S_TRAFFIC_AFTER_CONNECT, + EVENT_200_CONNECT_RESP_WITH_CL, + EVENT_200_CONNECT_RESP_WITH_TE, + EVENT_100_CONNECT_RESP, // 257 EVENT__MAX_VALUE }; diff --git a/src/service_inspectors/http_inspect/http_flow_data.h b/src/service_inspectors/http_inspect/http_flow_data.h index e0068cb99..7846ba1bd 100644 --- a/src/service_inspectors/http_inspect/http_flow_data.h +++ b/src/service_inspectors/http_inspect/http_flow_data.h @@ -113,6 +113,7 @@ private: int32_t num_head_lines[2] = { HttpCommon::STAT_NOT_PRESENT, HttpCommon::STAT_NOT_PRESENT }; bool tcp_close[2] = { false, false }; bool partial_flush[2] = { false, false }; + uint64_t last_connect_trans_w_early_traffic = 0; HttpInfractions* infractions[2] = { new HttpInfractions, new HttpInfractions }; HttpEventGen* events[2] = { new HttpEventGen, new HttpEventGen }; @@ -126,6 +127,7 @@ private: // *** Inspector => StreamSplitter (facts about the message section that is coming next) HttpEnums::SectionType type_expected[2] = { HttpEnums::SEC_REQUEST, HttpEnums::SEC_STATUS }; + uint64_t last_request_was_connect = false; // length of the data from Content-Length field z_stream* compress_stream[2] = { nullptr, nullptr }; uint64_t zero_nine_expected = 0; diff --git a/src/service_inspectors/http_inspect/http_msg_header.cc b/src/service_inspectors/http_inspect/http_msg_header.cc index 6a5587158..e8f0def5c 100644 --- a/src/service_inspectors/http_inspect/http_msg_header.cc +++ b/src/service_inspectors/http_inspect/http_msg_header.cc @@ -165,6 +165,38 @@ void HttpMsgHeader::update_flow() return; } + if ((source_id == SRC_SERVER) && request && (request->get_method_id() == METH_CONNECT) && + !session_data->for_http2) + { + // Successful CONNECT responses (2XX) switch to tunneled traffic immediately following the + // header. Transfer-Encoding and Content-Length headers are not allowed in successful + // responses by the RFC. + if(((session_data->last_connect_trans_w_early_traffic == 0) || + (trans_num > session_data->last_connect_trans_w_early_traffic)) && + ((status_code_num >= 200) && (status_code_num < 300))) + { + if ((get_header_count(HEAD_TRANSFER_ENCODING) > 0)) + { + add_infraction(INF_200_CONNECT_RESP_WITH_TE); + create_event(EVENT_200_CONNECT_RESP_WITH_TE); + } + if (get_header_count(HEAD_CONTENT_LENGTH) > 0) + { + add_infraction(INF_200_CONNECT_RESP_WITH_CL); + create_event(EVENT_200_CONNECT_RESP_WITH_CL); + } + // Temporary - Further traffic on this flow is tunneled traffic, and will get sent back + // to the wizard to determine the appropriate inspector. For now just abort. + session_data->type_expected[source_id] = SEC_ABORT; + return; + } + if ((status_code_num >= 100) && (status_code_num < 200)) + { + add_infraction(INF_100_CONNECT_RESP); + create_event(EVENT_100_CONNECT_RESP); + } + } + if ((source_id == SRC_SERVER) && ((100 <= status_code_num && status_code_num <= 199) || (status_code_num == 204) || (status_code_num == 304))) @@ -347,6 +379,13 @@ void HttpMsgHeader::prepare_body() if (source_id == SRC_CLIENT) { HttpModule::increment_peg_counts(PEG_REQUEST_BODY); + + // Message bodies for CONNECT requests have no defined semantics + if ((method_id == METH_CONNECT) && !session_data->for_http2) + { + add_infraction(INF_CONNECT_REQUEST_BODY); + create_event(EVENT_CONNECT_REQUEST_BODY); + } } } diff --git a/src/service_inspectors/http_inspect/http_msg_request.cc b/src/service_inspectors/http_inspect/http_msg_request.cc index c46c75d59..63c60f70b 100644 --- a/src/service_inspectors/http_inspect/http_msg_request.cc +++ b/src/service_inspectors/http_inspect/http_msg_request.cc @@ -293,6 +293,11 @@ void HttpMsgRequest::update_flow() return; } + if (method_id == METH_CONNECT) + { + session_data->last_request_was_connect = true; + } + session_data->type_expected[source_id] = SEC_HEADER; session_data->version_id[source_id] = version_id; session_data->method_id = method_id; diff --git a/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc b/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc index 6b1f65ed6..8176e43c9 100644 --- a/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc +++ b/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc @@ -193,6 +193,31 @@ StreamSplitter::Status HttpStreamSplitter::scan(Packet* pkt, const uint8_t* data } #endif + // If the last request was a CONNECT and we have not yet seen the response, this is early C2S + // traffic. If there has been a pipeline overflow or underflow we cannot match requests to + // responses, so there is no attempt to track early C2S traffic. + if ((source_id == SRC_CLIENT) && (type == SEC_REQUEST) && !session_data->for_http2 && + session_data->last_request_was_connect) + { + const uint64_t last_request_trans_num = session_data->expected_trans_num[SRC_CLIENT] - 1; + const bool server_behind_connect = + (session_data->expected_trans_num[SRC_SERVER] < last_request_trans_num); + const bool server_expecting_connect_status = + ((session_data->expected_trans_num[SRC_SERVER] == last_request_trans_num) + && (session_data->type_expected[SRC_SERVER] == SEC_STATUS)); + const bool pipeline_valid = !session_data->pipeline_overflow && + !session_data->pipeline_underflow; + + if ((server_behind_connect || server_expecting_connect_status) && pipeline_valid) + { + *session_data->get_infractions(source_id) += INF_EARLY_C2S_TRAFFIC_AFTER_CONNECT; + session_data->events[source_id]->create_event(EVENT_EARLY_C2S_TRAFFIC_AFTER_CONNECT); + session_data->last_connect_trans_w_early_traffic = + session_data->expected_trans_num[SRC_CLIENT] - 1; + } + session_data->last_request_was_connect = false; + } + assert(!session_data->tcp_close[source_id]); HttpModule::increment_peg_counts(PEG_SCAN); diff --git a/src/service_inspectors/http_inspect/http_tables.cc b/src/service_inspectors/http_inspect/http_tables.cc index 815bd1842..b48c53214 100644 --- a/src/service_inspectors/http_inspect/http_tables.cc +++ b/src/service_inspectors/http_inspect/http_tables.cc @@ -385,6 +385,12 @@ const RuleMap HttpModule::http_events[] = "value" }, { EVENT_H2_DATA_UNDERRUNS_CL, "HTTP/2 message body smaller than Content-Length header " "value" }, + { EVENT_CONNECT_REQUEST_BODY, "HTTP CONNECT request with a message body" }, + { EVENT_EARLY_C2S_TRAFFIC_AFTER_CONNECT, "HTTP client-to-server traffic after CONNECT request " + "but before CONNECT response" }, + { EVENT_200_CONNECT_RESP_WITH_CL, "HTTP CONNECT 2XX response with Content-Length header" }, + { EVENT_200_CONNECT_RESP_WITH_TE, "HTTP CONNECT 2XX response with Transfer-Encoding header" }, + { EVENT_100_CONNECT_RESP, "HTTP CONNECT response with 1XX status code" }, { 0, nullptr } };