]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2405 in SNORT/snort3 from ~THOPETER/snort3:nhttp140a to master
authorMike Stepanek (mstepane) <mstepane@cisco.com>
Tue, 25 Aug 2020 12:12:37 +0000 (12:12 +0000)
committerMike Stepanek (mstepane) <mstepane@cisco.com>
Tue, 25 Aug 2020 12:12:37 +0000 (12:12 +0000)
Squashed commit of the following:

commit 9576a7b759fa2a697ae18e56ec528460ec0f5a61
Author: Tom Peters <thopeter@cisco.com>
Date:   Tue Jun 23 13:35:10 2020 -0400

    http_inspect: script detection

14 files changed:
doc/user/http_inspect.txt
src/service_inspectors/http_inspect/dev_notes.txt
src/service_inspectors/http_inspect/http_cutter.cc
src/service_inspectors/http_inspect/http_cutter.h
src/service_inspectors/http_inspect/http_enum.h
src/service_inspectors/http_inspect/http_flow_data.cc
src/service_inspectors/http_inspect/http_flow_data.h
src/service_inspectors/http_inspect/http_inspect.cc
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_header.cc
src/service_inspectors/http_inspect/http_stream_splitter_scan.cc
src/service_inspectors/http_inspect/http_tables.cc

index afb1d2cef8f946fb157a006b3c42ffbb32727517..674248e822f4ea38a0a0c1cf5df8e96971ab30cd 100644 (file)
@@ -109,6 +109,16 @@ traffic it is designed for use with inline mode operation (-Q).
 This feature is off by default. detained_inspection = true will activate
 it.
 
+===== script_detection
+
+Script detection is an alternative to detained inspection. When
+http_inspect detects the end of a script it immediately forwards the
+available part of the message body for early detection. This enables
+malicious Javascripts to be detected more quickly but consumes somewhat
+more of the sensor's resources.
+
+This feature is off by default. script_detection = true will activate it.
+
 ===== gzip
 
 http_inspect by default decompresses deflate and gzip message bodies
index a6c50d076023093a9bd3061d1fe6730d53fee1ba..fa91439afcd1f3014ec772390b33851b0afb14ee 100644 (file)
@@ -31,6 +31,13 @@ message section. Only reassemble() knows that something different is happening.
 delivers message data for reassembly once. reassemble() stores data received for a partial
 inspection and prepends it to the buffer for the next inspection.
 
+Script detection is a different feature developed to solve the same problem. The scanning mechanism
+developed for detained inspection is repurposed to look for the end-of-script tag "</script>". When
+one is found an immediate partial inspection is performed. This avoids the adverse network
+consequences of detaining packets at the performance and memory cost of doing a much larger number
+of partial inspections. Code features that support both approaches are referred to as accelerated
+blocking.
+
 HttpFlowData is a data class representing all HI information relating to a flow. It serves as
 persistent memory between invocations of HI by the framework. It also glues together the inspector,
 the client-to-server splitter, and the server-to-client splitter which pass information through the
index 5e913f30baeee03e05ad91bdadb517e8ce296838..c88a3e8621478deed579113d6e2f8cb560b93059 100644 (file)
@@ -252,23 +252,44 @@ ScanResult HttpHeaderCutter::cut(const uint8_t* buffer, uint32_t length,
     return SCAN_NOT_FOUND;
 }
 
