]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2787 in SNORT/snort3 from ~SVLASIUK/snort3:script_data to master
authorMike Stepanek (mstepane) <mstepane@cisco.com>
Tue, 6 Apr 2021 15:50:46 +0000 (15:50 +0000)
committerMike Stepanek (mstepane) <mstepane@cisco.com>
Tue, 6 Apr 2021 15:50:46 +0000 (15:50 +0000)
Squashed commit of the following:

commit aac9aac7fdda1f5dd7ca37ac32690700156655eb
Author: Serhii Vlasiuk <svlasiuk@cisco.com>
Date:   Tue Mar 9 15:11:08 2021 +0200

    ips_options: add sticky buffer script_data ips option within normalized javascripts payload

    Update max value for js_normalization_depth = {-1, max53}
    Add mutual exclusion behaviour for js_normalization_depth and normalize_javascript
    js_normalization_depth - enables enhanced normalizer
    normalize_javascript - enables legacy normalizer

27 files changed:
doc/user/http_inspect.txt
src/detection/detection_engine.cc
src/detection/detection_engine.h
src/detection/detection_util.h
src/detection/fp_detect.cc
src/detection/fp_utils.cc
src/detection/ips_context.h
src/framework/ips_option.h
src/ips_options/CMakeLists.txt
src/ips_options/ips_options.cc
src/ips_options/ips_script_data.cc [new file with mode: 0644]
src/main/analyzer.cc
src/main/test/stubs.h
src/ports/port_group.h
src/service_inspectors/http_inspect/http_inspect.cc
src/service_inspectors/http_inspect/http_js_norm.cc
src/service_inspectors/http_inspect/http_js_norm.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/test/http_module_test.cc
src/service_inspectors/http_inspect/test/http_uri_norm_test.cc
src/utils/js_normalizer.cc
src/utils/js_normalizer.h
src/utils/stats.cc
src/utils/stats.h

index 90940c5289bbe489d36d3e44b88b8ff0a0302778..da8c3ec1fa713c8314ad65c937e8991c3a721db9 100755 (executable)
@@ -145,28 +145,32 @@ an uncompressed file.
 
 ===== normalize_javascript
 
-normalize_javascript = true will enable normalization of JavaScript within
+normalize_javascript = true will enable legacy normalizer of JavaScript within
 the HTTP response body. http_inspect looks for JavaScript by searching for
 the <script> tag without a type. Obfuscated data within the JavaScript
 functions such as unescape, String.fromCharCode, decodeURI, and
 decodeURIComponent are normalized. The different encodings handled within
 the unescape, decodeURI, or decodeURIComponent are %XX, %uXXXX, XX and
 uXXXXi. http_inspect also replaces consecutive whitespaces with a single
-space and normalizes the plus by concatenating the strings.
-Such normalizations refer to basic JavaScript normalization.
-
-===== normalization_depth
-
-normalization_depth = N {-1 : 65535} will set a number of input JavaScript
-bytes to normalize and enable the whitespace normalizer instead of the
-basic one. Meanwhile, normalize_javascript = true must be configured as
-well. When the depth is reached, normalization will be stopped. It's
-implemented per-script. normalization_depth = -1 will configure max depth
-value. By default, the value is set to 0. Configure this option to enable
-more precise whitespace normalization of JavaScript, that removes all
-redundant whitespaces and line terminators from the JavaScript syntax point
-of view (between identifier and punctuator, between identifier and operator,
-etc.) according to ECMAScript 5.1 standard.
+space and normalizes the plus by concatenating the strings. Such normalizations
+refer to basic JavaScript normalization. Сannot be used together with
+js_normalization_depth (doing so will cause Snort to fail to load). This is
+planned to be deprecated at some point.
+
+===== js_normalization_depth
+
+js_normalization_depth = N {-1 : 2ˆ53} will set a number of input
+JavaScript bytes to normalize and enable the enhanced normalizer. The enhanced
+and legacy normalizers have mutual exclusion behaviour, so you cannot enable
+both at the same time (doing so will cause Snort to fail to load). When the
+depth is reached, normalization will be stopped. It's implemented per-script.
+js_normalization_depth = -1, will set the max allowed depth value. By default,
+the value is set to 0 which means that normalizer is disabled. The enhanced
+normalizer provides more precise whitespace normalization of JavaScript, that
+removes all redundant whitespaces and line terminators from the JavaScript
+syntax point of view (between identifier and punctuator, between identifier and
+operator, etc.) according to ECMAScript 5.1 standard. This is currently
+experimental and still under development.
 
 ===== xff_headers
 
@@ -522,9 +526,19 @@ http_stat_msg.
 
 ===== file_data
 
