From d623c889af531a85f9f04f7c578c465e17daafee Mon Sep 17 00:00:00 2001 From: "Adrian Mamolea (admamole)" Date: Tue, 17 Sep 2024 12:37:03 +0000 Subject: [PATCH] Pull request #4425: http2_inspect: add IPS options for frame header and data Merge in SNORT/snort3 from ~ADMAMOLE/snort3:http2_header_len to master Squashed commit of the following: commit 98609c16c63feb3e8249d4d9f7b2e87456389ef0 Author: Adrian Mamolea Date: Thu Aug 15 14:46:45 2024 -0400 http2_inspect: add IPS options for frame header and data --- doc/user/http2_inspect.txt | 59 +++++++++++++++++++ .../http2_inspect/http2_api.cc | 9 ++- .../http2_inspect/http2_enum.h | 6 +- .../http2_inspect/http2_frame.cc | 2 + .../http2_inspect/http2_headers_frame.cc | 2 + .../http2_inspect/http2_settings_frame.h | 1 - .../http2_inspect/ips_http2.cc | 45 +++++++++++++- 7 files changed, 116 insertions(+), 8 deletions(-) diff --git a/doc/user/http2_inspect.txt b/doc/user/http2_inspect.txt index 4312197c8..f8685a676 100644 --- a/doc/user/http2_inspect.txt +++ b/doc/user/http2_inspect.txt @@ -55,3 +55,62 @@ large numbers of existing rules. New rules should explicitly specify "service http,http2;" if that is the desired behavior. Eventually support for http implies http2 may be deprecated and removed. +Occasionally one needs a rule that looks at the content of the raw HTTP/2 frame, for example to match +some odd value for an identifier in a settings frame: + + alert http2 ( + msg:"SETTINGS frame with odd max frame size"; + flow:to_server,established; + http2_frame_header; content:"|04|",offset 3,depth 1; + http2_frame_data; content:"|00 05 12 34 56 78|"; + sid:1; + ) + +Here http2_frame_header represents the 9 bytes of the HTTP/2 header of the frame, and +http2_frame_data represents the data part of the same frame after any padding was removed. + +Support for http2_frame_header is limited to data, headers, settings and push promise frames, while +support for http2_frame_data is limited to headers, settings, push promise and continuation frames. + +For frames that support both http2_frame_header and http2_frame_data the rule has to match both +on the same frame as in the example above. + +When http2_frame_data is matching on a headers or push promise continuation frame, http2_frame_header +will match on the header of the headers or push promise frame. In the example below the header string +is matched on a continuation of a headers frame. + + alert http2 ( + http2_frame_header; content:"|01|", offset 3, depth 1; + http2_frame_data; content:"header"; + sid:1; + ) + +In the example below the header string is matched on a continuation of a push promise frame. + + alert http2 ( + http2_frame_header; content:"|05|", offset 3, depth 1; + http2_frame_data; content:"header"; + sid:1; + ) + +Matching http2_frame_header on a data frame may be mixed matching on its payload, and, as one would +expect, the http2_frame_header is the one from the data frame that is matching the payload. + + alert http2 ( + http2_frame_header; content:"|00|", offset 3, depth 1; + file_data; content:"response"; + sid:1; + ) + +Mixing the two HTTP/2 frame options with HTTP options at the level of an HTTP transaction (where the +two matches correspond to different HTTP/2 frames) is not recommended. This is an example that will +not work, it tries to match on the header of a data frame and the payload of a headers frame. + + alert http2 ( + msg:"DO NOT ATTEMPT - THIS RULE WILL NOT WORK"; + http2_frame_header; content:"|00|", offset 3, depth 1; + http_method; content:"GET"; + sid:1; + ) + + diff --git a/src/service_inspectors/http2_inspect/http2_api.cc b/src/service_inspectors/http2_inspect/http2_api.cc index a5c554927..423fe6c24 100644 --- a/src/service_inspectors/http2_inspect/http2_api.cc +++ b/src/service_inspectors/http2_inspect/http2_api.cc @@ -39,8 +39,9 @@ Inspector* Http2Api::http2_ctor(Module* mod) const char* Http2Api::classic_buffer_names[] = { -#ifdef REG_TEST "http2_frame_header", + "http2_frame_data", +#ifdef REG_TEST "http2_decoded_header", #endif HTTP_CLASSIC_BUFFER_NAMES, @@ -75,8 +76,9 @@ const InspectApi Http2Api::http2_api = nullptr }; -#ifdef REG_TEST extern const BaseApi* ips_http2_frame_header; +extern const BaseApi* ips_http2_frame_data; +#ifdef REG_TEST extern const BaseApi* ips_http2_decoded_header; #endif @@ -87,8 +89,9 @@ const BaseApi* sin_http2[] = #endif { &Http2Api::http2_api.base, -#ifdef REG_TEST ips_http2_frame_header, + ips_http2_frame_data, +#ifdef REG_TEST ips_http2_decoded_header, #endif nullptr diff --git a/src/service_inspectors/http2_inspect/http2_enum.h b/src/service_inspectors/http2_inspect/http2_enum.h index d9bae2af5..8e6e0043c 100644 --- a/src/service_inspectors/http2_inspect/http2_enum.h +++ b/src/service_inspectors/http2_inspect/http2_enum.h @@ -45,7 +45,11 @@ enum StreamState { STREAM_EXPECT_HEADERS, STREAM_EXPECT_BODY, STREAM_BODY, STREA // Message buffers available to clients // This enum must remain synchronized with Http2Api::classic_buffer_names[] -enum HTTP2_BUFFER { HTTP2_BUFFER_FRAME_HEADER = 1, HTTP2_BUFFER_DECODED_HEADER, HTTP2_BUFFER__MAX }; +enum HTTP2_BUFFER { HTTP2_BUFFER_FRAME_HEADER = 1, HTTP2_BUFFER_FRAME_DATA, +#ifdef REG_TEST + HTTP2_BUFFER_DECODED_HEADER, +#endif + HTTP2_BUFFER__MAX }; // Peg counts // This enum must remain synchronized with Http2Module::peg_names[] in http2_tables.cc diff --git a/src/service_inspectors/http2_inspect/http2_frame.cc b/src/service_inspectors/http2_inspect/http2_frame.cc index 1fdb5717d..f6e7d0b67 100644 --- a/src/service_inspectors/http2_inspect/http2_frame.cc +++ b/src/service_inspectors/http2_inspect/http2_frame.cc @@ -124,6 +124,8 @@ const Field& Http2Frame::get_buf(unsigned id) { case HTTP2_BUFFER_FRAME_HEADER: return header; + case HTTP2_BUFFER_FRAME_DATA: + return data; default: return Field::FIELD_NULL; } diff --git a/src/service_inspectors/http2_inspect/http2_headers_frame.cc b/src/service_inspectors/http2_inspect/http2_headers_frame.cc index a42918c7d..d871ff4ee 100644 --- a/src/service_inspectors/http2_inspect/http2_headers_frame.cc +++ b/src/service_inspectors/http2_inspect/http2_headers_frame.cc @@ -154,8 +154,10 @@ const Field& Http2HeadersFrame::get_buf(unsigned id) switch (id) { // FIXIT-E need to add a buffer for the decoded start line +#ifdef REG_TEST case HTTP2_BUFFER_DECODED_HEADER: return http1_header; +#endif default: return Http2Frame::get_buf(id); } diff --git a/src/service_inspectors/http2_inspect/http2_settings_frame.h b/src/service_inspectors/http2_inspect/http2_settings_frame.h index c4aa6bccf..94f565bf1 100644 --- a/src/service_inspectors/http2_inspect/http2_settings_frame.h +++ b/src/service_inspectors/http2_inspect/http2_settings_frame.h @@ -31,7 +31,6 @@ class Http2SettingsFrame : public Http2Frame public: friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const uint32_t, const uint8_t*, const uint32_t, Http2FlowData*, HttpCommon::SourceId, Http2Stream* stream); - bool is_detection_required() const override { return false; } #ifdef REG_TEST void print_frame(FILE* output) override; diff --git a/src/service_inspectors/http2_inspect/ips_http2.cc b/src/service_inspectors/http2_inspect/ips_http2.cc index 9af356602..e9a11dd29 100644 --- a/src/service_inspectors/http2_inspect/ips_http2.cc +++ b/src/service_inspectors/http2_inspect/ips_http2.cc @@ -82,7 +82,46 @@ IpsOption::EvalStatus Http2IpsOption::eval(Cursor& c, Packet* p) return MATCH; } -#ifdef REG_TEST +//------------------------------------------------------------------------- +// http2_frame_data +//------------------------------------------------------------------------- + +#undef IPS_OPT +#define IPS_OPT "http2_frame_data" +#undef IPS_HELP +#define IPS_HELP "rule option to set detection cursor to the HTTP/2 frame body" + +static Module* frame_data_mod_ctor() +{ + return new Http2CursorModule(IPS_OPT, IPS_HELP, HTTP2_BUFFER_FRAME_DATA, CAT_SET_OTHER, + PSI_FRAME_DATA); +} + +static const IpsApi frame_data_api = +{ + { + PT_IPS_OPTION, + sizeof(IpsApi), + IPSAPI_VERSION, + 1, + API_RESERVED, + API_OPTIONS, + IPS_OPT, + IPS_HELP, + frame_data_mod_ctor, + Http2CursorModule::mod_dtor + }, + OPT_TYPE_DETECTION, + 0, PROTO_BIT__TCP, + nullptr, + nullptr, + nullptr, + nullptr, + Http2IpsOption::opt_ctor, + Http2IpsOption::opt_dtor, + nullptr +}; + //------------------------------------------------------------------------- // http2_frame_header //------------------------------------------------------------------------- @@ -122,7 +161,6 @@ static const IpsApi frame_header_api = Http2IpsOption::opt_dtor, nullptr }; -#endif #ifdef REG_TEST //------------------------------------------------------------------------- @@ -169,8 +207,9 @@ static const IpsApi decoded_header_api = //------------------------------------------------------------------------- // plugins //------------------------------------------------------------------------- -#ifdef REG_TEST +const BaseApi* ips_http2_frame_data = &frame_data_api.base; const BaseApi* ips_http2_frame_header = &frame_header_api.base; +#ifdef REG_TEST const BaseApi* ips_http2_decoded_header = &decoded_header_api.base; #endif -- 2.47.3