]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #229 in SNORT/snort3 from nhttp35 to master
authorRuss Combs (rucombs) <rucombs@cisco.com>
Tue, 2 Feb 2016 19:45:40 +0000 (14:45 -0500)
committerRuss Combs (rucombs) <rucombs@cisco.com>
Tue, 2 Feb 2016 19:45:40 +0000 (14:45 -0500)
Squashed commit of the following:

commit 1238765057b0a5b1208c64e1d7c5cf7200a53b4d
Author: Tom Peters <thopeter@cisco.com>
Date:   Wed Jan 27 15:44:16 2016 -0500

    URI normalization of headers, cookies, and post bodies

15 files changed:
src/service_inspectors/nhttp_inspect/ips_nhttp.cc
src/service_inspectors/nhttp_inspect/nhttp_event_gen.h
src/service_inspectors/nhttp_inspect/nhttp_field.cc
src/service_inspectors/nhttp_inspect/nhttp_field.h
src/service_inspectors/nhttp_inspect/nhttp_inspect.cc
src/service_inspectors/nhttp_inspect/nhttp_inspect.h
src/service_inspectors/nhttp_inspect/nhttp_msg_body.cc
src/service_inspectors/nhttp_inspect/nhttp_msg_body.h
src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.cc
src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.h
src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc
src/service_inspectors/nhttp_inspect/nhttp_msg_section.h
src/service_inspectors/nhttp_inspect/nhttp_uri.cc
src/service_inspectors/nhttp_inspect/nhttp_uri_norm.cc
src/service_inspectors/nhttp_inspect/nhttp_uri_norm.h

index d15d7e1e425aaeb253f07d64cf96e73b0490744f..e80498708c1a7da35fbd4aeb87fb8d7e05af48eb 100644 (file)
@@ -190,7 +190,7 @@ int NHttpIpsOption::eval(Cursor& c, Packet* p)
     InspectionBuffer hb;
 
     if (! ((NHttpInspect*)(p->flow->gadget))->
-           get_buf((unsigned)buffer_index, sub_id, form, nullptr, hb))
+           nhttp_get_buf((unsigned)buffer_index, sub_id, form, nullptr, hb))
         return DETECTION_OPTION_NO_MATCH;
 
     c.set(key, hb.data, hb.len);
