]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #598 in SNORT/snort3 from nhttp51 to master
authorTom Peters (thopeter) <thopeter@cisco.com>
Fri, 19 Aug 2016 18:46:57 +0000 (14:46 -0400)
committerTom Peters (thopeter) <thopeter@cisco.com>
Fri, 19 Aug 2016 18:46:57 +0000 (14:46 -0400)
Squashed commit of the following:

commit bf01ba0dba8b94d874cd2cb8036efde9a51646f0
Author: Tom Peters <thopeter@cisco.com>
Date:   Thu Aug 11 15:25:53 2016 -0400

    NHI alerts for Transfer-Encoding and Content-Encoding abuse.

src/service_inspectors/nhttp_inspect/CMakeLists.txt
src/service_inspectors/nhttp_inspect/Makefile.am
src/service_inspectors/nhttp_inspect/nhttp_enum.h
src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.h
src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared_util.cc [new file with mode: 0644]
src/service_inspectors/nhttp_inspect/nhttp_msg_header.cc
src/service_inspectors/nhttp_inspect/nhttp_tables.cc
src/service_inspectors/nhttp_inspect/test/CMakeLists.txt
src/service_inspectors/nhttp_inspect/test/Makefile.am
src/service_inspectors/nhttp_inspect/test/nhttp_msg_head_shared_util_test.cc [new file with mode: 0644]

index f499ff05c52fb627ea9c559c2c73c08629091497..48d34e24ac1e3aa554de6f9dab11bf71518bd173 100644 (file)
@@ -13,6 +13,7 @@ set (FILE_LIST
     nhttp_msg_status.cc
     nhttp_msg_status.h
     nhttp_msg_head_shared.cc
+    nhttp_msg_head_shared_util.cc
     nhttp_msg_head_shared.h
     nhttp_msg_header.cc
     nhttp_msg_header.h
index 4ef93641d59a04b8ce8ab84a8cf4da68d759563c..cd5548ef1e9d9040016f40baba89fa4c8b36a31a 100644 (file)
@@ -4,7 +4,7 @@ nhttp_msg_section.cc nhttp_msg_section.h \
 nhttp_msg_start.cc nhttp_msg_start.h \
 nhttp_msg_request.cc nhttp_msg_request.h \
 nhttp_msg_status.cc nhttp_msg_status.h \
-nhttp_msg_head_shared.cc nhttp_msg_head_shared.h \
+nhttp_msg_head_shared.cc nhttp_msg_head_shared_util.cc nhttp_msg_head_shared.h \
 nhttp_msg_header.cc nhttp_msg_header.h \
 nhttp_msg_body.cc nhttp_msg_body.h \
 nhttp_msg_body_cl.cc nhttp_msg_body_cl.h \
index 156ab86d8009e02e1b92efcff0b511daf3da2474..7e02dabfda72e6b2faa5380fa4379381d91df043 100644 (file)
@@ -192,6 +192,9 @@ enum Infraction
     INF_POST_WO_BODY,
     INF_UTF_NORM_FAIL,
     INF_UTF7,
+    INF_UNSUPPORTED_ENCODING,
+    INF_UNKNOWN_ENCODING,
+    INF_STACKED_ENCODINGS,
     INF__MAX_VALUE
 };
 
@@ -284,6 +287,9 @@ enum EventSid
     EVENT_FINAL_NOT_CHUNKED,
     EVENT_CHUNKED_BEFORE_END,
     EVENT_MISFORMATTED_HTTP,
+    EVENT_UNSUPPORTED_ENCODING,
+    EVENT_UNKNOWN_ENCODING,
+    EVENT_STACKED_ENCODINGS,
     EVENT__MAX_VALUE
 };
 
index cfcf2065349f6d153d3c25315f41d1ca8f1f0c2a..a7478181ad7a2ac2e765f2443c461cff837bae44 100644 (file)
@@ -62,6 +62,8 @@ protected:
         : NHttpMsgSection(buffer, buf_size, session_data_, source_id_, buf_owner, flow_, params_)
         { }
     ~NHttpMsgHeadShared();