-file_data contains the normalized message body. This is the normalization
-described above under gzip, normalize_utf, decompress_pdf, decompress_swf,
-and normalize_javascript.
+The file_data contains the normalized message body. This is the normalization
+described above under gzip, normalize_utf, decompress_pdf, decompress_swf, and
+normalize_javascript.
+
+===== script_data
+
+The script_data ips option is used as sticky buffer and contains only the
+normalized JavaScript HTTP response body without 'script' tags. In scope of
+rules the script_data option takes place with enabled new enhanced normalizer,
+so it is used in combination with http_inspect = { js_normalization_depth = N }.
+The js_normalization_depth option is described above. In rules the script_data
+can be used with file_data option where file_data would contain the whole HTTP
+response body for content matching.
 
 ==== Timing issues and combining rule options
 
index 799c3ea9f0ac66cad5ecc027cda11d4d47ef2ee6..c09f082a973447494005a237f692bf32b002d3fe 100644 (file)
@@ -101,7 +101,10 @@ void DetectionEngine::thread_term()
 DetectionEngine::DetectionEngine()
 {
     context = Analyzer::get_switcher()->interrupt();
-    context->file_data = { nullptr, 0 };
+
+    context->file_data = DataPointer(nullptr, 0);
+    context->script_data = DataPointer(nullptr, 0);
+
     reset();
 }
 
@@ -290,6 +293,12 @@ void DetectionEngine::set_file_data(const DataPointer& dp)
 DataPointer& DetectionEngine::get_file_data(IpsContext* c)
 { return c->file_data; }
 
+void DetectionEngine::set_script_data(const DataPointer& dp)
+{ Analyzer::get_switcher()->get_context()->script_data = dp; }
+
+DataPointer& DetectionEngine::get_script_data(IpsContext* c)
+{ return c->script_data; }
+
 void DetectionEngine::set_data(unsigned id, IpsContextData* p)
 { Analyzer::get_switcher()->get_context()->set_context_data(id, p); }
 
index bccfdb7404749faac908f8bab660e31bfe02a955..6e2063cfcebefc1ceddd891da4acc22bf5c5b0c9 100644 (file)
@@ -71,6 +71,9 @@ public:
     static void set_file_data(const DataPointer& dp);
     static DataPointer& get_file_data(IpsContext*);
 
+    static void set_script_data(const DataPointer& dp);
+    static DataPointer& get_script_data(IpsContext*);
+
     static uint8_t* get_buffer(unsigned& max);
     static struct DataBuffer& get_alt_buffer(Packet*);
 
@@ -130,6 +133,15 @@ static inline void set_file_data(const uint8_t* p, unsigned n)
 static inline void clear_file_data()
 { set_file_data(nullptr, 0); }
 
+static inline void set_script_data(const uint8_t* data, unsigned len)
+{
+    DataPointer dp { data, len };
+    DetectionEngine::set_script_data(dp);
+}
+
+static inline void clear_script_data()
+{ set_script_data(nullptr, 0); }
+
 } // namespace snort
 #endif
 
index 854f5f773b6fa35a7d562c70b7c5e3982e4b1df3..dd0008d94270740954b699e664fea0fb41b06b9a 100644 (file)
@@ -31,6 +31,8 @@
 
 struct DataPointer
 {
+    DataPointer(const uint8_t* d, unsigned l) :
+        data(d), len(l) {}
     const uint8_t* data;
     unsigned len;
 };
index a2b6eadb8bdb912c12e931f07a4c520e530bfe94..685bd597c677ce815af633b07f816426a5838c62 100644 (file)
@@ -929,24 +929,40 @@ static int fp_search(PortGroup* port_group, Packet* p, bool srvc)
         search_buffer(
             gadget, buf, buf.IBT_COOKIE, p, port_group, PM_TYPE_COOKIE, pc.cookie_searches);
     }
+
+    if ( MpseGroup* so = port_group->mpsegrp[PM_TYPE_SCRIPT] )
     {
-        // file searches file only
-        if ( MpseGroup* so = port_group->mpsegrp[PM_TYPE_FILE] )
+        // FIXIT-M script data should be obtained from
+        // inspector gadget as is done with search_buffer
+        DataPointer script_data = p->context->script_data;
+
+        if ( script_data.len )
         {
-            // FIXIT-M file data should be obtained from
-            // inspector gadget as is done with search_buffer
-            DataPointer file_data = p->context->file_data;
+            debug_logf(detection_trace, TRACE_FP_SEARCH, p,
+                "%" PRIu64 " fp search %s[%d]\n", p->context->packet_number,
+                pm_type_strings[PM_TYPE_SCRIPT], script_data.len);
 
-            if ( file_data.len )
-            {
-                debug_logf(detection_trace, TRACE_FP_SEARCH, p,
-                    "%" PRIu64 " fp search %s[%d]\n", p->context->packet_number,
-                    pm_type_strings[PM_TYPE_FILE], file_data.len);
+            batch_search(so, p, script_data.data, script_data.len, pc.script_searches);
+        }
+    }
 
-                batch_search(so, p, file_data.data, file_data.len, pc.file_searches);
-            }
+    // file searches file only
+    if ( MpseGroup* so = port_group->mpsegrp[PM_TYPE_FILE] )
+    {
+        // FIXIT-M file data should be obtained from
+        // inspector gadget as is done with search_buffer
+        DataPointer file_data = p->context->file_data;
+
+        if ( file_data.len )
+        {
+            debug_logf(detection_trace, TRACE_FP_SEARCH, p,
+                "%" PRIu64 " fp search %s[%d]\n", p->context->packet_number,
+                pm_type_strings[PM_TYPE_FILE], file_data.len);
+
+            batch_search(so, p, file_data.data, file_data.len, pc.file_searches);
         }
     }
