http_enum.h
http_field.cc
http_field.h
+ http_stream_splitter_finish.cc
http_stream_splitter_reassemble.cc
http_stream_splitter_scan.cc
http_stream_splitter.h
http_test_input.cc http_test_input.h \
http_flow_data.cc http_flow_data.h \
http_transaction.cc http_transaction.h \
-http_stream_splitter_reassemble.cc http_stream_splitter_scan.cc http_stream_splitter.h \
+http_stream_splitter_finish.cc http_stream_splitter_reassemble.cc http_stream_splitter_scan.cc \
+http_stream_splitter.h \
http_cutter.cc http_cutter.h \
http_enum.h \
http_test_manager.cc http_test_manager.h \
HEAD_WWW_AUTHENTICATE, HEAD_ALLOW, HEAD_CONTENT_ENCODING, HEAD_CONTENT_LANGUAGE,
HEAD_CONTENT_LENGTH, HEAD_CONTENT_LOCATION, HEAD_CONTENT_MD5, HEAD_CONTENT_RANGE,
HEAD_CONTENT_TYPE, HEAD_EXPIRES, HEAD_LAST_MODIFIED, HEAD_X_FORWARDED_FOR, HEAD_TRUE_CLIENT_IP,
- HEAD_X_WORKING_WITH,
+ HEAD_X_WORKING_WITH, HEAD_CONTENT_TRANSFER_ENCODING,
HEAD__MAX_VALUE };
// All the infractions we might find while parsing and analyzing a message
INF_EXPECT_WITHOUT_BODY_CL0,
INF_EXPECT_WITHOUT_BODY_NO_CL,
INF_CHUNKED_ONE_POINT_ZERO,
+ INF_CTE_HEADER,
+ INF_ILLEGAL_TRAILER,
INF__MAX_VALUE
};
EVENT_UNKNOWN_1XX_STATUS,
EVENT_EXPECT_WITHOUT_BODY, // 90
EVENT_CHUNKED_ONE_POINT_ZERO,
+ EVENT_CTE_HEADER,
+ EVENT_ILLEGAL_TRAILER,
EVENT__MAX_VALUE
};
}
session_data->latest_section->analyze();
+ session_data->latest_section->gen_events();
session_data->latest_section->update_flow();
#ifdef REG_TEST
}
}
-void HttpMsgHeader::update_flow()
+void HttpMsgHeader::gen_events()
{
- session_data->section_type[source_id] = SEC__NOT_COMPUTE;
-
if (get_header_count(HEAD_CONTENT_LENGTH) > 1)
{
add_infraction(INF_MULTIPLE_CONTLEN);
add_infraction(INF_BOTH_CL_AND_TE);
create_event(EVENT_BOTH_CL_AND_TE);
}
+ // Content-Transfer-Encoding is a MIME header not sanctioned by HTTP. Which may not prevent
+ // some clients from recognizing it and applying a decoding that Snort does not expect.
+ if (get_header_count(HEAD_CONTENT_TRANSFER_ENCODING) > 0)
+ {
+ add_infraction(INF_CTE_HEADER);
+ create_event(EVENT_CTE_HEADER);
+ }
+}
+
+void HttpMsgHeader::update_flow()
+{
+ session_data->section_type[source_id] = SEC__NOT_COMPUTE;
// The following logic to determine body type is by no means the last word on this topic.
if (tcp_close)
HttpEnums::InspectSection get_inspection_section() const override
{ return detection_section ? HttpEnums::IS_DETECTION : HttpEnums::IS_NONE; }
void update_flow() override;
-
+ void gen_events() override;
void publish() override;
private:
// Minimum necessary processing for every message
virtual void analyze() = 0;
+ // analyze() generates many events in the course of its work. Many other events are generated
+ // by JIT normalization but only if someone asks for the item in question. gen_events()
+ // addresses a third category--things that do not come up during analysis but must be
+ // inspected for every message even if no one else asks about them.
+ virtual void gen_events() {}
+
// Manages the splitter and communication between message sections
virtual void update_flow() = 0;
{
start_line.set(msg_text);
parse_start_line();
- gen_events();
}
void HttpMsgStart::derive_version_id()
: HttpMsgSection(buffer, buf_size, session_data_, source_id_, buf_owner, flow_, params_)
{ }
virtual void parse_start_line() = 0;
- virtual void gen_events() = 0;
void derive_version_id();
Field start_line;
transaction->set_trailer(this, source_id);
}
+void HttpMsgTrailer::gen_events()
+{
+ // Trailers not allowed by RFC 7230
+ static const HeaderId bad_trailer[] =
+ {
+ HEAD_AGE,
+ HEAD_AUTHORIZATION,
+ HEAD_CACHE_CONTROL,
+ HEAD_CONTENT_ENCODING,
+ HEAD_CONTENT_LENGTH,
+ HEAD_CONTENT_RANGE,
+ HEAD_CONTENT_TRANSFER_ENCODING,
+ HEAD_CONTENT_TYPE,
+ HEAD_COOKIE,
+ HEAD_DATE,
+ HEAD_EXPECT,
+ HEAD_EXPIRES,
+ HEAD_HOST,
+ HEAD_LOCATION,
+ HEAD_MAX_FORWARDS,
+ HEAD_PRAGMA,
+ HEAD_PROXY_AUTHENTICATE,
+ HEAD_PROXY_AUTHORIZATION,
+ HEAD_RANGE,
+ HEAD_RETRY_AFTER,
+ HEAD_SET_COOKIE,
+ HEAD_TE,
+ HEAD_TRAILER,
+ HEAD_TRANSFER_ENCODING,
+ HEAD_VARY,
+ HEAD_WARNING,
+ HEAD_WWW_AUTHENTICATE
+ };
+
+ for (HeaderId id: bad_trailer)
+ {
+ if (get_header_count(id) > 0)
+ {
+ add_infraction(INF_ILLEGAL_TRAILER);
+ create_event(EVENT_ILLEGAL_TRAILER);
+ break;
+ }
+ }
+}
+
void HttpMsgTrailer::update_flow()
{
session_data->half_reset(source_id);
const HttpParaList* params_);
HttpEnums::InspectSection get_inspection_section() const override
{ return HttpEnums::IS_TRAILER; }
+ void gen_events() override;
void update_flow() override;
#ifdef REG_TEST
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2017 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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 along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// http_stream_splitter_finish.cc author Tom Peters <thopeter@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "file_api/file_flows.h"
+
+#include "http_msg_request.h"
+#include "http_stream_splitter.h"
+#include "http_test_input.h"
+
+using namespace HttpEnums;
+
+bool HttpStreamSplitter::finish(Flow* flow)
+{
+ HttpFlowData* session_data = (HttpFlowData*)flow->get_flow_data(HttpFlowData::http_flow_id);
+ // FIXIT-M - this assert has been changed to check for null session data and return false if so
+ // due to lack of reliable feedback to stream that scan has been called...if that is
+ // addressed in stream reassembly rewrite this can be reverted to an assert
+ //assert(session_data != nullptr);
+ if(!session_data)
+ return false;
+
+#ifdef REG_TEST
+ if (HttpTestManager::use_test_output() && !HttpTestManager::use_test_input())
+ {
+ printf("Finish from flow data %" PRIu64 " direction %d\n", session_data->seq_num,
+ source_id);
+ fflush(stdout);
+ }
+#endif
+
+ if (session_data->type_expected[source_id] == SEC_ABORT)
+ {
+ return false;
+ }
+
+ session_data->tcp_close[source_id] = true;
+
+ // If there is leftover data for which we returned PAF_SEARCH and never flushed, we need to set
+ // up to process because it is about to go to reassemble(). But we don't support partial start
+ // lines.
+ if ((session_data->section_type[source_id] == SEC__NOT_COMPUTE) &&
+ (session_data->cutter[source_id] != nullptr) &&
+ (session_data->cutter[source_id]->get_octets_seen() > 0))
+ {
+ if ((session_data->type_expected[source_id] == SEC_REQUEST) ||
+ (session_data->type_expected[source_id] == SEC_STATUS))
+ {
+ *session_data->get_infractions(source_id) += INF_PARTIAL_START;
+ // FIXIT-M why not use generate_misformatted_http()?
+ session_data->get_events(source_id)->create_event(EVENT_LOSS_OF_SYNC);
+ return false;
+ }
+
+ uint32_t not_used;
+ prepare_flush(session_data, ¬_used, session_data->type_expected[source_id], 0,
+ session_data->cutter[source_id]->get_num_excess(),
+ session_data->cutter[source_id]->get_num_head_lines(),
+ session_data->cutter[source_id]->get_is_broken_chunk(),
+ session_data->cutter[source_id]->get_num_good_chunks(),
+ session_data->cutter[source_id]->get_octets_seen(),
+ true);
+ return true;
+ }
+
+ // If there is no more data to process we need to wrap up file processing right now
+ if ((session_data->section_type[source_id] == SEC__NOT_COMPUTE) &&
+ (session_data->file_depth_remaining[source_id] > 0) &&
+ (session_data->cutter[source_id] != nullptr) &&
+ (session_data->cutter[source_id]->get_octets_seen() == 0))
+ {
+ if (!session_data->mime_state[source_id])
+ {
+ FileFlows* file_flows = FileFlows::get_file_flows(flow);
+ const bool download = (source_id == SRC_SERVER);
+
+ size_t file_index = 0;
+
+ if (session_data->transaction[source_id] != nullptr)
+ {
+ HttpMsgRequest* request = session_data->transaction[source_id]->get_request();
+ if ((request != nullptr) and (request->get_http_uri() != nullptr))
+ {
+ file_index = request->get_http_uri()->get_file_proc_hash();
+ }
+ }
+
+ file_flows->file_process(nullptr, 0, SNORT_FILE_END, !download, file_index);
+ }
+ else
+ {
+ session_data->mime_state[source_id]->process_mime_data(flow, nullptr, 0, true,
+ SNORT_FILE_POSITION_UNKNOWN);
+ delete session_data->mime_state[source_id];
+ session_data->mime_state[source_id] = nullptr;
+ }
+ return false;
+ }
+
+ return true;
+}
+
#include "config.h"
#endif
-#include "file_api/file_flows.h"
-
#include "http_inspect.h"
-#include "http_msg_request.h"
#include "http_stream_splitter.h"
#include "http_test_input.h"
}
}
-bool HttpStreamSplitter::finish(Flow* flow)
-{
- HttpFlowData* session_data = (HttpFlowData*)flow->get_flow_data(HttpFlowData::http_flow_id);
- // FIXIT-M - this assert has been changed to check for null session data and return false if so
- // due to lack of reliable feedback to stream that scan has been called...if that is
- // addressed in stream reassembly rewrite this can be reverted to an assert
- //assert(session_data != nullptr);
- if(!session_data)
- return false;
-
-#ifdef REG_TEST
- if (HttpTestManager::use_test_output() && !HttpTestManager::use_test_input())
- {
- printf("Finish from flow data %" PRIu64 " direction %d\n", session_data->seq_num,
- source_id);
- fflush(stdout);
- }
-#endif
-
- if (session_data->type_expected[source_id] == SEC_ABORT)
- {
- return false;
- }
-
- session_data->tcp_close[source_id] = true;
-
- // If there is leftover data for which we returned PAF_SEARCH and never flushed, we need to set
- // up to process because it is about to go to reassemble(). But we don't support partial start
- // lines.
- if ((session_data->section_type[source_id] == SEC__NOT_COMPUTE) &&
- (session_data->cutter[source_id] != nullptr) &&
- (session_data->cutter[source_id]->get_octets_seen() > 0))
- {
- if ((session_data->type_expected[source_id] == SEC_REQUEST) ||
- (session_data->type_expected[source_id] == SEC_STATUS))
- {
- *session_data->get_infractions(source_id) += INF_PARTIAL_START;
- // FIXIT-M why not use generate_misformatted_http()?
- session_data->get_events(source_id)->create_event(EVENT_LOSS_OF_SYNC);
- return false;
- }
-
- uint32_t not_used;
- prepare_flush(session_data, ¬_used, session_data->type_expected[source_id], 0,
- session_data->cutter[source_id]->get_num_excess(),
- session_data->cutter[source_id]->get_num_head_lines(),
- session_data->cutter[source_id]->get_is_broken_chunk(),
- session_data->cutter[source_id]->get_num_good_chunks(),
- session_data->cutter[source_id]->get_octets_seen(),
- true);
- return true;
- }
-
- // If there is no more data to process we need to wrap up file processing right now
- if ((session_data->section_type[source_id] == SEC__NOT_COMPUTE) &&
- (session_data->file_depth_remaining[source_id] > 0) &&
- (session_data->cutter[source_id] != nullptr) &&
- (session_data->cutter[source_id]->get_octets_seen() == 0))
- {
- if (!session_data->mime_state[source_id])
- {
- FileFlows* file_flows = FileFlows::get_file_flows(flow);
- const bool download = (source_id == SRC_SERVER);
-
- size_t file_index = 0;
-
- if (session_data->transaction[source_id] != nullptr)
- {
- HttpMsgRequest* request = session_data->transaction[source_id]->get_request();
- if ((request != nullptr) and (request->get_http_uri() != nullptr))
- {
- file_index = request->get_http_uri()->get_file_proc_hash();
- }
- }
-
- file_flows->file_process(nullptr, 0, SNORT_FILE_END, !download, file_index);
- }
- else
- {
- session_data->mime_state[source_id]->process_mime_data(flow, nullptr, 0, true,
- SNORT_FILE_POSITION_UNKNOWN);
- delete session_data->mime_state[source_id];
- session_data->mime_state[source_id] = nullptr;
- }
- return false;
- }
-
- return true;
-}
-
const StrCode HttpMsgHeadShared::header_list[] =
{
- { HEAD_CACHE_CONTROL, "cache-control" },
- { HEAD_CONNECTION, "connection" },
- { HEAD_DATE, "date" },
- { HEAD_PRAGMA, "pragma" },
- { HEAD_TRAILER, "trailer" },
- { HEAD_COOKIE, "cookie" },
- { HEAD_SET_COOKIE, "set-cookie" },
- { HEAD_TRANSFER_ENCODING, "transfer-encoding" },
- { HEAD_UPGRADE, "upgrade" },
- { HEAD_VIA, "via" },
- { HEAD_WARNING, "warning" },
- { HEAD_ACCEPT, "accept" },
- { HEAD_ACCEPT_CHARSET, "accept-charset" },
- { HEAD_ACCEPT_ENCODING, "accept-encoding" },
- { HEAD_ACCEPT_LANGUAGE, "accept-language" },
- { HEAD_AUTHORIZATION, "authorization" },
- { HEAD_EXPECT, "expect" },
- { HEAD_FROM, "from" },
- { HEAD_HOST, "host" },
- { HEAD_IF_MATCH, "if-match" },
- { HEAD_IF_MODIFIED_SINCE, "if-modified-since" },
- { HEAD_IF_NONE_MATCH, "if-none-match" },
- { HEAD_IF_RANGE, "if-range" },
- { HEAD_IF_UNMODIFIED_SINCE, "if-unmodified-since" },
- { HEAD_MAX_FORWARDS, "max-forwards" },
- { HEAD_PROXY_AUTHORIZATION, "proxy-authorization" },
- { HEAD_RANGE, "range" },
- { HEAD_REFERER, "referer" },
- { HEAD_TE, "te" },
- { HEAD_USER_AGENT, "user-agent" },
- { HEAD_ACCEPT_RANGES, "accept-ranges" },
- { HEAD_AGE, "age" },
- { HEAD_ETAG, "etag" },
- { HEAD_LOCATION, "location" },
- { HEAD_PROXY_AUTHENTICATE, "proxy-authenticate" },
- { HEAD_RETRY_AFTER, "retry-after" },
- { HEAD_SERVER, "server" },
- { HEAD_VARY, "vary" },
- { HEAD_WWW_AUTHENTICATE, "www-authenticate" },
- { HEAD_ALLOW, "allow" },
- { HEAD_CONTENT_ENCODING, "content-encoding" },
- { HEAD_CONTENT_LANGUAGE, "content-language" },
- { HEAD_CONTENT_LENGTH, "content-length" },
- { HEAD_CONTENT_LOCATION, "content-location" },
- { HEAD_CONTENT_MD5, "content-md5" },
- { HEAD_CONTENT_RANGE, "content-range" },
- { HEAD_CONTENT_TYPE, "content-type" },
- { HEAD_EXPIRES, "expires" },
- { HEAD_LAST_MODIFIED, "last-modified" },
- { HEAD_X_FORWARDED_FOR, "x-forwarded-for" },
- { HEAD_TRUE_CLIENT_IP, "true-client-ip" },
- { HEAD_X_WORKING_WITH, "x-working-with" },
- { 0, nullptr }
+ { HEAD_CACHE_CONTROL, "cache-control" },
+ { HEAD_CONNECTION, "connection" },
+ { HEAD_DATE, "date" },
+ { HEAD_PRAGMA, "pragma" },
+ { HEAD_TRAILER, "trailer" },
+ { HEAD_COOKIE, "cookie" },
+ { HEAD_SET_COOKIE, "set-cookie" },
+ { HEAD_TRANSFER_ENCODING, "transfer-encoding" },
+ { HEAD_UPGRADE, "upgrade" },
+ { HEAD_VIA, "via" },
+ { HEAD_WARNING, "warning" },
+ { HEAD_ACCEPT, "accept" },
+ { HEAD_ACCEPT_CHARSET, "accept-charset" },
+ { HEAD_ACCEPT_ENCODING, "accept-encoding" },
+ { HEAD_ACCEPT_LANGUAGE, "accept-language" },
+ { HEAD_AUTHORIZATION, "authorization" },
+ { HEAD_EXPECT, "expect" },
+ { HEAD_FROM, "from" },
+ { HEAD_HOST, "host" },
+ { HEAD_IF_MATCH, "if-match" },
+ { HEAD_IF_MODIFIED_SINCE, "if-modified-since" },
+ { HEAD_IF_NONE_MATCH, "if-none-match" },
+ { HEAD_IF_RANGE, "if-range" },
+ { HEAD_IF_UNMODIFIED_SINCE, "if-unmodified-since" },
+ { HEAD_MAX_FORWARDS, "max-forwards" },
+ { HEAD_PROXY_AUTHORIZATION, "proxy-authorization" },
+ { HEAD_RANGE, "range" },
+ { HEAD_REFERER, "referer" },
+ { HEAD_TE, "te" },
+ { HEAD_USER_AGENT, "user-agent" },
+ { HEAD_ACCEPT_RANGES, "accept-ranges" },
+ { HEAD_AGE, "age" },
+ { HEAD_ETAG, "etag" },
+ { HEAD_LOCATION, "location" },
+ { HEAD_PROXY_AUTHENTICATE, "proxy-authenticate" },
+ { HEAD_RETRY_AFTER, "retry-after" },
+ { HEAD_SERVER, "server" },
+ { HEAD_VARY, "vary" },
+ { HEAD_WWW_AUTHENTICATE, "www-authenticate" },
+ { HEAD_ALLOW, "allow" },
+ { HEAD_CONTENT_ENCODING, "content-encoding" },
+ { HEAD_CONTENT_LANGUAGE, "content-language" },
+ { HEAD_CONTENT_LENGTH, "content-length" },
+ { HEAD_CONTENT_LOCATION, "content-location" },
+ { HEAD_CONTENT_MD5, "content-md5" },
+ { HEAD_CONTENT_RANGE, "content-range" },
+ { HEAD_CONTENT_TYPE, "content-type" },
+ { HEAD_EXPIRES, "expires" },
+ { HEAD_LAST_MODIFIED, "last-modified" },
+ { HEAD_X_FORWARDED_FOR, "x-forwarded-for" },
+ { HEAD_TRUE_CLIENT_IP, "true-client-ip" },
+ { HEAD_X_WORKING_WITH, "x-working-with" },
+ { HEAD_CONTENT_TRANSFER_ENCODING, "content-transfer-encoding" },
+ { 0, nullptr }
};
const StrCode HttpMsgHeadShared::content_code_list[] =
[HEAD_LAST_MODIFIED] = &NORMALIZER_BASIC,
[HEAD_X_FORWARDED_FOR] = &NORMALIZER_CAT,
[HEAD_TRUE_CLIENT_IP] = &NORMALIZER_BASIC,
- [HEAD_X_WORKING_WITH] = &NORMALIZER_BASIC
+ [HEAD_X_WORKING_WITH] = &NORMALIZER_BASIC,
+ [HEAD_CONTENT_TRANSFER_ENCODING] = &NORMALIZER_CAT,
};
/* *INDENT-ON* */
{ EVENT_MISFORMATTED_HTTP, "misformatted HTTP traffic" },
{ EVENT_UNSUPPORTED_ENCODING, "unsupported Content-Encoding used" },
{ EVENT_UNKNOWN_ENCODING, "unknown Content-Encoding used" },
- { EVENT_STACKED_ENCODINGS, "multiple layers of compression encodings applied" },
+ { EVENT_STACKED_ENCODINGS, "multiple Content-Encodings applied" },
{ EVENT_RESPONSE_WO_REQUEST, "server response before client request" },
{ EVENT_PDF_SWF_OVERRUN, "PDF/SWF decompression of server response too big" },
{ EVENT_BAD_CHAR_IN_HEADER_NAME, "nonprinting character in HTTP message header name" },
{ EVENT_UNKNOWN_1XX_STATUS, "1XX status code other than 100 or 101" },
{ EVENT_EXPECT_WITHOUT_BODY, "Expect header sent without a message body" },
{ EVENT_CHUNKED_ONE_POINT_ZERO, "HTTP 1.0 message with Transfer-Encoding header" },
+ { EVENT_CTE_HEADER, "Content-Transfer-Encoding used as HTTP header" },
+ { EVENT_ILLEGAL_TRAILER, "illegal field in chunked message trailers" },
{ 0, nullptr }
};