]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2915 in SNORT/snort3 from ~SVLASIUK/snort3:js_external_script...
authorMike Stepanek (mstepane) <mstepane@cisco.com>
Tue, 8 Jun 2021 10:22:25 +0000 (10:22 +0000)
committerMike Stepanek (mstepane) <mstepane@cisco.com>
Tue, 8 Jun 2021 10:22:25 +0000 (10:22 +0000)
Squashed commit of the following:

commit ec3d59e7ec908f71cddb89782e6c9c5d76379d2d
Author: Serhii Vlasiuk <svlasiuk@cisco.com>
Date:   Mon May 24 19:03:11 2021 +0300

    http_inspect: add JS normalization for external scripts

    Support multiple PDUs and partial detection
    Support existing JS built-in rules
    Add new built-in rule when script body after script-src attribute is not comments

src/service_inspectors/http_inspect/http_enum.h
src/service_inspectors/http_inspect/http_flow_data.h
src/service_inspectors/http_inspect/http_js_norm.cc
src/service_inspectors/http_inspect/http_js_norm.h
src/service_inspectors/http_inspect/http_msg_body.cc
src/service_inspectors/http_inspect/http_msg_head_shared.cc
src/service_inspectors/http_inspect/http_msg_head_shared.h
src/service_inspectors/http_inspect/http_msg_request.cc
src/service_inspectors/http_inspect/http_tables.cc

index 14a76521bdf975bcedfe8f4995ab3cae5fae4fda..7b4126c777c108f923781c808d6946abd621eb6b 100755 (executable)
@@ -63,7 +63,8 @@ enum PEG_COUNT { PEG_FLOW = 0, PEG_SCAN, PEG_REASSEMBLE, PEG_INSPECT, PEG_REQUES
     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_SCRIPT_DETECTION,
     PEG_PARTIAL_INSPECT, PEG_EXCESS_PARAMS, PEG_PARAMS, PEG_CUTOVERS, PEG_SSL_SEARCH_ABND_EARLY,
-    PEG_PIPELINED_FLOWS, PEG_PIPELINED_REQUESTS, PEG_TOTAL_BYTES, PEG_JS_INLINE, PEG_COUNT_MAX };
+    PEG_PIPELINED_FLOWS, PEG_PIPELINED_REQUESTS, PEG_TOTAL_BYTES, PEG_JS_INLINE, PEG_JS_EXTERNAL,
+    PEG_COUNT_MAX };
 
 // Result of scanning by splitter
 enum ScanResult { SCAN_NOT_FOUND, SCAN_NOT_FOUND_ACCELERATE, SCAN_FOUND, SCAN_FOUND_PIECE,
@@ -268,6 +269,7 @@ enum Infraction
     INF_JS_BAD_TOKEN,
     INF_JS_OPENING_TAG,
     INF_JS_CLOSING_TAG,
+    INF_JS_CODE_IN_EXTERNAL,
     INF__MAX_VALUE
 };
 
@@ -326,9 +328,6 @@ enum EventSid
     EVENT_PDF_UNSUP_COMP_TYPE = 115,
     EVENT_PDF_CASC_COMP = 116,
     EVENT_PDF_PARSE_FAILURE = 117,
-    EVENT_JS_BAD_TOKEN = 118,
-    EVENT_JS_OPENING_TAG = 119,
-    EVENT_JS_CLOSING_TAG = 120,
 
     EVENT_LOSS_OF_SYNC = 201,
     EVENT_CHUNK_ZEROS = 202,
@@ -394,6 +393,10 @@ enum EventSid
     EVENT_LONG_SCHEME = 262,
     EVENT_HTTP2_UPGRADE_REQUEST = 263,
     EVENT_HTTP2_UPGRADE_RESPONSE = 264,
+    EVENT_JS_BAD_TOKEN = 265,
+    EVENT_JS_OPENING_TAG = 266,
+    EVENT_JS_CLOSING_TAG = 267,
+    EVENT_JS_CODE_IN_EXTERNAL = 268,
     EVENT__MAX_VALUE
 };
 
index eb86e790d0358bdf583e917da5f17845bfbd5ab2..d56e234557bafbf96929deda0f22732b0c5ae8e1 100644 (file)
@@ -193,6 +193,7 @@ private:
 
     // *** HttpJsNorm
     snort::JSNormalizer* js_normalizer = nullptr;