+
     return 0;
 }
 
index 09c7f3e290cc41552dbc08abce9f6760f44f9f0a..89a8bcb13b08f783e5d026fdad401689bda51c8f 100644 (file)
@@ -77,6 +77,9 @@ PmType get_pm_type(CursorActionType cat)
     case CAT_SET_COOKIE:
         return PM_TYPE_COOKIE;
 
+    case CAT_SET_SCRIPT:
+        return PM_TYPE_SCRIPT;
+
     case CAT_SET_STAT_MSG:
         return PM_TYPE_STAT_MSG;
 
@@ -226,6 +229,7 @@ void validate_services(SnortConfig* sc, OptTreeNode* otn)
 {
     std::string svc;
     bool file = false;
+    bool script = false;
 
     for (OptFpList* ofl = otn->opt_func; ofl; ofl = ofl->next)
     {
@@ -246,6 +250,12 @@ void validate_services(SnortConfig* sc, OptTreeNode* otn)
             continue;
         }
 
+        if ( !strcmp(s, "script_data") )
+        {
+            script = true;
+            continue;
+        }
+
         s = get_service(s);
 
         if ( !s )
@@ -275,9 +285,14 @@ void validate_services(SnortConfig* sc, OptTreeNode* otn)
     {
         ParseWarning(WARN_RULES, "%u:%u:%u has no service with file_data",
             otn->sigInfo.gid, otn->sigInfo.sid, otn->sigInfo.rev);
-
         add_service_to_otn(sc, otn, "file");
     }
+    if ( otn->sigInfo.services.empty() and script )
+    {
+        ParseWarning(WARN_RULES, "%u:%u:%u has no service with script_data",
+            otn->sigInfo.gid, otn->sigInfo.sid, otn->sigInfo.rev);
+        add_service_to_otn(sc, otn, "http");
+    }
 }
 
 PatternMatchVector get_fp_content(
index 056ffea529c38daedf4ae95a948bd8fcf4f9abad..2b04a928de4231833ba3c82ef1b066bfeb02f4cc 100644 (file)
@@ -152,7 +152,8 @@ public:
     std::list<RegexRequest*>::iterator regex_req_it;
     SF_EVENTQ* equeue;
 
-    DataPointer file_data = {};
+    DataPointer file_data = DataPointer(nullptr, 0);
+    DataPointer script_data = DataPointer(nullptr, 0);
     DataBuffer alt_data = {};
 
     uint64_t context_num;
index 631de731373786f980c9e0ebac28e5c3ce004d37..9ffb003cc75083fa07408e0155f34ce99411562d 100644 (file)
@@ -53,6 +53,7 @@ enum CursorActionType
     CAT_SET_OTHER,
     CAT_SET_RAW,
     CAT_SET_COOKIE,
+    CAT_SET_SCRIPT,
     CAT_SET_STAT_MSG,
     CAT_SET_STAT_CODE,
     CAT_SET_METHOD,
index 6c7ddf0ecd928934194ed889f153984d0760654f..1db3c18d69e81ecd1d4bbd3a9729aca3218eec76 100644 (file)
@@ -36,6 +36,7 @@ SET( PLUGIN_LIST
     ips_rem.cc
     ips_rev.cc
     ips_rpc.cc
+    ips_script_data.cc
     ips_seq.cc
     ips_sid.cc
     ips_soid.cc
@@ -66,6 +67,7 @@ set (IPS_SOURCES
     ips_pkt_data.cc
     ips_reference.cc
     ips_replace.cc
+    ips_script_data.cc
     ips_service.cc
     ips_so.cc
 )
index e79e7016fa9410a0a7290761009395e471632d8e..2e6cd887755e41029c10480b3a646813a6f48cd9 100644 (file)
@@ -39,6 +39,7 @@ extern const BaseApi* ips_metadata;
 extern const BaseApi* ips_pkt_data;
 extern const BaseApi* ips_reference;
 extern const BaseApi* ips_replace;
+extern const BaseApi* ips_script_data;
 extern const BaseApi* ips_service;
 extern const BaseApi* ips_sha256;
 extern const BaseApi* ips_sha512;
@@ -105,6 +106,7 @@ static const BaseApi* ips_options[] =
     ips_pkt_data,
     ips_reference,
     ips_replace,
+    ips_script_data,
     ips_service,
     ips_sha256,
     ips_sha512,
diff --git a/src/ips_options/ips_script_data.cc b/src/ips_options/ips_script_data.cc
new file mode 100644 (file)
index 0000000..54be782
--- /dev/null
@@ -0,0 +1,137 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2021 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.
+//--------------------------------------------------------------------------
+// ips_script_data.cc author Serhii Vlasiuk <svlasiuk@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "detection/detection_engine.h"
+#include "framework/cursor.h"
+#include "framework/ips_option.h"
+#include "framework/module.h"
+#include "profiler/profiler.h"
+
+using namespace snort;
+
+#define s_name "script_data"
+#define s_help \
+    "rule option to set detection cursor to normalized script data"
+
+static THREAD_LOCAL ProfileStats scriptDataPerfStats;
+
+class ScriptDataOption : public IpsOption
+{
+public:
+    ScriptDataOption() : IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_SET) { }
+
+    CursorActionType get_cursor_type() const override
+    { return CAT_SET_SCRIPT; }
+
+    EvalStatus eval(Cursor&, Packet*) override;
+};
+
+IpsOption::EvalStatus ScriptDataOption::eval(Cursor& c, Packet* p)
+{
+    RuleProfile profile(scriptDataPerfStats);
+
+    DataPointer dp = DetectionEngine::get_script_data(p->context);
+
+    if ( !dp.data or !dp.len )
+        return NO_MATCH;
+
+    c.set(s_name, dp.data, dp.len);
+
+    return MATCH;
+}
+
+//-------------------------------------------------------------------------
+// module
+//-------------------------------------------------------------------------
+
+class ScriptDataModule : public Module
+{
+public:
+    ScriptDataModule() : Module(s_name, s_help) { }
+
+    ProfileStats* get_profile() const override
+    { return &scriptDataPerfStats; }
+
+    Usage get_usage() const override
+    { return DETECT; }
+};
+
+//-------------------------------------------------------------------------
+// api methods
+//-------------------------------------------------------------------------
+
+static Module* mod_ctor()
+{
+    return new ScriptDataModule;
+}
+
+static void mod_dtor(Module* m)
+{
+    delete m;
+}
+
+static IpsOption* script_data_ctor(Module*, OptTreeNode*)
+{
+    return new ScriptDataOption;
+}
+
+static void script_data_dtor(IpsOption* p)
+{
+    delete p;
+}
+
+static const IpsApi script_data_api =
+{
+    {
+        PT_IPS_OPTION,
+        sizeof(IpsApi),
+        IPSAPI_VERSION,
+        0,
+        API_RESERVED,
+        API_OPTIONS,
+        s_name,
+        s_help,
+        mod_ctor,
+        mod_dtor
+    },
+    OPT_TYPE_DETECTION,
+    0, 0,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    script_data_ctor,
+    script_data_dtor,
+    nullptr
+};
+
+#ifdef BUILDING_SO
+SO_PUBLIC const BaseApi* snort_plugins[] =
+#else
+const BaseApi* ips_script_data[] =
+#endif
+{
+    &script_data_api.base,
+    nullptr
+};
+
index 5891e2149f74860e85e66d0092576dabc5b839a8..3ea25c50910376a8058889704169287a6971253a 100644 (file)
@@ -203,6 +203,7 @@ static bool process_packet(Packet* p)
     if ( !(p->packet_flags & PKT_IGNORE) )
     {
         clear_file_data();
+        clear_script_data();
         // return incomplete status if the main hook indicates not all work was done
         if (!main_hook(p))
             return false;
index a2e466df43a985e171cd2cab1e9598fceb845458..156393b6e1a5efc447bf9c324cdac1223ae7c79a 100644 (file)
@@ -163,6 +163,7 @@ void DetectionEngine::idle() { }
 void DetectionEngine::reset() { }
 void DetectionEngine::wait_for_context() { }
 void DetectionEngine::set_file_data(const DataPointer&) { }
+void DetectionEngine::set_script_data(const DataPointer&) { }
 void DetectionEngine::clear_replacement() { }
 void DetectionEngine::disable_all(Packet*) { }
 unsigned get_instance_id() { return 0; }
index bcab1f398ecadb7167e6b359141b1e8969c07298..7fd6c5d8ed28a8ba0ffff0769ef81ba427506cc8 100644 (file)
@@ -47,6 +47,7 @@ enum PmType
     PM_TYPE_RAW_KEY,
     PM_TYPE_RAW_HEADER,
     PM_TYPE_METHOD,
+    PM_TYPE_SCRIPT,
     PM_TYPE_STAT_CODE,
     PM_TYPE_STAT_MSG,
     PM_TYPE_COOKIE,
@@ -56,7 +57,7 @@ enum PmType
 const char* const pm_type_strings[PM_TYPE_MAX] =
 {
     "packet", "alt", "key", "header", "body", "file", "raw_key", "raw_header",
-    "method", "stat_code", "stat_msg", "cookie"
+    "method", "script", "stat_code", "stat_msg", "cookie"
 };
 
 struct RULE_NODE
index 9c88839671bf068f5b2aa7f891b6905deafca5b3..6c6f6cf87aaca66b34607410a582d220a54bc7ec 100755 (executable)
@@ -128,7 +128,7 @@ HttpInspect::HttpInspect(const HttpParaList* params_) :
 
 bool HttpInspect::configure(SnortConfig* )
 {
-    if (params->js_norm_param.normalize_javascript)
+    if ( params->js_norm_param.js_norm )
         params->js_norm_param.js_norm->configure();
 
     return true;
@@ -153,6 +153,8 @@ void HttpInspect::show(const SnortConfig*) const
     ConfigLogger::log_flag("normalize_javascript", params->js_norm_param.normalize_javascript);
     ConfigLogger::log_value("max_javascript_whitespaces",
         params->js_norm_param.max_javascript_whitespaces);
+    ConfigLogger::log_value("js_normalization_depth",
+        params->js_norm_param.js_normalization_depth);
     ConfigLogger::log_value("bad_characters", bad_chars.c_str());
     ConfigLogger::log_value("ignore_unreserved", unreserved_chars.c_str());
     ConfigLogger::log_flag("percent_u", params->uri_param.percent_u);
index 70cbad6a8407e4ceae9b6a84e3c61cb6e8109482..2ce7fb0f4d21fb2bb8e7488441281c58fe310be0 100644 (file)
 using namespace HttpEnums;
 using namespace snort;
 
-class JsNormBase
-{
-public:
-    virtual ~JsNormBase() = default;
-
-    virtual int normalize(const char*, uint16_t, char*, uint16_t, const char**, int*, JSState*,
-    uint8_t*) = 0;
-
-};
-
-class UtilJsNorm : public JsNormBase
-{
-public:
-    UtilJsNorm() : JsNormBase() {}
-
-protected:
-    virtual int normalize(const char* src, uint16_t srclen, char* dst, uint16_t destlen,
-        const char** ptr, int* bytes_copied, JSState* js, uint8_t* iis_unicode_map) override
-    {
-        return JSNormalizeDecode(src, srclen, dst, destlen, ptr, bytes_copied, js, iis_unicode_map);
-    }
-
-};
-
-class JsNorm : public JsNormBase
-{
-public:
-    JsNorm(int normalization_depth)
-        : JsNormBase(),
-          norm_depth(normalization_depth)
-    {}
-
-protected:
-    virtual int normalize(const char* src, uint16_t srclen, char* dst, uint16_t destlen,
-        const char** ptr, int* bytes_copied, JSState*, uint8_t*) override
-    {
-        return JSNormalizer::normalize(src, srclen, dst, destlen, ptr, bytes_copied, norm_depth);
-    }
-
-private:
-    int norm_depth;
-
-};
-
-HttpJsNorm::HttpJsNorm(int max_javascript_whitespaces_, const HttpParaList::UriParam& uri_param_,
-    int normalization_depth) :
-    normalizer(nullptr), max_javascript_whitespaces(max_javascript_whitespaces_),
-    uri_param(uri_param_), normalization_depth(normalization_depth),
-    javascript_search_mpse(nullptr), htmltype_search_mpse(nullptr)
+HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_) :
+    uri_param(uri_param_), javascript_search_mpse(nullptr),
+    htmltype_search_mpse(nullptr)
 {}
 
 HttpJsNorm::~HttpJsNorm()
 {
-    delete normalizer;
     delete javascript_search_mpse;
     delete htmltype_search_mpse;
 }
@@ -95,14 +48,6 @@ void HttpJsNorm::configure()
     if ( configure_once )
         return;
 
-    // Based on this option configuration, default or whitespace normalizer will be initialized
-    // normalization_depth = 0 means to initialize default normalizer
-    // normalization_depth != 0 means to initialize whitespace normalizer with specified depth
-    if ( normalization_depth != 0 )
-        normalizer = new JsNorm(normalization_depth);
-    else
-        normalizer = new UtilJsNorm;
-
     javascript_search_mpse = new SearchTool;
     htmltype_search_mpse = new SearchTool;
 
@@ -133,8 +78,86 @@ void HttpJsNorm::configure()
     configure_once = true;
 }
 
-void HttpJsNorm::normalize(const Field& input, Field& output, HttpInfractions* infractions,
-    HttpEventGen* events) const
+void HttpJsNorm::enhanced_normalize(const Field& input, Field& output,
+    int64_t js_normalization_depth) const
+{
+    bool js_present = false;
+    int index = 0;
+    const char* ptr = (const char*)input.start();
+    const char* const end = ptr + input.length();
+
+    uint8_t* buffer = new uint8_t[input.length()];
+
+    while (ptr < end)
+    {
+        int bytes_copied = 0;
+        int mindex;
+
+        // Search for beginning of a javascript
+        if (javascript_search_mpse->find(ptr, end-ptr, search_js_found, false, &mindex) > 0)
+        {
+            const char* js_start = ptr + mindex;
+            const char* const angle_bracket =
+                (const char*)SnortStrnStr(js_start, end - js_start, ">");
+            if (angle_bracket == nullptr || (end - angle_bracket) == 0)
+                break;
+
+            bool type_js = false;
+            if (angle_bracket > js_start)
+            {
+                int mid;
+                const int script_found = htmltype_search_mpse->find(
+                    js_start, (angle_bracket-js_start), search_html_found, false, &mid);
+
+                js_start = angle_bracket + 1;
+                if (script_found > 0)
+                {
+                    switch (mid)
+                    {
+                    case HTML_JS:
+                        js_present = true;
+                        type_js = true;
+                        break;
+                    default:
+                        type_js = false;
+                        break;
+                    }
+                }
+                else
+                {
+                    // if no type or language is found we assume it is a javascript
+                    js_present = true;
+                    type_js = true;
+                }
+            }
+            // Save before the <script> begins
+            if (js_start > ptr)
+            {
+                if ((js_start - ptr) > (input.length() - index))
+                    break;
+            }
+
+            ptr = js_start;
+            if (!type_js)
+                continue;
+
+            JSNormalizer::normalize(js_start, (uint16_t)(end-js_start), (char*)buffer+index,
+                (uint16_t)(input.length() - index), &ptr, &bytes_copied, js_normalization_depth);
+
+            index += bytes_copied;
+        }
+        else
+            break;
+    }
+
+    if (js_present)
+        output.set(index, buffer, true);
+    else
+        delete[] buffer;
+}
+
+void HttpJsNorm::legacy_normalize(const Field& input, Field& output, HttpInfractions* infractions,
+    HttpEventGen* events, int max_javascript_whitespaces) const
 {
     bool js_present = false;
     int index = 0;
@@ -203,7 +226,7 @@ void HttpJsNorm::normalize(const Field& input, Field& output, HttpInfractions* i
             if (!type_js)
                 continue;
 
-            normalizer->normalize(js_start, (uint16_t)(end-js_start), (char*)buffer+index,
+            JSNormalizeDecode(js_start, (uint16_t)(end-js_start), (char*)buffer+index,
                 (uint16_t)(input.length() - index), &ptr, &bytes_copied, &js,
                 uri_param.iis_unicode ? uri_param.unicode_map : nullptr);
 
index a619a43645d880e785e537fb6904229b69324e5f..5f083349a9fd47193600718461e0dc04f55ba71a 100644 (file)
 // HttpJsNorm class
 //-------------------------------------------------------------------------
 
-class JsNormBase;
-
 class HttpJsNorm
 {
 public:
-    HttpJsNorm(int max_javascript_whitespaces_, const HttpParaList::UriParam& uri_param_,
-        int normalization_depth);
+    HttpJsNorm(const HttpParaList::UriParam& uri_param_);
     ~HttpJsNorm();
-    void normalize(const Field& input, Field& output, HttpInfractions* infractions,
-        HttpEventGen* events) const;
+    void legacy_normalize(const Field& input, Field& output, HttpInfractions* infractions,
+        HttpEventGen* events, int max_javascript_whitespaces) const;
+    void enhanced_normalize(const Field& input, Field& output,
+        int64_t js_normalization_depth) const;
+
     void configure();
 private:
     bool configure_once = false;
 
-    JsNormBase* normalizer;
-
     enum JsSearchId { JS_JAVASCRIPT };
     enum HtmlSearchId { HTML_JS, HTML_EMA, HTML_VB };
 
     static constexpr const char* script_start = "<SCRIPT";
     static constexpr int script_start_length = sizeof("<SCRIPT") - 1;
 
-    const int max_javascript_whitespaces;
     const HttpParaList::UriParam& uri_param;
-    const int normalization_depth;
 
     snort::SearchTool* javascript_search_mpse;
     snort::SearchTool* htmltype_search_mpse;
index d2e67fcc51317c42d4816832a91a0c42ad5ddc95..d80db3a28cb0a7949fd40ff26635925ba3f75606 100755 (executable)
@@ -87,10 +87,11 @@ const Parameter HttpModule::http_params[] =
       "inspect JavaScript immediately upon script end" },
 
     { "normalize_javascript", Parameter::PT_BOOL, nullptr, "false",
-      "normalize JavaScript in response bodies" },
+      "use legacy normalizer to normalize JavaScript in response bodies" },
 
-    { "normalization_depth", Parameter::PT_INT, "-1:65535", "0",
-      "number of input JavaScript bytes to normalize" },
+    { "js_normalization_depth", Parameter::PT_INT, "-1:max53", "0",
+      "number of input JavaScript bytes to normalize with enhanced normalizer "
+      "(-1 max allowed value) (experimental)" },
 
     { "max_javascript_whitespaces", Parameter::PT_INT, "1:65535", "200",
       "maximum consecutive whitespaces allowed within the JavaScript obfuscated data" },
@@ -216,11 +217,20 @@ bool HttpModule::set(const char*, Value& val, SnortConfig*)
     else if (val.is("normalize_javascript"))
     {
         params->js_norm_param.normalize_javascript = val.get_bool();
+
+        if ( !params->js_norm_param.is_javascript_normalization )
+            params->js_norm_param.is_javascript_normalization =
+                params->js_norm_param.normalize_javascript;
     }
-    else if (val.is("normalization_depth"))
+    else if (val.is("js_normalization_depth"))
     {
-        int v = val.get_int32();
-        params->js_norm_param.normalization_depth = (v == -1) ? 65535 : v;
+        int64_t v = val.get_int64();
+        params->js_norm_param.js_normalization_depth = (v == -1) ?
+          Parameter::get_int("max53") : v;
+
+        if ( !params->js_norm_param.is_javascript_normalization )
+            params->js_norm_param.is_javascript_normalization =
+                (params->js_norm_param.js_normalization_depth > 0);
     }
     else if (val.is("max_javascript_whitespaces"))
     {
@@ -398,12 +408,13 @@ bool HttpModule::end(const char*, int, SnortConfig*)
                 params->uri_param.iis_unicode_map_file.c_str(),
                 params->uri_param.iis_unicode_code_page);
     }
-    if (params->js_norm_param.normalize_javascript)
-    {
-        params->js_norm_param.js_norm =
-            new HttpJsNorm(params->js_norm_param.max_javascript_whitespaces, params->uri_param,
-            params->js_norm_param.normalization_depth);
-    }
+
+    if ( params->js_norm_param.normalize_javascript and
+      params->js_norm_param.js_normalization_depth )
+        ParseError("Cannot use normalize_javascript and js_normalization_depth together.");
+
+    if ( params->js_norm_param.is_javascript_normalization )
+        params->js_norm_param.js_norm = new HttpJsNorm(params->uri_param);
 
     prepare_http_header_list(params);
 
index 058d7e9b47ca19054a11857dd09d9f4c3a38a0d8..4b968e83271a26881f4c103f77f8e8355906a302 100755 (executable)
@@ -52,7 +52,8 @@ public:
     public:
         ~JsNormParam();
         bool normalize_javascript = false;
-        int normalization_depth = 0;
+        bool is_javascript_normalization = false;
+        int64_t js_normalization_depth = 0;
         int max_javascript_whitespaces = 200;
         class HttpJsNorm* js_norm = nullptr;
     };
index 3cb92996c1f34e1c7ed52266e5f9ab0274105626..623384b2a07f42fd8ac84c196931ff7cfd384d1d 100644 (file)
@@ -133,6 +133,7 @@ void HttpMsgBody::analyze()
         const int32_t detect_length =
             (js_norm_body.length() <= session_data->detect_depth_remaining[source_id]) ?
             js_norm_body.length() : session_data->detect_depth_remaining[source_id];
+
         detect_data.set(detect_length, js_norm_body.start());
 
         delete[] partial_detect_buffer;
@@ -278,14 +279,26 @@ void HttpMsgBody::fd_event_callback(void* context, int event)
 
 void HttpMsgBody::do_js_normalization(const Field& input, Field& output)
 {
-    if (!params->js_norm_param.normalize_javascript || source_id == SRC_CLIENT)
+    if ( !params->js_norm_param.is_javascript_normalization or source_id == SRC_CLIENT )
+        output.set(input);
+    else if ( params->js_norm_param.normalize_javascript )
+        params->js_norm_param.js_norm->legacy_normalize(input, output,
+            transaction->get_infractions(source_id), session_data->events[source_id],
+            params->js_norm_param.max_javascript_whitespaces);
+    else if ( params->js_norm_param.js_normalization_depth )
     {
         output.set(input);
-        return;
-    }
 
-    params->js_norm_param.js_norm->normalize(input, output,
-        transaction->get_infractions(source_id), session_data->events[source_id]);
+        params->js_norm_param.js_norm->enhanced_normalize(input, enhanced_js_norm_body,
+            params->js_norm_param.js_normalization_depth);
+
+        const int32_t norm_length =
+            (enhanced_js_norm_body.length() <= session_data->detect_depth_remaining[source_id]) ?
+            enhanced_js_norm_body.length() : session_data->detect_depth_remaining[source_id];
+
+        if ( norm_length > 0 )
+            set_script_data(enhanced_js_norm_body.start(), (unsigned int)norm_length);
+    }
 }
 
 void HttpMsgBody::do_file_processing(const Field& file_data)