-HttpBodyCutter::HttpBodyCutter(bool detained_inspection_, CompressId compression_) :
-    detained_inspection(detained_inspection_), compression(compression_)
+HttpBodyCutter::HttpBodyCutter(AcceleratedBlocking accelerated_blocking_, CompressId compression_)
+    : accelerated_blocking(accelerated_blocking_), compression(compression_)
 {
-    if (detained_inspection && ((compression == CMP_GZIP) || (compression == CMP_DEFLATE)))
+    if (accelerated_blocking != AB_NONE)
     {
-        compress_stream = new z_stream;
-        compress_stream->zalloc = Z_NULL;
-        compress_stream->zfree = Z_NULL;
-        compress_stream->next_in = Z_NULL;
-        compress_stream->avail_in = 0;
-        const int window_bits = (compression == CMP_GZIP) ? GZIP_WINDOW_BITS : DEFLATE_WINDOW_BITS;
-        if (inflateInit2(compress_stream, window_bits) != Z_OK)
+        if ((compression == CMP_GZIP) || (compression == CMP_DEFLATE))
         {
-            assert(false);
-            compression = CMP_NONE;
-            delete compress_stream;
-            compress_stream = nullptr;
+            compress_stream = new z_stream;
+            compress_stream->zalloc = Z_NULL;
+            compress_stream->zfree = Z_NULL;
+            compress_stream->next_in = Z_NULL;
+            compress_stream->avail_in = 0;
+            const int window_bits = (compression == CMP_GZIP) ? GZIP_WINDOW_BITS : DEFLATE_WINDOW_BITS;
+            if (inflateInit2(compress_stream, window_bits) != Z_OK)
+            {
+                assert(false);
+                compression = CMP_NONE;
+                delete compress_stream;
+                compress_stream = nullptr;
+            }
+        }
+
+        static const uint8_t detain_string[] = { '<', 's', 'c', 'r', 'i', 'p', 't' };
+        static const uint8_t detain_upper[] = { '<', 'S', 'C', 'R', 'I', 'P', 'T' };
+        static const uint8_t inspect_string[] = { '<', '/', 's', 'c', 'r', 'i', 'p', 't', '>' };
+        static const uint8_t inspect_upper[] = { '<', '/', 'S', 'C', 'R', 'I', 'P', 'T', '>' };
+
+        if (accelerated_blocking == AB_DETAIN)
+        {
+            match_string = detain_string;
+            match_string_upper = detain_upper;
+            string_length = sizeof(detain_string);
+        }
+        else
+        {
+            match_string = inspect_string;
+            match_string_upper = inspect_upper;
+            string_length = sizeof(inspect_string);
         }
     }
 }