+    // Get the next item in a comma-separated header value and convert it to an enum value
+    static int32_t get_next_code(const Field& field, int32_t& offset, const StrCode table[]);
 
 #ifdef REG_TEST
     void print_headers(FILE* output);
diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared_util.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared_util.cc
new file mode 100644 (file)
index 0000000..e2c54be
--- /dev/null
@@ -0,0 +1,32 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2016 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.
+//--------------------------------------------------------------------------
+// nhttp_msg_head_shared_util.cc author Tom Peters <thopeter@cisco.com>
+
+#include "nhttp_msg_head_shared.h"
+
+int32_t NHttpMsgHeadShared::get_next_code(const Field& field, int32_t& offset,
+    const StrCode table[])
+{
+    assert(field.length > 0);
+    const uint8_t* start = field.start + offset;
+    int32_t length;
+    for (length = 0; (offset+length < field.length) && (*(start+length) != ','); length++);
+    offset += length + 1;
+    return str_to_code(start, length, table);
+}
+
index f2996f6e782b56926fee36b5b1cf1fd0f6f4b013..266d6ce1ad2cb92ce086a5ebefdb75c572638e0c 100644 (file)
@@ -229,22 +229,90 @@ void NHttpMsgHeader::setup_decompression()
     if (!params->unzip)
         return;
 
-    // FIXIT-M add support for compression in transfer encoding
-    // FIXIT-M add support for multiple content encoding values
-    const Field& norm_content_encoding = get_header_value_norm(HEAD_CONTENT_ENCODING);
-    if (norm_content_encoding.length <= 0)
-        return;
+    CompressId& compression = session_data->compression[source_id];
 
-    const Contentcoding compress_code = (Contentcoding)norm_last_token_code(
-        norm_content_encoding, NHttpMsgHeadShared::content_code_list);
+    // Search the Content-Encoding and Transfer-Encoding headers to find the type of compression
+    // used. We detect and alert on multiple layers of compression but we only decompress the
+    // outermost layer. We proceed through the headers inside-out, starting at the front of the
+    // list of Content-Encodings and ending with the last Transfer-Encoding. Thus the last encoding
+    // we encounter (other than chunked) is the one we use. If we don't recognize or support the
+    // last encoding we won't do anything.
 
-    CompressId& compression = session_data->compression[source_id];
+    const Field& norm_content_encoding = get_header_value_norm(HEAD_CONTENT_ENCODING);
+    int32_t cont_offset = 0;
+    while (norm_content_encoding.length > cont_offset)
+    {
+        const Contentcoding content_code = (Contentcoding)get_next_code(norm_content_encoding,
+            cont_offset, NHttpMsgHeadShared::content_code_list);
+        if ((compression != CMP_NONE) && (content_code != CONTENTCODE_IDENTITY))
+        {
+            infractions += INF_STACKED_ENCODINGS;
+            events.create_event(EVENT_STACKED_ENCODINGS);
+            compression = CMP_NONE;
+        }
+        switch (content_code)
+        {
+        case CONTENTCODE_GZIP:
+        case CONTENTCODE_X_GZIP:
+            compression = CMP_GZIP;
+            break;
+        case CONTENTCODE_DEFLATE:
+            compression = CMP_DEFLATE;
+            break;
+        case CONTENTCODE_COMPRESS:
+        case CONTENTCODE_EXI:
+        case CONTENTCODE_PACK200_GZIP:
+        case CONTENTCODE_X_COMPRESS:
+            infractions += INF_UNSUPPORTED_ENCODING;
+            events.create_event(EVENT_UNSUPPORTED_ENCODING);
+            break;
+        case CONTENTCODE_IDENTITY:
+            break;
+        case CONTENTCODE__OTHER:
+            infractions += INF_UNKNOWN_ENCODING;
+            events.create_event(EVENT_UNKNOWN_ENCODING);
+            break;
+        }
+    }
 