index 63a254b60b71267977222703488b3c5a889ad099..d4e3f671bdb2941f4a1fdc68881f76b8146a5394 100644 (file)
@@ -73,6 +73,7 @@ private:
     Field cumulative_data;
     Field js_norm_body;
     Field detect_data;
+    Field enhanced_js_norm_body;
     Field classic_client_body;   // URI normalization applied
 };
 
index f232034fdc539123d04e709e256896b410a20527..709710239f9a2b1092d763c80d174dbd0ae96910 100755 (executable)
@@ -64,11 +64,11 @@ int32_t substr_to_code(const uint8_t*, const int32_t, const StrCode []) { return
 long HttpTestManager::print_amount {};
 bool HttpTestManager::print_hex {};
 
-HttpJsNorm::HttpJsNorm(int, const HttpParaList::UriParam& uri_param_, int) :
-    normalizer(nullptr), max_javascript_whitespaces(0), uri_param(uri_param_),
-    normalization_depth(0), javascript_search_mpse(nullptr), htmltype_search_mpse(nullptr) {}
+HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_) :
+    uri_param(uri_param_), javascript_search_mpse(nullptr), htmltype_search_mpse(nullptr) {}
 HttpJsNorm::~HttpJsNorm() = default;
 void HttpJsNorm::configure(){}