index 03364c50500455973b8d677017b38a9b777c7296..6f957107f28abbe972a9d18a9e7dd16ce2151a74 100644 (file)
@@ -34,8 +34,9 @@
 class NHttpEventGen
 {
 public:
+    virtual ~NHttpEventGen() = default;
     void reset() { events_generated = 0; }
-    void create_event(NHttpEnums::EventSid sid)
+    virtual void create_event(NHttpEnums::EventSid sid)
     {
         assert(((int)sid > 0) && ((int)sid <= MAX));
         if (!events_generated[sid-1])
index b71850cb88f21823268d3c2251ae5b6cccae7122..e00ad0fb966200e327b2dc94331673a594e1919c 100644 (file)
@@ -49,6 +49,13 @@ void Field::set(StatusCode stat_code)
     length = stat_code;
 }
 
+void Field::set(const Field& f)
+{
+    assert(length == STAT_NOT_COMPUTE);
+    assert(start == nullptr);
+    start = f.start;
+    length = f.length;
+}
 
 #ifdef REG_TEST
 void Field::print(FILE* output, const char* name) const
index 9342c8971e65b6faf3803f6956c73f3a797dc319..b98baed655d74d9e5665bee0f7081e5648bdc05c 100644 (file)
@@ -41,8 +41,13 @@ public:
     explicit Field(int32_t length_) : length(length_) { assert(length<=0); }
     Field() = default;
     void set(int32_t length_, const uint8_t* start_);
+    void set(const Field& f);
     void set(NHttpEnums::StatusCode stat_code);
     void set(int32_t length) { set(static_cast<NHttpEnums::StatusCode>(length)); }
+    // Only call this method if the field owns the dynamically allocated buffer you are deleting.
+    // This method is a convenience but you still must know where the buffer came from. Many fields
+    // refer to static buffers or a subfield of someone else's buffer.
+    void delete_buffer() { if (length >= 0) delete[] start; };
 
 #ifdef REG_TEST
     void print(FILE* output, const char* name) const;
index c1e4a8cbbb1b1393077dd575b646ff7c3a8212e0..d20cee2a2b0fdeac0e12e5cd7db292a0fe711f0f 100644 (file)
@@ -69,20 +69,20 @@ bool NHttpInspect::get_buf(InspectionBuffer::Type ibt, Packet*, InspectionBuffer
     switch (ibt)
     {
     case InspectionBuffer::IBT_KEY:
-        return get_buf(NHTTP_BUFFER_URI, 0, 0, nullptr, b);
+        return nhttp_get_buf(NHTTP_BUFFER_URI, 0, 0, nullptr, b);
     case InspectionBuffer::IBT_HEADER:
         if (get_latest_is() == IS_TRAILER)
-            return get_buf(NHTTP_BUFFER_TRAILER, 0, 0, nullptr, b);
+            return nhttp_get_buf(NHTTP_BUFFER_TRAILER, 0, 0, nullptr, b);
         else
-            return get_buf(NHTTP_BUFFER_HEADER, 0, 0, nullptr, b);
+            return nhttp_get_buf(NHTTP_BUFFER_HEADER, 0, 0, nullptr, b);
     case InspectionBuffer::IBT_BODY:
-        return get_buf(NHTTP_BUFFER_CLIENT_BODY, 0, 0, nullptr, b);
+        return nhttp_get_buf(NHTTP_BUFFER_CLIENT_BODY, 0, 0, nullptr, b);
     default:
         return false;
     }
 }
 
-SO_PUBLIC bool NHttpInspect::get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet*,
+SO_PUBLIC bool NHttpInspect::nhttp_get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet*,
     InspectionBuffer& b)
 {
     if (latest_section == nullptr)
index 48c001a4da1e4fdb433730af9592bacab5013fa4..ec003953112bd17f4755fd7c11c350adbe142dc1 100644 (file)
@@ -42,7 +42,7 @@ public:
     NHttpInspect(NHttpParaList params_);
 
     bool get_buf(InspectionBuffer::Type ibt, Packet*, InspectionBuffer& b) override;
-    bool get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet*, InspectionBuffer& b);
+    bool nhttp_get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet*, InspectionBuffer& b);
     bool get_fp_buf(InspectionBuffer::Type ibt, Packet*, InspectionBuffer& b) override;
     bool configure(SnortConfig*) override { return true; }
     void show(SnortConfig*) override { LogMessage("NHttpInspect\n"); }
index cabe241f5ec016abfd03c8e0a295b06487812028..1a6555db93a232f8698932a12a7a461cfef708f9 100644 (file)
@@ -43,6 +43,12 @@ NHttpMsgBody::NHttpMsgBody(const uint8_t* buffer, const uint16_t buf_size,
     transaction->set_body(this);
 }
 
+NHttpMsgBody::~NHttpMsgBody()
+{
+    if (classic_client_body_alloc)
+        classic_client_body.delete_buffer();
+}
+
 void NHttpMsgBody::analyze()
 {
     detect_data.length = (msg_text.length <= session_data->detect_depth_remaining[source_id]) ?
@@ -129,6 +135,11 @@ void NHttpMsgBody::do_file_processing()
     }
 }
 
+const Field& NHttpMsgBody::get_classic_client_body()
+{
+    return classic_normalize(detect_data, classic_client_body, classic_client_body_alloc);
+}
+
 #ifdef REG_TEST
 // Common elements of print_section() for body sections
 void NHttpMsgBody::print_body_section(FILE* output)