@@ -314,7 +335,8 @@ ScanResult HttpBodyClCutter::cut(const uint8_t* buffer, uint32_t length, HttpInf
     if (octets_seen + length < flow_target)
     {
         octets_seen += length;
-        return need_detained_inspection(buffer, length) ? SCAN_NOT_FOUND_DETAIN : SCAN_NOT_FOUND;
+        return need_accelerated_blocking(buffer, length) ?
+            SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
     }
 
     if (!stretch)
@@ -323,7 +345,7 @@ ScanResult HttpBodyClCutter::cut(const uint8_t* buffer, uint32_t length, HttpInf
         num_flush = flow_target - octets_seen;
         if (remaining > 0)
         {
-            need_detained_inspection(buffer, num_flush);
+            need_accelerated_blocking(buffer, num_flush);
             return SCAN_FOUND_PIECE;
         }
         else
@@ -339,7 +361,7 @@ ScanResult HttpBodyClCutter::cut(const uint8_t* buffer, uint32_t length, HttpInf
         else
             num_flush = flow_target - octets_seen;
         remaining -= octets_seen + num_flush;
-        need_detained_inspection(buffer, num_flush);
+        need_accelerated_blocking(buffer, num_flush);
         return SCAN_FOUND_PIECE;
     }
 
@@ -354,7 +376,7 @@ ScanResult HttpBodyClCutter::cut(const uint8_t* buffer, uint32_t length, HttpInf
     // Cannot stretch to the end of the message body. Cut at the original target.
     num_flush = flow_target - octets_seen;
     remaining -= flow_target;
-    need_detained_inspection(buffer, num_flush);
+    need_accelerated_blocking(buffer, num_flush);
     return SCAN_FOUND_PIECE;
 }
 
@@ -376,13 +398,14 @@ ScanResult HttpBodyOldCutter::cut(const uint8_t* buffer, uint32_t length, HttpIn
     {
         // Not enough data yet to create a message section
         octets_seen += length;
-        return need_detained_inspection(buffer, length) ? SCAN_NOT_FOUND_DETAIN : SCAN_NOT_FOUND;
+        return need_accelerated_blocking(buffer, length) ?
+            SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
     }
     else if (stretch && (octets_seen + length <= flow_target + MAX_SECTION_STRETCH))
     {
         // Cut the section at the end of this TCP segment to avoid splitting a packet
         num_flush = length;
-        need_detained_inspection(buffer, num_flush);
+        need_accelerated_blocking(buffer, num_flush);
         return SCAN_FOUND_PIECE;
     }
     else
@@ -390,7 +413,7 @@ ScanResult HttpBodyOldCutter::cut(const uint8_t* buffer, uint32_t length, HttpIn
         // Cut the section at the target length. Either stretching is not allowed or the end of
         // the segment is too far away.
         num_flush = flow_target - octets_seen;
-        need_detained_inspection(buffer, num_flush);
+        need_accelerated_blocking(buffer, num_flush);
         return SCAN_FOUND_PIECE;
     }
 }
@@ -403,7 +426,7 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
 
     const uint32_t adjusted_target = stretch ? MAX_SECTION_STRETCH + flow_target : flow_target;
 
-    bool detain_this_packet = false;
+    bool accelerate_this_packet = false;
 
     for (int32_t k=0; k < static_cast<int32_t>(length); k++)
     {
@@ -583,8 +606,8 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
                 skip_amount = adjusted_target-data_seen;
             }
 
-            if (!detain_this_packet)
-                detain_this_packet = need_detained_inspection(buffer+k, skip_amount);
+            accelerate_this_packet = need_accelerated_blocking(buffer+k, skip_amount) ||
+                accelerate_this_packet;
 
             k += skip_amount - 1;
             if ((expected -= skip_amount) == 0)
@@ -658,8 +681,8 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
             uint32_t skip_amount = length-k;
             skip_amount = (skip_amount <= adjusted_target-data_seen) ? skip_amount :
                 adjusted_target-data_seen;
-            if (!detain_this_packet)
-                detain_this_packet = need_detained_inspection(buffer+k, skip_amount);
+            accelerate_this_packet = need_accelerated_blocking(buffer+k, skip_amount) ||
+                accelerate_this_packet;
             k += skip_amount - 1;
             if ((data_seen += skip_amount) == adjusted_target)
             {
@@ -685,18 +708,15 @@ ScanResult HttpBodyChunkCutter::cut(const uint8_t* buffer, uint32_t length,
     }
 
     octets_seen += length;
-    return detain_this_packet ? SCAN_NOT_FOUND_DETAIN : SCAN_NOT_FOUND;
+    return accelerate_this_packet ? SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
 }
 
-ScanResult HttpBodyH2Cutter::cut(const uint8_t* buffer, uint32_t length,
-    HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch,
+ScanResult HttpBodyH2Cutter::cut(const uint8_t* /*buffer*/, uint32_t length,
+    HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool /*stretch*/,
     bool h2_body_finished)
 {
-    // FIXIT-E detained inspection not yet supported for HTTP/2
-    UNUSED(buffer);
-
+    // FIXIT-E accelerated blocking not yet supported for HTTP/2
     // FIXIT-E stretch not yet supported for HTTP/2 message bodies
-    UNUSED(stretch);
 
     // If the headers included a content length header (expected length >= 0), check it against the
     // actual message body length. Alert if it does not match at the end of the message body or if
@@ -749,25 +769,49 @@ ScanResult HttpBodyH2Cutter::cut(const uint8_t* buffer, uint32_t length,
 }
 
 // This method searches the input stream looking for the beginning of a script or other dangerous
-// content that requires detained inspection. Exactly what we are looking for is encapsulated in
+// content that requires accelerated blocking. Exactly what we are looking for is encapsulated in
 // dangerous().
 //
 // Return value true indicates a match and enables the packet that completes the matching sequence
-// to be detained.
+// to be detained (detained inspection) or sent for partial inspection (script detection).
 //
 // Once detained inspection is activated on a message body it never goes away. The first packet
 // of every subsequent message section must be detained (detention_required). Supporting this
 // requirement requires that the calling routine submit all data including buffers that are about
 // to be flushed.
-bool HttpBodyCutter::need_detained_inspection(const uint8_t* data, uint32_t length)
+//
+// Script detection (AB_INSPECT) is similar in that the message data must be scanned by dangerous()
+// looking for a particular string. It differs in the string being searched for and that difference
+// is built into dangerous(). Script detection does not automatically apply to subsequent message
+// sections. It only recurs when a new end-of-script tag is found.
+//
+// Any attempt to optimize this code should be mindful that once you skip any part of the message
+// body, dangerous() loses the ability to unzip subsequent data.
+
+bool HttpBodyCutter::need_accelerated_blocking(const uint8_t* data, uint32_t length)
 {
-    if (!detained_inspection || packet_detained)
-        return false;
-    if (detention_required || dangerous(data, length))
+    switch (accelerated_blocking)
     {
-        packet_detained = true;
-        detention_required = true;
-        return true;
+    case AB_DETAIN:
+        // With detained inspection we have two basic principles here: 1) having detained a packet
+        // we don't need to detain another one while the first one is still being held and 2) once
+        // we detain a packet we don't need to keep scanning content. We are always going to detain
+        // a new packet as soon as we release the previous one.
+        if (!packet_detained && (detention_required || dangerous(data, length)))
+        {
+            packet_detained = true;
+            detention_required = true;
+            return true;
+        }
+        break;
+    case AB_INSPECT:
+        // Script detection requires continuous scanning of the data because every packet is a new
+        // decision regardless of any previous determinations.
+        if (dangerous(data, length))
+            return true;
+        break;
+    case AB_NONE:
+        break;
     }
     return false;
 }
@@ -805,9 +849,6 @@ bool HttpBodyCutter::dangerous(const uint8_t* data, uint32_t length)
         input_length = decomp_buffer_size - compress_stream->avail_out;
     }
 
-    static const uint8_t match_string[] = { '<', 's', 'c', 'r', 'i', 'p', 't' };
-    static const uint8_t match_string_upper[] = { '<', 'S', 'C', 'R', 'I', 'P', 'T' };
-    static const uint8_t string_length = sizeof(match_string);
     for (uint32_t k = 0; k < input_length; k++)
     {
         // partial_match is persistent, enabling matches that cross data boundaries
@@ -816,6 +857,7 @@ bool HttpBodyCutter::dangerous(const uint8_t* data, uint32_t length)
         {
             if (++partial_match == string_length)
             {
+                partial_match = 0;
                 delete[] decomp_output;
                 return true;
             }
index ae3197829f5a03fea817dfc232eb28672a0a9987..d07b33dd89117fb862bca51644988ddbcf401157 100644 (file)
@@ -97,31 +97,36 @@ private:
 class HttpBodyCutter : public HttpCutter
 {
 public:
-    HttpBodyCutter(bool detained_inspection_, HttpEnums::CompressId compression_);
+    HttpBodyCutter(HttpEnums::AcceleratedBlocking accelerated_blocking_,
+        HttpEnums::CompressId compression_);
     ~HttpBodyCutter() override;
     void soft_reset() override { octets_seen = 0; packet_detained = false; }
     void detain_ended() { packet_detained = false; }
 
 protected:
-    bool need_detained_inspection(const uint8_t* data, uint32_t length);
+    bool need_accelerated_blocking(const uint8_t* data, uint32_t length);
 
 private:
     bool dangerous(const uint8_t* data, uint32_t length);
 
-    const bool detained_inspection;
+    const HttpEnums::AcceleratedBlocking accelerated_blocking;
     bool packet_detained = false;
     uint8_t partial_match = 0;
     bool detention_required = false;
     HttpEnums::CompressId compression;
     z_stream* compress_stream = nullptr;
+    const uint8_t* match_string;
+    const uint8_t* match_string_upper;
+    uint8_t string_length;
 };
 
 class HttpBodyClCutter : public HttpBodyCutter
 {
 public:
-    HttpBodyClCutter(int64_t expected_length, bool detained_inspection,
+    HttpBodyClCutter(int64_t expected_length,
+        HttpEnums::AcceleratedBlocking accelerated_blocking,
         HttpEnums::CompressId compression) :
-        HttpBodyCutter(detained_inspection, compression), remaining(expected_length)
+        HttpBodyCutter(accelerated_blocking, compression), remaining(expected_length)
         { assert(remaining > 0); }
     HttpEnums::ScanResult cut(const uint8_t*, uint32_t length, HttpInfractions*, HttpEventGen*,
         uint32_t flow_target, bool stretch, bool) override;
@@ -133,8 +138,10 @@ private:
 class HttpBodyOldCutter : public HttpBodyCutter
 {
 public:
-    explicit HttpBodyOldCutter(bool detained_inspection, HttpEnums::CompressId compression) :
-        HttpBodyCutter(detained_inspection, compression) {}
+    HttpBodyOldCutter(HttpEnums::AcceleratedBlocking accelerated_blocking,
+        HttpEnums::CompressId compression) :
+        HttpBodyCutter(accelerated_blocking, compression)
+        {}
     HttpEnums::ScanResult cut(const uint8_t*, uint32_t, HttpInfractions*, HttpEventGen*,
         uint32_t flow_target, bool stretch, bool) override;
 };
@@ -142,8 +149,10 @@ public:
 class HttpBodyChunkCutter : public HttpBodyCutter
 {
 public:
-    explicit HttpBodyChunkCutter(bool detained_inspection, HttpEnums::CompressId compression) :
-        HttpBodyCutter(detained_inspection, compression) {}
+    HttpBodyChunkCutter(HttpEnums::AcceleratedBlocking accelerated_blocking,
+        HttpEnums::CompressId compression) :
+        HttpBodyCutter(accelerated_blocking, compression)
+        {}
     HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length,
         HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch,
         bool) override;
@@ -164,9 +173,11 @@ private:
 class HttpBodyH2Cutter : public HttpBodyCutter
 {
 public:
-    explicit HttpBodyH2Cutter(int64_t expected_length, bool detained_inspection,
+    HttpBodyH2Cutter(int64_t expected_length,
+        HttpEnums::AcceleratedBlocking accelerated_blocking,
         HttpEnums::CompressId compression) :
-        HttpBodyCutter(detained_inspection, compression), expected_body_length(expected_length) {}
+        HttpBodyCutter(accelerated_blocking, compression), expected_body_length(expected_length)
+        {}
     HttpEnums::ScanResult cut(const uint8_t*, uint32_t, HttpInfractions*, HttpEventGen*,
         uint32_t flow_target, bool stretch, bool h2_body_finished) override;
 private:
@@ -174,6 +185,5 @@ private:
     uint32_t total_octets_scanned = 0;
 };
 
-
 #endif
 
index 75745511a1ac6afb4eac9acf0cfd5d9b44a449ec..980d992c6862a3be803763efa5e73f65d9747149 100644 (file)
@@ -48,26 +48,28 @@ enum DetectionStatus { DET_REACTIVATING = 1, DET_ON, DET_DEACTIVATING, DET_OFF }
 enum HTTP_BUFFER { HTTP_BUFFER_CLIENT_BODY = 1, HTTP_BUFFER_COOKIE, HTTP_BUFFER_HEADER,
     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 };
+    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
 // This enum must remain synchronized with HttpModule::peg_names[] in http_tables.cc
 enum PEG_COUNT { PEG_FLOW = 0, PEG_SCAN, PEG_REASSEMBLE, PEG_INSPECT, PEG_REQUEST, PEG_RESPONSE,
     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_EXCESS_PARAMS, PEG_PARAMS, PEG_CUTOVERS, PEG_COUNT_MAX };
+    PEG_CONCURRENT_SESSIONS, PEG_MAX_CONCURRENT_SESSIONS, PEG_DETAINED, PEG_SCRIPT_DETECTION,
+    PEG_PARTIAL_INSPECT, PEG_EXCESS_PARAMS, PEG_PARAMS, PEG_CUTOVERS, PEG_COUNT_MAX };
 
 // Result of scanning by splitter
-enum ScanResult { SCAN_NOT_FOUND, SCAN_NOT_FOUND_DETAIN, SCAN_FOUND, SCAN_FOUND_PIECE,
+enum ScanResult { SCAN_NOT_FOUND, SCAN_NOT_FOUND_ACCELERATE, SCAN_FOUND, SCAN_FOUND_PIECE,
     SCAN_DISCARD, SCAN_DISCARD_PIECE, SCAN_ABORT };
 
 // State machine for chunk parsing
 enum ChunkState { CHUNK_NEWLINES, CHUNK_ZEROS, CHUNK_LEADING_WS, CHUNK_NUMBER, CHUNK_TRAILING_WS,
     CHUNK_OPTIONS, CHUNK_HCRLF, CHUNK_DATA, CHUNK_DCRLF1, CHUNK_DCRLF2, CHUNK_BAD };
 
+enum AcceleratedBlocking { AB_DETAIN, AB_INSPECT, AB_NONE };
+
 // List of possible HTTP versions.
 enum VersionId { VERS__NO_SOURCE=-16, VERS__NOT_COMPUTE=-14, VERS__PROBLEMATIC=-12,
     VERS__NOT_PRESENT=-11, VERS__OTHER=1, VERS_1_0, VERS_1_1, VERS_2_0, VERS_0_9 };
index 61788939151a38448434f413dee16bb614a8bc0c..9536fe8c75b0eae4ca02237363486cf85c58deb7 100644 (file)
@@ -124,7 +124,7 @@ void HttpFlowData::half_reset(SourceId source_id)
     partial_inspected_octets[source_id] = 0;
     section_size_target[source_id] = 0;
     stretch_section_to_packet[source_id] = false;
-    detained_inspection[source_id] = false;
+    accelerated_blocking[source_id] = AB_NONE;
     file_depth_remaining[source_id] = STAT_NOT_PRESENT;
     detect_depth_remaining[source_id] = STAT_NOT_PRESENT;
     detection_status[source_id] = DET_REACTIVATING;
index b1a8ff3d42504e10719baee1f50296abf3fecb04..f2d91b7c3a317f2608edb63c4dca3cf786919569 100644 (file)
@@ -141,7 +141,8 @@ private:
     HttpEnums::CompressId compression[2] = { HttpEnums::CMP_NONE, HttpEnums::CMP_NONE };
     HttpEnums::DetectionStatus detection_status[2] = { HttpEnums::DET_ON, HttpEnums::DET_ON };
     bool stretch_section_to_packet[2] = { false, false };
-    bool detained_inspection[2] = { false, false };
+    HttpEnums::AcceleratedBlocking accelerated_blocking[2] =
+        { HttpEnums::AB_NONE, HttpEnums::AB_NONE };
 
     // *** Inspector's internal data about the current message
     struct FdCallbackContext
index e3f800b693064f29e8ffed4aba72aea44b3f574e..cc466afc671e20b788476dfd373e66732cec8b00 100644 (file)
@@ -131,6 +131,7 @@ void HttpInspect::show(const SnortConfig*) const
     ConfigLogger::log_flag("decompress_swf", params->decompress_swf);
     ConfigLogger::log_flag("decompress_zip", params->decompress_zip);
     ConfigLogger::log_flag("detained_inspection", params->detained_inspection);
+    ConfigLogger::log_flag("script_detection", params->script_detection);
     ConfigLogger::log_flag("normalize_javascript", params->js_norm_param.normalize_javascript);
     ConfigLogger::log_value("max_javascript_whitespaces",
         params->js_norm_param.max_javascript_whitespaces);
index 8ea340e090a8ac1ff6dad58e96aeb21b4545273a..4746276e87fbe20e6b1d7b50c8eef69f4a2fc76b 100644 (file)
@@ -58,6 +58,9 @@ const Parameter HttpModule::http_params[] =
     { "detained_inspection", Parameter::PT_BOOL, nullptr, "false",
       "store-and-forward as necessary to effectively block alerting JavaScript" },
 
+    { "script_detection", Parameter::PT_BOOL, nullptr, "false",
+      "inspect JavaScript immediately upon script end" },
+
     { "normalize_javascript", Parameter::PT_BOOL, nullptr, "false",
       "normalize JavaScript in response bodies" },
 
@@ -175,6 +178,10 @@ bool HttpModule::set(const char*, Value& val, SnortConfig*)
     {
         params->detained_inspection = val.get_bool();
     }
+    else if (val.is("script_detection"))
+    {
+        params->script_detection = val.get_bool();
+    }
     else if (val.is("normalize_javascript"))
     {
         params->js_norm_param.normalize_javascript = val.get_bool();
@@ -283,6 +290,12 @@ bool HttpModule::end(const char*, int, SnortConfig*)
         ParseWarning(WARN_CONF, "Meaningless to do bare byte when not doing UTF-8");
         params->uri_param.utf8_bare_byte = false;
     }
+
+    if (params->detained_inspection && params->script_detection)
+    {
+        ParseError("Cannot use detained inspection and script detection together.");
+    }
+
     if (params->uri_param.iis_unicode)
     {
         params->uri_param.unicode_map = new uint8_t[65536];
index c3abe40d1ace14ca2c77cb0c62067a6efd1b3ef6..e81e6aa974eb91b339ed1b41ff407c8dfff753fc 100644 (file)
@@ -43,6 +43,7 @@ public:
     bool decompress_swf = false;
     bool decompress_zip = false;
     bool detained_inspection = false;
+    bool script_detection = false;
 
     struct JsNormParam
     {
index 0e43af24b9161bbf82d8603d1e12d817fd332c83..3fdb2078d1da8028aeeb70660582668d9784ddfb 100644 (file)
@@ -72,7 +72,7 @@ void HttpMsgBody::analyze()
 
         uint32_t& partial_detect_length = session_data->partial_detect_length[source_id];
         uint8_t*& partial_detect_buffer = session_data->partial_detect_buffer[source_id];
-        const int32_t total_length = js_norm_body.length() + partial_detect_length;
+        const int32_t total_length = partial_detect_length + js_norm_body.length();
         const int32_t detect_length =
             (total_length <= session_data->detect_depth_remaining[source_id]) ?
             total_length : session_data->detect_depth_remaining[source_id];
@@ -83,7 +83,7 @@ void HttpMsgBody::analyze()
             memcpy(detect_buffer, partial_detect_buffer, partial_detect_length);
             memcpy(detect_buffer + partial_detect_length, js_norm_body.start(),
                 js_norm_body.length());
-            detect_data.set(total_length, detect_buffer, true);
+            detect_data.set(detect_length, detect_buffer, true);
         }
         else
         {
index d0ce5063eff538f086794f5b6922537c8970ae8a..b8d5dac74d79e20c8c730699e4b128eb0c3930de 100644 (file)
@@ -384,8 +384,15 @@ void HttpMsgHeader::prepare_body()
     setup_utf_decoding();
     setup_file_decompression();
     update_depth();
-    session_data->detained_inspection[source_id] =
-        params->detained_inspection && (source_id == SRC_SERVER);
+
+    if (source_id == SRC_SERVER)
+    {
+        if (params->script_detection)
+            session_data->accelerated_blocking[source_id] = AB_INSPECT;
+        else if (params->detained_inspection)
+            session_data->accelerated_blocking[source_id] = AB_DETAIN;
+    }
+
     if (source_id == SRC_CLIENT)
     {
         HttpModule::increment_peg_counts(PEG_REQUEST_BODY);
index ca845478b731db4824e43cf6a1c9b2d082e26193..eb0edb005871c03dc50f7c8ffc08e0b1a39471e1 100644 (file)
@@ -75,20 +75,20 @@ HttpCutter* HttpStreamSplitter::get_cutter(SectionType type,
     case SEC_BODY_CL:
         return (HttpCutter*)new HttpBodyClCutter(
             session_data->data_length[source_id],
-            session_data->detained_inspection[source_id],
+            session_data->accelerated_blocking[source_id],
             session_data->compression[source_id]);
     case SEC_BODY_CHUNK:
         return (HttpCutter*)new HttpBodyChunkCutter(
-            session_data->detained_inspection[source_id],
+            session_data->accelerated_blocking[source_id],
             session_data->compression[source_id]);
     case SEC_BODY_OLD:
         return (HttpCutter*)new HttpBodyOldCutter(
-            session_data->detained_inspection[source_id],
+            session_data->accelerated_blocking[source_id],
             session_data->compression[source_id]);
     case SEC_BODY_H2:
         return (HttpCutter*)new HttpBodyH2Cutter(
             session_data->data_length[source_id],
-            session_data->detained_inspection[source_id],
+            AB_NONE,
             session_data->compression[source_id]);
     default:
         assert(false);
@@ -266,7 +266,7 @@ StreamSplitter::Status HttpStreamSplitter::scan(Packet* pkt, const uint8_t* data
     switch (cut_result)
     {
     case SCAN_NOT_FOUND:
-    case SCAN_NOT_FOUND_DETAIN:
+    case SCAN_NOT_FOUND_ACCELERATE:
         if (cutter->get_octets_seen() == MAX_OCTETS)
         {
             *session_data->get_infractions(source_id) += INF_ENDLESS_HEADER;
@@ -279,8 +279,28 @@ StreamSplitter::Status HttpStreamSplitter::scan(Packet* pkt, const uint8_t* data
             cutter = nullptr;
             return status_value(StreamSplitter::ABORT);
         }
-        if (cut_result == SCAN_NOT_FOUND_DETAIN)
-            detain_packet(pkt);
+
+        if (cut_result == SCAN_NOT_FOUND_ACCELERATE)
+        {
+            if (session_data->accelerated_blocking[source_id] == AB_DETAIN)
+                detain_packet(pkt);
+            else
+            {
+                assert(session_data->accelerated_blocking[source_id] == AB_INSPECT);
+                HttpModule::increment_peg_counts(PEG_SCRIPT_DETECTION);
+                init_partial_flush(flow);
+#ifdef REG_TEST
+                if (HttpTestManager::use_test_input(HttpTestManager::IN_HTTP))
+                {
+                    HttpTestManager::get_test_input_source()->flush(length);
+                }
+                else
+#endif
+                    *flush_offset = length;
+                return status_value(StreamSplitter::FLUSH);
+            }
+        }
+
         // Wait patiently for more data
         return status_value(StreamSplitter::SEARCH);
     case SCAN_ABORT:
index c14767c81d5731b705443ce4562b2411bbe2f6b4..6b69a5daedfb9f2f31854af8fbe886a72a65dc8b 100644 (file)
@@ -420,6 +420,7 @@ const PegInfo HttpModule::peg_names[PEG_COUNT_MAX+1] =
     { CountType::NOW, "concurrent_sessions", "total concurrent http sessions" },
     { CountType::MAX, "max_concurrent_sessions", "maximum concurrent http sessions" },
     { CountType::SUM, "detains_requested", "packet hold requests for detained inspection" },
+    { CountType::SUM, "script_detections", "early inspections of scripts in HTTP responses" },
     { CountType::SUM, "partial_inspections", "pre-inspections for detained inspection" },
     { CountType::SUM, "excess_parameters", "repeat parameters exceeding max" },
     { CountType::SUM, "parameters", "HTTP parameters inspected" },