-    if ((compress_code == CONTENTCODE_GZIP) || (compress_code == CONTENTCODE_X_GZIP))
-        compression = CMP_GZIP;
-    else if (compress_code == CONTENTCODE_DEFLATE)
-        compression = CMP_DEFLATE;
-    else
+    const Field& norm_transfer_encoding = get_header_value_norm(HEAD_TRANSFER_ENCODING);
+    int32_t trans_offset = 0;
+    while (norm_transfer_encoding.length > trans_offset)
+    {
+        const Transcoding transfer_code = (Transcoding)get_next_code(norm_transfer_encoding,
+            trans_offset, NHttpMsgHeadShared::trans_code_list);
+        if ((compression != CMP_NONE) &&
+            !((transfer_code == TRANSCODE_IDENTITY) || (transfer_code == TRANSCODE_CHUNKED)))
+        {
+            infractions += INF_STACKED_ENCODINGS;
+            events.create_event(EVENT_STACKED_ENCODINGS);
+            compression = CMP_NONE;
+        }
+        switch (transfer_code)
+        {
+        case TRANSCODE_GZIP:
+        case TRANSCODE_X_GZIP:
+            compression = CMP_GZIP;
+            break;
+        case TRANSCODE_DEFLATE:
+            compression = CMP_DEFLATE;
+            break;
+        case TRANSCODE_COMPRESS:
+        case TRANSCODE_X_COMPRESS:
+            infractions += INF_UNSUPPORTED_ENCODING;
+            events.create_event(EVENT_UNSUPPORTED_ENCODING);
+            break;
+        case TRANSCODE_CHUNKED:
+        case TRANSCODE_IDENTITY:
+            break;
+        case TRANSCODE__OTHER:
+            infractions += INF_UNKNOWN_ENCODING;
+            events.create_event(EVENT_UNKNOWN_ENCODING);
+            break;
+        }
+    }
+
+    if (compression == CMP_NONE)
         return;
 
     session_data->compress_stream[source_id] = new z_stream;
index b0e1e9fcad713f3848643aa57e6b85a86f0e4e38..33d39b33845177400ae004e198d7cf819ed44463 100644 (file)
@@ -357,6 +357,9 @@ const RuleMap NHttpModule::nhttp_events[] =
     { EVENT_FINAL_NOT_CHUNKED,          "Transfer-Encoding did not end with chunked" },
     { EVENT_CHUNKED_BEFORE_END,         "Transfer-Encoding with chunked not at end" },
     { EVENT_MISFORMATTED_HTTP,          "Misformatted HTTP traffic" },
+    { EVENT_UNSUPPORTED_ENCODING,       "Unsupported Transfer-Encoding or Content-Encoding used" },
+    { EVENT_UNKNOWN_ENCODING,           "Unknown Transfer-Encoding or Content-Encoding used" },
+    { EVENT_STACKED_ENCODINGS,          "Multiple layers of compression encodings applied" },
     { 0, nullptr }
 };
 
index 7486a4616917ace36b68bc3ed48e13014ae79de4..675f0b79990b6d060e55760e520651c27b3926e8 100644 (file)
@@ -2,4 +2,5 @@ add_cpputest(nhttp_uri_norm_test nhttp_inspect framework)
 add_cpputest(nhttp_normalizers_test nhttp_inspect framework)
 add_cpputest(nhttp_module_test nhttp_inspect framework)
 add_cpputest(nhttp_transaction_test nhttp_inspect framework -lz)
+add_cpputest(nhttp_msg_head_shared_util_test nhttp_inspect framework)
 
index 27197ea806edf530af4d006441add981bef7493e..48732984f7e7c9829de4a2983c29460d232d05f3 100644 (file)
@@ -5,7 +5,8 @@ check_PROGRAMS = \
 nhttp_uri_norm_test \
 nhttp_normalizers_test \
 nhttp_module_test \
-nhttp_transaction_test
+nhttp_transaction_test \
+nhttp_msg_head_shared_util_test
 
 TESTS = $(check_PROGRAMS)
 
@@ -46,3 +47,11 @@ nhttp_transaction_test_LDADD = \
 ../nhttp_test_input.o \
 @CPPUTEST_LDFLAGS@
 
