]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #1947 in SNORT/snort3 from ~BRASTULT/snort3:http_param to master
authorRuss Combs (rucombs) <rucombs@cisco.com>
Tue, 18 Feb 2020 16:32:41 +0000 (16:32 +0000)
committerRuss Combs (rucombs) <rucombs@cisco.com>
Tue, 18 Feb 2020 16:32:41 +0000 (16:32 +0000)
Squashed commit of the following:

commit 7372ff7c4455456788e055bb74a8ff957042ad70
Author: Brandon Stultz <brastult@cisco.com>
Date:   Wed Dec 11 18:29:15 2019 -0500

    http_inspect: add http_param rule option

30 files changed:
doc/http_inspect.txt
src/detection/detection_options.cc
src/framework/cursor.cc
src/framework/cursor.h
src/service_inspectors/http_inspect/CMakeLists.txt
src/service_inspectors/http_inspect/http_api.cc
src/service_inspectors/http_inspect/http_buffer_info.cc [new file with mode: 0644]
src/service_inspectors/http_inspect/http_buffer_info.h [new file with mode: 0644]
src/service_inspectors/http_inspect/http_cursor_data.h [new file with mode: 0644]
src/service_inspectors/http_inspect/http_enum.h
src/service_inspectors/http_inspect/http_flow_data.h
src/service_inspectors/http_inspect/http_inspect.cc
src/service_inspectors/http_inspect/http_inspect.h
src/service_inspectors/http_inspect/http_module.cc
src/service_inspectors/http_inspect/http_module.h
src/service_inspectors/http_inspect/http_msg_body.cc
src/service_inspectors/http_inspect/http_msg_body.h
src/service_inspectors/http_inspect/http_msg_head_shared.cc
src/service_inspectors/http_inspect/http_msg_request.cc
src/service_inspectors/http_inspect/http_msg_request.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_param.h [new file with mode: 0644]
src/service_inspectors/http_inspect/http_query_parser.cc [new file with mode: 0644]
src/service_inspectors/http_inspect/http_query_parser.h [new file with mode: 0644]
src/service_inspectors/http_inspect/http_tables.cc
src/service_inspectors/http_inspect/http_uri_norm.cc
src/service_inspectors/http_inspect/http_uri_norm.h
src/service_inspectors/http_inspect/ips_http.cc
src/service_inspectors/http_inspect/ips_http.h

index 526cf11731d4b8fdfbf855baaf88fd0954970b9b..755966d6267c066c63e9e80ca079fb8fe672eb42 100644 (file)
@@ -171,7 +171,7 @@ by an attacker. The options provide tools for the user to cope with that.
     percent_u = false
     utf8_bare_byte = false
     iis_unicode = false
-    iis_double_decode = false
+    iis_double_decode = true
 
 The HTTP inspector normalizes percent encodings found in URIs. For instance
 it will convert "%48%69%64%64%65%6e" to "Hidden". All the options listed
@@ -216,7 +216,7 @@ In the example, the lower-case letters a, b, and c and the digits 1, 2, and
 3 are exempted. These may be percent-encoded without generating an alert.
 
     simplify_path = true
-    backslash_to_slash = false
+    backslash_to_slash = true
 
 HTTP inspector simplifies directory paths in URIs by eliminating extra
 traversals using ., .., and /.
@@ -240,9 +240,8 @@ directories to be separated by backslashes:
 
     \this\is\the\other\way\to\write\a\path
 
-backslash_to_slash is turned off by default. If you are protecting such a
-server then set backslash_to_slash = true and all the backslashes will be
-replaced with slashes during normalization.
+backslash_to_slash is turned on by default. It replaces all the backslashes
+with slashes during normalization.
 
 ==== Detection rules
 
index a64f0944e8753bec507d7a3ec045e9cd3f5d2188..c6b573f83431e56d6ecfb56baa52cead4ecd22a8 100644 (file)
@@ -571,7 +571,7 @@ int detection_option_node_evaluate(
 
                                     continue;
                                 }