index 2470ebbad64e132a99c2dbe6cb0c6aa977d08fd7..9da03277a3b67139af26a692fae40bf1049ea201 100644 (file)
 class NHttpMsgBody : public NHttpMsgSection
 {
 public:
+    virtual ~NHttpMsgBody();
     void analyze() override;
     const Field& get_detect_buf() const override { return detect_data; }
     NHttpEnums::InspectSection get_inspection_section() const override
         { return detection_section ? NHttpEnums::IS_DETECTION : NHttpEnums::IS_BODY; }
+    const Field& get_classic_client_body();
 
 protected:
     NHttpMsgBody(const uint8_t* buffer, const uint16_t buf_size, NHttpFlowData* session_data_,
         NHttpEnums::SourceId source_id_, bool buf_owner, Flow* flow_,
         const NHttpParaList* params_);
-    void do_file_processing();
 
     int64_t body_octets;
-    Field detect_data;
-    Field file_data;
-    const bool detection_section;
 
 #ifdef REG_TEST
     void print_body_section(FILE* output);
 #endif
+
+private:
+    void do_file_processing();
+
+    Field detect_data;
+    Field file_data;
+    const bool detection_section;
+    Field classic_client_body;   // URI normalization applied
+    bool classic_client_body_alloc = false;
 };
 
 #endif
index ccce1123b11c88e515a91712da6c535a4c09d195..65d5a2ef1407e71f812bbe59db3678f523dbdabb 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "nhttp_enum.h"
 #include "nhttp_normalizers.h"
+#include "nhttp_uri_norm.h"
 #include "nhttp_msg_head_shared.h"
 
 using namespace NHttpEnums;
@@ -38,10 +39,14 @@ NHttpMsgHeadShared::~NHttpMsgHeadShared()
     {
         NormalizedHeader* temp_ptr = list_ptr;
         list_ptr = list_ptr->next;
-        if (temp_ptr->norm.length >= 0)
-            delete[] temp_ptr->norm.start;
-        delete temp_ptr;
+        temp_ptr->norm.delete_buffer();
     }
+    if (classic_raw_header_alloc)
+        classic_raw_header.delete_buffer();
+    if (classic_norm_header_alloc)
+        classic_norm_header.delete_buffer();
+    if (classic_norm_cookie_alloc)
+        classic_norm_cookie.delete_buffer();
 }
 
 // All the header processing that is done for every message (i.e. not just-in-time) is done here.
@@ -230,6 +235,69 @@ int NHttpMsgHeadShared::get_header_count(HeaderId header_id) const
     return (node != nullptr) ? node->count : 0;
 }
 
