From: Tom Peters (thopeter) Date: Fri, 25 Feb 2022 18:23:22 +0000 (+0000) Subject: Pull request #3273: US 688507: http_inspect: rule option to compare numeric header... X-Git-Tag: 3.1.25.0~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=53a6b664af5a6fe268e4ecc91d2788c1cede0f3c;p=thirdparty%2Fsnort3.git Pull request #3273: US 688507: http_inspect: rule option to compare numeric header values Merge in SNORT/snort3 from ~MDAGON/snort3:numeric2 to master Squashed commit of the following: commit aafe16b64d6b9620cdb8869459072f86381da7e7 Author: Maya Dagon Date: Mon Feb 14 15:34:50 2022 -0500 http_inspect: http_header_test, http_trailer_test rule options --- diff --git a/doc/user/http_inspect.txt b/doc/user/http_inspect.txt index 107e26029..4c3f6176a 100755 --- a/doc/user/http_inspect.txt +++ b/doc/user/http_inspect.txt @@ -706,6 +706,13 @@ the general HTTP/1 format but contain version fields falsely claiming to be HTTP/2.0 or HTTP/0.9 will match "other" as described above. The http_version rule option is available to examine the actual bytes in the version field. +===== http_header_test and http_trailer_test + +Rule options that perform various tests against a specific header and +trailer field, respectively. It can perform a range test, check whether the +value is numeric or whether it is absent. Negative values are considered +non-numeric. Values with more than 18 digits are considered non-numeric. + ==== Timing issues and combining rule options HTTP inspector is stateful. That means it is aware of a bigger picture than diff --git a/src/service_inspectors/http_inspect/CMakeLists.txt b/src/service_inspectors/http_inspect/CMakeLists.txt index 0a151aac0..c9069f2ce 100644 --- a/src/service_inspectors/http_inspect/CMakeLists.txt +++ b/src/service_inspectors/http_inspect/CMakeLists.txt @@ -78,6 +78,8 @@ set (FILE_LIST ips_http_num_hdrs.h ips_http_param.cc ips_http_param.h + ips_http_test.cc + ips_http_test.h ips_http_version.cc ips_http_version.h ) diff --git a/src/service_inspectors/http_inspect/http_api.cc b/src/service_inspectors/http_inspect/http_api.cc index 22d814c87..604ed780e 100644 --- a/src/service_inspectors/http_inspect/http_api.cc +++ b/src/service_inspectors/http_inspect/http_api.cc @@ -103,6 +103,7 @@ const InspectApi HttpApi::http_api = extern const BaseApi* ips_http_client_body; extern const BaseApi* ips_http_cookie; extern const BaseApi* ips_http_header; +extern const BaseApi* ips_http_header_test; extern const BaseApi* ips_http_method; extern const BaseApi* ips_http_num_headers; extern const BaseApi* ips_http_num_trailers; @@ -117,6 +118,7 @@ extern const BaseApi* ips_http_raw_uri; extern const BaseApi* ips_http_stat_code; extern const BaseApi* ips_http_stat_msg; extern const BaseApi* ips_http_trailer; +extern const BaseApi* ips_http_trailer_test; extern const BaseApi* ips_http_true_ip; extern const BaseApi* ips_http_uri; extern const BaseApi* ips_http_version; @@ -133,6 +135,7 @@ const BaseApi* sin_http[] = ips_http_client_body, ips_http_cookie, ips_http_header, + ips_http_header_test, ips_http_method, ips_http_num_headers, ips_http_num_trailers, @@ -147,6 +150,7 @@ const BaseApi* sin_http[] = ips_http_stat_code, ips_http_stat_msg, ips_http_trailer, + ips_http_trailer_test, ips_http_true_ip, ips_http_uri, ips_http_version, diff --git a/src/service_inspectors/http_inspect/http_enum.h b/src/service_inspectors/http_inspect/http_enum.h index 054ad68a8..7e28b3fcd 100755 --- a/src/service_inspectors/http_inspect/http_enum.h +++ b/src/service_inspectors/http_inspect/http_enum.h @@ -54,7 +54,7 @@ enum SectionType { SEC_DISCARD = -19, SEC_ABORT = -18, SEC__NOT_COMPUTE=-14, SEC SEC_BODY_OLD, SEC_BODY_H2 }; // HTTP rule options. -// Lower portion is message buffers available to clients. +// Lower numbered portion is message buffers available to clients. // That part must remain synchronized with HttpApi::classic_buffer_names[] enum HTTP_RULE_OPT { HTTP_BUFFER_CLIENT_BODY = 1, HTTP_BUFFER_COOKIE, HTTP_BUFFER_HEADER, HTTP_BUFFER_METHOD, HTTP_BUFFER_PARAM, HTTP_BUFFER_RAW_BODY, HTTP_BUFFER_RAW_COOKIE, @@ -62,7 +62,8 @@ enum HTTP_RULE_OPT { HTTP_BUFFER_CLIENT_BODY = 1, HTTP_BUFFER_COOKIE, HTTP_BUFFE 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, BUFFER_JS_DATA, BUFFER_VBA_DATA , HTTP__BUFFER_MAX = BUFFER_VBA_DATA, - HTTP_RANGE_NUM_HDRS, HTTP_RANGE_NUM_TRAILERS, HTTP_VERSION_MATCH, HTTP__MAX_RULE_OPTION }; + HTTP_RANGE_NUM_HDRS, HTTP_RANGE_NUM_TRAILERS, HTTP_VERSION_MATCH, + HTTP_HEADER_TEST, HTTP_TRAILER_TEST, HTTP__MAX_RULE_OPTION }; // Peg counts // This enum must remain synchronized with HttpModule::peg_names[] in http_tables.cc diff --git a/src/service_inspectors/http_inspect/http_msg_section.cc b/src/service_inspectors/http_inspect/http_msg_section.cc index b12cbfff7..6f8c9f7b5 100644 --- a/src/service_inspectors/http_inspect/http_msg_section.cc +++ b/src/service_inspectors/http_inspect/http_msg_section.cc @@ -148,7 +148,7 @@ const Field& HttpMsgSection::classic_normalize(const Field& raw, Field& norm, const Field& HttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, uint64_t form) { - HttpBufferInfo buffer_info(id, sub_id, form); + const HttpBufferInfo buffer_info(id, sub_id, form); return get_classic_buffer(buffer_info); } @@ -176,8 +176,10 @@ const Field& HttpMsgSection::get_classic_buffer(const HttpBufferInfo& buf) } case HTTP_BUFFER_HEADER: case HTTP_BUFFER_TRAILER: + case HTTP_HEADER_TEST: + case HTTP_TRAILER_TEST: { - HttpMsgHeadShared* const head = (buf.type == HTTP_BUFFER_HEADER) ? + HttpMsgHeadShared* const head = (buf.type == HTTP_BUFFER_HEADER || buf.type == HTTP_HEADER_TEST) ? (HttpMsgHeadShared*)header[buffer_side] : (HttpMsgHeadShared*)trailer[buffer_side]; if (head == nullptr) return Field::FIELD_NULL; diff --git a/src/service_inspectors/http_inspect/ips_http_num_hdrs.cc b/src/service_inspectors/http_inspect/ips_http_num_hdrs.cc index 545167d22..c3c4d5d0d 100644 --- a/src/service_inspectors/http_inspect/ips_http_num_hdrs.cc +++ b/src/service_inspectors/http_inspect/ips_http_num_hdrs.cc @@ -82,7 +82,7 @@ uint32_t HttpNumHdrsIpsOption::hash() const bool HttpNumHdrsIpsOption::operator==(const IpsOption& ips) const { const HttpNumHdrsIpsOption& hio = static_cast(ips); - return HttpIpsOption::operator==(static_cast(ips)) && + return HttpIpsOption::operator==(ips) && range == hio.range; } diff --git a/src/service_inspectors/http_inspect/ips_http_param.cc b/src/service_inspectors/http_inspect/ips_http_param.cc index 17bc5286b..a864f8df2 100644 --- a/src/service_inspectors/http_inspect/ips_http_param.cc +++ b/src/service_inspectors/http_inspect/ips_http_param.cc @@ -87,7 +87,7 @@ bool HttpParamIpsOption::operator==(const IpsOption& ips) const { const HttpParamIpsOption& hio = static_cast(ips); - return HttpIpsOption::operator==(static_cast(ips)) && + return HttpIpsOption::operator==(ips) && http_param == hio.http_param; } diff --git a/src/service_inspectors/http_inspect/ips_http_test.cc b/src/service_inspectors/http_inspect/ips_http_test.cc new file mode 100644 index 000000000..835054a73 --- /dev/null +++ b/src/service_inspectors/http_inspect/ips_http_test.cc @@ -0,0 +1,302 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2022-2022 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. +//-------------------------------------------------------------------------- +// ips_http_test.cc author Maya Dagon + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ips_http_test.h" + +#include "framework/cursor.h" +#include "hash/hash_key_operations.h" +#include "log/messages.h" +#include "parser/parse_utils.h" +#include "protocols/packet.h" + +#include "http_common.h" +#include "http_enum.h" +#include "http_inspect.h" + +using namespace snort; +using namespace HttpCommon; +using namespace HttpEnums; + +THREAD_LOCAL std::array HttpTestRuleOptModule::http_test_ps; + +const std::string hdr_test_range = "0:999999999999999999"; // max 18 digit uint + +bool HttpTestRuleOptModule::begin(const char*, int, SnortConfig*) +{ + HttpRuleOptModule::begin(nullptr, 0, nullptr); + check.init(); + numeric = NV_UNDEFINED; + absent = false; + if (rule_opt_index == HTTP_HEADER_TEST) + inspect_section = IS_FLEX_HEADER; + else + { + inspect_section = IS_TRAILER; + is_trailer_opt = true; + } + + return true; +} + +bool HttpTestRuleOptModule::set(const char*, Value& v, SnortConfig*) +{ + if (v.is("check")) + { + return check.validate(v.get_string(), hdr_test_range.c_str()); + } + else if (v.is("numeric")) + { + numeric = v.get_bool()? NV_TRUE : NV_FALSE; + } + else if (v.is("absent")) + { + absent = true; + } + else + { + return HttpRuleOptModule::set(nullptr, v, nullptr); + } + + return true; +} + +bool HttpTestRuleOptModule::end(const char*, int, SnortConfig*) +{ + if (sub_id == 0) + ParseError("Specify field name"); + + if (!absent && !check.is_set() && numeric == NV_UNDEFINED) + ParseError("check, numeric, or absent should be specified"); + + if ((absent && (check.is_set() || numeric != NV_UNDEFINED)) || + (check.is_set() && numeric == NV_FALSE)) + ParseWarning(WARN_RULES, "conflicting suboptions"); + + return HttpRuleOptModule::end(nullptr, 0, nullptr); +} + +uint32_t HttpTestIpsOption::hash() const +{ + uint32_t a = HttpIpsOption::hash(); + uint32_t b = check.hash(); + uint32_t c = numeric; + mix(a,b,c); + c = absent ? 1 : 0; + mix(a,b,c); + finalize(a,b,c); + return c; +} + +bool HttpTestIpsOption::operator==(const IpsOption& ips) const +{ + const HttpTestIpsOption& hio = static_cast(ips); + return HttpIpsOption::operator==(ips) && + check == hio.check && + numeric == hio.numeric && + absent == hio.absent; +} + +static int64_t get_decimal_num(enum NumericValue& is_numeric, const uint8_t* start, int32_t length) +{ + int64_t total = 0; + int32_t k = 0; + do + { + int value = start[k] - '0'; + if ((value < 0) || (value > 9)) + { + is_numeric = NV_FALSE; + return -1; + } + total = total*10 + value; + } + while (++k < length); + + is_numeric = NV_TRUE; + return total; +} + +IpsOption::EvalStatus HttpTestIpsOption::eval_header_test(const Field& http_buffer) const +{ + bool is_absent = false; + enum NumericValue is_numeric = NV_UNDEFINED; + int64_t num = 0; + + const int32_t length = http_buffer.length(); + if (length <= 0) + is_absent = true; + // Limit to 18 decimal digits, to fit comfortably into int64_t. + else if (length <= 18) + num = get_decimal_num(is_numeric, http_buffer.start(), length); + else + is_numeric = NV_FALSE; + + const bool absent_passed = !absent || (absent && is_absent); + const bool numeric_passed = (numeric == NumericValue::NV_UNDEFINED) || + (is_numeric == numeric); + const bool range_passed = !check.is_set() || (is_numeric == NV_TRUE && check.eval(num)); + + return (absent_passed && numeric_passed && range_passed) ? MATCH : NO_MATCH; +} + + +IpsOption::EvalStatus HttpTestIpsOption::eval(Cursor&, Packet* p) +{ + RuleProfile profile(HttpTestRuleOptModule::http_test_ps[idx]); + + const HttpInspect* const hi = eval_helper(p); + if (hi == nullptr) + return NO_MATCH; + + const Field& http_buffer = hi->http_get_buf(p, buffer_info); + + return eval_header_test(http_buffer); +} + + +//------------------------------------------------------------------------- +// http_header_test +//------------------------------------------------------------------------- +#undef IPS_OPT +#define IPS_OPT "http_header_test" +#undef IPS_HELP +#define IPS_HELP "rule option to perform range check on specified header field, \ +check whether it is a number, or check if the field is absent" + +static const Parameter hdr_test_params[] = +{ + { "field", Parameter::PT_STRING, nullptr, nullptr, + "Header to perform check on. Header name is case insensitive." }, + { "request", Parameter::PT_IMPLIED, nullptr, nullptr, + "match against the headers from the request message even when examining the response" }, + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "this rule is limited to examining HTTP message headers" }, + { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, + "parts of this rule examine HTTP message body" }, + { "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr, + "parts of this rule examine HTTP message trailers" }, + { "check", Parameter::PT_INTERVAL, hdr_test_range.c_str(), nullptr, + "range check to perform on header value" }, + { "numeric", Parameter::PT_BOOL, nullptr, nullptr, + "header value is a number" }, + { "absent", Parameter::PT_IMPLIED, nullptr, nullptr, + "header is absent" }, + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } +}; + +static Module* http_header_test_mod_ctor() +{ + return new HttpTestRuleOptModule(IPS_OPT, IPS_HELP, HTTP_HEADER_TEST, CAT_SET_OTHER, + TEST_PSI_HEADER_TEST, hdr_test_params); +} + +static const IpsApi header_test_api = +{ + { + PT_IPS_OPTION, + sizeof(IpsApi), + IPSAPI_VERSION, + 1, + API_RESERVED, + API_OPTIONS, + IPS_OPT, + IPS_HELP, + http_header_test_mod_ctor, + HttpTestRuleOptModule::mod_dtor + }, + OPT_TYPE_DETECTION, + 0, PROTO_BIT__TCP, + nullptr, + nullptr, + nullptr, + nullptr, + HttpTestIpsOption::opt_ctor, + HttpTestIpsOption::opt_dtor, + nullptr +}; + +//------------------------------------------------------------------------- +// http_trailer_test +//------------------------------------------------------------------------- +static const Parameter trailer_test_params[] = +{ + { "field", Parameter::PT_STRING, nullptr, nullptr, + "Trailer to perform check on. Trailer name is case insensitive." }, + { "request", Parameter::PT_IMPLIED, nullptr, nullptr, + "match against the trailers from the request message even when examining the response" }, + { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr, + "parts of this rule examine HTTP headers" }, + { "with_body", Parameter::PT_IMPLIED, nullptr, nullptr, + "parts of this rule examine HTTP message body" }, + { "check", Parameter::PT_INTERVAL, hdr_test_range.c_str(), nullptr, + "range check to perform on trailer value" }, + { "numeric", Parameter::PT_BOOL, nullptr, nullptr, + "trailer value is a number" }, + { "absent", Parameter::PT_IMPLIED, nullptr, nullptr, + "trailer is absent" }, + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } +}; + +#undef IPS_OPT +#define IPS_OPT "http_trailer_test" +#undef IPS_HELP +#define IPS_HELP "rule option to perform range check on specified trailer field, \ +check whether it is a number, or check if the field is absent" + +static Module* http_trailer_test_mod_ctor() +{ + return new HttpTestRuleOptModule(IPS_OPT, IPS_HELP, HTTP_TRAILER_TEST, CAT_SET_OTHER, + TEST_PSI_TRAILER_TEST, trailer_test_params); +} + +static const IpsApi trailer_test_api = +{ + { + PT_IPS_OPTION, + sizeof(IpsApi), + IPSAPI_VERSION, + 1, + API_RESERVED, + API_OPTIONS, + IPS_OPT, + IPS_HELP, + http_trailer_test_mod_ctor, + HttpTestRuleOptModule::mod_dtor + }, + OPT_TYPE_DETECTION, + 0, PROTO_BIT__TCP, + nullptr, + nullptr, + nullptr, + nullptr, + HttpTestIpsOption::opt_ctor, + HttpTestIpsOption::opt_dtor, + nullptr +}; + +//------------------------------------------------------------------------- +// plugins +//------------------------------------------------------------------------- + +const BaseApi* ips_http_header_test = &header_test_api.base; +const BaseApi* ips_http_trailer_test = &trailer_test_api.base; diff --git a/src/service_inspectors/http_inspect/ips_http_test.h b/src/service_inspectors/http_inspect/ips_http_test.h new file mode 100644 index 000000000..681e47a15 --- /dev/null +++ b/src/service_inspectors/http_inspect/ips_http_test.h @@ -0,0 +1,85 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2022-2022 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. +//-------------------------------------------------------------------------- +// ips_http_test.h author Maya Dagon + +#ifndef IPS_HTTP_TEST_H +#define IPS_HTTP_TEST_H + +#include + +#include "profiler/profiler.h" +#include "framework/ips_option.h" +#include "framework/module.h" +#include "framework/range.h" + +#include "http_enum.h" +#include "http_field.h" +#include "ips_http.h" + +enum TestPsIdx { TEST_PSI_HEADER_TEST, TEST_PSI_TRAILER_TEST, TEST_PSI_MAX }; + +enum NumericValue { NV_UNDEFINED, NV_TRUE, NV_FALSE }; + +class HttpTestRuleOptModule : public HttpRuleOptModule +{ +public: + HttpTestRuleOptModule(const char* key_, const char* help, HttpEnums::HTTP_RULE_OPT rule_opt_index_, + snort::CursorActionType cat_, TestPsIdx idx_, const snort::Parameter params[]) + : HttpRuleOptModule(key_, help, rule_opt_index_, cat_, params), idx(idx_) {} + + snort::ProfileStats* get_profile() const override + { return &http_test_ps[idx]; } + + static void mod_dtor(snort::Module* m) { delete m; } + bool begin(const char*, int, snort::SnortConfig*) override; + bool set(const char*, snort::Value&, snort::SnortConfig*) override; + bool end(const char*, int, snort::SnortConfig*) override; + +private: + friend class HttpTestIpsOption; + static THREAD_LOCAL std::array http_test_ps; + const TestPsIdx idx; + snort::RangeCheck check; + enum NumericValue numeric; + bool absent; +}; + +class HttpTestIpsOption : public HttpIpsOption +{ +public: + HttpTestIpsOption(const HttpTestRuleOptModule* cm) : + HttpIpsOption(cm, RULE_OPTION_TYPE_OTHER), idx(cm->idx), check(cm->check), + numeric(cm->numeric), absent(cm->absent) {} + EvalStatus eval(Cursor&, snort::Packet*) override; + uint32_t hash() const override; + bool operator==(const snort::IpsOption& ips) const override; + static IpsOption* opt_ctor(snort::Module* m, OptTreeNode*) + { return new HttpTestIpsOption((HttpTestRuleOptModule*)m); } + static void opt_dtor(snort::IpsOption* p) { delete p; } + +private: + const TestPsIdx idx; + const snort::RangeCheck check; + const enum NumericValue numeric; + const bool absent; + + IpsOption::EvalStatus eval_header_test(const Field& http_buffer) const; +}; + +#endif + diff --git a/src/service_inspectors/http_inspect/ips_http_version.cc b/src/service_inspectors/http_inspect/ips_http_version.cc index 7a60c31e9..90b2e3ffb 100644 --- a/src/service_inspectors/http_inspect/ips_http_version.cc +++ b/src/service_inspectors/http_inspect/ips_http_version.cc @@ -108,7 +108,7 @@ uint32_t HttpVersionIpsOption::hash() const bool HttpVersionIpsOption::operator==(const IpsOption& ips) const { const HttpVersionIpsOption& hio = static_cast(ips); - return HttpIpsOption::operator==(static_cast(ips)) && + return HttpIpsOption::operator==(ips) && version_flags == hio.version_flags; }