+nhttp_msg_head_shared_util_test_CPPFLAGS = $(AM_CPPFLAGS) @CPPUTEST_CPPFLAGS@
+nhttp_msg_head_shared_util_test_LDADD = \
+../nhttp_msg_head_shared_util.o \
+../nhttp_field.o \
+../nhttp_str_to_code.o \
+@CPPUTEST_LDFLAGS@
+
+
diff --git a/src/service_inspectors/nhttp_inspect/test/nhttp_msg_head_shared_util_test.cc b/src/service_inspectors/nhttp_inspect/test/nhttp_msg_head_shared_util_test.cc
new file mode 100644 (file)
index 0000000..a1a454b
--- /dev/null
@@ -0,0 +1,106 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2016 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.
+//--------------------------------------------------------------------------
+
+// nhttp_msg_head_shared_util_test.cc author Tom Peters <thopeter@cisco.com>
+// unit test main
+
+#include "service_inspectors/nhttp_inspect/nhttp_msg_head_shared.h"
+#include "service_inspectors/nhttp_inspect/nhttp_field.h"
+#include "service_inspectors/nhttp_inspect/nhttp_str_to_code.h"
+#include "service_inspectors/nhttp_inspect/nhttp_test_manager.h"
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+#include <CppUTestExt/MockSupport.h>
+
+// Stubs whose sole purpose is to make the test code link
+long NHttpTestManager::print_amount {};
+bool NHttpTestManager::print_hex {};
+
+TEST_GROUP(nhttp_msg_head_shared_util)
+{
+    enum Color { COLOR_OTHER=1, COLOR_GREEN, COLOR_BLUE, COLOR_RED, COLOR_YELLOW, COLOR_PURPLE };
+    int32_t offset = 0;
+    const StrCode color_table[6] =
+    {
+        { COLOR_GREEN,  "green" },
+        { COLOR_BLUE,   "blue" },
+        { COLOR_RED,    "red" },
+        { COLOR_YELLOW, "yellow" },
+        { COLOR_PURPLE, "purple" },
+        { 0,            nullptr }
+    };
+
+    // This allows access to test a protected static member function
+    class NHttpMsgHeadTest : public NHttpMsgHeadShared
+    {
+    public:
+        static int32_t get_next_code_test(const Field& field, int32_t& offset,
+            const StrCode table[])
+        {
+            return NHttpMsgHeadShared::get_next_code(field, offset, table);
+        }
+    };
+};
+
+TEST(nhttp_msg_head_shared_util, basic)
+{
+    Field input(10, (const uint8_t*) "green,blue");
+    Color color = (Color) NHttpMsgHeadTest::get_next_code_test(input, offset, color_table);
+    CHECK(offset == 6);
+    CHECK(color == COLOR_GREEN);
+    color = (Color) NHttpMsgHeadTest::get_next_code_test(input, offset, color_table);
+    CHECK(offset == 11);
+    CHECK(color == COLOR_BLUE);
+}
+
+TEST(nhttp_msg_head_shared_util, single_token)
+{
+    Field input(6, (const uint8_t*) "purple");
+    Color color = (Color) NHttpMsgHeadTest::get_next_code_test(input, offset, color_table);
+    CHECK(offset == 7);
+    CHECK(color == COLOR_PURPLE);
+}
+
+TEST(nhttp_msg_head_shared_util, unknown_token)
+{
+    Field input(14, (const uint8_t*) "madeup,red,red");
+    Color color = (Color) NHttpMsgHeadTest::get_next_code_test(input, offset, color_table);
+    CHECK(offset == 7);
+    CHECK(color == COLOR_OTHER);
+}
+
+TEST(nhttp_msg_head_shared_util, null_token)
+{
+    Field input(11, (const uint8_t*) "green,,blue");
+    Color color = (Color) NHttpMsgHeadTest::get_next_code_test(input, offset, color_table);
+    CHECK(offset == 6);
+    CHECK(color == COLOR_GREEN);
+    color = (Color) NHttpMsgHeadTest::get_next_code_test(input, offset, color_table);
+    CHECK(offset == 7);
+    CHECK(color == COLOR_OTHER);
+    color = (Color) NHttpMsgHeadTest::get_next_code_test(input, offset, color_table);
+    CHECK(offset == 12);
+    CHECK(color == COLOR_BLUE);
+}
+
+int main(int argc, char** argv)
+{
+    return CommandLineTestRunner::RunAllTests(argc, argv);
+}
+