+int64_t Parameter::get_int(char const*) { return 0; }
 
 TEST_GROUP(http_peg_count_test)
 {
index 76e9e0e5a0390c30cffd2b56d4d5bbe1387328d4..fb33bd3e19c09b98f73eff03df0a6ae52a14f706 100755 (executable)
@@ -53,11 +53,11 @@ LiteralSearch* LiteralSearch::instantiate(LiteralSearch::Handle*, const uint8_t*
 void show_stats(PegCount*, const PegInfo*, unsigned, const char*) { }
 void show_stats(PegCount*, const PegInfo*, const IndexVec&, const char*, FILE*) { }
 
-HttpJsNorm::HttpJsNorm(int, const HttpParaList::UriParam& uri_param_, int) :
-    normalizer(nullptr), max_javascript_whitespaces(0), uri_param(uri_param_),
-    normalization_depth(0), javascript_search_mpse(nullptr), htmltype_search_mpse(nullptr) {}
+HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_) :
+    uri_param(uri_param_), javascript_search_mpse(nullptr), htmltype_search_mpse(nullptr) {}
 HttpJsNorm::~HttpJsNorm() = default;
 void HttpJsNorm::configure() {}
+int64_t Parameter::get_int(char const*) { return 0; }
 
 TEST_GROUP(http_inspect_uri_norm)
 {
index f5793b9ac6445b2d9ab124fcfd6377438b809272..1c41eaddc1d4684d9f0369c2dc2aed9fb3b57ffa 100644 (file)
@@ -30,7 +30,7 @@
 using namespace snort;
 
 int JSNormalizer::normalize(const char* srcbuf, uint16_t srclen, char* dstbuf, uint16_t dstlen,
-        const char** ptr, int* bytes_copied, int norm_depth)
+        const char** ptr, int* bytes_copied, int64_t norm_depth)
 {
     std::stringstream in, out;
 
index 4688e98d1f32d7555104ff3ce2d23f523622c31e..9152e214236d6a1434dc1432ee21525633a48a24 100644 (file)
@@ -28,7 +28,7 @@ class JSNormalizer
 {
 public:
     static int normalize(const char* srcbuf, uint16_t srclen, char* dstbuf, uint16_t dstlen,
-        const char** ptr, int* bytes_copied, int norm_depth);
+        const char** ptr, int* bytes_copied, int64_t norm_depth);
 };
 }
 