+const Field& NHttpMsgHeadShared::get_classic_raw_header()
+{
+    if (classic_raw_header.length != STAT_NOT_COMPUTE)
+        return classic_raw_header;
+    const HeaderId cookie_head = (source_id == SRC_CLIENT) ? HEAD_COOKIE : HEAD_SET_COOKIE;
+    if (!headers_present[cookie_head])
+    {
+        // There are no cookies so the classic headers are the whole thing
+        classic_raw_header.set(msg_text);
+        return classic_raw_header;
+    }
+
+    // Figure out how much space to allocate by stepping through the headers in advance
+    int32_t length = 0;
+    for (int k = 0; k < num_headers; k++)
+    {
+        if (header_name_id[k] == cookie_head)
+            continue;
+        // All header line Fields point into the buffer holding the entire message section.
+        // Calculation must account for separators between header lines, but there are none
+        // following the final header line.
+        const int32_t head_len = (k == num_headers-1) ? header_line[k].length :
+            header_line[k+1].start - header_line[k].start;
+        length += head_len;
+    }
+
+    // Step through headers again and do the copying this time
+    uint8_t* const buffer = new uint8_t[length];
+    int32_t current = 0;
+    for (int k = 0; k < num_headers; k++)
+    {
+        if (header_name_id[k] == cookie_head)
+            continue;
+        const int32_t head_len = (k == num_headers-1) ? header_line[k].length :
+            header_line[k+1].start - header_line[k].start;
+        memcpy(buffer + current, header_line[k].start, head_len);
+        current += head_len;
+    }
+    assert(current == length);
+
+    classic_raw_header.set(length, buffer);
+    classic_raw_header_alloc = true;
+    return classic_raw_header;
+}
+
+const Field& NHttpMsgHeadShared::get_classic_norm_header()
+{
+    return classic_normalize(get_classic_raw_header(), classic_norm_header,
+        classic_norm_header_alloc);
+}
+
+const Field& NHttpMsgHeadShared::get_classic_raw_cookie()
+{
+    HeaderId cookie_head = (source_id == SRC_CLIENT) ? HEAD_COOKIE : HEAD_SET_COOKIE;
+    return get_header_value_norm(cookie_head);
+}
+
+const Field& NHttpMsgHeadShared::get_classic_norm_cookie()
+{
+    return classic_normalize(get_classic_raw_cookie(), classic_norm_cookie,
+        classic_norm_cookie_alloc);
+}
+
 const Field& NHttpMsgHeadShared::get_header_value_norm(HeaderId header_id)
 {
     NormalizedHeader* node = get_header_node(header_id);
index da809d463d233a425e8a8cad72e5b289987b5b7a..757a8e0f42cff5f3d15831a97011f007029afc47 100644 (file)
@@ -37,7 +37,10 @@ public:
     void analyze() override;
 
     int32_t get_num_headers() const { return num_headers; }
-    const Field& get_headers() const { return msg_text; }
+    const Field& get_classic_raw_header();
+    const Field& get_classic_raw_cookie();
+    const Field& get_classic_norm_header();
+    const Field& get_classic_norm_cookie();
     const Field& get_header_line(int k) const { return header_line[k]; }
     const Field& get_header_name(int k) const { return header_name[k]; }
     const Field& get_header_value(int k) const { return header_value[k]; }
@@ -58,6 +61,13 @@ protected:
         { }
     ~NHttpMsgHeadShared();
 
+#ifdef REG_TEST
+    void print_headers(FILE* output);
+#endif
+
+private:
+    static const int MAX = NHttpEnums::HEAD__MAX_VALUE;
+
     // Header normalization strategies. There should be one defined for every different way we can
     // process a header field value.
     static const HeaderNormalizer NORMALIZER_BASIC;
@@ -69,28 +79,29 @@ protected:
     // Master table of known header fields and their normalization strategies.
     static const HeaderNormalizer* const header_norms[];
 
+    // All of these are indexed by the relative position of the header field in the message
+    static const int MAX_HEADERS = 200;  // I'm an arbitrary number. FIXIT-L
+    static const int MAX_HEADER_LENGTH = 4096; // Based on max cookie size of some browsers
+
     void parse_header_block();
     uint32_t find_header_end(const uint8_t* buffer, int32_t length, int& num_seps);
     void parse_header_lines();
     void create_norm_head_list();
     void derive_header_name_id(int index);
 
-    // All of these are indexed by the relative position of the header field in the message
-    static const int MAX_HEADERS = 200;  // I'm an arbitrary number. FIXIT-L
-    static const int MAX_HEADER_LENGTH = 4096; // Based on max cookie size of some browsers
+    std::bitset<MAX> headers_present = 0;
     int32_t num_headers = NHttpEnums::STAT_NOT_COMPUTE;
     Field* header_line = nullptr;
     Field* header_name = nullptr;
     NHttpEnums::HeaderId* header_name_id = nullptr;
     Field* header_value = nullptr;
 
-#ifdef REG_TEST
-    void print_headers(FILE* output);
-#endif
-
-private:
-    static const int MAX = NHttpEnums::HEAD__MAX_VALUE;
-    std::bitset<MAX> headers_present = 0;
+    Field classic_raw_header;    // raw headers with cookies spliced out
+    bool classic_raw_header_alloc = false;
+    Field classic_norm_header;   // URI normalization applied
+    bool classic_norm_header_alloc = false;
+    Field classic_norm_cookie;   // URI normalization applied to concatenated cookie values
+    bool classic_norm_cookie_alloc = false;
 
     struct NormalizedHeader
     {
index 0215018ac5e0e015e179314ca939ee7992042124..505c48c3e56e8c1505736228390470dcd484d75b 100644 (file)
@@ -78,9 +78,25 @@ void NHttpMsgSection::update_depth() const
     }
 }
 