+    bool js_built_in_event = false;
 
     snort::JSNormalizer& acquire_js_ctx();
     void release_js_ctx();
index 90e7666f6c6d08cc0b13eb9f853e23d0e8f7c170..e1ec662a4c84338903aea827df42dbf634f4f740 100644 (file)
 using namespace HttpEnums;
 using namespace snort;
 
+static inline JSTokenizer::JSRet js_normalize(JSNormalizer& ctx, const char* const end,
+    const char* dst_end, const char*& ptr, char*& dst)
+{
+    auto ret = ctx.normalize(ptr, end - ptr, dst, dst_end - dst);
+    ptr = ctx.get_src_next();
+    dst = ctx.get_dst_next();
+
+    return ret;
+}
+
 HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_, int64_t normalization_depth_) :
     uri_param(uri_param_),
     normalization_depth(normalization_depth_),
@@ -81,7 +91,73 @@ void HttpJsNorm::configure()
     configure_once = true;
 }
 
-void HttpJsNorm::enhanced_normalize(const Field& input, Field& output,
+void HttpJsNorm::enhanced_external_normalize(const Field& input, Field& output,
+    HttpInfractions* infractions, HttpFlowData* ssn) const
+{
+    if (ssn->js_built_in_event)
+        return;
+
+    const char* ptr = (const char*)input.start();
+    const char* const end = ptr + input.length();
+
+    HttpEventGen* events = ssn->events[HttpCommon::SRC_SERVER];
+
+    char* buffer = nullptr;
+    char* dst = nullptr;
+    const char* dst_end = nullptr;
+
+    if (!alive_ctx(ssn))
+        HttpModule::increment_peg_counts(PEG_JS_EXTERNAL);
+
+    while (ptr < end)
+    {
+        if (!buffer)
+        {
+            auto len = end - ptr; // not more than the remaining raw data
+            buffer = new char[len];
+            dst = buffer;
+            dst_end = buffer + len;
+        }
+
+        auto& ctx = ssn->acquire_js_ctx();
+        ctx.set_depth(normalization_depth);
+        auto ret = js_normalize(ctx, end, dst_end, ptr, dst);
+
+        switch (ret)
+        {
+        case JSTokenizer::EOS:
+        case JSTokenizer::SCRIPT_CONTINUE:
+            break;
+        case JSTokenizer::SCRIPT_ENDED:
+        case JSTokenizer::CLOSING_TAG:
+            *infractions += INF_JS_CLOSING_TAG;
+            events->create_event(EVENT_JS_CLOSING_TAG);
+            ssn->js_built_in_event = true;
+            break;
+        case JSTokenizer::OPENING_TAG:
+            *infractions += INF_JS_OPENING_TAG;
+            events->create_event(EVENT_JS_OPENING_TAG);
+            ssn->js_built_in_event = true;
+            break;
+        case JSTokenizer::BAD_TOKEN:
+            *infractions += INF_JS_BAD_TOKEN;
+            events->create_event(EVENT_JS_BAD_TOKEN);
+            ssn->js_built_in_event = true;
+            break;
+        default:
+            assert(false);
+            break;
+        }
+
+        if (ssn->js_built_in_event)
+            break;
+    }
+
+    if (buffer)
+        output.set(dst - buffer, (const uint8_t*)buffer, true);
+}
+
+void HttpJsNorm::enhanced_inline_normalize(const Field& input, Field& output,
     HttpInfractions* infractions, HttpFlowData* ssn) const
 {
     const char* ptr = (const char*)input.start();
@@ -94,6 +170,7 @@ void HttpJsNorm::enhanced_normalize(const Field& input, Field& output,
     const char* dst_end = nullptr;
 
     bool script_continue = alive_ctx(ssn);
+    bool script_external = false;
 
     while (ptr < end)
     {
@@ -115,11 +192,14 @@ void HttpJsNorm::enhanced_normalize(const Field& input, Field& output,
                 ptr = sctx.next;
             }
 
-            if (!sctx.is_javascript || sctx.is_external)
+            if (!sctx.is_javascript)
                 continue;
 
+            script_external = sctx.is_external;
+
             // script found
-            HttpModule::increment_peg_counts(PEG_JS_INLINE);
+            if (!script_external)
+                HttpModule::increment_peg_counts(PEG_JS_INLINE);
         }
 
         if (!buffer)
@@ -127,7 +207,7 @@ void HttpJsNorm::enhanced_normalize(const Field& input, Field& output,
             uint8_t* nbuf = ssn->js_detect_buffer[HttpCommon::SRC_SERVER];
             uint32_t nlen = ssn->js_detect_length[HttpCommon::SRC_SERVER];
 
-            auto len = nlen + (end - ptr); // not more then the remaining raw data
+            auto len = nlen + (end - ptr); // not more than the remaining raw data
             buffer = new char[len];
             if (nbuf)
                 memcpy(buffer, nbuf, nlen);
@@ -137,10 +217,8 @@ void HttpJsNorm::enhanced_normalize(const Field& input, Field& output,
 
         auto& ctx = ssn->acquire_js_ctx();
         ctx.set_depth(normalization_depth);
-
-        auto ret = ctx.normalize(ptr, end - ptr, dst, dst_end - dst);
-        ptr = ctx.get_src_next();
-        dst = ctx.get_dst_next();
+        auto dst_before = dst;
+        auto ret = js_normalize(ctx, end, dst_end, ptr, dst);
 
         switch (ret)
         {
@@ -174,6 +252,12 @@ void HttpJsNorm::enhanced_normalize(const Field& input, Field& output,
             script_continue = false;
             break;
         }
+
+        if (script_external && dst_before != dst)
+        {
+            *infractions += INF_JS_CODE_IN_EXTERNAL;
+            events->create_event(EVENT_JS_CODE_IN_EXTERNAL);
+        }
     }
 
     if (!script_continue)
index 385754e169fdd5844853c93d1b4611a0db524567..4fb1d6126a79510505c5065b6a818485c2fd30eb 100644 (file)
@@ -41,7 +41,8 @@ public:
 
     void legacy_normalize(const Field& input, Field& output, HttpInfractions*, HttpEventGen*,
         int max_javascript_whitespaces) const;
-    void enhanced_normalize(const Field& input, Field& output, HttpInfractions*, HttpFlowData*) const;
+    void enhanced_inline_normalize(const Field& input, Field& output, HttpInfractions*, HttpFlowData*) const;
+    void enhanced_external_normalize(const Field& input, Field& output, HttpInfractions*, HttpFlowData*) const;
 
     void configure();
 
index 2ba5f71a631a5903020d24f98ecbfcb8b904ee91..869391577274e1e04762e620dea19de65b2980c6 100644 (file)
@@ -341,8 +341,13 @@ void HttpMsgBody::do_js_normalization(const Field& input, Field& output, bool pa
             len = 0;
         }
 
-        params->js_norm_param.js_norm->enhanced_normalize(input, enhanced_js_norm_body,
-            transaction->get_infractions(source_id), session_data);
+        auto http_header = get_header(source_id);
+        if (http_header and http_header->is_external_js())
+            params->js_norm_param.js_norm->enhanced_external_normalize(input, enhanced_js_norm_body,
+                transaction->get_infractions(source_id), session_data);
+        else
+            params->js_norm_param.js_norm->enhanced_inline_normalize(input, enhanced_js_norm_body,
+                transaction->get_infractions(source_id), session_data);
 
         const int32_t norm_length =
             (enhanced_js_norm_body.length() <= session_data->detect_depth_remaining[source_id]) ?
index 300b5ac1c93825f6b3ac8197a5be42322809713a..45c18d5947aa6e2f95076223e93825496e32df3d 100755 (executable)
@@ -84,6 +84,58 @@ HttpMsgHeadShared::~HttpMsgHeadShared()
     session_data->update_deallocations(extra_memory_allocations);
 }
 
+bool HttpMsgHeadShared::is_external_js()
+{
+    if (js_external != STAT_NOT_COMPUTE)
+        return js_external;
+
+    const Field& content_type = get_header_value_raw(HEAD_CONTENT_TYPE);
+    const char* cur = (const char*)content_type.start();
+    int len = content_type.length();
+    if (SnortStrcasestr(cur, len, "application/"))
+    {
+        if (SnortStrcasestr(cur, len, "javascript"))
+        {
+            js_external = 1;
+            return true;
+        }
+
+        if (SnortStrcasestr(cur, len, "ecmascript"))
+        {
+            js_external = 1;
+            return true;
+        }
+    }
+    else if (SnortStrcasestr(cur, len, "text/"))
+    {
+        if (SnortStrcasestr(cur, len, "javascript"))
+        {
+            js_external = 1;
+            return true;
+        }
+
+        if (SnortStrcasestr(cur, len, "ecmascript"))
+        {
+            js_external = 1;
+            return true;
+        }
+
+        if (SnortStrcasestr(cur, len, "jscript"))
+        {
+            js_external = 1;
+            return true;
+        }
+
+        if (SnortStrcasestr(cur, len, "livescript"))
+        {
+            js_external = 1;
+            return true;
+        }
+    }
+    js_external = 0;
+    return js_external;
+}
+
 // All the header processing that is done for every message (i.e. not just-in-time) is done here.
 void HttpMsgHeadShared::analyze()
 {
index 5a93fb23eead5c2ca9ceba947f9e3edc7a5fabb6..dcbc6e85cceb782df3529cdc2e60346786f9162f 100755 (executable)
@@ -58,6 +58,7 @@ public:
     // verdicts.
     uint64_t get_file_cache_index();
     const Field& get_content_disposition_filename();
+    bool is_external_js();
 
 protected:
     HttpMsgHeadShared(const uint8_t* buffer, const uint16_t buf_size,
@@ -133,6 +134,7 @@ private:
 
     bool own_msg_buffer;
     const uint32_t extra_memory_allocations;
+    int js_external = HttpCommon::STAT_NOT_COMPUTE;
 };
 
 #endif
index c59df1f49ce0ac4441085d2dd1a6741fdf682741..3535d93a8260ee129d7ef50605db38d6895753f3 100644 (file)
@@ -40,6 +40,7 @@ HttpMsgRequest::HttpMsgRequest(const uint8_t* buffer, const uint16_t buf_size,
 {
     transaction->set_request(this);
     get_related_sections();
+    session_data->release_js_ctx();
 }
 
 HttpMsgRequest::~HttpMsgRequest()
index 26909e2fa3864a3a4bb6112f3cfcea3fae270745..ac671f6a58793f6c6a23633e75915a7a1d52786e 100755 (executable)
@@ -357,9 +357,6 @@ const RuleMap HttpModule::http_events[] =
     { EVENT_PDF_UNSUP_COMP_TYPE,        "PDF file unsupported compression type" },
     { EVENT_PDF_CASC_COMP,              "PDF file cascaded compression" },
     { EVENT_PDF_PARSE_FAILURE,          "PDF file parse failure" },
-    { EVENT_JS_BAD_TOKEN,               "bad token in JavaScript" },
-    { EVENT_JS_OPENING_TAG,             "unexpected script opening tag in JavaScript" },
-    { EVENT_JS_CLOSING_TAG,             "unexpected script closing tag in JavaScript" },
     { EVENT_LOSS_OF_SYNC,               "not HTTP traffic" },
     { EVENT_CHUNK_ZEROS,                "chunk length has excessive leading zeros" },
     { EVENT_WS_BETWEEN_MSGS,            "white space before or between messages" },
@@ -430,6 +427,10 @@ const RuleMap HttpModule::http_events[] =
     { EVENT_LONG_SCHEME,                "HTTP URI scheme longer than 10 characters" },
     { EVENT_HTTP2_UPGRADE_REQUEST,      "HTTP/1 client requested HTTP/2 upgrade" },
     { EVENT_HTTP2_UPGRADE_RESPONSE,     "HTTP/1 server granted HTTP/2 upgrade" },
+    { EVENT_JS_BAD_TOKEN,               "bad token in JavaScript" },
+    { EVENT_JS_OPENING_TAG,             "unexpected script opening tag in JavaScript" },
+    { EVENT_JS_CLOSING_TAG,             "unexpected script closing tag in JavaScript" },
+    { EVENT_JS_CODE_IN_EXTERNAL,        "JavaScript code under the external script tags" },
     { 0, nullptr }
 };
 
@@ -467,6 +468,7 @@ const PegInfo HttpModule::peg_names[PEG_COUNT_MAX+1] =
     { CountType::SUM, "pipelined_requests", "total requests placed in a pipeline" },
     { CountType::SUM, "total_bytes", "total HTTP data bytes inspected" },
     { CountType::SUM, "js_inline_scripts", "total number of inline JavaScripts processed" },
+    { CountType::SUM, "js_external_scripts", "total number of external JavaScripts processed" },
     { CountType::END, nullptr, nullptr }
 };