From: Russ Combs (rucombs) Date: Tue, 18 Feb 2020 16:32:41 +0000 (+0000) Subject: Merge pull request #1947 in SNORT/snort3 from ~BRASTULT/snort3:http_param to master X-Git-Tag: 3.0.0-268~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=853ca92ec190d377cd6c89dca45009e8a7a85e89;p=thirdparty%2Fsnort3.git Merge pull request #1947 in SNORT/snort3 from ~BRASTULT/snort3:http_param to master Squashed commit of the following: commit 7372ff7c4455456788e055bb74a8ff957042ad70 Author: Brandon Stultz Date: Wed Dec 11 18:29:15 2019 -0500 http_inspect: add http_param rule option --- diff --git a/doc/http_inspect.txt b/doc/http_inspect.txt index 526cf1173..755966d62 100644 --- a/doc/http_inspect.txt +++ b/doc/http_inspect.txt @@ -171,7 +171,7 @@ by an attacker. The options provide tools for the user to cope with that. percent_u = false utf8_bare_byte = false iis_unicode = false - iis_double_decode = false + iis_double_decode = true The HTTP inspector normalizes percent encodings found in URIs. For instance it will convert "%48%69%64%64%65%6e" to "Hidden". All the options listed @@ -216,7 +216,7 @@ In the example, the lower-case letters a, b, and c and the digits 1, 2, and 3 are exempted. These may be percent-encoded without generating an alert. simplify_path = true - backslash_to_slash = false + backslash_to_slash = true HTTP inspector simplifies directory paths in URIs by eliminating extra traversals using ., .., and /. @@ -240,9 +240,8 @@ directories to be separated by backslashes: \this\is\the\other\way\to\write\a\path -backslash_to_slash is turned off by default. If you are protecting such a -server then set backslash_to_slash = true and all the backslashes will be -replaced with slashes during normalization. +backslash_to_slash is turned on by default. It replaces all the backslashes +with slashes during normalization. ==== Detection rules diff --git a/src/detection/detection_options.cc b/src/detection/detection_options.cc index a64f0944e..c6b573f83 100644 --- a/src/detection/detection_options.cc +++ b/src/detection/detection_options.cc @@ -571,7 +571,7 @@ int detection_option_node_evaluate( continue; } - else + else if ( node->option_type != RULE_OPTION_TYPE_BUFFER_SET ) { // Check for an unbounded relative search. If this // failed before, it's going to fail again so don't diff --git a/src/framework/cursor.cc b/src/framework/cursor.cc index 08b292f18..a62f835ef 100644 --- a/src/framework/cursor.cc +++ b/src/framework/cursor.cc @@ -21,6 +21,8 @@ #include "config.h" #endif +#include + #include "cursor.h" #include "detection/detection_util.h" @@ -28,6 +30,8 @@ using namespace snort; +unsigned CursorData::cursor_data_id = 0; + Cursor::Cursor(Packet* p) { reset(p); @@ -35,8 +39,57 @@ Cursor::Cursor(Packet* p) Cursor::Cursor(const Cursor& rhs) { - *this = rhs; - delta = 0; + name = rhs.name; + buf = rhs.buf; + sz = rhs.sz; + pos = rhs.pos; + + if (rhs.data) + { + data = new CursorDataVec; + + for (CursorData*& cd : *rhs.data) + data->push_back(cd->clone()); + } +} + +CursorData* Cursor::get_data(unsigned id) const +{ + if (data) + { + for (CursorData*& cd : *data) + { + if (cd->get_id() == id) + return cd; + } + } + + return nullptr; +} + +void Cursor::set_data(CursorData* cd) +{ + assert(cd); + + if (data) + { + unsigned id = cd->get_id(); + for (CursorData*& old : *data) + { + if (old->get_id() == id) + { + delete old; + old = cd; + return; + } + } + } + else + { + data = new CursorDataVec; + } + + data->push_back(cd); } void Cursor::reset(Packet* p) diff --git a/src/framework/cursor.h b/src/framework/cursor.h index b231e0ca8..a91ff1dab 100644 --- a/src/framework/cursor.h +++ b/src/framework/cursor.h @@ -27,19 +27,50 @@ #include #include +#include namespace snort { struct Packet; } +class CursorData +{ +public: + CursorData(unsigned u) : id(u) {} + virtual ~CursorData() = default; + virtual CursorData* clone() = 0; + + unsigned get_id() + { return id; } + + static unsigned create_cursor_data_id() + { return ++cursor_data_id; } + +private: + static unsigned cursor_data_id; + unsigned id; +}; + class Cursor { public: + Cursor() = default; Cursor(snort::Packet*); Cursor(const Cursor&); - Cursor& operator=(const Cursor&) = default; + Cursor& operator=(const Cursor&) = delete; + + ~Cursor() + { + if (!data) + return; + + for (CursorData*& cd : *data) + delete cd; + + delete data; + } const char* get_name() const { return name; } @@ -50,10 +81,10 @@ public: void reset(snort::Packet*); void set(const char* s, const uint8_t* b, unsigned n) - { name = s; data = b; sz = n; pos = delta = 0; } + { name = s; buf = b; sz = n; pos = delta = 0; } const uint8_t* buffer() const - { return data; } + { return buf; } unsigned size() const { return sz; } @@ -61,10 +92,10 @@ public: // the NEXT octect after last in buffer // (this pointer is out of bounds) const uint8_t* endo() const - { return data + sz; } + { return buf + sz; } const uint8_t* start() const - { return data + pos; } + { return buf + pos; } unsigned length() const { return sz - pos; } @@ -75,6 +106,8 @@ public: unsigned get_delta() const { return delta; } + CursorData* get_data(unsigned id) const; + bool add_pos(unsigned n) { if (pos + n > sz) @@ -100,12 +133,17 @@ public: return true; } + void set_data(CursorData* cd); + + typedef std::vector CursorDataVec; + private: - const char* name; // rule option name ("pkt_data", "http_uri", etc.) - const uint8_t* data; // start of buffer - unsigned sz; // size of buffer - unsigned pos; // current pos - unsigned delta; // loop offset + const char* name = nullptr; // rule option name ("pkt_data", "http_uri", etc.) + const uint8_t* buf = nullptr; // start of buffer + unsigned sz = 0; // size of buffer + unsigned pos = 0; // current pos + unsigned delta = 0; // loop offset + CursorDataVec* data = nullptr; // data stored on the cursor }; #endif diff --git a/src/service_inspectors/http_inspect/CMakeLists.txt b/src/service_inspectors/http_inspect/CMakeLists.txt index d1918aebb..7e0a49055 100644 --- a/src/service_inspectors/http_inspect/CMakeLists.txt +++ b/src/service_inspectors/http_inspect/CMakeLists.txt @@ -2,6 +2,8 @@ set (FILE_LIST ips_http.cc ips_http.h + http_buffer_info.h + http_buffer_info.cc http_inspect.cc http_inspect.h http_msg_section.cc @@ -27,6 +29,9 @@ set (FILE_LIST http_msg_body_old.h http_msg_trailer.cc http_msg_trailer.h + http_param.h + http_query_parser.cc + http_query_parser.h http_header_normalizer.cc http_header_normalizer.h http_uri.cc @@ -48,6 +53,7 @@ set (FILE_LIST http_flow_data.h http_context_data.cc http_context_data.h + http_cursor_data.h http_transaction.cc http_transaction.h http_test_manager.cc diff --git a/src/service_inspectors/http_inspect/http_api.cc b/src/service_inspectors/http_inspect/http_api.cc index 7475fe2a7..ef407cab8 100644 --- a/src/service_inspectors/http_inspect/http_api.cc +++ b/src/service_inspectors/http_inspect/http_api.cc @@ -24,6 +24,7 @@ #include "http_api.h" #include "http_context_data.h" +#include "http_cursor_data.h" #include "http_inspect.h" using namespace snort; @@ -31,6 +32,8 @@ using namespace snort; const char* HttpApi::http_my_name = HTTP_NAME; const char* HttpApi::http_help = "the new HTTP inspector!"; +unsigned HttpCursorData::id = 0; + Inspector* HttpApi::http_ctor(Module* mod) { HttpModule* const http_mod = (HttpModule*)mod; @@ -41,6 +44,7 @@ void HttpApi::http_init() { HttpFlowData::init(); HttpContextData::init(); + HttpCursorData::init(); } const char* HttpApi::classic_buffer_names[] = @@ -49,6 +53,7 @@ const char* HttpApi::classic_buffer_names[] = "http_cookie", "http_header", "http_method", + "http_param", "http_raw_body", "http_raw_cookie", "http_raw_header", @@ -97,6 +102,7 @@ extern const BaseApi* ips_http_client_body; extern const BaseApi* ips_http_cookie; extern const BaseApi* ips_http_header; extern const BaseApi* ips_http_method; +extern const BaseApi* ips_http_param; extern const BaseApi* ips_http_raw_body; extern const BaseApi* ips_http_raw_cookie; extern const BaseApi* ips_http_raw_header; @@ -122,6 +128,7 @@ const BaseApi* sin_http[] = ips_http_cookie, ips_http_header, ips_http_method, + ips_http_param, ips_http_raw_body, ips_http_raw_cookie, ips_http_raw_header, diff --git a/src/service_inspectors/http_inspect/http_buffer_info.cc b/src/service_inspectors/http_inspect/http_buffer_info.cc new file mode 100644 index 000000000..e72f672d1 --- /dev/null +++ b/src/service_inspectors/http_inspect/http_buffer_info.cc @@ -0,0 +1,65 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2020-2020 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_buffer_info.cc author Brandon Stultz + +#include "hash/hashfcn.h" +#include "http_buffer_info.h" + +using namespace snort; + +uint32_t HttpBufferInfo::hash() const +{ + uint32_t a = type; + uint32_t b = sub_id >> 32; + uint32_t c = sub_id & 0xFFFFFFFF; + uint32_t d = form >> 32; + uint32_t e = form & 0xFFFFFFFF; + uint32_t f = 0; + mix(a,b,c); + if (param) + f = param->is_nocase() ? 1 : 0; + mix(d,e,f); + mix(a,c,f); + if (param) + mix_str(a,c,f,param->c_str(),param->length()); + finalize(a,c,f); + return f; +} + +bool HttpBufferInfo::operator==(const HttpBufferInfo& rhs) const +{ + bool param_match = false; + + if (param && rhs.param) + { + HttpParam& lhs_param = *param; + HttpParam& rhs_param = *rhs.param; + + param_match = (lhs_param == rhs_param); + } + else if (!param && !rhs.param) + { + param_match = true; + } + + return type == rhs.type && + sub_id == rhs.sub_id && + form == rhs.form && + param_match; +} + diff --git a/src/service_inspectors/http_inspect/http_buffer_info.h b/src/service_inspectors/http_inspect/http_buffer_info.h new file mode 100644 index 000000000..490511fe2 --- /dev/null +++ b/src/service_inspectors/http_inspect/http_buffer_info.h @@ -0,0 +1,57 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2020-2020 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_buffer_info.h author Brandon Stultz + +#ifndef HTTP_BUFFER_INFO_H +#define HTTP_BUFFER_INFO_H + +#include + +#include "http_enum.h" +#include "http_param.h" + +class HttpBufferInfo +{ +public: + HttpBufferInfo(unsigned type_, uint64_t sub_id_ = 0, uint64_t form_ = 0) + : type(type_), sub_id(sub_id_), form(form_) {} + + HttpBufferInfo(unsigned type_, uint64_t sub_id_, uint64_t form_, + const std::string& param_str, bool nocase) + : type(type_), sub_id(sub_id_), form(form_) + { + if (param_str.length() > 0) + param = new HttpParam(param_str, nocase); + } + + ~HttpBufferInfo() + { delete param; } + + uint32_t hash() const; + + bool operator==(const HttpBufferInfo& rhs) const; + +public: + unsigned type; + uint64_t sub_id; + uint64_t form; + HttpParam* param = nullptr; +}; + +#endif + diff --git a/src/service_inspectors/http_inspect/http_cursor_data.h b/src/service_inspectors/http_inspect/http_cursor_data.h new file mode 100644 index 000000000..0314332cd --- /dev/null +++ b/src/service_inspectors/http_inspect/http_cursor_data.h @@ -0,0 +1,51 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2020-2020 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_cursor_data.h author Brandon Stultz + +#ifndef HTTP_CURSOR_DATA_H +#define HTTP_CURSOR_DATA_H + +#include "framework/cursor.h" + +class HttpCursorData : public CursorData +{ +public: + HttpCursorData() : CursorData(id) {} + + static void init() + { id = CursorData::create_cursor_data_id(); } + + HttpCursorData* clone() override + { return new HttpCursorData(*this); } + + bool retry() + { + return query_index < num_query_params || + body_index < num_body_params; + } + +public: + static unsigned id; + unsigned num_query_params = 0; + unsigned num_body_params = 0; + unsigned query_index = 0; + unsigned body_index = 0; +}; + +#endif + diff --git a/src/service_inspectors/http_inspect/http_enum.h b/src/service_inspectors/http_inspect/http_enum.h index 40719bdda..495943bee 100644 --- a/src/service_inspectors/http_inspect/http_enum.h +++ b/src/service_inspectors/http_inspect/http_enum.h @@ -46,9 +46,10 @@ enum DetectionStatus { DET_REACTIVATING = 1, DET_ON, DET_DEACTIVATING, DET_OFF } // Message buffers available to clients // This enum must remain synchronized with HttpApi::classic_buffer_names[] enum HTTP_BUFFER { HTTP_BUFFER_CLIENT_BODY = 1, HTTP_BUFFER_COOKIE, HTTP_BUFFER_HEADER, - HTTP_BUFFER_METHOD, HTTP_BUFFER_RAW_BODY, HTTP_BUFFER_RAW_COOKIE, HTTP_BUFFER_RAW_HEADER, - HTTP_BUFFER_RAW_REQUEST, HTTP_BUFFER_RAW_STATUS, HTTP_BUFFER_RAW_TRAILER, HTTP_BUFFER_RAW_URI, - HTTP_BUFFER_STAT_CODE, HTTP_BUFFER_STAT_MSG, HTTP_BUFFER_TRAILER, HTTP_BUFFER_TRUE_IP, + HTTP_BUFFER_METHOD, HTTP_BUFFER_PARAM, HTTP_BUFFER_RAW_BODY, HTTP_BUFFER_RAW_COOKIE, + HTTP_BUFFER_RAW_HEADER, HTTP_BUFFER_RAW_REQUEST, HTTP_BUFFER_RAW_STATUS, + HTTP_BUFFER_RAW_TRAILER, HTTP_BUFFER_RAW_URI, HTTP_BUFFER_STAT_CODE, + HTTP_BUFFER_STAT_MSG, HTTP_BUFFER_TRAILER, HTTP_BUFFER_TRUE_IP, HTTP_BUFFER_URI, HTTP_BUFFER_VERSION, HTTP_BUFFER_MAX }; // Peg counts @@ -57,7 +58,7 @@ enum PEG_COUNT { PEG_FLOW = 0, PEG_SCAN, PEG_REASSEMBLE, PEG_INSPECT, PEG_REQUES PEG_GET, PEG_HEAD, PEG_POST, PEG_PUT, PEG_DELETE, PEG_CONNECT, PEG_OPTIONS, PEG_TRACE, PEG_OTHER_METHOD, PEG_REQUEST_BODY, PEG_CHUNKED, PEG_URI_NORM, PEG_URI_PATH, PEG_URI_CODING, PEG_CONCURRENT_SESSIONS, PEG_MAX_CONCURRENT_SESSIONS, PEG_DETAINED, PEG_PARTIAL_INSPECT, - PEG_COUNT_MAX }; + PEG_EXCESS_PARAMS, PEG_PARAMS, PEG_COUNT_MAX }; // Result of scanning by splitter enum ScanResult { SCAN_NOT_FOUND, SCAN_NOT_FOUND_DETAIN, SCAN_FOUND, SCAN_FOUND_PIECE, @@ -343,6 +344,7 @@ enum EventSid EVENT_VERSION_NOT_UPPERCASE, EVENT_BAD_HEADER_WHITESPACE, EVENT_GZIP_EARLY_END, // 248 + EVENT_EXCESS_REPEAT_PARAMS, 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 3b037186a..e4fe48159 100644 --- a/src/service_inspectors/http_inspect/http_flow_data.h +++ b/src/service_inspectors/http_inspect/http_flow_data.h @@ -37,6 +37,7 @@ class HttpTransaction; class HttpJsNorm; class HttpMsgSection; class HttpCutter; +class HttpQueryParser; class HttpFlowData : public snort::FlowData { @@ -59,6 +60,7 @@ public: friend class HttpMsgBodyChunk; friend class HttpMsgBodyCl; friend class HttpMsgBodyOld; + friend class HttpQueryParser; friend class HttpStreamSplitter; friend class HttpTransaction; #if defined(REG_TEST) || defined(UNIT_TEST) diff --git a/src/service_inspectors/http_inspect/http_inspect.cc b/src/service_inspectors/http_inspect/http_inspect.cc index 39f522d86..f6f6f2cfa 100644 --- a/src/service_inspectors/http_inspect/http_inspect.cc +++ b/src/service_inspectors/http_inspect/http_inspect.cc @@ -156,14 +156,14 @@ bool HttpInspect::get_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffe switch (ibt) { case InspectionBuffer::IBT_KEY: - return http_get_buf(HTTP_BUFFER_URI, 0, 0, p, b); + return get_buf(HTTP_BUFFER_URI, p, b); case InspectionBuffer::IBT_HEADER: if (get_latest_is(p) == IS_TRAILER) - return http_get_buf(HTTP_BUFFER_TRAILER, 0, 0, p, b); + return get_buf(HTTP_BUFFER_TRAILER, p, b); else - return http_get_buf(HTTP_BUFFER_HEADER, 0, 0, p, b); + return get_buf(HTTP_BUFFER_HEADER, p , b); case InspectionBuffer::IBT_BODY: - return http_get_buf(HTTP_BUFFER_CLIENT_BODY, 0, 0, p, b); + return get_buf(HTTP_BUFFER_CLIENT_BODY, p, b); default: return false; } @@ -171,25 +171,28 @@ bool HttpInspect::get_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffe bool HttpInspect::get_buf(unsigned id, Packet* p, InspectionBuffer& b) { - return http_get_buf(id, 0, 0, p, b); + Cursor c; + HttpBufferInfo buffer_info(id); + + const Field& http_buffer = http_get_buf(c, p, buffer_info); + + if (http_buffer.length() <= 0) + return false; + + b.data = http_buffer.start(); + b.len = http_buffer.length(); + return true; } -bool HttpInspect::http_get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet* p, - InspectionBuffer& b) +const Field& HttpInspect::http_get_buf(Cursor& c, Packet* p, + HttpBufferInfo& buffer_info) { HttpMsgSection* current_section = HttpContextData::get_snapshot(p); if (current_section == nullptr) - return false; - - const Field& buffer = current_section->get_classic_buffer(id, sub_id, form); + return Field::FIELD_NULL; - if (buffer.length() <= 0) - return false; - - b.data = buffer.start(); - b.len = buffer.length(); - return true; + return current_section->get_classic_buffer(c, buffer_info); } bool HttpInspect::get_fp_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffer& b) diff --git a/src/service_inspectors/http_inspect/http_inspect.h b/src/service_inspectors/http_inspect/http_inspect.h index 77b024804..ae62c44cb 100644 --- a/src/service_inspectors/http_inspect/http_inspect.h +++ b/src/service_inspectors/http_inspect/http_inspect.h @@ -24,13 +24,16 @@ // HttpInspect class //------------------------------------------------------------------------- +#include "framework/cursor.h" +#include "log/messages.h" + +#include "http_buffer_info.h" #include "http_common.h" #include "http_enum.h" #include "http_field.h" #include "http_module.h" #include "http_msg_section.h" #include "http_stream_splitter.h" -#include "log/messages.h" class HttpApi; @@ -43,9 +46,10 @@ public: bool get_buf(snort::InspectionBuffer::Type ibt, snort::Packet* p, snort::InspectionBuffer& b) override; bool get_buf(unsigned id, snort::Packet* p, snort::InspectionBuffer& b) override; - bool http_get_buf(unsigned id, uint64_t sub_id, uint64_t form, snort::Packet* p, - snort::InspectionBuffer& b); - bool get_fp_buf(snort::InspectionBuffer::Type ibt, snort::Packet* p, snort::InspectionBuffer& b) override; + const Field& http_get_buf(Cursor& c, snort::Packet* p, + HttpBufferInfo& buffer_info); + bool get_fp_buf(snort::InspectionBuffer::Type ibt, snort::Packet* p, + snort::InspectionBuffer& b) override; bool configure(snort::SnortConfig*) override; void show(snort::SnortConfig*) override; void eval(snort::Packet* p) override; diff --git a/src/service_inspectors/http_inspect/http_module.cc b/src/service_inspectors/http_inspect/http_module.cc index 115792131..3af6878d9 100644 --- a/src/service_inspectors/http_inspect/http_module.cc +++ b/src/service_inspectors/http_inspect/http_module.cc @@ -89,13 +89,13 @@ const Parameter HttpModule::http_params[] = { "iis_unicode_code_page", Parameter::PT_INT, "0:65535", "1252", "code page to use from the IIS unicode map file" }, - { "iis_double_decode", Parameter::PT_BOOL, nullptr, "false", + { "iis_double_decode", Parameter::PT_BOOL, nullptr, "true", "perform double decoding of percent encodings to normalize characters" }, { "oversize_dir_length", Parameter::PT_INT, "1:65535", "300", "maximum length for URL directory" }, - { "backslash_to_slash", Parameter::PT_BOOL, nullptr, "false", + { "backslash_to_slash", Parameter::PT_BOOL, nullptr, "true", "replace \\ with / when normalizing URIs" }, { "plus_to_space", Parameter::PT_BOOL, nullptr, "true", diff --git a/src/service_inspectors/http_inspect/http_module.h b/src/service_inspectors/http_inspect/http_module.h index 89c9a4718..e9021ed52 100644 --- a/src/service_inspectors/http_inspect/http_module.h +++ b/src/service_inspectors/http_inspect/http_module.h @@ -68,8 +68,8 @@ public: std::string iis_unicode_map_file; int iis_unicode_code_page = 1252; uint8_t* unicode_map = nullptr; - bool iis_double_decode = false; - bool backslash_to_slash = false; + bool iis_double_decode = true; + bool backslash_to_slash = true; bool plus_to_space = true; bool simplify_path = true; std::bitset<256> bad_characters; diff --git a/src/service_inspectors/http_inspect/http_msg_body.cc b/src/service_inspectors/http_inspect/http_msg_body.cc index 0f85c91ae..0f35cf857 100644 --- a/src/service_inspectors/http_inspect/http_msg_body.cc +++ b/src/service_inspectors/http_inspect/http_msg_body.cc @@ -274,7 +274,7 @@ void HttpMsgBody::do_file_processing(const Field& file_data) const Field& HttpMsgBody::get_classic_client_body() { - return classic_normalize(detect_data, classic_client_body, params->uri_param); + return classic_normalize(detect_data, classic_client_body, false, params->uri_param); } #ifdef REG_TEST diff --git a/src/service_inspectors/http_inspect/http_msg_body.h b/src/service_inspectors/http_inspect/http_msg_body.h index 010198504..b086588b7 100644 --- a/src/service_inspectors/http_inspect/http_msg_body.h +++ b/src/service_inspectors/http_inspect/http_msg_body.h @@ -40,6 +40,7 @@ public: const Field& get_classic_client_body(); const Field& get_detect_data() { return detect_data; } static void fd_event_callback(void* context, int event); + bool is_first() { return first_body; } protected: HttpMsgBody(const uint8_t* buffer, const uint16_t buf_size, HttpFlowData* session_data_, diff --git a/src/service_inspectors/http_inspect/http_msg_head_shared.cc b/src/service_inspectors/http_inspect/http_msg_head_shared.cc index 445715928..138a5d9be 100644 --- a/src/service_inspectors/http_inspect/http_msg_head_shared.cc +++ b/src/service_inspectors/http_inspect/http_msg_head_shared.cc @@ -296,7 +296,8 @@ const Field& HttpMsgHeadShared::get_classic_raw_header() const Field& HttpMsgHeadShared::get_classic_norm_header() { - return classic_normalize(get_classic_raw_header(), classic_norm_header, params->uri_param); + return classic_normalize(get_classic_raw_header(), classic_norm_header, + false, params->uri_param); } const Field& HttpMsgHeadShared::get_classic_raw_cookie() @@ -307,7 +308,8 @@ const Field& HttpMsgHeadShared::get_classic_raw_cookie() const Field& HttpMsgHeadShared::get_classic_norm_cookie() { - return classic_normalize(get_classic_raw_cookie(), classic_norm_cookie, params->uri_param); + return classic_normalize(get_classic_raw_cookie(), classic_norm_cookie, + false, params->uri_param); } const Field& HttpMsgHeadShared::get_header_value_raw(HeaderId header_id) const diff --git a/src/service_inspectors/http_inspect/http_msg_request.cc b/src/service_inspectors/http_inspect/http_msg_request.cc index 09840812a..c46c75d59 100644 --- a/src/service_inspectors/http_inspect/http_msg_request.cc +++ b/src/service_inspectors/http_inspect/http_msg_request.cc @@ -40,6 +40,13 @@ HttpMsgRequest::HttpMsgRequest(const uint8_t* buffer, const uint16_t buf_size, get_related_sections(); } +HttpMsgRequest::~HttpMsgRequest() +{ + delete uri; + delete query_params; + delete body_params; +} + void HttpMsgRequest::parse_start_line() { // Version field @@ -183,6 +190,22 @@ const Field& HttpMsgRequest::get_uri_norm_classic() return Field::FIELD_NULL; } +ParameterMap& HttpMsgRequest::get_query_params() +{ + if (query_params == nullptr) + query_params = new ParameterMap; + + return *query_params; +} + +ParameterMap& HttpMsgRequest::get_body_params() +{ + if (body_params == nullptr) + body_params = new ParameterMap; + + return *body_params; +} + void HttpMsgRequest::gen_events() { if (*transaction->get_infractions(source_id) & INF_BAD_REQ_LINE) diff --git a/src/service_inspectors/http_inspect/http_msg_request.h b/src/service_inspectors/http_inspect/http_msg_request.h index df926ea34..a8aba0fad 100644 --- a/src/service_inspectors/http_inspect/http_msg_request.h +++ b/src/service_inspectors/http_inspect/http_msg_request.h @@ -23,6 +23,7 @@ #include "http_common.h" #include "http_enum.h" #include "http_msg_start.h" +#include "http_query_parser.h" #include "http_str_to_code.h" #include "http_uri.h" #include "http_uri_norm.h" @@ -37,13 +38,15 @@ public: HttpMsgRequest(const uint8_t* buffer, const uint16_t buf_size, HttpFlowData* session_data_, HttpCommon::SourceId source_id_, bool buf_owner, snort::Flow* flow_, const HttpParaList* params_); - ~HttpMsgRequest() override { delete uri; } + ~HttpMsgRequest() override; void gen_events() override; void update_flow() override; const Field& get_method() { return method; } const Field& get_uri(); const Field& get_uri_norm_classic(); HttpUri* get_http_uri() { return uri; } + ParameterMap& get_query_params(); + ParameterMap& get_body_params(); static bool is_webdav(HttpEnums::MethodId method) { @@ -69,6 +72,9 @@ private: Field method; HttpUri* uri = nullptr; + + ParameterMap* query_params = nullptr; + ParameterMap* body_params = nullptr; }; #endif diff --git a/src/service_inspectors/http_inspect/http_msg_section.cc b/src/service_inspectors/http_inspect/http_msg_section.cc index b3a2fbab0..c138de369 100644 --- a/src/service_inspectors/http_inspect/http_msg_section.cc +++ b/src/service_inspectors/http_inspect/http_msg_section.cc @@ -21,6 +21,8 @@ #include "config.h" #endif +#include + #include "service_inspectors/http2_inspect/http2_flow_data.h" #include "http_msg_section.h" @@ -28,12 +30,15 @@ #include "http_context_data.h" #include "http_common.h" #include "http_enum.h" +#include "http_module.h" #include "http_msg_body.h" #include "http_msg_head_shared.h" #include "http_msg_header.h" #include "http_msg_request.h" #include "http_msg_status.h" #include "http_msg_trailer.h" +#include "http_param.h" +#include "http_query_parser.h" #include "http_test_manager.h" #include "stream/flush_bucket.h" @@ -129,26 +134,34 @@ void HttpMsgSection::update_depth() const } const Field& HttpMsgSection::classic_normalize(const Field& raw, Field& norm, - const HttpParaList::UriParam& uri_param) + bool do_path, const HttpParaList::UriParam& uri_param) { if (norm.length() != STAT_NOT_COMPUTE) return norm; - if ((raw.length() <= 0) || !UriNormalizer::classic_need_norm(raw, true, uri_param)) + if ((raw.length() <= 0) || !UriNormalizer::classic_need_norm(raw, do_path, uri_param)) { norm.set(raw); return norm; } - UriNormalizer::classic_normalize(raw, norm, uri_param); + UriNormalizer::classic_normalize(raw, norm, do_path, uri_param); return norm; } const Field& HttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, uint64_t form) +{ + Cursor c; + HttpBufferInfo buffer_info(id, sub_id, form); + + return get_classic_buffer(c, buffer_info); +} + +const Field& HttpMsgSection::get_classic_buffer(Cursor& c, HttpBufferInfo& buf) { // buffer_side replaces source_id for buffers that support the request option - const SourceId buffer_side = (form & FORM_REQUEST) ? SRC_CLIENT : source_id; + const SourceId buffer_side = (buf.form & FORM_REQUEST) ? SRC_CLIENT : source_id; - switch (id) + switch (buf.type) { case HTTP_BUFFER_CLIENT_BODY: { @@ -161,25 +174,139 @@ const Field& HttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, ui { if (header[buffer_side] == nullptr) return Field::FIELD_NULL; - return (id == HTTP_BUFFER_COOKIE) ? header[buffer_side]->get_classic_norm_cookie() : + return (buf.type == HTTP_BUFFER_COOKIE) ? header[buffer_side]->get_classic_norm_cookie() : header[buffer_side]->get_classic_raw_cookie(); } case HTTP_BUFFER_HEADER: case HTTP_BUFFER_TRAILER: { // FIXIT-L Someday want to be able to return field name or raw field value - HttpMsgHeadShared* const head = (id == HTTP_BUFFER_HEADER) ? + HttpMsgHeadShared* const head = (buf.type == HTTP_BUFFER_HEADER) ? (HttpMsgHeadShared*)header[buffer_side] : (HttpMsgHeadShared*)trailer[buffer_side]; if (head == nullptr) return Field::FIELD_NULL; - if (sub_id == 0) + if (buf.sub_id == 0) return head->get_classic_norm_header(); - return head->get_header_value_norm((HeaderId)sub_id); + return head->get_header_value_norm((HeaderId)buf.sub_id); } case HTTP_BUFFER_METHOD: { return (request != nullptr) ? request->get_method() : Field::FIELD_NULL; } + case HTTP_BUFFER_PARAM: + { + if (buf.param == nullptr || request == nullptr) + return Field::FIELD_NULL; + + HttpUri* query = request->get_http_uri(); + HttpMsgBody* body = (source_id == SRC_CLIENT) ? get_body() : nullptr; + + if (query == nullptr && body == nullptr) + return Field::FIELD_NULL; + + const HttpParaList::UriParam& uri_config = params->uri_param; + + ParameterMap& query_params = request->get_query_params(); + ParameterMap& body_params = request->get_body_params(); + + // cache lookup + HttpParam& param = *buf.param; + ParameterData& query_data = query_params[param.str_upper()]; + ParameterData& body_data = body_params[param.str_upper()]; + + if (!query_data.parsed && query != nullptr) + { + // query has not been parsed for this parameter + const Field& rq = query->get_query(); + const Field& nq = query->get_norm_query(); + + if (rq.length() > 0 && nq.length() > 0) + { + HttpQueryParser parser(rq.start(), rq.length(), + nq.start(), nq.length(), uri_config, + session_data, source_id); + + parser.parse(param, query_data); + query_data.parsed = true; + } + } + + if (!body_data.parsed && body != nullptr) + { + // body has not been parsed for this parameter + const Field& rb = body->get_detect_data(); + const Field& nb = body->get_classic_client_body(); + + if (rb.length() > 0 && nb.length() > 0 && body->is_first()) + { + HttpQueryParser parser(rb.start(), rb.length(), + nb.start(), nb.length(), uri_config, + session_data, source_id); + + parser.parse(param, body_data); + body_data.parsed = true; + } + } + + KeyValueVec& query_kv = query_data.kv_vec; + KeyValueVec& body_kv = body_data.kv_vec; + + unsigned num_query_params = query_kv.size(); + unsigned num_body_params = body_kv.size(); + + if (num_query_params == 0 && num_body_params == 0) + return Field::FIELD_NULL; + + // get data stored on the cursor + HttpCursorData* cd = (HttpCursorData*)c.get_data(HttpCursorData::id); + + if (!cd) + { + cd = new HttpCursorData(); + c.set_data(cd); + } + + // save the parameter count on the cursor + cd->num_query_params = num_query_params; + cd->num_body_params = num_body_params; + + unsigned& query_index = cd->query_index; + unsigned& body_index = cd->body_index; + + while (query_index < num_query_params) + { + KeyValue* fields = query_kv[query_index]; + + Field& key = fields->key; + Field& value = fields->value; + + ++query_index; + + if (param.is_nocase()) + return value; + + if (!memcmp(key.start(), param.c_str(), key.length())) + return value; + } + + while (body_index < num_body_params) + { + KeyValue* fields = body_kv[body_index]; + + Field& key = fields->key; + Field& value = fields->value; + + ++body_index; + + if (param.is_nocase()) + return value; + + if (!memcmp(key.start(), param.c_str(), key.length())) + return value; + } + + return Field::FIELD_NULL; + } case HTTP_BUFFER_RAW_BODY: { return (get_body() != nullptr) ? get_body()->msg_text : Field::FIELD_NULL; @@ -218,15 +345,15 @@ const Field& HttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, ui case HTTP_BUFFER_URI: case HTTP_BUFFER_RAW_URI: { - const bool raw = (id == HTTP_BUFFER_RAW_URI); + const bool raw = (buf.type == HTTP_BUFFER_RAW_URI); if (request == nullptr) return Field::FIELD_NULL; - if (sub_id == 0) + if (buf.sub_id == 0) return raw ? request->get_uri() : request->get_uri_norm_classic(); HttpUri* const uri = request->get_http_uri(); if (uri == nullptr) return Field::FIELD_NULL; - switch ((UriComponent)sub_id) + switch ((UriComponent)buf.sub_id) { case UC_SCHEME: return uri->get_scheme(); diff --git a/src/service_inspectors/http_inspect/http_msg_section.h b/src/service_inspectors/http_inspect/http_msg_section.h index 95ae5085f..844f52228 100644 --- a/src/service_inspectors/http_inspect/http_msg_section.h +++ b/src/service_inspectors/http_inspect/http_msg_section.h @@ -21,12 +21,15 @@ #define HTTP_MSG_SECTION_H #include "detection/detection_util.h" +#include "framework/cursor.h" +#include "http_buffer_info.h" #include "http_common.h" +#include "http_cursor_data.h" #include "http_enum.h" #include "http_field.h" -#include "http_module.h" #include "http_flow_data.h" +#include "http_module.h" #include "http_transaction.h" //------------------------------------------------------------------------- @@ -64,6 +67,7 @@ public: virtual void update_flow() = 0; const Field& get_classic_buffer(unsigned id, uint64_t sub_id, uint64_t form); + const Field& get_classic_buffer(Cursor& c, HttpBufferInfo& buf); HttpEnums::MethodId get_method_id() const { return method_id; } @@ -116,7 +120,7 @@ protected: void create_event(int sid); void update_depth() const; static const Field& classic_normalize(const Field& raw, Field& norm, - const HttpParaList::UriParam& uri_param); + bool do_path, const HttpParaList::UriParam& uri_param); #ifdef REG_TEST void print_section_title(FILE* output, const char* title) const; void print_section_wrapup(FILE* output) const; diff --git a/src/service_inspectors/http_inspect/http_param.h b/src/service_inspectors/http_inspect/http_param.h new file mode 100644 index 000000000..1634277cd --- /dev/null +++ b/src/service_inspectors/http_inspect/http_param.h @@ -0,0 +1,89 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2020-2020 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_param.h author Brandon Stultz + +#ifndef HTTP_PARAM_H +#define HTTP_PARAM_H + +#include +#include +#include + +#include "helpers/literal_search.h" + +class HttpParam +{ +public: + HttpParam(const std::string& param_, bool nocase_) + : param(param_), param_upper(param_), nocase(nocase_) + { + assert(param.length() > 0); + + std::transform(param_upper.begin(), param_upper.end(), + param_upper.begin(), ::toupper); + + const uint8_t* pattern = (const uint8_t*)param_upper.c_str(); + unsigned pattern_length = param_upper.length(); + + search_handle = snort::LiteralSearch::setup(); + + searcher = snort::LiteralSearch::instantiate( + search_handle, pattern, pattern_length, true + ); + } + + ~HttpParam() + { + delete searcher; + snort::LiteralSearch::cleanup(search_handle); + } + + bool operator==(const HttpParam& rhs) const + { return param == rhs.param && nocase == rhs.nocase; } + + const std::string& str() const + { return param; } + + const std::string& str_upper() const + { return param_upper; } + + const char* c_str() const + { return param.c_str(); } + + unsigned length() const + { return param.length(); } + + bool is_nocase() const + { return nocase; } + + int search_nocase(const uint8_t* buffer, unsigned buffer_len) const + { + assert(searcher); + return searcher->search(search_handle, buffer, buffer_len); + } + +private: + std::string param; + std::string param_upper; + bool nocase = false; + snort::LiteralSearch* searcher = nullptr; + snort::LiteralSearch::Handle* search_handle = nullptr; +}; + +#endif + diff --git a/src/service_inspectors/http_inspect/http_query_parser.cc b/src/service_inspectors/http_inspect/http_query_parser.cc new file mode 100644 index 000000000..dfd590ace --- /dev/null +++ b/src/service_inspectors/http_inspect/http_query_parser.cc @@ -0,0 +1,187 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2020-2020 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_query_parser.cc author Brandon Stultz + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "http_enum.h" +#include "http_query_parser.h" +#include "http_uri_norm.h" + +using namespace HttpEnums; + +void HttpQueryParser::create_event(int sid) +{ + session_data->events[source_id]->create_event(sid); +} + +void HttpQueryParser::unescape(const Field& raw, Field& norm) +{ + if ( raw.length() > 0 ) + { + if ( UriNormalizer::classic_need_norm(raw, false, uri_config) ) + { + UriNormalizer::classic_normalize(raw, norm, false, uri_config); + return; + } + } + + norm.set(raw); +} + +void HttpQueryParser::parse(const HttpParam& param, ParameterData& data) +{ + // check if parameter is present in normalized buffer + if( param.search_nocase(norm_buffer, norm_buffer_len) < 0 ) + return; + + const std::string& key = param.str_upper(); + + unsigned kv_index = 0; + index = 0; + + while ( index < buffer_len ) + { + if ( kv_index >= MAX_REPEAT_PARAMS ) + { + HttpModule::increment_peg_counts(PEG_EXCESS_PARAMS); + create_event(EVENT_EXCESS_REPEAT_PARAMS); + return; + } + + Parameter p = {}; + + if ( !parse_parameter(p) ) + return; + + if ( p.key_len == 0 ) + continue; + + Field raw_key(p.key_len, p.key); + Field raw_value(p.value_len, p.value); + + KeyValue* fields = new KeyValue; + + Field& norm_key = fields->key; + Field& norm_value = fields->value; + + // normalize the key + unescape(raw_key, norm_key); + + if ( (unsigned)norm_key.length() != key.length() ) + { + delete fields; + continue; + } + + const char* norm_key_str = (const char*)norm_key.start(); + + if ( strncasecmp(norm_key_str, key.c_str(), key.length()) ) + { + delete fields; + continue; + } + + // normalize the value + unescape(raw_value, norm_value); + + // cache the parameter + data.kv_vec.push_back(fields); + HttpModule::increment_peg_counts(PEG_PARAMS); + kv_index++; + } +} + +bool HttpQueryParser::parse_parameter(Parameter& p) +{ + if ( !parse_key(p) ) + return false; + + if ( !parse_value(p) ) + return false; + + return true; +} + +bool HttpQueryParser::parse_key(Parameter& p) +{ + const uint8_t* term; + + if ( index >= buffer_len ) + return false; + + p.key = buffer + index; + + unsigned remaining = buffer_len - index; + + // locate delimiter + term = (const uint8_t*)memchr(p.key, '=', remaining); + + if ( !term ) + return false; + + p.key_len = term - p.key; + + index += p.key_len + 1; + + return true; +} + +bool HttpQueryParser::parse_value(Parameter& p) +{ + const uint8_t* amp; + const uint8_t* semi; + const uint8_t* term; + + if ( index >= buffer_len ) + return false; + + p.value = buffer + index; + + unsigned remaining = buffer_len - index; + + // locate delimiter + amp = (const uint8_t*)memchr(p.value, '&', remaining); + semi = (const uint8_t*)memchr(p.value, ';', remaining); + + if ( amp && !semi ) + term = amp; + else if ( !amp && semi ) + term = semi; + else + term = (amp < semi) ? amp : semi; + + if ( !term ) + { + // last parameter + p.value_len = remaining; + index += remaining; + return true; + } + + p.value_len = term - p.value; + + index += p.value_len + 1; + + return true; +} + diff --git a/src/service_inspectors/http_inspect/http_query_parser.h b/src/service_inspectors/http_inspect/http_query_parser.h new file mode 100644 index 000000000..554d9497b --- /dev/null +++ b/src/service_inspectors/http_inspect/http_query_parser.h @@ -0,0 +1,107 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2020-2020 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_query_parser.h author Brandon Stultz + +#ifndef HTTP_QUERY_PARSER_H +#define HTTP_QUERY_PARSER_H + +#include +#include +#include + +#include "http_common.h" +#include "http_field.h" +#include "http_flow_data.h" +#include "http_module.h" +#include "http_param.h" + +struct KeyValue +{ + Field key; + Field value; +}; + +typedef std::vector KeyValueVec; + +class ParameterData +{ +public: + ParameterData() = default; + + ~ParameterData() + { + for ( KeyValue* kv : kv_vec ) + delete kv; + } + +public: + KeyValueVec kv_vec; + bool parsed = false; +}; + +typedef std::unordered_map ParameterMap; + +class HttpQueryParser +{ +public: + HttpQueryParser(const uint8_t* buffer_, unsigned buffer_len_, + const uint8_t* norm_buffer_, unsigned norm_buffer_len_, + const HttpParaList::UriParam& uri_config_, + HttpFlowData* session_data_, + HttpCommon::SourceId source_id_) + : buffer(buffer_), buffer_len(buffer_len_), + norm_buffer(norm_buffer_), norm_buffer_len(norm_buffer_len_), + uri_config(uri_config_), session_data(session_data_), + source_id(source_id_) {} + + void parse(const HttpParam& param, ParameterData& data); + + struct Parameter + { + const uint8_t* key; + const uint8_t* value; + unsigned key_len; + unsigned value_len; + }; + +private: + void create_event(int sid); + + void unescape(const Field& raw, Field& norm); + + bool parse_parameter(Parameter& p); + bool parse_key(Parameter& p); + bool parse_value(Parameter& p); + + const uint8_t* buffer; + unsigned buffer_len; + + const uint8_t* norm_buffer; + unsigned norm_buffer_len; + + unsigned index; + + static const unsigned MAX_REPEAT_PARAMS = 100; + + const HttpParaList::UriParam& uri_config; + HttpFlowData* const session_data; + const HttpCommon::SourceId source_id; +}; + +#endif + diff --git a/src/service_inspectors/http_inspect/http_tables.cc b/src/service_inspectors/http_inspect/http_tables.cc index ed6906cdd..d13fe4ff0 100644 --- a/src/service_inspectors/http_inspect/http_tables.cc +++ b/src/service_inspectors/http_inspect/http_tables.cc @@ -379,6 +379,7 @@ const RuleMap HttpModule::http_events[] = { EVENT_BAD_HEADER_WHITESPACE, "white space embedded in critical header value" }, { EVENT_GZIP_EARLY_END, "gzip compressed data followed by unexpected non-gzip " "data" }, + { EVENT_EXCESS_REPEAT_PARAMS, "excessive HTTP parameter key repeats" }, { 0, nullptr } }; @@ -408,6 +409,8 @@ const PegInfo HttpModule::peg_names[PEG_COUNT_MAX+1] = { CountType::MAX, "max_concurrent_sessions", "maximum concurrent http sessions" }, { CountType::SUM, "detained_packets", "TCP packets delayed by detained inspection" }, { CountType::SUM, "partial_inspections", "pre-inspections for detained inspection" }, + { CountType::SUM, "excess_parameters", "repeat parameters exceeding max" }, + { CountType::SUM, "parameters", "HTTP parameters inspected" }, { CountType::END, nullptr, nullptr } }; diff --git a/src/service_inspectors/http_inspect/http_uri_norm.cc b/src/service_inspectors/http_inspect/http_uri_norm.cc index 586959d99..af1c61178 100644 --- a/src/service_inspectors/http_inspect/http_uri_norm.cc +++ b/src/service_inspectors/http_inspect/http_uri_norm.cc @@ -450,7 +450,7 @@ int32_t UriNormalizer::norm_path_clean(uint8_t* buf, const int32_t in_length, // Provide traditional URI-style normalization for buffers that usually are not URIs void UriNormalizer::classic_normalize(const Field& input, Field& result, - const HttpParaList::UriParam& uri_param) + bool do_path, const HttpParaList::UriParam& uri_param) { // The requirements for generating events related to these normalizations are unclear. It // definitely doesn't seem right to generate standard URI events. For now we won't generate @@ -469,7 +469,7 @@ void UriNormalizer::classic_normalize(const Field& input, Field& result, // Normalize character escape sequences int32_t data_length = norm_char_clean(input, buffer, uri_param, &unused, &dummy_ev); - if (uri_param.simplify_path) + if (do_path && uri_param.simplify_path) { // Normalize path directory traversals // Find the leading slash if there is one diff --git a/src/service_inspectors/http_inspect/http_uri_norm.h b/src/service_inspectors/http_inspect/http_uri_norm.h index cb9095f46..721141492 100644 --- a/src/service_inspectors/http_inspect/http_uri_norm.h +++ b/src/service_inspectors/http_inspect/http_uri_norm.h @@ -41,7 +41,7 @@ public: HttpEventGen* events); static bool classic_need_norm(const Field& uri_component, bool do_path, const HttpParaList::UriParam& uri_param); - static void classic_normalize(const Field& input, Field& result, + static void classic_normalize(const Field& input, Field& result, bool do_path, const HttpParaList::UriParam& uri_param); static void load_default_unicode_map(uint8_t map[65536]); static void load_unicode_map(uint8_t map[65536], const char* filename, int code_page); diff --git a/src/service_inspectors/http_inspect/ips_http.cc b/src/service_inspectors/http_inspect/ips_http.cc index 9260ae898..1859f37e3 100644 --- a/src/service_inspectors/http_inspect/ips_http.cc +++ b/src/service_inspectors/http_inspect/ips_http.cc @@ -26,6 +26,7 @@ #include "framework/cursor.h" #include "hash/hashfcn.h" #include "log/messages.h" +#include "parser/parse_utils.h" #include "protocols/packet.h" #include "service_inspectors/http2_inspect/http2_flow_data.h" @@ -34,6 +35,7 @@ #include "http_flow_data.h" #include "http_inspect.h" #include "http_msg_head_shared.h" +#include "http_param.h" using namespace snort; using namespace HttpCommon; @@ -56,6 +58,7 @@ bool HttpCursorModule::begin(const char*, int, SnortConfig*) case HTTP_BUFFER_COOKIE: case HTTP_BUFFER_HEADER: case HTTP_BUFFER_METHOD: + case HTTP_BUFFER_PARAM: case HTTP_BUFFER_RAW_COOKIE: case HTTP_BUFFER_RAW_HEADER: case HTTP_BUFFER_RAW_REQUEST: @@ -98,6 +101,17 @@ bool HttpCursorModule::set(const char*, Value& v, SnortConfig*) if (sub_id == STAT_OTHER) ParseError("Unrecognized header field name"); } + else if (v.is("~param")) + { + std::string bc = v.get_string(); + bool negated = false; + if (!parse_byte_code(bc.c_str(), negated, para_list.param) or negated) + ParseError("Invalid http_param"); + } + else if (v.is("nocase")) + { + para_list.nocase = true; + } else if (v.is("request")) { para_list.request = true; @@ -167,12 +181,16 @@ bool HttpCursorModule::end(const char*, int, SnortConfig*) if (para_list.scheme + para_list.host + para_list.port + para_list.path + para_list.query + para_list.fragment > 1) ParseError("Only specify one part of the URI"); + if (buffer_index == HTTP_BUFFER_PARAM && para_list.param.length() == 0) + ParseError("Specify parameter name"); return true; } void HttpCursorModule::HttpRuleParaList::reset() { field.clear(); + param.clear(); + nocase = false; request = false; with_header = false; with_body = false; @@ -189,15 +207,10 @@ uint32_t HttpIpsOption::hash() const { uint32_t a = IpsOption::hash(); uint32_t b = (uint32_t)inspect_section; - uint32_t c = sub_id >> 32; - uint32_t d = sub_id & 0xFFFFFFFF; - uint32_t e = form >> 32; - uint32_t f = form & 0xFFFFFFFF; + uint32_t c = buffer_info.hash(); mix(a,b,c); - mix(d,e,f); - mix(a,c,f); - finalize(a,c,f); - return f; + finalize(a,b,c); + return c; } bool HttpIpsOption::operator==(const IpsOption& ips) const @@ -205,8 +218,19 @@ bool HttpIpsOption::operator==(const IpsOption& ips) const const HttpIpsOption& hio = static_cast(ips); return IpsOption::operator==(ips) && inspect_section == hio.inspect_section && - sub_id == hio.sub_id && - form == hio.form; + buffer_info == hio.buffer_info; +} + +bool HttpIpsOption::retry(Cursor& c) +{ + if (buffer_info.type == HTTP_BUFFER_PARAM) + { + HttpCursorData* cd = (HttpCursorData*)c.get_data(HttpCursorData::id); + + if (cd) + return cd->retry(); + } + return false; } IpsOption::EvalStatus HttpIpsOption::eval(Cursor& c, Packet* p) @@ -225,17 +249,17 @@ IpsOption::EvalStatus HttpIpsOption::eval(Cursor& c, Packet* p) return NO_MATCH; const Http2FlowData* const h2i_flow_data = - (Http2FlowData*)p->flow->get_flow_data(Http2FlowData::inspector_id); + (Http2FlowData*)p->flow->get_flow_data(Http2FlowData::inspector_id); HttpInspect* const hi = (h2i_flow_data != nullptr) ? (HttpInspect*)(p->flow->assistant_gadget) : (HttpInspect*)(p->flow->gadget); - InspectionBuffer hb; + const Field& http_buffer = hi->http_get_buf(c, p, buffer_info); - if (! (hi->http_get_buf((unsigned)buffer_index, sub_id, form, p, hb))) + if (http_buffer.length() <= 0) return NO_MATCH; - c.set(key, hb.data, hb.len); + c.set(key, http_buffer.start(), http_buffer.length()); return MATCH; } @@ -445,6 +469,55 @@ static const IpsApi method_api = nullptr }; +//------------------------------------------------------------------------- +// http_param +//------------------------------------------------------------------------- + +static const Parameter http_param_params[] = +{ + { "~param", Parameter::PT_STRING, nullptr, nullptr, + "parameter to match" }, + { "nocase", Parameter::PT_IMPLIED, nullptr, nullptr, + "case insensitive match" }, + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } +}; + +#undef IPS_OPT +#define IPS_OPT "http_param" +#undef IPS_HELP +#define IPS_HELP "rule option to set the detection cursor to the value of the specified HTTP parameter key which may be in the query or body" + +static Module* param_mod_ctor() +{ + return new HttpCursorModule(IPS_OPT, IPS_HELP, HTTP_BUFFER_PARAM, CAT_SET_OTHER, PSI_PARAM, + http_param_params); +} + +static const IpsApi param_api = +{ + { + PT_IPS_OPTION, + sizeof(IpsApi), + IPSAPI_VERSION, + 1, + API_RESERVED, + API_OPTIONS, + IPS_OPT, + IPS_HELP, + param_mod_ctor, + HttpCursorModule::mod_dtor + }, + OPT_TYPE_DETECTION, + 0, PROTO_BIT__TCP, + nullptr, + nullptr, + nullptr, + nullptr, + HttpIpsOption::opt_ctor, + HttpIpsOption::opt_dtor, + nullptr +}; + //------------------------------------------------------------------------- // http_raw_body //------------------------------------------------------------------------- @@ -1132,6 +1205,7 @@ const BaseApi* ips_http_client_body = &client_body_api.base; const BaseApi* ips_http_cookie = &cookie_api.base; const BaseApi* ips_http_header = &header_api.base; const BaseApi* ips_http_method = &method_api.base; +const BaseApi* ips_http_param = ¶m_api.base; const BaseApi* ips_http_raw_body = &raw_body_api.base; const BaseApi* ips_http_raw_cookie = &raw_cookie_api.base; const BaseApi* ips_http_raw_header = &raw_header_api.base; diff --git a/src/service_inspectors/http_inspect/ips_http.h b/src/service_inspectors/http_inspect/ips_http.h index 64fbda52f..f5db9026f 100644 --- a/src/service_inspectors/http_inspect/ips_http.h +++ b/src/service_inspectors/http_inspect/ips_http.h @@ -26,11 +26,13 @@ #include "framework/ips_option.h" #include "framework/module.h" +#include "http_buffer_info.h" #include "http_enum.h" -enum PsIdx { PSI_CLIENT_BODY, PSI_COOKIE, PSI_HEADER, PSI_METHOD, PSI_RAW_BODY, PSI_RAW_COOKIE, - PSI_RAW_HEADER, PSI_RAW_REQUEST, PSI_RAW_STATUS, PSI_RAW_TRAILER, PSI_RAW_URI, PSI_STAT_CODE, - PSI_STAT_MSG, PSI_TRAILER, PSI_TRUE_IP, PSI_URI, PSI_VERSION, PSI_MAX }; +enum PsIdx { PSI_CLIENT_BODY, PSI_COOKIE, PSI_HEADER, PSI_METHOD, PSI_PARAM, + PSI_RAW_BODY, PSI_RAW_COOKIE, PSI_RAW_HEADER, PSI_RAW_REQUEST, PSI_RAW_STATUS, + PSI_RAW_TRAILER, PSI_RAW_URI, PSI_STAT_CODE, PSI_STAT_MSG, PSI_TRAILER, + PSI_TRUE_IP, PSI_URI, PSI_VERSION, PSI_MAX }; class HttpCursorModule : public snort::Module { @@ -60,6 +62,8 @@ private: { public: std::string field; // provide buffer containing specific header field + std::string param; // provide buffer containing specific parameter + bool nocase; // case insensitive match bool request; // provide buffer from request not response bool with_header; // provide buffer with a later section than it appears in bool with_body; @@ -89,24 +93,25 @@ class HttpIpsOption : public snort::IpsOption { public: HttpIpsOption(const HttpCursorModule* cm) : - snort::IpsOption(cm->key, RULE_OPTION_TYPE_BUFFER_SET), key(cm->key), - buffer_index(cm->buffer_index), cat(cm->cat), psi(cm->psi), - inspect_section(cm->inspect_section), sub_id(cm->sub_id), form(cm->form) {} + snort::IpsOption(cm->key, RULE_OPTION_TYPE_BUFFER_SET), + key(cm->key), cat(cm->cat), psi(cm->psi), + inspect_section(cm->inspect_section), + buffer_info(cm->buffer_index, cm->sub_id, cm->form, + cm->para_list.param, cm->para_list.nocase) {} snort::CursorActionType get_cursor_type() const override { return cat; } EvalStatus eval(Cursor&, snort::Packet*) override; uint32_t hash() const override; bool operator==(const snort::IpsOption& ips) const override; + bool retry(Cursor&) override; static IpsOption* opt_ctor(snort::Module* m, OptTreeNode*) { return new HttpIpsOption((HttpCursorModule*)m); } static void opt_dtor(snort::IpsOption* p) { delete p; } private: const char* const key; - const HttpEnums::HTTP_BUFFER buffer_index; const snort::CursorActionType cat; const PsIdx psi; const HttpEnums::InspectSection inspect_section; - const uint64_t sub_id; - const uint64_t form; + HttpBufferInfo buffer_info; }; #endif