+const Field& NHttpMsgSection::classic_normalize(const Field& raw, Field& norm, bool& norm_alloc)
+{
+    if (norm.length != STAT_NOT_COMPUTE)
+        return norm;
+
+    if ((raw.length <= 0) || !UriNormalizer::need_norm_path(raw))
+    {
+        norm.set(raw);
+        return norm;
+    }
+    uint8_t* buffer = new uint8_t[raw.length + UriNormalizer::URI_NORM_EXPANSION];
+    UriNormalizer::classic_normalize(raw, norm, buffer);
+    norm_alloc = true;
+    return norm;
+}
+
 const Field& NHttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, uint64_t form)
 {
-    // Only use with buffers that support the request option
+    // buffer_side replaces source_id for buffers that support the request option
     const SourceId buffer_side = (form & FORM_REQUEST) ? SRC_CLIENT : source_id;
 
     switch (id)
@@ -90,19 +106,16 @@ const Field& NHttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, u
         if (source_id != SRC_CLIENT)
             return Field::FIELD_NULL;
         NHttpMsgBody* body = transaction->get_body();
-        return (body != nullptr) ? body->get_detect_buf() : Field::FIELD_NULL;
+        return (body != nullptr) ? body->get_classic_client_body() : Field::FIELD_NULL;
       }
     case NHTTP_BUFFER_COOKIE:
     case NHTTP_BUFFER_RAW_COOKIE:
-    // FIXIT-M when real cookie normalization is implemented these need to become separate cases.
-    // Currently "normalization" is aggregation of multiple cookies. That is correct for raw
-    // cookies and all there is for normalized cookies.
       {
         NHttpMsgHeader* header = transaction->get_header(buffer_side);
         if (header == nullptr)
             return Field::FIELD_NULL;
-        HeaderId cookie_head = (buffer_side == SRC_CLIENT) ? HEAD_COOKIE : HEAD_SET_COOKIE;
-        return header->get_header_value_norm(cookie_head);
+        return (id == NHTTP_BUFFER_COOKIE) ? header->get_classic_norm_cookie() :
+            header->get_classic_raw_cookie();
       }
     case NHTTP_BUFFER_HEADER:
     case NHTTP_BUFFER_TRAILER:
@@ -114,7 +127,7 @@ const Field& NHttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, u
         if (header == nullptr)
             return Field::FIELD_NULL;
         if (sub_id == 0)
-            return header->get_headers();
+            return header->get_classic_norm_header();
         return header->get_header_value_norm((HeaderId)sub_id);
       }
     case NHTTP_BUFFER_METHOD:
@@ -125,7 +138,7 @@ const Field& NHttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, u
     case NHTTP_BUFFER_RAW_HEADER:
       {
         NHttpMsgHeader* header = transaction->get_header(buffer_side);
-        return (header != nullptr) ? header->get_headers() : Field::FIELD_NULL;
+        return (header != nullptr) ? header->get_classic_raw_header() : Field::FIELD_NULL;
       }
     case NHTTP_BUFFER_STAT_CODE:
       {
@@ -186,7 +199,7 @@ const Field& NHttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, u
     case NHTTP_BUFFER_RAW_TRAILER:
       {
         NHttpMsgTrailer* trailer = transaction->get_trailer(buffer_side);
-        return (trailer != nullptr) ? trailer->get_headers() : Field::FIELD_NULL;
+        return (trailer != nullptr) ? trailer->get_classic_raw_header() : Field::FIELD_NULL;
       }
     default:
         assert(false);
index 623f1e8a0ba0d935649855adda5478d2f1906a63..4189264fe04564bfe2114f35dc707194e4876e9c 100644 (file)
@@ -79,8 +79,9 @@ protected:
     NHttpEnums::MethodId method_id;
     int32_t status_code_num;
 
-    // Convenience methods
+    // Convenience methods shared by multiple subclasses
     void update_depth() const;
+    static const Field& classic_normalize(const Field& raw, Field& norm, bool& norm_alloc);
 #ifdef REG_TEST
     void print_message_title(FILE* output, const char* title) const;
     void print_message_wrapup(FILE* output);
index e398cb3cc4f110ae343af7e1c1ca47a7f1b4d663..c080c1fa6b07645d20fe411a892c4b11f0485a8b 100644 (file)
@@ -23,7 +23,6 @@
 #include <stdio.h>
 
 #include "nhttp_enum.h"
-#include "nhttp_normalizers.h"
 #include "nhttp_uri.h"
 
 using namespace NHttpEnums;
index 9f9050d5485f918f32cfcabbc83ed4c7c49ab1f7..6fd54ba59a0f382921fc14a711148b5eec1d1316 100644 (file)
@@ -171,6 +171,10 @@ void UriNormalizer::norm_backslash(uint8_t* buf, int32_t length, NHttpInfraction
 int32_t UriNormalizer::norm_path_clean(uint8_t* buf, const int32_t in_length,
     NHttpInfractions& infractions, NHttpEventGen& events)
 {
+    // This is supposed to be the path portion of a URI. Read NHttpUri::parse_uri() for an
+    // explanation.
+    assert(buf[0] == '/');
+
     int32_t length = 0;
     // It simplifies the code that handles /./ and /../ to pretend there is an extra '/' after the
     // buffer. Avoids making a special case of URIs that end in . or .. That is why the loop steps
@@ -231,3 +235,40 @@ int32_t UriNormalizer::norm_path_clean(uint8_t* buf, const int32_t in_length,
     return length;
 }
 
+// Provide traditional URI-style normalization for buffers that usually are not URIs
+void UriNormalizer::classic_normalize(const Field& input, Field& result, uint8_t* buffer)
+{
+    // The requirements for generating events related to these normalizations are unclear. It
+    // definitely doesn't seem right to generate standard URI events. For now we won't generate
+    // any events at all because these buffers may well not be URIs so regardless of what we find
+    // it is "normal". Similarly we don't have any reason to track any infractions.
+
+    // We want to reuse all the URI-normalization functions without complicating their event and
+    // infraction logic with legacy problems. The following centralizes all the messiness here so
+    // that we can conveniently modify it as requirements are better understood.
+
+    class NHttpDummyEventGen : public NHttpEventGen
+    {
+        void create_event(NHttpEnums::EventSid) override {}
+    };
+
+    NHttpInfractions unused;
+    NHttpDummyEventGen dummy_ev;
+
+    // Normalize character escape sequences
+    int32_t data_length = norm_char_clean(input.start, input.length, buffer, unused, dummy_ev);
+
+    // Normalize path directory traversals
+    // Find the leading slash if there is one
+    int32_t uri_offset;
+    for (uri_offset = 0; (uri_offset < data_length) && (buffer[uri_offset] != '/'); uri_offset++);
+    if (uri_offset < data_length)
+    {
+        norm_backslash(buffer + uri_offset, data_length - uri_offset, unused, dummy_ev);
+        data_length = uri_offset +
+            norm_path_clean(buffer + uri_offset, data_length - uri_offset, unused, dummy_ev);
+    }
+
+    result.set(data_length, buffer);
+}
+
index 1136d03b47e7733e849456aa08d91325094c6f72..2d2ba8728a530d61371b1fa6b516c444d95fd79f 100644 (file)
 #include "nhttp_field.h"
 #include "nhttp_infractions.h"
 #include "nhttp_event_gen.h"
-#include "nhttp_normalizers.h"
 
 class UriNormalizer
 {
 public:
+    static const unsigned URI_NORM_EXPANSION = 1;
+
     static void normalize(const Field& input, Field& result, bool do_path, uint8_t* buffer,
         NHttpInfractions& infractions, NHttpEventGen& events);
     static bool need_norm_path(const Field& uri_component);
     static bool need_norm_no_path(const Field& uri_component);
-    static const unsigned URI_NORM_EXPANSION = 1;
+    static void classic_normalize(const Field& input, Field& result, uint8_t* buffer);
 
 private:
     static const NHttpEnums::CharAction uri_char[256];
     static const bool good_percent[256];
 
-    static NormFunc norm_char_clean;
+    static int32_t norm_char_clean(const uint8_t* in_buf, int32_t in_length, uint8_t* out_buf,
+        NHttpInfractions& infractions, NHttpEventGen& events);
     static void norm_backslash(uint8_t* buf, int32_t length, NHttpInfractions& infractions,
         NHttpEventGen& events);
     static int32_t norm_path_clean(uint8_t* buf, const int32_t in_length,