From: Mike Stepanek (mstepane) Date: Tue, 25 Aug 2020 12:12:37 +0000 (+0000) Subject: Merge pull request #2405 in SNORT/snort3 from ~THOPETER/snort3:nhttp140a to master X-Git-Tag: 3.0.2-6~44 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=168791a25b51fbfa61763a0acd539bca6cf579fb;p=thirdparty%2Fsnort3.git Merge pull request #2405 in SNORT/snort3 from ~THOPETER/snort3:nhttp140a to master Squashed commit of the following: commit 9576a7b759fa2a697ae18e56ec528460ec0f5a61 Author: Tom Peters Date: Tue Jun 23 13:35:10 2020 -0400 http_inspect: script detection --- diff --git a/doc/user/http_inspect.txt b/doc/user/http_inspect.txt index afb1d2cef..674248e82 100644 --- a/doc/user/http_inspect.txt +++ b/doc/user/http_inspect.txt @@ -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 diff --git a/src/service_inspectors/http_inspect/dev_notes.txt b/src/service_inspectors/http_inspect/dev_notes.txt index a6c50d076..fa91439af 100644 --- a/src/service_inspectors/http_inspect/dev_notes.txt +++ b/src/service_inspectors/http_inspect/dev_notes.txt @@ -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 "". 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 diff --git a/src/service_inspectors/http_inspect/http_cutter.cc b/src/service_inspectors/http_inspect/http_cutter.cc index 5e913f30b..c88a3e862 100644 --- a/src/service_inspectors/http_inspect/http_cutter.cc +++ b/src/service_inspectors/http_inspect/http_cutter.cc @@ -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(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; } diff --git a/src/service_inspectors/http_inspect/http_cutter.h b/src/service_inspectors/http_inspect/http_cutter.h index ae3197829..d07b33dd8 100644 --- a/src/service_inspectors/http_inspect/http_cutter.h +++ b/src/service_inspectors/http_inspect/http_cutter.h @@ -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 diff --git a/src/service_inspectors/http_inspect/http_enum.h b/src/service_inspectors/http_inspect/http_enum.h index 75745511a..980d992c6 100644 --- a/src/service_inspectors/http_inspect/http_enum.h +++ b/src/service_inspectors/http_inspect/http_enum.h @@ -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 }; diff --git a/src/service_inspectors/http_inspect/http_flow_data.cc b/src/service_inspectors/http_inspect/http_flow_data.cc index 617889391..9536fe8c7 100644 --- a/src/service_inspectors/http_inspect/http_flow_data.cc +++ b/src/service_inspectors/http_inspect/http_flow_data.cc @@ -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; diff --git a/src/service_inspectors/http_inspect/http_flow_data.h b/src/service_inspectors/http_inspect/http_flow_data.h index b1a8ff3d4..f2d91b7c3 100644 --- a/src/service_inspectors/http_inspect/http_flow_data.h +++ b/src/service_inspectors/http_inspect/http_flow_data.h @@ -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 diff --git a/src/service_inspectors/http_inspect/http_inspect.cc b/src/service_inspectors/http_inspect/http_inspect.cc index e3f800b69..cc466afc6 100644 --- a/src/service_inspectors/http_inspect/http_inspect.cc +++ b/src/service_inspectors/http_inspect/http_inspect.cc @@ -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); diff --git a/src/service_inspectors/http_inspect/http_module.cc b/src/service_inspectors/http_inspect/http_module.cc index 8ea340e09..4746276e8 100644 --- a/src/service_inspectors/http_inspect/http_module.cc +++ b/src/service_inspectors/http_inspect/http_module.cc @@ -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]; diff --git a/src/service_inspectors/http_inspect/http_module.h b/src/service_inspectors/http_inspect/http_module.h index c3abe40d1..e81e6aa97 100644 --- a/src/service_inspectors/http_inspect/http_module.h +++ b/src/service_inspectors/http_inspect/http_module.h @@ -43,6 +43,7 @@ public: bool decompress_swf = false; bool decompress_zip = false; bool detained_inspection = false; + bool script_detection = false; struct JsNormParam { diff --git a/src/service_inspectors/http_inspect/http_msg_body.cc b/src/service_inspectors/http_inspect/http_msg_body.cc index 0e43af24b..3fdb2078d 100644 --- a/src/service_inspectors/http_inspect/http_msg_body.cc +++ b/src/service_inspectors/http_inspect/http_msg_body.cc @@ -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 { diff --git a/src/service_inspectors/http_inspect/http_msg_header.cc b/src/service_inspectors/http_inspect/http_msg_header.cc index d0ce5063e..b8d5dac74 100644 --- a/src/service_inspectors/http_inspect/http_msg_header.cc +++ b/src/service_inspectors/http_inspect/http_msg_header.cc @@ -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); diff --git a/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc b/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc index ca845478b..eb0edb005 100644 --- a/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc +++ b/src/service_inspectors/http_inspect/http_stream_splitter_scan.cc @@ -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: diff --git a/src/service_inspectors/http_inspect/http_tables.cc b/src/service_inspectors/http_inspect/http_tables.cc index c14767c81..6b69a5dae 100644 --- a/src/service_inspectors/http_inspect/http_tables.cc +++ b/src/service_inspectors/http_inspect/http_tables.cc @@ -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" },