From: Mike Stepanek (mstepane) Date: Thu, 27 Aug 2020 19:31:52 +0000 (+0000) Subject: Merge pull request #2407 in SNORT/snort3 from ~MDAGON/snort3:translate2 to master X-Git-Tag: 3.0.2-6~34 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bfadc12a8b7489bdf2c54c8cbe38d27a9bb03e48;p=thirdparty%2Fsnort3.git Merge pull request #2407 in SNORT/snort3 from ~MDAGON/snort3:translate2 to master Squashed commit of the following: commit 169cd2c9214765cf3756a7ba82e5f15161dd13fa Author: mdagon Date: Wed Jul 29 10:37:03 2020 -0400 payload_injector: support http2 injection --- diff --git a/src/payload_injector/dev_notes.txt b/src/payload_injector/dev_notes.txt index 0c99cf1d1..99500cff7 100644 --- a/src/payload_injector/dev_notes.txt +++ b/src/payload_injector/dev_notes.txt @@ -4,7 +4,10 @@ what to inject. Payload injector is responsible for flow-control concerns such as transmitting RST flags to endpoints as well as blocking flows within Snort. It coordinates with the Active component of Snort to perform these functions. -Currently it is being used for HTTP/1 injections. HTTP/2 support is in development. +Currently it is being used for HTTP/1 and HTTP/2 injections. For HTTP/2 +injection triggered while server-to-client flow of traffic is in a middle of a +frame is not supported. The traffic will be blocked, but the page will not be +injected/displayed. get_http2_payload supports translation of HTTP block/redirect page to HTTP2. Current implementation is limited, the constraints are specified in diff --git a/src/payload_injector/payload_injector_module.cc b/src/payload_injector/payload_injector_module.cc index 8a1d33a64..dab8cb89c 100644 --- a/src/payload_injector/payload_injector_module.cc +++ b/src/payload_injector/payload_injector_module.cc @@ -27,6 +27,8 @@ #include "detection/detection_engine.h" #include "packet_io/active.h" #include "protocols/packet.h" +#include "service_inspectors/http2_inspect/http2_flow_data.h" +#include "utils/util.h" #define s_name "payload_injector" #define s_help \ @@ -40,9 +42,28 @@ const PegInfo payload_injector_pegs[] = { { CountType::SUM, "http_injects", "total number of http injections" }, { CountType::SUM, "http2_injects", "total number of http2 injections" }, + { CountType::SUM, "http2_translate_err", "total number of http2 page translation errors" }, + { CountType::SUM, "http2_mid_frame", "total number of attempts to inject mid-frame" }, { CountType::END, nullptr, nullptr } }; +// Should have an entry for each error in InjectionReturnStatus +static const std::map InjectionErrorToString = +{ + { ERR_INJECTOR_NOT_CONFIGURED, "Payload injector is not configured" }, + { ERR_STREAM_NOT_ESTABLISHED, "TCP stream not established" }, + { ERR_UNIDENTIFIED_PROTOCOL, "Unidentified protocol" }, + { ERR_HTTP2_STREAM_ID_0, "HTTP/2 - injection to stream 0" }, + { ERR_PAGE_TRANSLATION, "Error in translating HTTP block page to HTTP/2. " + "Unsupported or bad format." }, + { ERR_HTTP2_MID_FRAME, "HTTP/2 - attempt to inject mid frame. Currently not supported." }, + { ERR_HTTP2_HDR_FIELD_VAL_LEN, "HTTP/2 header field value length > 127. " + " Currently not supported." }, + { ERR_TRANSLATED_HDRS_SIZE, + "HTTP/2 translated header size is bigger than expected. Update max size." }, + { ERR_HTTP2_BODY_SIZE, "HTTP/2 body is > 16k. Currently not supported." } +}; + bool PayloadInjectorModule::configured = false; PayloadInjectorModule::PayloadInjectorModule() : @@ -61,6 +82,46 @@ bool PayloadInjectorModule::end(const char*, int, SnortConfig*) return true; } +InjectionReturnStatus PayloadInjectorModule::inject_http2_payload(Packet* p, + const InjectionControl& control, EncodeFlags df) +{ + InjectionReturnStatus status; + + if (control.stream_id == 0) + status = ERR_HTTP2_STREAM_ID_0; + else + { + // Check if mid frame + Http2FlowData* const session_data = + (Http2FlowData*)p->flow->get_flow_data(Http2FlowData::inspector_id); + if (!session_data || session_data->is_mid_frame()) + { + payload_injector_stats.http2_mid_frame++; + // FIXIT-E mid-frame injection not supported + status = ERR_HTTP2_MID_FRAME; + } + else + { + uint8_t* http2_payload; + uint32_t payload_len; + status = get_http2_payload(control, http2_payload, payload_len); + if (status == INJECTION_SUCCESS) + { + p->active->send_data(p, df, http2_payload, payload_len); + snort_free(http2_payload); + payload_injector_stats.http2_injects++; + return INJECTION_SUCCESS; + } + else + payload_injector_stats.http2_translate_err++; + } + } + + // If we got here, shouldn't inject the page + p->active->send_data(p, df, nullptr, 0); + return status; +} + InjectionReturnStatus PayloadInjectorModule::inject_http_payload(Packet* p, const InjectionControl& control) { @@ -83,18 +144,9 @@ InjectionReturnStatus PayloadInjectorModule::inject_http_payload(Packet* p, p->active->send_data(p, df, control.http_page, control.http_page_len); } else if (strcmp(p->flow->gadget->get_name(),"http2_inspect") == 0) - { - if (control.stream_id != 0) - { - payload_injector_stats.http2_injects++; - // FIXIT-E translate page, inject payload - p->active->send_data(p, df, nullptr, 0); - } - else - status = ERR_HTTP2_STREAM_ID_0; - } - else - status = ERR_UNIDENTIFIED_PROTOCOL; + status = inject_http2_payload(p, control, df); + else + status = ERR_UNIDENTIFIED_PROTOCOL; } else status = ERR_STREAM_NOT_ESTABLISHED; @@ -111,3 +163,11 @@ InjectionReturnStatus PayloadInjectorModule::inject_http_payload(Packet* p, return status; } + +const char* PayloadInjectorModule::get_err_string(InjectionReturnStatus status) +{ + auto iter = InjectionErrorToString.find(status); + assert (iter != InjectionErrorToString.end()); + return iter->second; +} + diff --git a/src/payload_injector/payload_injector_module.h b/src/payload_injector/payload_injector_module.h index 7bd6eb241..582ade686 100644 --- a/src/payload_injector/payload_injector_module.h +++ b/src/payload_injector/payload_injector_module.h @@ -21,6 +21,7 @@ #ifndef PAYLOAD_INJECTOR_MODULE_H #define PAYLOAD_INJECTOR_MODULE_H +#include "framework/codec.h" #include "framework/module.h" namespace snort @@ -32,6 +33,8 @@ struct PayloadInjectorCounts { PegCount http_injects; PegCount http2_injects; + PegCount http2_translate_err; + PegCount http2_mid_frame; }; extern THREAD_LOCAL PayloadInjectorCounts payload_injection_stats; @@ -41,9 +44,14 @@ enum InjectionReturnStatus : int8_t INJECTION_SUCCESS = 1, ERR_INJECTOR_NOT_CONFIGURED = -1, ERR_STREAM_NOT_ESTABLISHED = -2, - ERR_HTTP2_STREAM_ID_0 = -3, - ERR_UNIDENTIFIED_PROTOCOL = -4, - ERR_PAGE_TRANSLATION = -4, + ERR_UNIDENTIFIED_PROTOCOL = -3, + ERR_HTTP2_STREAM_ID_0 = -4, + ERR_PAGE_TRANSLATION = -5, + ERR_HTTP2_MID_FRAME = -6, + ERR_HTTP2_HDR_FIELD_VAL_LEN = -7, + ERR_TRANSLATED_HDRS_SIZE = -8, + ERR_HTTP2_BODY_SIZE = -9, + // Update InjectionErrorToString when adding/removing error codes }; struct InjectionControl @@ -65,8 +73,11 @@ public: bool end(const char*, int, snort::SnortConfig*) override; - static InjectionReturnStatus inject_http_payload(snort::Packet* p, const InjectionControl& control); + static InjectionReturnStatus inject_http_payload(snort::Packet* p, const + InjectionControl& control); + static const char* get_err_string(InjectionReturnStatus status); + #ifdef UNIT_TEST void set_configured(bool val) { configured = val; } #endif @@ -74,10 +85,14 @@ public: private: static bool configured; + static InjectionReturnStatus inject_http2_payload(snort::Packet* p, const + InjectionControl& control, snort::EncodeFlags df); + #ifdef UNIT_TEST + public: #endif - static InjectionReturnStatus get_http2_payload(InjectionControl control, uint8_t *& http2_payload, uint32_t & payload_len); + static InjectionReturnStatus get_http2_payload(InjectionControl control, uint8_t*& http2_payload, uint32_t& payload_len); }; #endif diff --git a/src/payload_injector/payload_injector_translate_page.cc b/src/payload_injector/payload_injector_translate_page.cc index c0179af26..31da9ea23 100644 --- a/src/payload_injector/payload_injector_translate_page.cc +++ b/src/payload_injector/payload_injector_translate_page.cc @@ -19,11 +19,11 @@ // payload_injector_translate_page.cc author Maya Dagon // Translates HTTP 1.1 block/redirect page to HTTP2. -// 1. Headers are separated by /r/n -// 2. Headers end with /r/n +// 1. Headers are separated by \r\n or \n +// 2. Headers end with \r\n or \n // 3. Must have headers and body // 4. Translated header length <= 2000 -// 5. Supported: HTTP/1.1 403, HTTP/1.1 307, Connection: close, +// 5. Supported: HTTP/1.1 200, HTTP/1.1 403, HTTP/1.1 307, Connection: close, // Content-Length: , Content-Type: , Set-Cookie: , Location: #ifdef HAVE_CONFIG_H @@ -35,10 +35,14 @@ #include "service_inspectors/http2_inspect/http2_enum.h" #include "utils/util.h" +using namespace Http2Enums; + static const char status_403[] = "HTTP/1.1 403"; static uint8_t status_403_h2[] = { 0, 7, ':', 's', 't', 'a', 't', 'u', 's', 3, '4', '0', '3' }; static const char status_307[] = "HTTP/1.1 307"; static uint8_t status_307_h2[] = { 0, 7, ':', 's', 't', 'a', 't', 'u', 's', 3, '3', '0', '7' }; +static const char status_200[] = "HTTP/1.1 200"; +static uint8_t status_200_h2[] = { 0x88 }; static const char connection[] = "Connection: close"; static const uint8_t connection_h2[] = { 0, 10, 'c','o','n','n','e','c','t','i','o','n', 5, 'c', 'l', 'o', 's', 'e' }; @@ -50,10 +54,10 @@ static const char location[] = "Location: "; static const uint32_t max_hdr_size = 2000; // Write literal header field -static InjectionReturnStatus write_indexed(char* hdr, uint32_t len, uint8_t*& out, +static InjectionReturnStatus write_indexed(const uint8_t* hdr, uint32_t len, uint8_t*& out, uint32_t& out_free_space, const uint8_t* ind, uint8_t ind_size) { - const char* sep = (char*)memchr(hdr,':',len); + const uint8_t* sep = (const uint8_t*)memchr(hdr,':',len); assert(sep != nullptr); const uint32_t skip_len = strlen(": "); assert((sep - hdr) >= skip_len); @@ -61,7 +65,10 @@ static InjectionReturnStatus write_indexed(char* hdr, uint32_t len, uint8_t*& ou const uint8_t max_val_len = (1<<7) - 1; // FIXIT-E bigger than this will have to be 7 bit // prefix // encoded - currently not supported - if ((val_len == 0) || (val_len > max_val_len)) + if (val_len > max_val_len) + return ERR_HTTP2_HDR_FIELD_VAL_LEN; + + if (val_len == 0) return ERR_PAGE_TRANSLATION; if (out_free_space < (val_len + 1 + ind_size)) @@ -69,7 +76,7 @@ static InjectionReturnStatus write_indexed(char* hdr, uint32_t len, uint8_t*& ou #ifndef UNIT_TEST assert(false); // increase max_hdr_size #endif - return ERR_PAGE_TRANSLATION; + return ERR_TRANSLATED_HDRS_SIZE; } memcpy(out, ind, ind_size); @@ -91,7 +98,7 @@ static InjectionReturnStatus write_translation(uint8_t*& out, uint32_t& out_free #ifndef UNIT_TEST assert(false); // increase max_hdr_size #endif - return ERR_PAGE_TRANSLATION; + return ERR_TRANSLATED_HDRS_SIZE; } memcpy(out, translation, size); @@ -101,7 +108,7 @@ static InjectionReturnStatus write_translation(uint8_t*& out, uint32_t& out_free return INJECTION_SUCCESS; } -static InjectionReturnStatus translate_hdr_field(char* hdr, uint32_t len, uint8_t*& out, +static InjectionReturnStatus translate_hdr_field(const uint8_t* hdr, uint32_t len, uint8_t*& out, uint32_t& out_free_space) { if (len > strlen(status_403) && memcmp(hdr, status_403, strlen(status_403)) == 0) @@ -110,7 +117,11 @@ static InjectionReturnStatus translate_hdr_field(char* hdr, uint32_t len, uint8_ } else if (len > strlen(status_307) && memcmp(hdr, status_307, strlen(status_307)) == 0) { - return write_translation(out, out_free_space,status_307_h2, sizeof(status_307_h2)); + return write_translation(out, out_free_space, status_307_h2, sizeof(status_307_h2)); + } + else if (len > strlen(status_200) && memcmp(hdr, status_200, strlen(status_200)) == 0) + { + return write_translation(out, out_free_space, status_200_h2, sizeof(status_200_h2)); } else if (len == strlen(connection) && memcmp(hdr, connection, strlen(connection))==0) { @@ -143,37 +154,46 @@ static InjectionReturnStatus translate_hdr_field(char* hdr, uint32_t len, uint8_ return ERR_PAGE_TRANSLATION; } -static InjectionReturnStatus get_http2_hdr(char* http_page, uint32_t len, +static InjectionReturnStatus get_http2_hdr(const uint8_t* http_page, uint32_t len, uint8_t* http2_hdr, uint32_t& hdr_len, uint32_t& body_offset) { InjectionReturnStatus status = ERR_PAGE_TRANSLATION; body_offset = 0; uint32_t hdr_free_space = max_hdr_size; - char* page_cur = http_page; + const uint8_t* page_cur = http_page; uint8_t* hdr_cur = http2_hdr; while ((page_cur - http_page) < len) { - char* cr_newline = strstr(page_cur, "\r\n"); - if (cr_newline != nullptr) + const uint8_t* newline = (const uint8_t*)memchr(page_cur, '\n', len - (page_cur - http_page)); + if (newline != nullptr) { - if (cr_newline == page_cur) + // FIXIT-E only \r\n should be supported + if (newline == page_cur || (newline == page_cur + 1 && *page_cur == '\r')) { // reached end of headers - if ((page_cur - http_page + 2) < len) - body_offset = page_cur - http_page + 2; + if ((newline + 1 - http_page) < len) + body_offset = newline + 1 - http_page; break; } - status = translate_hdr_field(page_cur, cr_newline-page_cur, hdr_cur, hdr_free_space); + if (*(newline - 1) == '\r') + status = translate_hdr_field(page_cur, newline - page_cur - 1, hdr_cur, + hdr_free_space); + else + status = translate_hdr_field(page_cur, newline - page_cur, hdr_cur, + hdr_free_space); if (status != INJECTION_SUCCESS) break; - page_cur = cr_newline + 2; + page_cur = newline + 1; } else break; } - if (status == ERR_PAGE_TRANSLATION || body_offset == 0) + if (status != INJECTION_SUCCESS) + return status; + + if (body_offset == 0) return ERR_PAGE_TRANSLATION; hdr_len = hdr_cur - http2_hdr; @@ -211,35 +231,27 @@ InjectionReturnStatus PayloadInjectorModule::get_http2_payload(InjectionControl if (control.http_page == nullptr || control.http_page_len == 0) return ERR_PAGE_TRANSLATION; - // create a string version to run with strstr - char* page_string = (char*)snort_alloc(control.http_page_len + 1); - memcpy(page_string, control.http_page, control.http_page_len); - page_string[control.http_page_len] = '\0'; - uint8_t http2_hdr[max_hdr_size]; uint32_t hdr_len, body_offset; - InjectionReturnStatus status = get_http2_hdr(page_string, control.http_page_len, http2_hdr, - hdr_len, body_offset); + InjectionReturnStatus status = get_http2_hdr(control.http_page, control.http_page_len, + http2_hdr, hdr_len, body_offset); - snort_free(page_string); - - if (status == ERR_PAGE_TRANSLATION) + if (status != INJECTION_SUCCESS) return status; const uint32_t body_len = control.http_page_len - body_offset; // FIXIT-E support larger body size if (body_len > 1<<14) - return ERR_PAGE_TRANSLATION; + return ERR_HTTP2_BODY_SIZE; - payload_len = 2*Http2Enums::FRAME_HEADER_LENGTH + hdr_len + body_len; + payload_len = 2*FRAME_HEADER_LENGTH + hdr_len + body_len; http2_payload = (uint8_t*)snort_alloc(payload_len); uint8_t* http2_payload_cur = http2_payload; - // FIXIT-E update flags - write_frame_hdr(http2_payload_cur, hdr_len, Http2Enums::FT_HEADERS, 0, control.stream_id); + write_frame_hdr(http2_payload_cur, hdr_len, FT_HEADERS, END_HEADERS, control.stream_id); memcpy(http2_payload_cur, http2_hdr, hdr_len); http2_payload_cur += hdr_len; - write_frame_hdr(http2_payload_cur, body_len, Http2Enums::FT_DATA, 0, control.stream_id); + write_frame_hdr(http2_payload_cur, body_len, FT_DATA, END_STREAM, control.stream_id); memcpy(http2_payload_cur, control.http_page + body_offset, body_len); return INJECTION_SUCCESS; diff --git a/src/payload_injector/test/payload_injector_test.cc b/src/payload_injector/test/payload_injector_test.cc index d13946e8d..30c4fb8ff 100644 --- a/src/payload_injector/test/payload_injector_test.cc +++ b/src/payload_injector/test/payload_injector_test.cc @@ -29,11 +29,15 @@ #include "flow/flow.h" #include "packet_io/active.h" #include "protocols/packet.h" +#include "utils/util.h" +#include "service_inspectors/http_inspect/http_enum.h" +#include "service_inspectors/http2_inspect/http2_flow_data.h" #include #include using namespace snort; +using namespace HttpCommon; //-------------------------------------------------------------------------- // mocks @@ -44,38 +48,78 @@ uint32_t Active::send_data(snort::Packet*, EncodeFlags, unsigned char const*, un { return 1; } + void Active::block_session(snort::Packet*, bool) { } void DetectionEngine::disable_all(snort::Packet*) { } Flow::Flow() { memset(this, 0, sizeof(*this)); } Flow::~Flow() { } Packet::Packet(bool) { packet_flags = 0; flow = nullptr; } Packet::~Packet() { } +int DetectionEngine::queue_event(unsigned int, unsigned int, snort::Actions::Type) { return 0; } +FlowData::~FlowData() { } +FlowData::FlowData(unsigned int, snort::Inspector*) { } +// Inspector mocks, used by MockInspector class InspectApi mock_api; Inspector::Inspector() { set_api(&mock_api); } + Inspector::~Inspector() = default; bool Inspector::likes(Packet*) { return true; } bool Inspector::get_buf(const char*, Packet*, InspectionBuffer&) { return true; } class StreamSplitter* Inspector::get_splitter(bool) { return nullptr; } - } void show_stats(PegCount*, const PegInfo*, unsigned, const char*) { } void show_stats(PegCount*, const PegInfo*, const IndexVec&, const char*, FILE*) { } +// MockInspector class + class MockInspector : public snort::Inspector { public: - MockInspector() {} - ~MockInspector() override {} - void eval(snort::Packet*) override {} - bool configure(snort::SnortConfig*) override { return true;} + MockInspector() { } + ~MockInspector() override { } + void eval(snort::Packet*) override { } + bool configure(snort::SnortConfig*) override { return true; } }; +// Mocks for PayloadInjectorModule::get_http2_payload + +static InjectionReturnStatus translation_status = INJECTION_SUCCESS; +InjectionReturnStatus PayloadInjectorModule::get_http2_payload(InjectionControl, + uint8_t*& http2_payload, uint32_t& payload_len) +{ + if (translation_status == INJECTION_SUCCESS) + { + http2_payload = (uint8_t*)snort_alloc(1); + payload_len = 1; + } + + return translation_status; +} + +// Mocks for snort::Flow::get_flow_data + +unsigned Http2FlowData::inspector_id = 0; +Http2Stream::~Http2Stream() { } +HpackDynamicTable::~HpackDynamicTable() { } +Http2FlowData::Http2FlowData(snort::Flow*) : + FlowData(inspector_id), + flow(nullptr), + hi(nullptr), + hpack_decoder + { + Http2HpackDecoder(this, SRC_CLIENT, events[SRC_CLIENT], infractions[SRC_CLIENT]), + Http2HpackDecoder(this, SRC_SERVER, events[SRC_SERVER], infractions[SRC_SERVER]) + } + { } +Http2FlowData::~Http2FlowData() { } +Http2FlowData http2_flow_data(nullptr); +FlowData* snort::Flow::get_flow_data(unsigned int) const { return &http2_flow_data; } TEST_GROUP(payload_injector_test) { @@ -88,9 +132,14 @@ TEST_GROUP(payload_injector_test) { counts->http_injects = 0; counts->http2_injects = 0; + counts->http2_translate_err = 0; + counts->http2_mid_frame = 0; control.http_page = (const uint8_t*)"test"; control.http_page_len = 4; flow.set_state(Flow::FlowState::INSPECT); + translation_status = INJECTION_SUCCESS; + http2_flow_data.set_continuation_expected(SRC_SERVER, false); + http2_flow_data.set_reading_frame(SRC_SERVER, false); } }; @@ -103,6 +152,8 @@ TEST(payload_injector_test, not_configured_stream_not_established) CHECK(counts->http_injects == 0); CHECK(status == ERR_INJECTOR_NOT_CONFIGURED); CHECK(flow.flow_state == Flow::FlowState::BLOCK); + const char* err_string = mod.get_err_string(status); + CHECK(strcmp(err_string, "Payload injector is not configured") == 0); } TEST(payload_injector_test, not_configured_stream_established) @@ -125,6 +176,8 @@ TEST(payload_injector_test, configured_stream_not_established) InjectionReturnStatus status = mod.inject_http_payload(&p, control); CHECK(counts->http_injects == 0); CHECK(status == ERR_STREAM_NOT_ESTABLISHED); + const char* err_string = mod.get_err_string(status); + CHECK(strcmp(err_string, "TCP stream not established") == 0); CHECK(flow.flow_state == Flow::FlowState::BLOCK); } @@ -155,6 +208,8 @@ TEST(payload_injector_test, http2_stream0) CHECK(counts->http2_injects == 0); CHECK(status == ERR_HTTP2_STREAM_ID_0); CHECK(flow.flow_state == Flow::FlowState::BLOCK); + const char* err_string = mod.get_err_string(status); + CHECK(strcmp(err_string, "HTTP/2 - injection to stream 0") == 0); delete flow.gadget; } @@ -183,6 +238,8 @@ TEST(payload_injector_test, unidentified_gadget_is_null) InjectionReturnStatus status = mod.inject_http_payload(&p, control); CHECK(status == ERR_UNIDENTIFIED_PROTOCOL); CHECK(flow.flow_state == Flow::FlowState::BLOCK); + const char* err_string = mod.get_err_string(status); + CHECK(strcmp(err_string, "Unidentified protocol") == 0); } TEST(payload_injector_test, unidentified_gadget_name) @@ -199,6 +256,124 @@ TEST(payload_injector_test, unidentified_gadget_name) delete flow.gadget; } +TEST(payload_injector_test, http2_mid_frame) +{ + mod.set_configured(true); + Packet p(false); + p.packet_flags = PKT_STREAM_EST; + mock_api.base.name = "http2_inspect"; + flow.gadget = new MockInspector(); + p.flow = &flow; + control.stream_id = 1; + http2_flow_data.set_reading_frame(SRC_SERVER, true); + InjectionReturnStatus status = mod.inject_http_payload(&p, control); + CHECK(counts->http2_mid_frame == 1); + CHECK(status == ERR_HTTP2_MID_FRAME); + CHECK(flow.flow_state == Flow::FlowState::BLOCK); + const char* err_string = mod.get_err_string(status); + CHECK(strcmp(err_string, "HTTP/2 - attempt to inject mid frame. Currently not supported.") + == 0); + delete flow.gadget; +} + +TEST(payload_injector_test, http2_continuation_expected) +{ + mod.set_configured(true); + Packet p(false); + p.packet_flags = PKT_STREAM_EST; + mock_api.base.name = "http2_inspect"; + flow.gadget = new MockInspector(); + p.flow = &flow; + control.stream_id = 1; + http2_flow_data.set_continuation_expected(SRC_SERVER, true); + InjectionReturnStatus status = mod.inject_http_payload(&p, control); + CHECK(counts->http2_mid_frame == 1); + CHECK(status == ERR_HTTP2_MID_FRAME); + CHECK(flow.flow_state == Flow::FlowState::BLOCK); + delete flow.gadget; +} + +TEST_GROUP(payload_injector_translate_err_test) +{ + PayloadInjectorModule mod; + InjectionControl control; + PayloadInjectorCounts* counts = (PayloadInjectorCounts*)mod.get_counts(); + Flow flow; + InjectionReturnStatus status = INJECTION_SUCCESS; + + void setup() override + { + counts->http_injects = 0; + counts->http2_injects = 0; + counts->http2_translate_err = 0; + counts->http2_mid_frame = 0; + control.http_page = (const uint8_t*)"test"; + control.http_page_len = 4; + flow.set_state(Flow::FlowState::INSPECT); + http2_flow_data.set_continuation_expected(SRC_SERVER, false); + http2_flow_data.set_reading_frame(SRC_SERVER, false); + mod.set_configured(true); + mock_api.base.name = "http2_inspect"; + flow.gadget = new MockInspector(); + control.stream_id = 1; + } + + void teardown() override + { + CHECK(counts->http2_translate_err == 1); + CHECK(status == translation_status); + CHECK(flow.flow_state == Flow::FlowState::BLOCK); + delete flow.gadget; + } +}; + +TEST(payload_injector_translate_err_test, http2_page_translation_err) +{ + Packet p(false); + p.packet_flags = PKT_STREAM_EST; + p.flow = &flow; + translation_status = ERR_PAGE_TRANSLATION; + status = mod.inject_http_payload(&p, control); + const char* err_string = mod.get_err_string(status); + CHECK(strcmp(err_string, "Error in translating HTTP block page to HTTP/2. " + "Unsupported or bad format.") == 0); +} + +TEST(payload_injector_translate_err_test, http2_field_len_err) +{ + Packet p(false); + p.packet_flags = PKT_STREAM_EST; + p.flow = &flow; + translation_status = ERR_HTTP2_HDR_FIELD_VAL_LEN; + status = mod.inject_http_payload(&p, control); + const char* err_string = mod.get_err_string(status); + CHECK(strcmp(err_string, "HTTP/2 header field value length > 127. " + " Currently not supported.") == 0); +} + +TEST(payload_injector_translate_err_test, http2_hdrs_size) +{ + Packet p(false); + p.packet_flags = PKT_STREAM_EST; + p.flow = &flow; + translation_status = ERR_TRANSLATED_HDRS_SIZE; + status = mod.inject_http_payload(&p, control); + const char* err_string = mod.get_err_string(status); + CHECK(strcmp(err_string, + "HTTP/2 translated header size is bigger than expected. Update max size.") == 0); +} + +TEST(payload_injector_translate_err_test, http2_body_size) +{ + Packet p(false); + p.packet_flags = PKT_STREAM_EST; + p.flow = &flow; + translation_status = ERR_HTTP2_BODY_SIZE; + status = mod.inject_http_payload(&p, control); + const char* err_string = mod.get_err_string(status); + CHECK(strcmp(err_string, "HTTP/2 body is > 16k. Currently not supported.") == 0); +} + int main(int argc, char** argv) { return CommandLineTestRunner::RunAllTests(argc, argv); diff --git a/src/payload_injector/test/payload_injector_translate_test.cc b/src/payload_injector/test/payload_injector_translate_test.cc index f60ec1f45..27c964acc 100644 --- a/src/payload_injector/test/payload_injector_translate_test.cc +++ b/src/payload_injector/test/payload_injector_translate_test.cc @@ -49,30 +49,22 @@ TEST(payload_injector_translate_test, basic_hdr_translation) status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len); CHECK(status == INJECTION_SUCCESS); - uint8_t out[] = { 0x0, 0x0, 0x40, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x3, 0x34, 0x30, 0x33, 0x0, 0xa, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0xf, 0xd, - 0x3, 0x35, - 0x30, 0x34, 0xf, 0x10, 0x18, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, - 0x6c, - 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x55, 0x54, 0x46, - 0x2d, - 0x38, 0x0, 0x0, 0x62, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3c, 0x21, 0x44, 0x4f, - 0x43, 0x54, - 0x59, 0x50, 0x45, 0x20, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0xa, 0x3c, 0x68, 0x74, - 0x6d, - 0x6c, 0x3e, 0x3c, 0x68, 0x65, 0x61, 0x64, 0x3e, 0xa, 0x3c, 0x6d, 0x65, 0x74, - 0x61, - 0x68, 0x74, 0x74, 0x70, 0x2d, 0x65, 0x71, 0x75, 0x69, 0x76, 0x3d, 0x22, 0x63, - 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x22, 0x20, 0x63, - 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68, - 0x74, - 0x6d, 0x6c, 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x55, - 0x54, - 0x46, 0x2d, 0x38, 0x22, 0x20, 0x2f, 0x3e, 0xa }; + uint8_t out[] = + { + 0x0, 0x0, 0x40, 0x1, 0x4, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x3, 0x34, 0x30, 0x33, 0x0, 0xa, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0xf, 0xd, + 0x3, 0x35, 0x30, 0x34, 0xf, 0x10, 0x18, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, + 0x6c, 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x55, 0x54, 0x46, + 0x2d, 0x38, 0x0, 0x0, 0x62, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x3c, 0x21, 0x44, 0x4f, + 0x43, 0x54, 0x59, 0x50, 0x45, 0x20, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0xa, 0x3c, 0x68, 0x74, + 0x6d, 0x6c, 0x3e, 0x3c, 0x68, 0x65, 0x61, 0x64, 0x3e, 0xa, 0x3c, 0x6d, 0x65, 0x74, + 0x61, 0x68, 0x74, 0x74, 0x70, 0x2d, 0x65, 0x71, 0x75, 0x69, 0x76, 0x3d, 0x22, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x22, 0x20, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68, + 0x74, 0x6d, 0x6c, 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x55, + 0x54, 0x46, 0x2d, 0x38, 0x22, 0x20, 0x2f, 0x3e, 0xa + }; CHECK(payload_len == sizeof(out)); CHECK(memcmp(http2_payload, out, payload_len) == 0); @@ -92,16 +84,75 @@ TEST(payload_injector_translate_test, basic_hdr_translation2) CHECK(status == INJECTION_SUCCESS); - uint8_t out[] = { 0x0, 0x0, 0x36, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x3, 0x33, 0x30, 0x37, 0xf, 0x1f, 0x8, 0x68, 0x74, - 0x74, 0x70, - 0x73, 0x3a, 0x2f, 0x2f, 0xf, 0x28, 0x1b, 0x30, 0x34, 0x66, 0x32, 0x3b, 0x20, - 0x4d, - 0x61, 0x78, 0x2d, 0x41, 0x67, 0x65, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x3b, 0x20, - 0x70, - 0x61, 0x74, 0x68, 0x3d, 0x2f, 0x3b, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x42, - 0x6f, 0x64, 0x79, 0xa }; + uint8_t out[] = + { + 0x0, 0x0, 0x36, 0x1, 0x4, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x3, 0x33, 0x30, 0x37, 0xf, 0x1f, 0x8, 0x68, 0x74, + 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0xf, 0x28, 0x1b, 0x30, 0x34, 0x66, 0x32, 0x3b, 0x20, + 0x4d, 0x61, 0x78, 0x2d, 0x41, 0x67, 0x65, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x3b, 0x20, + 0x70, 0x61, 0x74, 0x68, 0x3d, 0x2f, 0x3b, 0x0, 0x0, 0x5, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x1, 0x42, 0x6f, 0x64, 0x79, 0xa + }; + CHECK(payload_len == sizeof(out)); + CHECK(memcmp(http2_payload, out, payload_len) == 0); + + snort_free(http2_payload); +} + +// Same like 2 , using \n instead of \r\n +TEST(payload_injector_translate_test, basic_hdr_translation3) +{ + char http_page[] = + "HTTP/1.1 307 Proxy Redirect\nLocation: https://\nSet-Cookie: 04f2; Max-Age: 600; path=/;\n\nBody\n"; + + InjectionControl control; + control.stream_id = 1; + control.http_page = (uint8_t*)http_page; + control.http_page_len = strlen(http_page); + status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len); + + CHECK(status == INJECTION_SUCCESS); + + uint8_t out[] = + { + 0x0, 0x0, 0x36, 0x1, 0x4, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x3, 0x33, 0x30, 0x37, 0xf, 0x1f, 0x8, 0x68, 0x74, + 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0xf, 0x28, 0x1b, 0x30, 0x34, 0x66, 0x32, 0x3b, 0x20, + 0x4d, 0x61, 0x78, 0x2d, 0x41, 0x67, 0x65, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x3b, 0x20, + 0x70, 0x61, 0x74, 0x68, 0x3d, 0x2f, 0x3b, 0x0, 0x0, 0x5, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x1, 0x42, 0x6f, 0x64, 0x79, 0xa + }; + CHECK(payload_len == sizeof(out)); + CHECK(memcmp(http2_payload, out, payload_len) == 0); + + snort_free(http2_payload); +} + +// Mix of \r\n and \n, body length == 1 +TEST(payload_injector_translate_test, mix_n_and_rn) +{ + char http_page[] = + "HTTP/1.1 200 OK\nConnection: close\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\nContent-Length: 956\r\nContent-Type: text/html; charset=UTF-8\n\nb"; + + InjectionControl control; + control.stream_id = 1; + control.http_page = (uint8_t*)http_page; + control.http_page_len = strlen(http_page); + status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len); + + CHECK(status == INJECTION_SUCCESS); + + uint8_t out[] = + { + 0x0, 0x0, 0x52, 0x1, 0x4, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0xa, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5, 0x63, 0x6c, 0x6f, 0x73, + 0x65, 0xf, 0x28, 0x1b, 0x30, 0x34, 0x66, 0x32, 0x3b, 0x20, 0x4d, 0x61, 0x78, 0x2d, 0x41, + 0x67, 0x65, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x3b, 0x20, 0x70, 0x61, 0x74, 0x68, 0x3d, + 0x2f, 0x3b, 0xf, 0xd, 0x3, 0x39, 0x35, 0x36, 0xf, 0x10, 0x18, 0x74, 0x65, 0x78, + 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, + 0x3d, 0x55, 0x54, 0x46, 0x2d, 0x38, 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, + 0x62 + }; CHECK(payload_len == sizeof(out)); CHECK(memcmp(http2_payload, out, payload_len) == 0); @@ -177,10 +228,10 @@ TEST(payload_injector_translate_test, extra_space_before_colon) CHECK(status == ERR_PAGE_TRANSLATION); } -TEST(payload_injector_translate_test, unsuporrted_status) +TEST(payload_injector_translate_test, unsupported_status) { char http_page[] = - "HTTP/1.1 200 OK\r\nLocation: https://\r\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\n\r\nBody\n"; + "HTTP/1.1 201 Created\r\nLocation: https://\r\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\n\r\nBody\n"; InjectionControl control; control.stream_id = 1; @@ -246,7 +297,7 @@ TEST(payload_injector_translate_test, val_len_too_big) control.http_page_len = size; status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len); - CHECK(status == ERR_PAGE_TRANSLATION); + CHECK(status == ERR_HTTP2_HDR_FIELD_VAL_LEN); } // Header value has maximum supported length 127 @@ -295,9 +346,9 @@ TEST(payload_injector_translate_test, http2_hdr_is_max) CHECK(status == INJECTION_SUCCESS); CHECK(payload_len == 2019); - uint8_t hdr[] = { 0x0, 0x7, 0xd0, 0x1, 0x0, 0x0, 0x0, 0xf0, 0x0 }; + uint8_t hdr[] = { 0x0, 0x7, 0xd0, 0x1, 0x4, 0x0, 0x0, 0xf0, 0x0 }; CHECK(memcmp(http2_payload, hdr, sizeof(hdr))==0); - uint8_t body[] = { 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0xf0, 0x0, 'b' }; + uint8_t body[] = { 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0xf0, 0x0, 'b' }; CHECK(memcmp(http2_payload + 2009, body, sizeof(body))==0); snort_free(http2_payload); } @@ -325,7 +376,7 @@ TEST(payload_injector_translate_test, http2_hdr_too_big) control.http_page_len = size; status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len); - CHECK(status == ERR_PAGE_TRANSLATION); + CHECK(status == ERR_TRANSLATED_HDRS_SIZE); } // Translated header > 2000. Goes through write_translation code path. @@ -347,7 +398,7 @@ TEST(payload_injector_translate_test, http2_hdr_too_big2) control.http_page = http_page; control.http_page_len = size; status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len); - CHECK(status == ERR_PAGE_TRANSLATION); + CHECK(status == ERR_TRANSLATED_HDRS_SIZE); } TEST(payload_injector_translate_test, payload_body_larger_than_max) @@ -362,7 +413,7 @@ TEST(payload_injector_translate_test, payload_body_larger_than_max) control.http_page = http_page; control.http_page_len = size; status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len); - CHECK(status == ERR_PAGE_TRANSLATION); + CHECK(status == ERR_HTTP2_BODY_SIZE); } TEST(payload_injector_translate_test, http_page_is_nullptr) @@ -376,7 +427,7 @@ TEST(payload_injector_translate_test, http_page_is_nullptr) TEST(payload_injector_translate_test, http_page_is_0_length) { - uint8_t http_page[] = {1}; + uint8_t http_page[] = { 1 }; InjectionControl control; control.http_page = http_page; diff --git a/src/service_inspectors/http2_inspect/http2_data_cutter.cc b/src/service_inspectors/http2_inspect/http2_data_cutter.cc index e764f9e01..fea205216 100644 --- a/src/service_inspectors/http2_inspect/http2_data_cutter.cc +++ b/src/service_inspectors/http2_inspect/http2_data_cutter.cc @@ -110,6 +110,9 @@ bool Http2DataCutter::http2_scan(const uint8_t* data, uint32_t length, } } + if (data_state == FULL_FRAME) + session_data->reading_frame[source_id] = false; + frame_bytes_seen += (cur_pos - leftover_bytes - data_offset - leftover_padding); *flush_offset = data_offset = cur_pos; session_data->scan_remaining_frame_octets[source_id] = frame_length - frame_bytes_seen; diff --git a/src/service_inspectors/http2_inspect/http2_flow_data.h b/src/service_inspectors/http2_inspect/http2_flow_data.h index 93f20b026..4e10dfb02 100644 --- a/src/service_inspectors/http2_inspect/http2_flow_data.h +++ b/src/service_inspectors/http2_inspect/http2_flow_data.h @@ -96,6 +96,16 @@ public: Http2ConnectionSettings* get_connection_settings(const HttpCommon::SourceId source_id) { return &connection_settings[source_id]; } + bool is_mid_frame(const HttpCommon::SourceId source_id = HttpCommon::SRC_SERVER) + { return (continuation_expected[source_id] || reading_frame[source_id]); } + +#ifdef UNIT_TEST + void set_reading_frame(HttpCommon::SourceId source_id, bool val) + { reading_frame[source_id] = val;} + void set_continuation_expected(HttpCommon::SourceId source_id, bool val) + { continuation_expected[source_id] = val;} +#endif + protected: snort::Flow* flow; HttpInspect* const hi; @@ -156,6 +166,10 @@ protected: uint8_t padding_octets_in_frame[2] = { 0, 0 }; bool get_padding_len[2] = { false, false }; + // used to signal frame wasn't fully read yet, + // currently used by payload injector + bool reading_frame[2] = { false, false }; + #ifdef REG_TEST static uint64_t instance_count; uint64_t seq_num; diff --git a/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc b/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc index 1966c216e..0cf0422d7 100644 --- a/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc +++ b/src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc @@ -142,6 +142,7 @@ StreamSplitter::Status Http2StreamSplitter::non_data_scan(Http2FlowData* session } // Have the full frame + session_data->reading_frame[source_id] = false; StreamSplitter::Status status = StreamSplitter::FLUSH; switch (type) { @@ -215,6 +216,7 @@ bool Http2StreamSplitter::read_frame_hdr(Http2FlowData* session_data, const uint { // Scanning a new frame session_data->num_frame_headers[source_id] += 1; + session_data->reading_frame[source_id] = true; } // The first nine bytes are the frame header. But all nine might not all be