-                                else
+                                else if ( node->option_type != RULE_OPTION_TYPE_BUFFER_SET )
                                 {
                                     // Check for an unbounded relative search.  If this
                                     // failed before, it's going to fail again so don't
index 08b292f18036afcb9320085c36a44d682ae32bfe..a62f835efc14c08c5fe2b80334058728ba146bc3 100644 (file)
@@ -21,6 +21,8 @@
 #include "config.h"
 #endif
 
+#include <cassert>
+
 #include "cursor.h"
 
 #include "detection/detection_util.h"
@@ -28,6 +30,8 @@
 
 using namespace snort;
 
+unsigned CursorData::cursor_data_id = 0;
+
 Cursor::Cursor(Packet* p)
 {
     reset(p);
@@ -35,8 +39,57 @@ Cursor::Cursor(Packet* p)
 
 Cursor::Cursor(const Cursor& rhs)
 {
-    *this = rhs;
-    delta = 0;
+    name = rhs.name;
+    buf = rhs.buf;
+    sz = rhs.sz;
+    pos = rhs.pos;
+
+    if (rhs.data)
+    {
+        data = new CursorDataVec;
+
+        for (CursorData*& cd : *rhs.data)
+            data->push_back(cd->clone());
+    }
+}
+
+CursorData* Cursor::get_data(unsigned id) const
+{
+    if (data)
+    {
+        for (CursorData*& cd : *data)
+        {
+            if (cd->get_id() == id)
+                return cd;
+        }
+    }
+
+    return nullptr;
+}
+
+void Cursor::set_data(CursorData* cd)
+{
+    assert(cd);
+
+    if (data)
+    {
+        unsigned id = cd->get_id();
+        for (CursorData*& old : *data)
+        {
+            if (old->get_id() == id)
+            {
+                delete old;
+                old = cd;
+                return;
+            }
+        }
+    }
+    else
+    {
+        data = new CursorDataVec;
+    }
+
+    data->push_back(cd);
 }
 
 void Cursor::reset(Packet* p)
index b231e0ca88c1a65aea13ee4f2faf0d4dd1b7d2f3..a91ff1dabb563c8b39800dc1cab5a6c9b0ed0155 100644 (file)
 
 #include <cstdint>
 #include <cstring>
+#include <vector>
 
 namespace snort
 {
 struct Packet;
 }
 
+class CursorData
+{
+public:
+    CursorData(unsigned u) : id(u) {}
+    virtual ~CursorData() = default;
+    virtual CursorData* clone() = 0;
+
+    unsigned get_id()
+    { return id; }
+
+    static unsigned create_cursor_data_id()
+    { return ++cursor_data_id; }
+
+private:
+    static unsigned cursor_data_id;
+    unsigned id;
+};
+
 class Cursor
 {
 public:
+    Cursor() = default;
     Cursor(snort::Packet*);
     Cursor(const Cursor&);
 
-    Cursor& operator=(const Cursor&) = default;
+    Cursor& operator=(const Cursor&) = delete;
+
+    ~Cursor()
+    {
+        if (!data)
+            return;
+
+        for (CursorData*& cd : *data)
+            delete cd;
+
+        delete data;
+    }
 
     const char* get_name() const
     { return name; }
@@ -50,10 +81,10 @@ public:
     void reset(snort::Packet*);
 
     void set(const char* s, const uint8_t* b, unsigned n)
-    { name = s; data = b; sz = n; pos = delta = 0; }
+    { name = s; buf = b; sz = n; pos = delta = 0; }
 
     const uint8_t* buffer() const
-    { return data; }
+    { return buf; }
 
     unsigned size() const
     { return sz; }
@@ -61,10 +92,10 @@ public:
     // the NEXT octect after last in buffer
     // (this pointer is out of bounds)
     const uint8_t* endo() const
-    { return data + sz; }
+    { return buf + sz; }
 
     const uint8_t* start() const
-    { return data + pos; }
+    { return buf + pos; }
 
     unsigned length() const
     { return sz - pos; }
@@ -75,6 +106,8 @@ public:
     unsigned get_delta() const
     { return delta; }
 
+    CursorData* get_data(unsigned id) const;
+
     bool add_pos(unsigned n)
     {
         if (pos + n > sz)
@@ -100,12 +133,17 @@ public:
         return true;
     }
 
+    void set_data(CursorData* cd);
+
+    typedef std::vector<CursorData*> CursorDataVec;
+
 private:
-    const char* name;     // rule option name ("pkt_data", "http_uri", etc.)
-    const uint8_t* data;  // start of buffer
-    unsigned sz;          // size of buffer
-    unsigned pos;         // current pos
-    unsigned delta;       // loop offset
+    const char* name = nullptr;    // rule option name ("pkt_data", "http_uri", etc.)
+    const uint8_t* buf = nullptr;  // start of buffer
+    unsigned sz = 0;               // size of buffer
+    unsigned pos = 0;              // current pos
+    unsigned delta = 0;            // loop offset
+    CursorDataVec* data = nullptr; // data stored on the cursor
 };
 
 #endif
index d1918aebbeb1df9fdd25ef0d8c1954e83838ba31..7e0a49055c93eff2c9c1f3921ec8aac371455674 100644 (file)
@@ -2,6 +2,8 @@
 set (FILE_LIST
     ips_http.cc
     ips_http.h
+    http_buffer_info.h
+    http_buffer_info.cc
     http_inspect.cc
     http_inspect.h
     http_msg_section.cc
@@ -27,6 +29,9 @@ set (FILE_LIST
     http_msg_body_old.h
     http_msg_trailer.cc
     http_msg_trailer.h
+    http_param.h
+    http_query_parser.cc
+    http_query_parser.h
     http_header_normalizer.cc
     http_header_normalizer.h
     http_uri.cc
@@ -48,6 +53,7 @@ set (FILE_LIST
     http_flow_data.h
     http_context_data.cc
     http_context_data.h
+    http_cursor_data.h
     http_transaction.cc
     http_transaction.h
     http_test_manager.cc
index 7475fe2a79090f5bec2ebc663f84721c948d82a1..ef407cab8a891fe7e19c0739b4be14f2809d797f 100644 (file)
@@ -24,6 +24,7 @@
 #include "http_api.h"
 
 #include "http_context_data.h"
+#include "http_cursor_data.h"
 #include "http_inspect.h"
 
 using namespace snort;
@@ -31,6 +32,8 @@ using namespace snort;
 const char* HttpApi::http_my_name = HTTP_NAME;
 const char* HttpApi::http_help = "the new HTTP inspector!";
 
+unsigned HttpCursorData::id = 0;
+
 Inspector* HttpApi::http_ctor(Module* mod)
 {
     HttpModule* const http_mod = (HttpModule*)mod;
@@ -41,6 +44,7 @@ void HttpApi::http_init()
 {
     HttpFlowData::init();
     HttpContextData::init();
+    HttpCursorData::init();
 }
 
 const char* HttpApi::classic_buffer_names[] =
@@ -49,6 +53,7 @@ const char* HttpApi::classic_buffer_names[] =
     "http_cookie",
     "http_header",
     "http_method",
+    "http_param",
     "http_raw_body",
     "http_raw_cookie",
     "http_raw_header",
@@ -97,6 +102,7 @@ extern const BaseApi* ips_http_client_body;
 extern const BaseApi* ips_http_cookie;
 extern const BaseApi* ips_http_header;
 extern const BaseApi* ips_http_method;
+extern const BaseApi* ips_http_param;
 extern const BaseApi* ips_http_raw_body;
 extern const BaseApi* ips_http_raw_cookie;
 extern const BaseApi* ips_http_raw_header;
@@ -122,6 +128,7 @@ const BaseApi* sin_http[] =
     ips_http_cookie,
     ips_http_header,
     ips_http_method,
+    ips_http_param,
     ips_http_raw_body,
     ips_http_raw_cookie,
     ips_http_raw_header,
diff --git a/src/service_inspectors/http_inspect/http_buffer_info.cc b/src/service_inspectors/http_inspect/http_buffer_info.cc
new file mode 100644 (file)
index 0000000..e72f672
--- /dev/null
@@ -0,0 +1,65 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// http_buffer_info.cc author Brandon Stultz <brastult@cisco.com>
+
+#include "hash/hashfcn.h"
+#include "http_buffer_info.h"
+
+using namespace snort;
+
+uint32_t HttpBufferInfo::hash() const
+{
+    uint32_t a = type;
+    uint32_t b = sub_id >> 32;
+    uint32_t c = sub_id & 0xFFFFFFFF;
+    uint32_t d = form >> 32;
+    uint32_t e = form & 0xFFFFFFFF;
+    uint32_t f = 0;
+    mix(a,b,c);
+    if (param)
+        f = param->is_nocase() ? 1 : 0;
+    mix(d,e,f);
+    mix(a,c,f);
+    if (param)
+        mix_str(a,c,f,param->c_str(),param->length());
+    finalize(a,c,f);
+    return f;
+}
+
+bool HttpBufferInfo::operator==(const HttpBufferInfo& rhs) const
+{
+    bool param_match = false;
+
+    if (param && rhs.param)
+    {
+        HttpParam& lhs_param = *param;
+        HttpParam& rhs_param = *rhs.param;
+
+        param_match = (lhs_param == rhs_param);
+    }
+    else if (!param && !rhs.param)
+    {
+        param_match = true;
+    }
+
+    return type == rhs.type &&
+        sub_id == rhs.sub_id &&
+        form == rhs.form &&
+        param_match;
+}
+
diff --git a/src/service_inspectors/http_inspect/http_buffer_info.h b/src/service_inspectors/http_inspect/http_buffer_info.h
new file mode 100644 (file)
index 0000000..490511f
--- /dev/null
@@ -0,0 +1,57 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// http_buffer_info.h author Brandon Stultz <brastult@cisco.com>
+
+#ifndef HTTP_BUFFER_INFO_H
+#define HTTP_BUFFER_INFO_H
+
+#include <string>
+
+#include "http_enum.h"
+#include "http_param.h"
+
+class HttpBufferInfo
+{
+public:
+    HttpBufferInfo(unsigned type_, uint64_t sub_id_ = 0, uint64_t form_ = 0)
+        : type(type_), sub_id(sub_id_), form(form_) {}
+
+    HttpBufferInfo(unsigned type_, uint64_t sub_id_, uint64_t form_,
+        const std::string& param_str, bool nocase)
+        : type(type_), sub_id(sub_id_), form(form_)
+    {
+        if (param_str.length() > 0)
+            param = new HttpParam(param_str, nocase);
+    }
+
+    ~HttpBufferInfo()
+    { delete param; }
+
+    uint32_t hash() const;
+
+    bool operator==(const HttpBufferInfo& rhs) const;
+
+public:
+    unsigned type;
+    uint64_t sub_id;
+    uint64_t form;
+    HttpParam* param = nullptr;
+};
+
+#endif
+
diff --git a/src/service_inspectors/http_inspect/http_cursor_data.h b/src/service_inspectors/http_inspect/http_cursor_data.h
new file mode 100644 (file)
index 0000000..0314332
--- /dev/null
@@ -0,0 +1,51 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// http_cursor_data.h author Brandon Stultz <brastult@cisco.com>
+
+#ifndef HTTP_CURSOR_DATA_H
+#define HTTP_CURSOR_DATA_H
+
+#include "framework/cursor.h"
+
+class HttpCursorData : public CursorData
+{
+public:
+    HttpCursorData() : CursorData(id) {}
+
+    static void init()
+    { id = CursorData::create_cursor_data_id(); }
+
+    HttpCursorData* clone() override
+    { return new HttpCursorData(*this); }
+
+    bool retry()
+    {
+        return query_index < num_query_params ||
+            body_index < num_body_params;
+    }
+
+public:
+    static unsigned id;
+    unsigned num_query_params = 0;
+    unsigned num_body_params = 0;
+    unsigned query_index = 0;
+    unsigned body_index = 0;
+};
+
+#endif
+
index 40719bdda2883715278ef0cae7ec1a33cc6f7782..495943beeee5ab1e14260c276f92b56a24069037 100644 (file)
@@ -46,9 +46,10 @@ enum DetectionStatus { DET_REACTIVATING = 1, DET_ON, DET_DEACTIVATING, DET_OFF }
 // Message buffers available to clients
 // This enum must remain synchronized with HttpApi::classic_buffer_names[]
 enum HTTP_BUFFER { HTTP_BUFFER_CLIENT_BODY = 1, HTTP_BUFFER_COOKIE, HTTP_BUFFER_HEADER,
-    HTTP_BUFFER_METHOD, HTTP_BUFFER_RAW_BODY, HTTP_BUFFER_RAW_COOKIE, HTTP_BUFFER_RAW_HEADER,
-    HTTP_BUFFER_RAW_REQUEST, HTTP_BUFFER_RAW_STATUS, 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_METHOD, HTTP_BUFFER_PARAM, HTTP_BUFFER_RAW_BODY, HTTP_BUFFER_RAW_COOKIE,
+    HTTP_BUFFER_RAW_HEADER, HTTP_BUFFER_RAW_REQUEST, HTTP_BUFFER_RAW_STATUS,
+    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, HTTP_BUFFER_MAX };
 
 // Peg counts
@@ -57,7 +58,7 @@ enum PEG_COUNT { PEG_FLOW = 0, PEG_SCAN, PEG_REASSEMBLE, PEG_INSPECT, PEG_REQUES
     PEG_GET, PEG_HEAD, PEG_POST, PEG_PUT, PEG_DELETE, PEG_CONNECT, PEG_OPTIONS, PEG_TRACE,
     PEG_OTHER_METHOD, PEG_REQUEST_BODY, PEG_CHUNKED, PEG_URI_NORM, PEG_URI_PATH, PEG_URI_CODING,
     PEG_CONCURRENT_SESSIONS, PEG_MAX_CONCURRENT_SESSIONS, PEG_DETAINED, PEG_PARTIAL_INSPECT,
-    PEG_COUNT_MAX };
+    PEG_EXCESS_PARAMS, PEG_PARAMS, PEG_COUNT_MAX };
 
 // Result of scanning by splitter
 enum ScanResult { SCAN_NOT_FOUND, SCAN_NOT_FOUND_DETAIN, SCAN_FOUND, SCAN_FOUND_PIECE,
@@ -343,6 +344,7 @@ enum EventSid
     EVENT_VERSION_NOT_UPPERCASE,
     EVENT_BAD_HEADER_WHITESPACE,
     EVENT_GZIP_EARLY_END,                  // 248
+    EVENT_EXCESS_REPEAT_PARAMS,
     EVENT__MAX_VALUE
 };
 
index 3b037186a75868169f18f963e9c3a86505aa32b7..e4fe48159c0ea65792e9a35a6ec472b9f45c3a21 100644 (file)
@@ -37,6 +37,7 @@ class HttpTransaction;
 class HttpJsNorm;
 class HttpMsgSection;
 class HttpCutter;
+class HttpQueryParser;
 
 class HttpFlowData : public snort::FlowData
 {
@@ -59,6 +60,7 @@ public:
     friend class HttpMsgBodyChunk;
     friend class HttpMsgBodyCl;
     friend class HttpMsgBodyOld;
+    friend class HttpQueryParser;
     friend class HttpStreamSplitter;
     friend class HttpTransaction;
 #if defined(REG_TEST) || defined(UNIT_TEST)
index 39f522d86c094dd39f5e975d92b6e84597f9bd2c..f6f6f2cfad97ad4c6161c9e31ebbe1f028a67918 100644 (file)
@@ -156,14 +156,14 @@ bool HttpInspect::get_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffe
     switch (ibt)
     {
     case InspectionBuffer::IBT_KEY:
-        return http_get_buf(HTTP_BUFFER_URI, 0, 0, p, b);
+        return get_buf(HTTP_BUFFER_URI, p, b);
     case InspectionBuffer::IBT_HEADER:
         if (get_latest_is(p) == IS_TRAILER)
-            return http_get_buf(HTTP_BUFFER_TRAILER, 0, 0, p, b);
+            return get_buf(HTTP_BUFFER_TRAILER, p, b);
         else
-            return http_get_buf(HTTP_BUFFER_HEADER, 0, 0, p, b);
+            return get_buf(HTTP_BUFFER_HEADER, p , b);
     case InspectionBuffer::IBT_BODY:
-        return http_get_buf(HTTP_BUFFER_CLIENT_BODY, 0, 0, p, b);
+        return get_buf(HTTP_BUFFER_CLIENT_BODY, p, b);
     default:
         return false;
     }
@@ -171,25 +171,28 @@ bool HttpInspect::get_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffe
 
 bool HttpInspect::get_buf(unsigned id, Packet* p, InspectionBuffer& b)
 {
-    return http_get_buf(id, 0, 0, p, b);
+    Cursor c;
+    HttpBufferInfo buffer_info(id);
+
+    const Field& http_buffer = http_get_buf(c, p, buffer_info);
+
+    if (http_buffer.length() <= 0)
+        return false;
+
+    b.data = http_buffer.start();
+    b.len = http_buffer.length();
+    return true;
 }
 
-bool HttpInspect::http_get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet* p,
-    InspectionBuffer& b)
+const Field& HttpInspect::http_get_buf(Cursor& c, Packet* p,
+    HttpBufferInfo& buffer_info)
 {
     HttpMsgSection* current_section = HttpContextData::get_snapshot(p);
 
     if (current_section == nullptr)
-        return false;
-
-    const Field& buffer = current_section->get_classic_buffer(id, sub_id, form);
+        return Field::FIELD_NULL;
 
-    if (buffer.length() <= 0)
-        return false;
-
-    b.data = buffer.start();
-    b.len = buffer.length();
-    return true;
+    return current_section->get_classic_buffer(c, buffer_info);
 }
 
 bool HttpInspect::get_fp_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffer& b)
index 77b02480492024bb04b389b345810153ff215f80..ae62c44cb3879098d389e9a199e638c2ecdb6ebd 100644 (file)
 // HttpInspect class
 //-------------------------------------------------------------------------
 
+#include "framework/cursor.h"
+#include "log/messages.h"
+
+#include "http_buffer_info.h"
 #include "http_common.h"
 #include "http_enum.h"
 #include "http_field.h"
 #include "http_module.h"
 #include "http_msg_section.h"
 #include "http_stream_splitter.h"
-#include "log/messages.h"
 
 class HttpApi;
 
@@ -43,9 +46,10 @@ public:
     bool get_buf(snort::InspectionBuffer::Type ibt, snort::Packet* p,
         snort::InspectionBuffer& b) override;
     bool get_buf(unsigned id, snort::Packet* p, snort::InspectionBuffer& b) override;
-    bool http_get_buf(unsigned id, uint64_t sub_id, uint64_t form, snort::Packet* p,
-        snort::InspectionBuffer& b);
-    bool get_fp_buf(snort::InspectionBuffer::Type ibt, snort::Packet* p, snort::InspectionBuffer& b) override;
+    const Field& http_get_buf(Cursor& c, snort::Packet* p,
+        HttpBufferInfo& buffer_info);
+    bool get_fp_buf(snort::InspectionBuffer::Type ibt, snort::Packet* p,
+        snort::InspectionBuffer& b) override;
     bool configure(snort::SnortConfig*) override;
     void show(snort::SnortConfig*) override;
     void eval(snort::Packet* p) override;
index 115792131e4cc5f811dd1dbb755b5122f7100655..3af6878d932ee382f7b20309a03b066f913a0513 100644 (file)
@@ -89,13 +89,13 @@ const Parameter HttpModule::http_params[] =
     { "iis_unicode_code_page", Parameter::PT_INT, "0:65535", "1252",
       "code page to use from the IIS unicode map file" },
 
-    { "iis_double_decode", Parameter::PT_BOOL, nullptr, "false",
+    { "iis_double_decode", Parameter::PT_BOOL, nullptr, "true",
       "perform double decoding of percent encodings to normalize characters" },
 
     { "oversize_dir_length", Parameter::PT_INT, "1:65535", "300",
       "maximum length for URL directory" },
 
-    { "backslash_to_slash", Parameter::PT_BOOL, nullptr, "false",
+    { "backslash_to_slash", Parameter::PT_BOOL, nullptr, "true",
       "replace \\ with / when normalizing URIs" },
 
     { "plus_to_space", Parameter::PT_BOOL, nullptr, "true",
index 89c9a471859771164a7ed0fdedb984c9edd07900..e9021ed52059ff920be230ac4237b24b8f0de04a 100644 (file)
@@ -68,8 +68,8 @@ public:
         std::string iis_unicode_map_file;
         int iis_unicode_code_page = 1252;
         uint8_t* unicode_map = nullptr;
-        bool iis_double_decode = false;
-        bool backslash_to_slash = false;
+        bool iis_double_decode = true;
+        bool backslash_to_slash = true;
         bool plus_to_space = true;
         bool simplify_path = true;
         std::bitset<256> bad_characters;
index 0f85c91ae4b9ef8617fb5234ef194f58245c22db..0f35cf857b783f35d426ca9f6eb00fedef3a1250 100644 (file)
@@ -274,7 +274,7 @@ void HttpMsgBody::do_file_processing(const Field& file_data)
 
 const Field& HttpMsgBody::get_classic_client_body()
 {
-    return classic_normalize(detect_data, classic_client_body, params->uri_param);
+    return classic_normalize(detect_data, classic_client_body, false, params->uri_param);
 }
 
 #ifdef REG_TEST
index 0101985042beecb12af55240b956ce312d78d095..b086588b74875bfdb52057dfc9395d28a87539cf 100644 (file)
@@ -40,6 +40,7 @@ public:
     const Field& get_classic_client_body();
     const Field& get_detect_data() { return detect_data; }
     static void fd_event_callback(void* context, int event);
+    bool is_first() { return first_body; }
 
 protected:
     HttpMsgBody(const uint8_t* buffer, const uint16_t buf_size, HttpFlowData* session_data_,
index 445715928bc3ca586021a65c40ec214dfc577344..138a5d9be4d616940927a4d046e4aa0763c2d3d7 100644 (file)
@@ -296,7 +296,8 @@ const Field& HttpMsgHeadShared::get_classic_raw_header()
 
 const Field& HttpMsgHeadShared::get_classic_norm_header()
 {
-    return classic_normalize(get_classic_raw_header(), classic_norm_header, params->uri_param);
+    return classic_normalize(get_classic_raw_header(), classic_norm_header,
+        false, params->uri_param);
 }
 
 const Field& HttpMsgHeadShared::get_classic_raw_cookie()
@@ -307,7 +308,8 @@ const Field& HttpMsgHeadShared::get_classic_raw_cookie()
 
 const Field& HttpMsgHeadShared::get_classic_norm_cookie()
 {
-    return classic_normalize(get_classic_raw_cookie(), classic_norm_cookie, params->uri_param);
+    return classic_normalize(get_classic_raw_cookie(), classic_norm_cookie,
+        false, params->uri_param);
 }
 
 const Field& HttpMsgHeadShared::get_header_value_raw(HeaderId header_id) const
index 09840812a381c3eb98d6b5fdb0032e4b6308f74b..c46c75d59f6a4ced51bc595deabfd84d4d342b64 100644 (file)
@@ -40,6 +40,13 @@ HttpMsgRequest::HttpMsgRequest(const uint8_t* buffer, const uint16_t buf_size,
     get_related_sections();
 }
 
+HttpMsgRequest::~HttpMsgRequest()
+{
+    delete uri;
+    delete query_params;
+    delete body_params;
+}
+
 void HttpMsgRequest::parse_start_line()
 {
     // Version field
@@ -183,6 +190,22 @@ const Field& HttpMsgRequest::get_uri_norm_classic()
     return Field::FIELD_NULL;
 }
 
+ParameterMap& HttpMsgRequest::get_query_params()
+{
+    if (query_params == nullptr)
+        query_params = new ParameterMap;
+
+    return *query_params;
+}
+
+ParameterMap& HttpMsgRequest::get_body_params()
+{
+    if (body_params == nullptr)
+        body_params = new ParameterMap;
+
+    return *body_params;
+}
+
 void HttpMsgRequest::gen_events()
 {
     if (*transaction->get_infractions(source_id) & INF_BAD_REQ_LINE)
index df926ea34c8d191057afe0b5b788be51e09f6c7c..a8aba0fada7773f28741058305355eac3f522f1c 100644 (file)
@@ -23,6 +23,7 @@
 #include "http_common.h"
 #include "http_enum.h"
 #include "http_msg_start.h"
+#include "http_query_parser.h"
 #include "http_str_to_code.h"
 #include "http_uri.h"
 #include "http_uri_norm.h"
@@ -37,13 +38,15 @@ public:
     HttpMsgRequest(const uint8_t* buffer, const uint16_t buf_size, HttpFlowData* session_data_,
         HttpCommon::SourceId source_id_, bool buf_owner, snort::Flow* flow_,
         const HttpParaList* params_);
-    ~HttpMsgRequest() override { delete uri; }
+    ~HttpMsgRequest() override;
     void gen_events() override;
     void update_flow() override;
     const Field& get_method() { return method; }
     const Field& get_uri();
     const Field& get_uri_norm_classic();
     HttpUri* get_http_uri() { return uri; }
+    ParameterMap& get_query_params();
+    ParameterMap& get_body_params();
 
     static bool is_webdav(HttpEnums::MethodId method)
     {
@@ -69,6 +72,9 @@ private:
 
     Field method;
     HttpUri* uri = nullptr;
+
+    ParameterMap* query_params = nullptr;
+    ParameterMap* body_params = nullptr;
 };
 
 #endif
index b3a2fbab0761762654a01de1cda6a33e93fcc1c2..c138de36900c8a043821da1e57b514998c6e47aa 100644 (file)
@@ -21,6 +21,8 @@
 #include "config.h"
 #endif
 
+#include <cstring>
+
 #include "service_inspectors/http2_inspect/http2_flow_data.h"
 
 #include "http_msg_section.h"
 #include "http_context_data.h"
 #include "http_common.h"
 #include "http_enum.h"
+#include "http_module.h"
 #include "http_msg_body.h"
 #include "http_msg_head_shared.h"
 #include "http_msg_header.h"
 #include "http_msg_request.h"
 #include "http_msg_status.h"
 #include "http_msg_trailer.h"
+#include "http_param.h"
+#include "http_query_parser.h"
 #include "http_test_manager.h"
 #include "stream/flush_bucket.h"
 
@@ -129,26 +134,34 @@ void HttpMsgSection::update_depth() const
 }
 
 const Field& HttpMsgSection::classic_normalize(const Field& raw, Field& norm,
-    const HttpParaList::UriParam& uri_param)
+    bool do_path, const HttpParaList::UriParam& uri_param)
 {
     if (norm.length() != STAT_NOT_COMPUTE)
         return norm;
 
-    if ((raw.length() <= 0) || !UriNormalizer::classic_need_norm(raw, true, uri_param))
+    if ((raw.length() <= 0) || !UriNormalizer::classic_need_norm(raw, do_path, uri_param))
     {
         norm.set(raw);
         return norm;
     }
-    UriNormalizer::classic_normalize(raw, norm, uri_param);
+    UriNormalizer::classic_normalize(raw, norm, do_path, uri_param);
     return norm;
 }
 
 const Field& HttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, uint64_t form)
+{
+    Cursor c;
+    HttpBufferInfo buffer_info(id, sub_id, form);
+
+    return get_classic_buffer(c, buffer_info);
+}
+
+const Field& HttpMsgSection::get_classic_buffer(Cursor& c, HttpBufferInfo& buf)
 {
     // buffer_side replaces source_id for buffers that support the request option
-    const SourceId buffer_side = (form & FORM_REQUEST) ? SRC_CLIENT : source_id;
+    const SourceId buffer_side = (buf.form & FORM_REQUEST) ? SRC_CLIENT : source_id;
 
-    switch (id)
+    switch (buf.type)
     {
     case HTTP_BUFFER_CLIENT_BODY:
       {
@@ -161,25 +174,139 @@ const Field& HttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, ui
       {
         if (header[buffer_side] == nullptr)
             return Field::FIELD_NULL;
-        return (id == HTTP_BUFFER_COOKIE) ? header[buffer_side]->get_classic_norm_cookie() :
+        return (buf.type == HTTP_BUFFER_COOKIE) ? header[buffer_side]->get_classic_norm_cookie() :
             header[buffer_side]->get_classic_raw_cookie();
       }
     case HTTP_BUFFER_HEADER:
     case HTTP_BUFFER_TRAILER:
       {
         // FIXIT-L Someday want to be able to return field name or raw field value
-        HttpMsgHeadShared* const head = (id == HTTP_BUFFER_HEADER) ?
+        HttpMsgHeadShared* const head = (buf.type == HTTP_BUFFER_HEADER) ?
             (HttpMsgHeadShared*)header[buffer_side] : (HttpMsgHeadShared*)trailer[buffer_side];
         if (head == nullptr)
             return Field::FIELD_NULL;
-        if (sub_id == 0)
+        if (buf.sub_id == 0)
             return head->get_classic_norm_header();
-        return head->get_header_value_norm((HeaderId)sub_id);
+        return head->get_header_value_norm((HeaderId)buf.sub_id);
       }
     case HTTP_BUFFER_METHOD:
       {
         return (request != nullptr) ? request->get_method() : Field::FIELD_NULL;
       }
+    case HTTP_BUFFER_PARAM:
+      {
+        if (buf.param == nullptr || request == nullptr)
+            return Field::FIELD_NULL;
+
+        HttpUri* query = request->get_http_uri();
+        HttpMsgBody* body = (source_id == SRC_CLIENT) ? get_body() : nullptr;
+
+        if (query == nullptr && body == nullptr)
+            return Field::FIELD_NULL;
+
+        const HttpParaList::UriParam& uri_config = params->uri_param;
+
+        ParameterMap& query_params = request->get_query_params();
+        ParameterMap& body_params = request->get_body_params();
+
+        // cache lookup
+        HttpParam& param = *buf.param;
+        ParameterData& query_data = query_params[param.str_upper()];
+        ParameterData& body_data = body_params[param.str_upper()];
+
+        if (!query_data.parsed && query != nullptr)
+        {
+            // query has not been parsed for this parameter
+            const Field& rq = query->get_query();
+            const Field& nq = query->get_norm_query();
+
+            if (rq.length() > 0 && nq.length() > 0)
+            {
+                HttpQueryParser parser(rq.start(), rq.length(),
+                    nq.start(), nq.length(), uri_config,
+                    session_data, source_id);
+
+                parser.parse(param, query_data);
+                query_data.parsed = true;
+            }
+        }
+
+        if (!body_data.parsed && body != nullptr)
+        {
+            // body has not been parsed for this parameter
+            const Field& rb = body->get_detect_data();
+            const Field& nb = body->get_classic_client_body();
+
+            if (rb.length() > 0 && nb.length() > 0 && body->is_first())
+            {
+                HttpQueryParser parser(rb.start(), rb.length(),
+                    nb.start(), nb.length(), uri_config,
+                    session_data, source_id);
+
+                parser.parse(param, body_data);
+                body_data.parsed = true;
+            }
+        }
+
+        KeyValueVec& query_kv = query_data.kv_vec;
+        KeyValueVec& body_kv = body_data.kv_vec;
+
+        unsigned num_query_params = query_kv.size();
+        unsigned num_body_params = body_kv.size();
+
+        if (num_query_params == 0 && num_body_params == 0)
+            return Field::FIELD_NULL;
+
+        // get data stored on the cursor
+        HttpCursorData* cd = (HttpCursorData*)c.get_data(HttpCursorData::id);
+
+        if (!cd)
+        {
+            cd = new HttpCursorData();
+            c.set_data(cd);
+        }
+
+        // save the parameter count on the cursor
+        cd->num_query_params = num_query_params;
+        cd->num_body_params = num_body_params;
+
+        unsigned& query_index = cd->query_index;
+        unsigned& body_index = cd->body_index;
+
+        while (query_index < num_query_params)
+        {
+            KeyValue* fields = query_kv[query_index];
+
+            Field& key = fields->key;
+            Field& value = fields->value;
+
+            ++query_index;
+
+            if (param.is_nocase())
+                return value;
+
+            if (!memcmp(key.start(), param.c_str(), key.length()))
+                return value;
+        }
+
+        while (body_index < num_body_params)
+        {
+            KeyValue* fields = body_kv[body_index];
+
+            Field& key = fields->key;
+            Field& value = fields->value;
+
+            ++body_index;
+
+            if (param.is_nocase())
+                return value;
+
+            if (!memcmp(key.start(), param.c_str(), key.length()))
+                return value;
+        }
+
+        return Field::FIELD_NULL;
+      }
     case HTTP_BUFFER_RAW_BODY:
       {
         return (get_body() != nullptr) ? get_body()->msg_text : Field::FIELD_NULL;
@@ -218,15 +345,15 @@ const Field& HttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, ui
     case HTTP_BUFFER_URI:
     case HTTP_BUFFER_RAW_URI:
       {
-        const bool raw = (id == HTTP_BUFFER_RAW_URI);
+        const bool raw = (buf.type == HTTP_BUFFER_RAW_URI);
         if (request == nullptr)
             return Field::FIELD_NULL;
-        if (sub_id == 0)
+        if (buf.sub_id == 0)
             return raw ? request->get_uri() : request->get_uri_norm_classic();
         HttpUri* const uri = request->get_http_uri();
         if (uri == nullptr)
             return Field::FIELD_NULL;
-        switch ((UriComponent)sub_id)
+        switch ((UriComponent)buf.sub_id)
         {
         case UC_SCHEME:
             return uri->get_scheme();
index 95ae5085f56ee04454293abc8285d69f850fa24b..844f52228277a460a4e1b07f6d5220244b29db47 100644 (file)
 #define HTTP_MSG_SECTION_H
 
 #include "detection/detection_util.h"
+#include "framework/cursor.h"
 
+#include "http_buffer_info.h"
 #include "http_common.h"
+#include "http_cursor_data.h"
 #include "http_enum.h"
 #include "http_field.h"
-#include "http_module.h"
 #include "http_flow_data.h"
+#include "http_module.h"
 #include "http_transaction.h"
 
 //-------------------------------------------------------------------------
@@ -64,6 +67,7 @@ public:
     virtual void update_flow() = 0;
 
     const Field& get_classic_buffer(unsigned id, uint64_t sub_id, uint64_t form);
+    const Field& get_classic_buffer(Cursor& c, HttpBufferInfo& buf);
 
     HttpEnums::MethodId get_method_id() const { return method_id; }
 
@@ -116,7 +120,7 @@ protected:
     void create_event(int sid);
     void update_depth() const;
     static const Field& classic_normalize(const Field& raw, Field& norm,
-        const HttpParaList::UriParam& uri_param);
+        bool do_path, const HttpParaList::UriParam& uri_param);
 #ifdef REG_TEST
     void print_section_title(FILE* output, const char* title) const;
     void print_section_wrapup(FILE* output) const;
diff --git a/src/service_inspectors/http_inspect/http_param.h b/src/service_inspectors/http_inspect/http_param.h
new file mode 100644 (file)
index 0000000..1634277
--- /dev/null
@@ -0,0 +1,89 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// http_param.h author Brandon Stultz <brastult@cisco.com>
+
+#ifndef HTTP_PARAM_H
+#define HTTP_PARAM_H
+
+#include <algorithm>
+#include <cassert>
+#include <string>
+
+#include "helpers/literal_search.h"
+
+class HttpParam
+{
+public:
+    HttpParam(const std::string& param_, bool nocase_)
+        : param(param_), param_upper(param_), nocase(nocase_)
+    {
+        assert(param.length() > 0);
+
+        std::transform(param_upper.begin(), param_upper.end(),
+            param_upper.begin(), ::toupper);
+
+        const uint8_t* pattern = (const uint8_t*)param_upper.c_str();
+        unsigned pattern_length = param_upper.length();
+
+        search_handle = snort::LiteralSearch::setup();
+
+        searcher = snort::LiteralSearch::instantiate(
+            search_handle, pattern, pattern_length, true
+        );
+    }
+
+    ~HttpParam()
+    {
+        delete searcher;
+        snort::LiteralSearch::cleanup(search_handle);
+    }
+
+    bool operator==(const HttpParam& rhs) const
+    { return param == rhs.param && nocase == rhs.nocase; }
+
+    const std::string& str() const
+    { return param; }
+
+    const std::string& str_upper() const
+    { return param_upper; }
+
+    const char* c_str() const
+    { return param.c_str(); }
+
+    unsigned length() const
+    { return param.length(); }
+
+    bool is_nocase() const
+    { return nocase; }
+
+    int search_nocase(const uint8_t* buffer, unsigned buffer_len) const
+    {
+        assert(searcher);
+        return searcher->search(search_handle, buffer, buffer_len);
+    }
+
+private:
+    std::string param;
+    std::string param_upper;
+    bool nocase = false;
+    snort::LiteralSearch* searcher = nullptr;
+    snort::LiteralSearch::Handle* search_handle = nullptr;
+};
+
+#endif
+
diff --git a/src/service_inspectors/http_inspect/http_query_parser.cc b/src/service_inspectors/http_inspect/http_query_parser.cc
new file mode 100644 (file)
index 0000000..dfd590a
--- /dev/null
@@ -0,0 +1,187 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// http_query_parser.cc author Brandon Stultz <brastult@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <cstring>
+
+#include "http_enum.h"
+#include "http_query_parser.h"
+#include "http_uri_norm.h"
+
+using namespace HttpEnums;
+
+void HttpQueryParser::create_event(int sid)
+{
+    session_data->events[source_id]->create_event(sid);
+}
+
+void HttpQueryParser::unescape(const Field& raw, Field& norm)
+{
+    if ( raw.length() > 0 )
+    {
+        if ( UriNormalizer::classic_need_norm(raw, false, uri_config) )
+        {
+            UriNormalizer::classic_normalize(raw, norm, false, uri_config);
+            return;
+        }
+    }
+
+    norm.set(raw);
+}
+
+void HttpQueryParser::parse(const HttpParam& param, ParameterData& data)
+{
+    // check if parameter is present in normalized buffer
+    if( param.search_nocase(norm_buffer, norm_buffer_len) < 0 )
+        return;
+
+    const std::string& key = param.str_upper();
+
+    unsigned kv_index = 0;
+    index = 0;
+
+    while ( index < buffer_len )
+    {
+        if ( kv_index >= MAX_REPEAT_PARAMS )
+        {
+            HttpModule::increment_peg_counts(PEG_EXCESS_PARAMS);
+            create_event(EVENT_EXCESS_REPEAT_PARAMS);
+            return;
+        }
+
+        Parameter p = {};
+
+        if ( !parse_parameter(p) )
+            return;
+
+        if ( p.key_len == 0 )
+            continue;
+
+        Field raw_key(p.key_len, p.key);
+        Field raw_value(p.value_len, p.value);
+
+        KeyValue* fields = new KeyValue;
+
+        Field& norm_key = fields->key;
+        Field& norm_value = fields->value;
+
+        // normalize the key
+        unescape(raw_key, norm_key);
+
+        if ( (unsigned)norm_key.length() != key.length() )
+        {
+            delete fields;
+            continue;
+        }
+
+        const char* norm_key_str = (const char*)norm_key.start();
+
+        if ( strncasecmp(norm_key_str, key.c_str(), key.length()) )
+        {
+            delete fields;
+            continue;
+        }
+
+        // normalize the value
+        unescape(raw_value, norm_value);
+
+        // cache the parameter
+        data.kv_vec.push_back(fields);
+        HttpModule::increment_peg_counts(PEG_PARAMS);
+        kv_index++;
+    }
+}
+
+bool HttpQueryParser::parse_parameter(Parameter& p)
+{
+    if ( !parse_key(p) )
+        return false;
+
+    if ( !parse_value(p) )
+        return false;
+
+    return true;
+}
+
+bool HttpQueryParser::parse_key(Parameter& p)
+{
+    const uint8_t* term;
+
+    if ( index >= buffer_len )
+        return false;
+
+    p.key = buffer + index;
+
+    unsigned remaining = buffer_len - index;
+
+    // locate delimiter
+    term = (const uint8_t*)memchr(p.key, '=', remaining);
+
+    if ( !term )
+        return false;
+
+    p.key_len = term - p.key;
+
+    index += p.key_len + 1;
+
+    return true;
+}
+
+bool HttpQueryParser::parse_value(Parameter& p)
+{
+    const uint8_t* amp;
+    const uint8_t* semi;
+    const uint8_t* term;
+
+    if ( index >= buffer_len )
+        return false;
+
+    p.value = buffer + index;
+
+    unsigned remaining = buffer_len - index;
+
+    // locate delimiter
+    amp = (const uint8_t*)memchr(p.value, '&', remaining);
+    semi = (const uint8_t*)memchr(p.value, ';', remaining);
+
+    if ( amp && !semi )
+        term = amp;
+    else if ( !amp && semi )
+        term = semi;
+    else
+        term = (amp < semi) ? amp : semi;
+
+    if ( !term )
+    {
+        // last parameter
+        p.value_len = remaining;
+        index += remaining;
+        return true;
+    }
+
+    p.value_len = term - p.value;
+
+    index += p.value_len + 1;
+
+    return true;
+}
+
diff --git a/src/service_inspectors/http_inspect/http_query_parser.h b/src/service_inspectors/http_inspect/http_query_parser.h
new file mode 100644 (file)
index 0000000..554d949
--- /dev/null
@@ -0,0 +1,107 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// http_query_parser.h author Brandon Stultz <brastult@cisco.com>
+
+#ifndef HTTP_QUERY_PARSER_H
+#define HTTP_QUERY_PARSER_H
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "http_common.h"
+#include "http_field.h"
+#include "http_flow_data.h"
+#include "http_module.h"
+#include "http_param.h"
+
+struct KeyValue
+{
+    Field key;
+    Field value;
+};
+
+typedef std::vector<KeyValue*> KeyValueVec;
+
+class ParameterData
+{
+public:
+    ParameterData() = default;
+
+    ~ParameterData()
+    {
+        for ( KeyValue* kv : kv_vec )
+            delete kv;
+    }
+
+public:
+    KeyValueVec kv_vec;
+    bool parsed = false;
+};
+
+typedef std::unordered_map<std::string, ParameterData> ParameterMap;
+
+class HttpQueryParser
+{
+public:
+    HttpQueryParser(const uint8_t* buffer_, unsigned buffer_len_,
+        const uint8_t* norm_buffer_, unsigned norm_buffer_len_,
+        const HttpParaList::UriParam& uri_config_,
+        HttpFlowData* session_data_,
+        HttpCommon::SourceId source_id_)
+        : buffer(buffer_), buffer_len(buffer_len_),
+          norm_buffer(norm_buffer_), norm_buffer_len(norm_buffer_len_),
+          uri_config(uri_config_), session_data(session_data_),
+          source_id(source_id_) {}
+
+    void parse(const HttpParam& param, ParameterData& data);
+
+    struct Parameter
+    {
+        const uint8_t* key;
+        const uint8_t* value;
+        unsigned key_len;
+        unsigned value_len;
+    };
+
+private:
+    void create_event(int sid);
+
+    void unescape(const Field& raw, Field& norm);
+
+    bool parse_parameter(Parameter& p);
+    bool parse_key(Parameter& p);
+    bool parse_value(Parameter& p);
+
+    const uint8_t* buffer;
+    unsigned buffer_len;
+
+    const uint8_t* norm_buffer;
+    unsigned norm_buffer_len;
+
+    unsigned index;
+
+    static const unsigned MAX_REPEAT_PARAMS = 100;
+
+    const HttpParaList::UriParam& uri_config;
+    HttpFlowData* const session_data;
+    const HttpCommon::SourceId source_id;
+};
+
+#endif
+
index ed6906cddd04db0e1f2ee3312a08532254bd7784..d13fe4ff07cf4fca13006a477a02ee5e2a52566f 100644 (file)
@@ -379,6 +379,7 @@ const RuleMap HttpModule::http_events[] =
     { EVENT_BAD_HEADER_WHITESPACE,      "white space embedded in critical header value" },
     { EVENT_GZIP_EARLY_END,             "gzip compressed data followed by unexpected non-gzip "
                                         "data" },
+    { EVENT_EXCESS_REPEAT_PARAMS,       "excessive HTTP parameter key repeats" },
     { 0, nullptr }
 };
 
@@ -408,6 +409,8 @@ const PegInfo HttpModule::peg_names[PEG_COUNT_MAX+1] =
     { CountType::MAX, "max_concurrent_sessions", "maximum concurrent http sessions" },
     { CountType::SUM, "detained_packets", "TCP packets delayed by detained inspection" },
     { CountType::SUM, "partial_inspections", "pre-inspections for detained inspection" },
+    { CountType::SUM, "excess_parameters", "repeat parameters exceeding max" },
+    { CountType::SUM, "parameters", "HTTP parameters inspected" },
     { CountType::END, nullptr, nullptr }
 };
 
index 586959d991ef1824e2d7e4bb66c65cf67154e9fd..af1c611781c7ff1941a628e730a5dd2eebea1c5a 100644 (file)
@@ -450,7 +450,7 @@ int32_t UriNormalizer::norm_path_clean(uint8_t* buf, const int32_t in_length,
 
 // Provide traditional URI-style normalization for buffers that usually are not URIs
 void UriNormalizer::classic_normalize(const Field& input, Field& result,
-    const HttpParaList::UriParam& uri_param)
+    bool do_path, const HttpParaList::UriParam& uri_param)
 {
     // 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
@@ -469,7 +469,7 @@ void UriNormalizer::classic_normalize(const Field& input, Field& result,
     // Normalize character escape sequences
     int32_t data_length = norm_char_clean(input, buffer, uri_param, &unused, &dummy_ev);
 
-    if (uri_param.simplify_path)
+    if (do_path && uri_param.simplify_path)
     {
         // Normalize path directory traversals
         // Find the leading slash if there is one
index cb9095f4668d446b0bb5bfe560732a434190a8a6..72114149252618aeed7a898a2f851b5158a6e196 100644 (file)
@@ -41,7 +41,7 @@ public:
         HttpEventGen* events);
     static bool classic_need_norm(const Field& uri_component, bool do_path,
         const HttpParaList::UriParam& uri_param);
-    static void classic_normalize(const Field& input, Field& result,
+    static void classic_normalize(const Field& input, Field& result, bool do_path,
         const HttpParaList::UriParam& uri_param);
     static void load_default_unicode_map(uint8_t map[65536]);
     static void load_unicode_map(uint8_t map[65536], const char* filename, int code_page);
index 9260ae898b8937c968e294bab0a203e9849df575..1859f37e3dbb9493ed43f714343b1762a2a22657 100644 (file)
@@ -26,6 +26,7 @@
 #include "framework/cursor.h"
 #include "hash/hashfcn.h"
 #include "log/messages.h"
+#include "parser/parse_utils.h"
 #include "protocols/packet.h"
 #include "service_inspectors/http2_inspect/http2_flow_data.h"
 
@@ -34,6 +35,7 @@
 #include "http_flow_data.h"
 #include "http_inspect.h"
 #include "http_msg_head_shared.h"
+#include "http_param.h"
 
 using namespace snort;
 using namespace HttpCommon;
@@ -56,6 +58,7 @@ bool HttpCursorModule::begin(const char*, int, SnortConfig*)
     case HTTP_BUFFER_COOKIE:
     case HTTP_BUFFER_HEADER:
     case HTTP_BUFFER_METHOD:
+    case HTTP_BUFFER_PARAM:
     case HTTP_BUFFER_RAW_COOKIE:
     case HTTP_BUFFER_RAW_HEADER:
     case HTTP_BUFFER_RAW_REQUEST:
@@ -98,6 +101,17 @@ bool HttpCursorModule::set(const char*, Value& v, SnortConfig*)
         if (sub_id == STAT_OTHER)
             ParseError("Unrecognized header field name");
     }
+    else if (v.is("~param"))
+    {
+        std::string bc = v.get_string();
+        bool negated = false;
+        if (!parse_byte_code(bc.c_str(), negated, para_list.param) or negated)
+            ParseError("Invalid http_param");
+    }
+    else if (v.is("nocase"))
+    {
+        para_list.nocase = true;
+    }
     else if (v.is("request"))
     {
         para_list.request = true;
@@ -167,12 +181,16 @@ bool HttpCursorModule::end(const char*, int, SnortConfig*)
     if (para_list.scheme + para_list.host + para_list.port + para_list.path + para_list.query +
           para_list.fragment > 1)
         ParseError("Only specify one part of the URI");
+    if (buffer_index == HTTP_BUFFER_PARAM && para_list.param.length() == 0)
+        ParseError("Specify parameter name");
     return true;
 }
 
 void HttpCursorModule::HttpRuleParaList::reset()
 {
     field.clear();
+    param.clear();
+    nocase = false;
     request = false;
     with_header = false;
     with_body = false;
@@ -189,15 +207,10 @@ uint32_t HttpIpsOption::hash() const
 {
     uint32_t a = IpsOption::hash();
     uint32_t b = (uint32_t)inspect_section;
-    uint32_t c = sub_id >> 32;
-    uint32_t d = sub_id & 0xFFFFFFFF;
-    uint32_t e = form >> 32;
-    uint32_t f = form & 0xFFFFFFFF;
+    uint32_t c = buffer_info.hash();
     mix(a,b,c);
-    mix(d,e,f);
-    mix(a,c,f);
-    finalize(a,c,f);
-    return f;
+    finalize(a,b,c);
+    return c;
 }
 
 bool HttpIpsOption::operator==(const IpsOption& ips) const
@@ -205,8 +218,19 @@ bool HttpIpsOption::operator==(const IpsOption& ips) const
     const HttpIpsOption& hio = static_cast<const HttpIpsOption&>(ips);
     return IpsOption::operator==(ips) &&
            inspect_section == hio.inspect_section &&
-           sub_id == hio.sub_id &&
-           form == hio.form;
+           buffer_info == hio.buffer_info;
+}
+
+bool HttpIpsOption::retry(Cursor& c)
+{
+    if (buffer_info.type == HTTP_BUFFER_PARAM)
+    {
+        HttpCursorData* cd = (HttpCursorData*)c.get_data(HttpCursorData::id);
+
+        if (cd)
+            return cd->retry();
+    }
+    return false;
 }
 
 IpsOption::EvalStatus HttpIpsOption::eval(Cursor& c, Packet* p)
@@ -225,17 +249,17 @@ IpsOption::EvalStatus HttpIpsOption::eval(Cursor& c, Packet* p)
         return NO_MATCH;
 
     const Http2FlowData* const h2i_flow_data =
-       (Http2FlowData*)p->flow->get_flow_data(Http2FlowData::inspector_id);
+        (Http2FlowData*)p->flow->get_flow_data(Http2FlowData::inspector_id);
 
     HttpInspect* const hi = (h2i_flow_data != nullptr) ?
         (HttpInspect*)(p->flow->assistant_gadget) : (HttpInspect*)(p->flow->gadget);
 
-    InspectionBuffer hb;
+    const Field& http_buffer = hi->http_get_buf(c, p, buffer_info);
 
-    if (! (hi->http_get_buf((unsigned)buffer_index, sub_id, form, p, hb)))
+    if (http_buffer.length() <= 0)
         return NO_MATCH;
 
-    c.set(key, hb.data, hb.len);
+    c.set(key, http_buffer.start(), http_buffer.length());
 
     return MATCH;
 }
@@ -445,6 +469,55 @@ static const IpsApi method_api =
     nullptr
 };
 
+//-------------------------------------------------------------------------
+// http_param
+//-------------------------------------------------------------------------
+
+static const Parameter http_param_params[] =
+{
+    { "~param", Parameter::PT_STRING, nullptr, nullptr,
+        "parameter to match" },
+    { "nocase", Parameter::PT_IMPLIED, nullptr, nullptr,
+        "case insensitive match" },
+    { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
+#undef IPS_OPT
+#define IPS_OPT "http_param"
+#undef IPS_HELP
+#define IPS_HELP "rule option to set the detection cursor to the value of the specified HTTP parameter key which may be in the query or body"
+
+static Module* param_mod_ctor()
+{
+    return new HttpCursorModule(IPS_OPT, IPS_HELP, HTTP_BUFFER_PARAM, CAT_SET_OTHER, PSI_PARAM,
+        http_param_params);
+}
+
+static const IpsApi param_api =
+{
+    {
+        PT_IPS_OPTION,
+        sizeof(IpsApi),
+        IPSAPI_VERSION,
+        1,
+        API_RESERVED,
+        API_OPTIONS,
+        IPS_OPT,
+        IPS_HELP,
+        param_mod_ctor,
+        HttpCursorModule::mod_dtor
+    },
+    OPT_TYPE_DETECTION,
+    0, PROTO_BIT__TCP,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    HttpIpsOption::opt_ctor,
+    HttpIpsOption::opt_dtor,
+    nullptr
+};
+
 //-------------------------------------------------------------------------
 // http_raw_body
 //-------------------------------------------------------------------------
@@ -1132,6 +1205,7 @@ const BaseApi* ips_http_client_body = &client_body_api.base;
 const BaseApi* ips_http_cookie = &cookie_api.base;
 const BaseApi* ips_http_header = &header_api.base;
 const BaseApi* ips_http_method = &method_api.base;
+const BaseApi* ips_http_param = &param_api.base;
 const BaseApi* ips_http_raw_body = &raw_body_api.base;
 const BaseApi* ips_http_raw_cookie = &raw_cookie_api.base;
 const BaseApi* ips_http_raw_header = &raw_header_api.base;
index 64fbda52f38e4e460964194a33b8bf2f0888ff18..f5db9026f8af6cc4029f5d8c306f7b238180f36b 100644 (file)
 #include "framework/ips_option.h"
 #include "framework/module.h"
 
+#include "http_buffer_info.h"
 #include "http_enum.h"
 
-enum PsIdx { PSI_CLIENT_BODY, PSI_COOKIE, PSI_HEADER, PSI_METHOD, PSI_RAW_BODY, PSI_RAW_COOKIE,
-    PSI_RAW_HEADER, PSI_RAW_REQUEST, PSI_RAW_STATUS, PSI_RAW_TRAILER, PSI_RAW_URI, PSI_STAT_CODE,
-    PSI_STAT_MSG, PSI_TRAILER, PSI_TRUE_IP, PSI_URI, PSI_VERSION, PSI_MAX };
+enum PsIdx { PSI_CLIENT_BODY, PSI_COOKIE, PSI_HEADER, PSI_METHOD, PSI_PARAM,
+    PSI_RAW_BODY, PSI_RAW_COOKIE, PSI_RAW_HEADER, PSI_RAW_REQUEST, PSI_RAW_STATUS,
+    PSI_RAW_TRAILER, PSI_RAW_URI, PSI_STAT_CODE, PSI_STAT_MSG, PSI_TRAILER,
+    PSI_TRUE_IP, PSI_URI, PSI_VERSION, PSI_MAX };
 
 class HttpCursorModule : public snort::Module
 {
@@ -60,6 +62,8 @@ private:
     {
     public:
         std::string field;        // provide buffer containing specific header field
+        std::string param;        // provide buffer containing specific parameter
+        bool nocase;              // case insensitive match
         bool request;             // provide buffer from request not response
         bool with_header;         // provide buffer with a later section than it appears in
         bool with_body;
@@ -89,24 +93,25 @@ class HttpIpsOption : public snort::IpsOption
 {
 public:
     HttpIpsOption(const HttpCursorModule* cm) :
-        snort::IpsOption(cm->key, RULE_OPTION_TYPE_BUFFER_SET), key(cm->key),
-        buffer_index(cm->buffer_index), cat(cm->cat), psi(cm->psi),
-        inspect_section(cm->inspect_section), sub_id(cm->sub_id), form(cm->form) {}
+        snort::IpsOption(cm->key, RULE_OPTION_TYPE_BUFFER_SET),
+        key(cm->key), cat(cm->cat), psi(cm->psi),
+        inspect_section(cm->inspect_section),
+        buffer_info(cm->buffer_index, cm->sub_id, cm->form,
+            cm->para_list.param, cm->para_list.nocase) {}
     snort::CursorActionType get_cursor_type() const override { return cat; }
     EvalStatus eval(Cursor&, snort::Packet*) override;
     uint32_t hash() const override;
     bool operator==(const snort::IpsOption& ips) const override;
+    bool retry(Cursor&) override;
     static IpsOption* opt_ctor(snort::Module* m, OptTreeNode*)
         { return new HttpIpsOption((HttpCursorModule*)m); }
     static void opt_dtor(snort::IpsOption* p) { delete p; }
 private:
     const char* const key;
-    const HttpEnums::HTTP_BUFFER buffer_index;
     const snort::CursorActionType cat;
     const PsIdx psi;
     const HttpEnums::InspectSection inspect_section;
-    const uint64_t sub_id;
-    const uint64_t form;
+    HttpBufferInfo buffer_info;
 };
 
 #endif