index 2cd651116a5c3952183963b0c946767cf3325242..1803440623376af62308eda3517aab0ef80998cb 100644 (file)
@@ -185,6 +185,7 @@ const PegInfo pc_names[] =
     { CountType::SUM, "raw_key_searches", "fast pattern searches in raw key buffer" },
     { CountType::SUM, "raw_header_searches", "fast pattern searches in raw header buffer" },
     { CountType::SUM, "method_searches", "fast pattern searches in method buffer" },
+    { CountType::SUM, "script_searches", "fast pattern searches in script buffer" },
     { CountType::SUM, "stat_code_searches", "fast pattern searches in status code buffer" },
     { CountType::SUM, "stat_msg_searches", "fast pattern searches in status message buffer" },
     { CountType::SUM, "cookie_searches", "fast pattern searches in cookie buffer" },
index b9ca61d2a8037ad73d8475275ba4bdb53bbe603a..85d04f37ff00604b185acd534984c0078ffcf1ea 100644 (file)
@@ -47,6 +47,7 @@ struct PacketCount
     PegCount raw_key_searches;
     PegCount raw_header_searches;
     PegCount method_searches;
+    PegCount script_searches;
     PegCount stat_code_searches;
     PegCount stat_msg_searches;
     PegCount cookie_searches;