]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4893: http_inspect: waf buffers
authorAdrian Mamolea (admamole) <admamole@cisco.com>
Mon, 3 Nov 2025 20:14:05 +0000 (20:14 +0000)
committerRayen Mohanty (ramohant) <ramohant@cisco.com>
Mon, 3 Nov 2025 20:14:05 +0000 (20:14 +0000)
Merge in SNORT/snort3 from ~ADMAMOLE/snort3:log_buffers to master

Squashed commit of the following:

commit c2b242a909c4bd36d03b4b16f9c267857ce27580
Author: Adrian Mamolea <admamole@cisco.com>
Date:   Tue Sep 2 12:32:45 2025 -0400

    http_inspect: add waf buffers

12 files changed:
src/log/unified2.h
src/loggers/unified2.cc
src/pub_sub/test/pub_sub_http_transaction_end_event_test.cc
src/service_inspectors/http_inspect/http_api.h
src/service_inspectors/http_inspect/http_enum.h
src/service_inspectors/http_inspect/http_flow_data.cc
src/service_inspectors/http_inspect/http_msg_head_shared.h
src/service_inspectors/http_inspect/http_msg_section.cc
src/service_inspectors/http_inspect/http_msg_section.h
src/service_inspectors/http_inspect/http_msg_start.h
src/service_inspectors/http_inspect/test/http_transaction_test.cc
tools/u2spewfoo/u2spewfoo.cc

index 3ff3e981b0a9c8ddef5d8c3aa7b5ac8aa74e8f47..4db3822c9d8479a551496a1033b032e3f4d50b51 100644 (file)
 #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
index 8a44202fa8aed82aed9a86e1b3d9afa91d285b3a..5caf1219916c4bd1dc1879d1ce5f37a34e50bb33 100644 (file)
@@ -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<const char*>(buf.data), MAX_HTTP_METHOD_LEN - 1);
+
+            if ( gadget->get_buf("request_size", p, buf) )
+                u2_event.request_size = htonl(*reinterpret_cast<const uint32_t*>(buf.data));
+
+            if ( gadget->get_buf("response_size", p, buf) )
+                u2_event.response_size = htonl(*reinterpret_cast<const uint32_t*>(buf.data));
+
+            if ( gadget->get_buf("http_version_str", p, buf) )
+                strncpy(u2_event.http_version,
+                    reinterpret_cast<const char*>(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<const char*>(buf.data), MAX_HTTP_USER_AGENT_LEN - 1);
+
+            if ( gadget->get_buf("http_referer_str", p, buf) )
+                strncpy(u2_event.http_referer,
+                    reinterpret_cast<const char*>(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<const char*>(buf.data), MAX_MESSAGE_DETAIL_LEN - 1);
+        }
     }
 
     Serial_Unified2_Header hdr;
index 116265a0cb6b4e924e74f5ed0596e63c748c5bbd..4956e38db9c5ef6a4f53331ccb857aeac949c5d1 100644 (file)
@@ -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() {}
 
index 3a5f3342509527f7038b005158c1a259eb7f1ac8..b0e015e7ce34feab2b68f3dd80c838cf604294db 100644 (file)
     "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
 {
index edbeb4db8464d7f8ed54e6520147f2132ede2585..1a18a498266e42d52aa88267abf66bd0e037c95f 100755 (executable)
@@ -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 };
index 6ad049d19b80a8cd2fa5bbf07339340f3e40f43f..03320c09f1cb704ed6d01053becb576212437732 100644 (file)
@@ -125,6 +125,8 @@ HttpFlowData::~HttpFlowData()
         discard_list = discard_list->next;
         delete tmp;
     }
+
+    HttpMsgSection::clear_tmp_buffers();
 }
 
 void HttpFlowData::half_reset(SourceId source_id)
index 2edf06857ed7093139d871c4c9f1d077b6032094..4f520c0abfa2d93b5d322a5f8dae13db30d5ad0a 100755 (executable)
@@ -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[];
index d1a5d3f9338a435ea8b6768bf1d0a63b51b8071b..b31571d95c484cb0c357f58dd091ec2a3a72be45 100644 (file)
@@ -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<uint8_t*>(val_buf), true);
+}
+
+static Field* tmp_field(const std::string& str)
+{
+    return tmp_field_from_data(reinterpret_cast<const uint8_t*>(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<const char*>(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<const uint8_t*>(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("<no header ");
+        val.append(std::to_string(cnt));
+        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<const char*>(method.start()), method.length());
+    else
+    {
+        val.append("<no method ");
+        val.append(std::to_string(method.length()));
+        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;
 }
index 668a7601eb00fb8992ded7953a62e0ff635cf511..442a5543e3eb0c2578776c68e27d37135e470f6e 100644 (file)
@@ -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
index 377cfa669f9b238e7be35f8c148178ec2c7bbdfd..c5af8127d78dae6ecfad7857caa2f5d39bebf5b1 100644 (file)
@@ -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_,
index 1519e56c478c3a4a27c5c47797bad06cd429b203..31f42ce4fad9f6d281b0f431beb4b41d5a70cea9 100644 (file)
@@ -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 };
index 36cf531440db31e1f84f302f750aa28b0e367b8c..d3915d952ce5bd1e2cc11a2f8e650e5743811eac 100644 (file)
@@ -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));