From: Adrian Mamolea (admamole) Date: Mon, 3 Nov 2025 20:14:05 +0000 (+0000) Subject: Pull request #4893: http_inspect: waf buffers X-Git-Tag: 3.9.7.0~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4ab9aaf774d651299a7033ff341b6cf552237ab5;p=thirdparty%2Fsnort3.git Pull request #4893: http_inspect: waf buffers Merge in SNORT/snort3 from ~ADMAMOLE/snort3:log_buffers to master Squashed commit of the following: commit c2b242a909c4bd36d03b4b16f9c267857ce27580 Author: Adrian Mamolea Date: Tue Sep 2 12:32:45 2025 -0400 http_inspect: add waf buffers --- diff --git a/src/log/unified2.h b/src/log/unified2.h index 3ff3e981b..4db3822c9 100644 --- a/src/log/unified2.h +++ b/src/log/unified2.h @@ -44,6 +44,11 @@ #define UNIFIED2_EVENT3 114 #define MAX_EVENT_APPNAME_LEN 64 +#define MAX_HTTP_METHOD_LEN 80 +#define MAX_HTTP_VERSION_LEN 16 +#define MAX_HTTP_USER_AGENT_LEN 256 +#define MAX_HTTP_REFERER_LEN 256 +#define MAX_MESSAGE_DETAIL_LEN 256 /* Data structure used for serialization of Unified2 Records */ struct Serial_Unified2_Header @@ -90,6 +95,15 @@ struct Unified2Event uint8_t snort_action; // pass=0, drop, block, reset char app_name[MAX_EVENT_APPNAME_LEN]; + + uint32_t request_size; + uint32_t response_size; + + char http_method[MAX_HTTP_METHOD_LEN]; + char http_version[MAX_HTTP_VERSION_LEN]; + char http_user_agent[MAX_HTTP_USER_AGENT_LEN]; + char http_referer[MAX_HTTP_REFERER_LEN]; + char message_detail[MAX_MESSAGE_DETAIL_LEN]; }; // UNIFIED2_IDS_EVENT_VLAN = type 104 diff --git a/src/loggers/unified2.cc b/src/loggers/unified2.cc index 8a44202fa..5caf12199 100644 --- a/src/loggers/unified2.cc +++ b/src/loggers/unified2.cc @@ -232,6 +232,39 @@ static void alert_event(Packet* p, const char*, Unified2Config* config, const Ev u2_event.snort_status = p->active->get_status(); u2_event.snort_action = p->active->get_action(); + + if ( p->flow && p->flow->gadget ) + { + Inspector* gadget = p->flow->gadget; + InspectionBuffer buf; + + if ( gadget->get_buf("http_method_str", p, buf) ) + strncpy(u2_event.http_method, + reinterpret_cast(buf.data), MAX_HTTP_METHOD_LEN - 1); + + if ( gadget->get_buf("request_size", p, buf) ) + u2_event.request_size = htonl(*reinterpret_cast(buf.data)); + + if ( gadget->get_buf("response_size", p, buf) ) + u2_event.response_size = htonl(*reinterpret_cast(buf.data)); + + if ( gadget->get_buf("http_version_str", p, buf) ) + strncpy(u2_event.http_version, + reinterpret_cast(buf.data), MAX_HTTP_VERSION_LEN - 1); + + if ( gadget->get_buf("http_user_agent_str", p, buf) ) + strncpy(u2_event.http_user_agent, + reinterpret_cast(buf.data), MAX_HTTP_USER_AGENT_LEN - 1); + + if ( gadget->get_buf("http_referer_str", p, buf) ) + strncpy(u2_event.http_referer, + reinterpret_cast(buf.data), MAX_HTTP_REFERER_LEN - 1); + + std::string message_detail = "detail_" + std::to_string(gid) + "_" + std::to_string(sid); + if ( gadget->get_buf(message_detail.c_str(), p, buf) ) + strncpy(u2_event.message_detail, + reinterpret_cast(buf.data), MAX_MESSAGE_DETAIL_LEN - 1); + } } Serial_Unified2_Header hdr; diff --git a/src/pub_sub/test/pub_sub_http_transaction_end_event_test.cc b/src/pub_sub/test/pub_sub_http_transaction_end_event_test.cc index 116265a0c..4956e38db 100644 --- a/src/pub_sub/test/pub_sub_http_transaction_end_event_test.cc +++ b/src/pub_sub/test/pub_sub_http_transaction_end_event_test.cc @@ -139,6 +139,7 @@ HttpMsgSection::HttpMsgSection(const uint8_t* buffer, const uint16_t buf_size, tcp_close(false) {} void HttpMsgSection::clear(){} +void HttpMsgSection::clear_tmp_buffers() { } bool HttpMsgSection::run_detection(snort::Packet*) { return false; } void HttpMsgHeadShared::analyze() {} diff --git a/src/service_inspectors/http_inspect/http_api.h b/src/service_inspectors/http_inspect/http_api.h index 3a5f33425..b0e015e7c 100644 --- a/src/service_inspectors/http_inspect/http_api.h +++ b/src/service_inspectors/http_inspect/http_api.h @@ -47,7 +47,15 @@ "http_uri", \ "http_version", \ "js_data", \ - "vba_data" + "vba_data", \ + "http_method_str", \ + "request_size", \ + "response_size", \ + "http_version_str", \ + "http_user_agent_str", \ + "http_referer_str", \ + "detail_119_20", \ + "detail_119_287" class HttpApi { diff --git a/src/service_inspectors/http_inspect/http_enum.h b/src/service_inspectors/http_inspect/http_enum.h index edbeb4db8..1a18a4982 100755 --- a/src/service_inspectors/http_inspect/http_enum.h +++ b/src/service_inspectors/http_inspect/http_enum.h @@ -56,6 +56,10 @@ enum HTTP_RULE_OPT { HTTP_BUFFER_CLIENT_BODY = 2, 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_BUFFER_METHOD_STR, BUFFER_REQUEST_SIZE, BUFFER_RESPONSE_SIZE, + HTTP_BUFFER_VERSION_STR, HTTP_BUFFER_USER_AGENT_STR, HTTP_BUFFER_REFERER_STR, + DETAIL_119_20, DETAIL_119_287, + HTTP__TMP_BUFFER_MAX = DETAIL_119_287, HTTP_RANGE_NUM_HDRS, HTTP_RANGE_NUM_TRAILERS, HTTP_VERSION_MATCH, HTTP_HEADER_TEST, HTTP_TRAILER_TEST, HTTP_RANGE_NUM_COOKIES, HTTP_RANGE_MAX_HEADER_LINE, HTTP_RANGE_MAX_TRAILER_LINE, HTTP__MAX_RULE_OPTION }; diff --git a/src/service_inspectors/http_inspect/http_flow_data.cc b/src/service_inspectors/http_inspect/http_flow_data.cc index 6ad049d19..03320c09f 100644 --- a/src/service_inspectors/http_inspect/http_flow_data.cc +++ b/src/service_inspectors/http_inspect/http_flow_data.cc @@ -125,6 +125,8 @@ HttpFlowData::~HttpFlowData() discard_list = discard_list->next; delete tmp; } + + HttpMsgSection::clear_tmp_buffers(); } void HttpFlowData::half_reset(SourceId source_id) diff --git a/src/service_inspectors/http_inspect/http_msg_head_shared.h b/src/service_inspectors/http_inspect/http_msg_head_shared.h index 2edf06857..4f520c0ab 100755 --- a/src/service_inspectors/http_inspect/http_msg_head_shared.h +++ b/src/service_inspectors/http_inspect/http_msg_head_shared.h @@ -45,6 +45,9 @@ public: const Field& get_all_header_values_raw(HttpEnums::HeaderId header_id); const Field& get_header_value_norm(HttpEnums::HeaderId header_id); int get_header_count(HttpEnums::HeaderId header_id) const; + uint32_t get_length() const override + // +4 to account for \r\n\r\n at the end of the section that was removed by the splitter + { auto len = HttpMsgSection::get_length(); return (len > 0) ? len + 4 : 0; } // Tables of header field names and header value names static const StrCode header_list[]; diff --git a/src/service_inspectors/http_inspect/http_msg_section.cc b/src/service_inspectors/http_inspect/http_msg_section.cc index d1a5d3f93..b31571d95 100644 --- a/src/service_inspectors/http_inspect/http_msg_section.cc +++ b/src/service_inspectors/http_inspect/http_msg_section.cc @@ -44,6 +44,193 @@ using namespace HttpCommon; using namespace HttpEnums; using namespace snort; +static constexpr uint TMP_BUFFER_CNT = HTTP__TMP_BUFFER_MAX - HTTP__BUFFER_MAX; +THREAD_LOCAL Field* tmp_buffers[TMP_BUFFER_CNT] = { nullptr }; + +void HttpMsgSection::clear_tmp_buffers() +{ + for (unsigned i = 0; i < TMP_BUFFER_CNT; i++) + { + delete tmp_buffers[i]; + tmp_buffers[i] = nullptr; + } +} + +static Field* tmp_field_from_data(const uint8_t* data, uint32_t len) +{ + uint8_t* val_buf = new uint8_t[len + 1]; + memcpy(val_buf, data, len); + val_buf[len] = '\0'; + return new Field(len + 1, val_buf, true); +} + +static Field* tmp_field(uint32_t val) +{ + uint32_t* val_buf = new uint32_t[1]; + *val_buf = val; + return new Field(sizeof(uint32_t), reinterpret_cast(val_buf), true); +} + +static Field* tmp_field(const std::string& str) +{ + return tmp_field_from_data(reinterpret_cast(str.c_str()), str.length()); +} + +static Field* tmp_field(const Field& f) +{ + int32_t len = f.length(); + if (len <= 0) + return new Field(STAT_NOT_PRESENT); + + const uint8_t* data = f.start(); + const uint8_t* end = data + len; + const uint8_t* p = data; + for (; p < end; ++p) + { + uint8_t c = *p; + if (c < 0x20 || c > 0x7E || c == '\\') + break; + } + + if (p == end) + return tmp_field_from_data(data, len); // No escaping needed + + static constexpr char hex_chars[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + std::string escaped; + escaped.reserve(len * 2); // Will grow if needed + escaped.append(reinterpret_cast(data), p - data); + + for (; p < end; ++p) + { + uint8_t c = *p; + + if (c == '\\') + escaped.append("\\x5c"); + else if (c < 0x20 || c > 0x7E) + { + escaped.append("\\x"); + escaped.push_back(hex_chars[(c >> 4) & 0x0F]); + escaped.push_back(hex_chars[c & 0x0F]); + } + else + escaped.push_back(c); + } + + return tmp_field_from_data(reinterpret_cast(escaped.c_str()), escaped.length()); +} + +Field* HttpMsgSection::compute_http_method_str(const HttpBufferInfo&) +{ + return tmp_field(get_classic_buffer(HTTP_BUFFER_METHOD, 0, 0)); +} + +Field* HttpMsgSection::compute_request_size(const HttpBufferInfo&) +{ + uint32_t val = 0; + val += get_section_len(request); + val += get_section_len(header[SRC_CLIENT]); + val += transaction->get_body_len(HttpCommon::SRC_CLIENT); + val += get_section_len(trailer[SRC_CLIENT]); + return tmp_field(val); +} + +Field* HttpMsgSection::compute_response_size(const HttpBufferInfo&) +{ + uint32_t val = 0; + val += get_section_len(status); + val += get_section_len(header[SRC_SERVER]); + val += transaction->get_body_len(HttpCommon::SRC_SERVER); + val += get_section_len(trailer[SRC_SERVER]); + return tmp_field(val); +} + +Field* HttpMsgSection::compute_http_version_str(const HttpBufferInfo& buf) +{ + VersionId version = get_version_id(buf); + const auto& iter = VersionEnumToStr.find(version); + if (iter == VersionEnumToStr.end()) + return new Field(STAT_NOT_PRESENT); + + std::string val = "HTTP/"; + val.append(iter->second); + return tmp_field(val); +} + +Field* HttpMsgSection::compute_http_user_agent_str(const HttpBufferInfo&) +{ + return tmp_field(get_classic_buffer(HTTP_BUFFER_HEADER, HEAD_USER_AGENT, 0)); +} + +Field* HttpMsgSection::compute_http_referer_str(const HttpBufferInfo&) +{ + return tmp_field(get_classic_buffer(HTTP_BUFFER_HEADER, HEAD_REFERER, 0)); +} + +Field* HttpMsgSection::compute_detail_119_20(const HttpBufferInfo& buf) +{ + // EVENT_MAX_HEADERS + // Header Count: XXX + std::string val = "Header Count: "; + auto cnt = get_num_headers(buf); + if (cnt >= 0) + val.append(std::to_string(cnt)); + else + { + val.append(""); + } + + return tmp_field(val); +} + +Field* HttpMsgSection::compute_detail_119_287(const HttpBufferInfo&) +{ + // EVENT_DISALLOWED_METHOD + // HTTP Method: XXX + std::string val = "HTTP Method: "; + const Field& method = get_classic_buffer(HTTP_BUFFER_METHOD, 0, 0); + if (method.length() > 0) + val.append(reinterpret_cast(method.start()), method.length()); + else + { + val.append(""); + } + + return tmp_field(val); +} + +const Field& HttpMsgSection::get_tmp_buffer(const HttpBufferInfo& buf) +{ + typedef Field* (HttpMsgSection::*ComputeFunction)(const HttpBufferInfo&); + + static const ComputeFunction compute_functions[TMP_BUFFER_CNT] = { + &HttpMsgSection::compute_http_method_str, // HTTP_BUFFER_METHOD_STR + &HttpMsgSection::compute_request_size, // BUFFER_REQUEST_SIZE + &HttpMsgSection::compute_response_size, // BUFFER_RESPONSE_SIZE + &HttpMsgSection::compute_http_version_str, // HTTP_BUFFER_VERSION_STR + &HttpMsgSection::compute_http_user_agent_str, // HTTP_BUFFER_USER_AGENT_STR + &HttpMsgSection::compute_http_referer_str, // HTTP_BUFFER_REFERER_STR + &HttpMsgSection::compute_detail_119_20, // DETAIL_119_20, EVENT_MAX_HEADERS + &HttpMsgSection::compute_detail_119_287 // DETAIL_119_287, EVENT_DISALLOWED_METHOD + }; + + const unsigned index = buf.type - (HTTP__BUFFER_MAX + 1); + assert(index < TMP_BUFFER_CNT); + + if (tmp_buffers[index] == nullptr) + tmp_buffers[index] = (this->*compute_functions[index])(buf); + + assert(tmp_buffers[index] != nullptr); + return *tmp_buffers[index]; +} + HttpMsgSection::HttpMsgSection(const uint8_t* buffer, const uint16_t buf_size, HttpFlowData* session_data_, SourceId source_id_, bool buf_owner, Flow* flow_, const HttpParaList* params_) : @@ -281,7 +468,11 @@ const Field& HttpMsgSection::get_classic_buffer(const HttpBufferInfo& buf) return Field::FIELD_NULL; } default: - assert(buf.type <= HTTP__BUFFER_MAX); + if (buf.type <= HTTP__BUFFER_MAX) + return Field::FIELD_NULL; + else if (buf.type <= HTTP__TMP_BUFFER_MAX) + return get_tmp_buffer(buf); + assert(false); return Field::FIELD_NULL; } } @@ -464,6 +655,7 @@ void HttpMsgSection::get_related_sections() void HttpMsgSection::clear() { + clear_tmp_buffers(); transaction->clear_section(); cleared = true; } diff --git a/src/service_inspectors/http_inspect/http_msg_section.h b/src/service_inspectors/http_inspect/http_msg_section.h index 668a7601e..442a5543e 100644 --- a/src/service_inspectors/http_inspect/http_msg_section.h +++ b/src/service_inspectors/http_inspect/http_msg_section.h @@ -82,6 +82,7 @@ public: int32_t get_status_code_num() const { return status_code_num; } + static void clear_tmp_buffers(); virtual void clear(); bool is_clear() { return cleared; } @@ -91,6 +92,11 @@ public: int32_t get_num_cookies(const HttpBufferInfo& buf) const; HttpEnums::VersionId get_version_id(const HttpBufferInfo& buf) const; + static uint32_t get_section_len(const HttpMsgSection* sec) + { return (sec != nullptr) ? sec->get_length() : 0; } + virtual uint32_t get_length() const + { return (msg_text.length() > 0) ? msg_text.length() : 0; } + HttpMsgSection* next = nullptr; #ifdef REG_TEST @@ -136,6 +142,17 @@ protected: void print_section_wrapup(FILE* output) const; void print_peg_counts(FILE* output) const; #endif + +private: + const Field& get_tmp_buffer(const HttpBufferInfo& buf); + Field* compute_http_method_str(const HttpBufferInfo& buf); + Field* compute_request_size(const HttpBufferInfo& buf); + Field* compute_response_size(const HttpBufferInfo& buf); + Field* compute_http_version_str(const HttpBufferInfo& buf); + Field* compute_http_user_agent_str(const HttpBufferInfo& buf); + Field* compute_http_referer_str(const HttpBufferInfo& buf); + Field* compute_detail_119_20(const HttpBufferInfo& buf); + Field* compute_detail_119_287(const HttpBufferInfo& buf); }; #endif diff --git a/src/service_inspectors/http_inspect/http_msg_start.h b/src/service_inspectors/http_inspect/http_msg_start.h index 377cfa669..c5af8127d 100644 --- a/src/service_inspectors/http_inspect/http_msg_start.h +++ b/src/service_inspectors/http_inspect/http_msg_start.h @@ -35,6 +35,9 @@ public: bool detection_required() const override { return false; } const Field& get_version() const { return version; } HttpEnums::VersionId get_version_id() const { return version_id; } + uint32_t get_length() const override + // +2 to account for \r\n at the end of the section that was removed by the splitter + { auto len = HttpMsgSection::get_length(); return (len > 0) ? len + 2 : 0; } protected: HttpMsgStart(const uint8_t* buffer, const uint16_t buf_size, HttpFlowData* session_data_, diff --git a/src/service_inspectors/http_inspect/test/http_transaction_test.cc b/src/service_inspectors/http_inspect/test/http_transaction_test.cc index 1519e56c4..31f42ce4f 100644 --- a/src/service_inspectors/http_inspect/test/http_transaction_test.cc +++ b/src/service_inspectors/http_inspect/test/http_transaction_test.cc @@ -29,6 +29,7 @@ #include "service_inspectors/http_inspect/http_flow_data.h" #include "service_inspectors/http_inspect/http_inspect.h" #include "service_inspectors/http_inspect/http_module.h" +#include "service_inspectors/http_inspect/http_msg_section.h" #include "service_inspectors/http_inspect/http_transaction.h" #include "service_inspectors/http2_inspect/http2_flow_data.h" @@ -112,6 +113,7 @@ const snort::StreamBuffer HttpStreamSplitter::reassemble(snort::Flow*, unsigned, } bool HttpStreamSplitter::finish(snort::Flow*) { return false; } void HttpStreamSplitter::prep_partial_flush(snort::Flow*, uint32_t, uint32_t, uint32_t) { } +void HttpMsgSection::clear_tmp_buffers() { } THREAD_LOCAL PegCount HttpModule::peg_counts[PEG_COUNT_MAX] = { }; const Field Field::FIELD_NULL { STAT_NO_SOURCE }; diff --git a/tools/u2spewfoo/u2spewfoo.cc b/tools/u2spewfoo/u2spewfoo.cc index 36cf53144..d3915d952 100644 --- a/tools/u2spewfoo/u2spewfoo.cc +++ b/tools/u2spewfoo/u2spewfoo.cc @@ -349,6 +349,21 @@ static void event3_dump(u2record* record) printf("\tApp Name: %s\n", event.app_name[0] ? event.app_name : "none"); + if (event.http_method[0]) + printf("\tHTTP Method: %s\n", event.http_method); + if (event.request_size) + printf("\tRequest Size: %u\n", htonl(event.request_size)); + if (event.response_size) + printf("\tResponse Size: %u\n", htonl(event.response_size)); + if (event.http_version[0]) + printf("\tHTTP Version: %s\n", event.http_version); + if (event.http_user_agent[0]) + printf("\tHTTP User-Agent: %s\n", event.http_user_agent); + if (event.http_referer[0]) + printf("\tHTTP Referer: %s\n", event.http_referer); + if (event.message_detail[0]) + printf("\tMessage Detail: %s\n", event.message_detail); + printf( "\tStatus: %s\tAction: %s\n", get_status(event.snort_status), get_action(event.snort_action));