]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2803 in SNORT/snort3 from ~THOPETER/snort3:nhttp156 to master
authorTom Peters (thopeter) <thopeter@cisco.com>
Wed, 24 Mar 2021 16:05:05 +0000 (16:05 +0000)
committerTom Peters (thopeter) <thopeter@cisco.com>
Wed, 24 Mar 2021 16:05:05 +0000 (16:05 +0000)
Squashed commit of the following:

commit 124ef14653ebd8c95178155ef5fa94d76cb60aa0
Author: Tom Peters <thopeter@cisco.com>
Date:   Wed Mar 17 13:46:37 2021 -0400

    http_inspect: alert on HTTP/2 upgrade attempts

doc/user/http_inspect.txt
src/service_inspectors/http_inspect/http_enum.h
src/service_inspectors/http_inspect/http_msg_head_shared.h
src/service_inspectors/http_inspect/http_msg_header.cc
src/service_inspectors/http_inspect/http_str_to_code.cc
src/service_inspectors/http_inspect/http_str_to_code.h
src/service_inspectors/http_inspect/http_tables.cc

index 00ce605dc54231acd486563ff601f716855a1954..90940c5289bbe489d36d3e44b88b8ff0a0302778 100755 (executable)
@@ -487,10 +487,6 @@ http_header when no specific header is specified.
 
 This is the body of a request or response message. It will be dechunked
 and unzipped if applicable but will not be normalized in any other way.
-The difference between http_raw_body and packet data is a rule that uses
-packet data will search and may match an HTTP header, but http_raw_body
-is limited to the message body. Thus the latter is more efficient and
-more accurate for most uses.
 
 ===== http_method
 
@@ -524,15 +520,12 @@ request message those are http_method, http_raw_uri, and http_version. For
 a response message those are http_version, http_stat_code, and
 http_stat_msg.
 
-===== file_data and packet data
+===== 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 unnormalized message content is available in the packet data. If gzip
-is configured the packet data will be unzipped.
-
 ==== Timing issues and combining rule options
 
 HTTP inspector is stateful. That means it is aware of a bigger picture than
index cc39b7feacbd115f9408b533503e7876b1f682ea..a1a957a38cbc03ff6a336c43f2e7eba72a4df11f 100755 (executable)
@@ -108,6 +108,22 @@ enum InspectSection { IS_NONE, IS_HEADER, IS_FLEX_HEADER, IS_FIRST_BODY, IS_BODY
 // Part of the URI to be provided
 enum UriComponent { UC_SCHEME = 1, UC_HOST, UC_PORT, UC_PATH, UC_QUERY, UC_FRAGMENT };
 
+// Types of character for URI scanning
+enum CharAction { CHAR_NORMAL=2, CHAR_PERCENT, CHAR_PATH, CHAR_EIGHTBIT, CHAR_SUBSTIT };
+
+// Content codings
+enum Contentcoding { CONTENTCODE__OTHER=1, CONTENTCODE_GZIP, CONTENTCODE_DEFLATE,
+    CONTENTCODE_COMPRESS, CONTENTCODE_EXI, CONTENTCODE_PACK200_GZIP, CONTENTCODE_X_GZIP,
+    CONTENTCODE_X_COMPRESS, CONTENTCODE_IDENTITY, CONTENTCODE_CHUNKED, CONTENTCODE_BR,
+    CONTENTCODE_BZIP2, CONTENTCODE_LZMA, CONTENTCODE_PEERDIST, CONTENTCODE_SDCH,
+    CONTENTCODE_XPRESS, CONTENTCODE_XZ };
+
+// Transfer-Encoding header values
+enum TransferEncoding { TE__OTHER=1, TE_CHUNKED, TE_IDENTITY };
+
+// Upgrade header values
+enum Upgrade { UP__OTHER=1, UP_H2C, UP_H2, UP_HTTP20 };
+
 // Every header we have ever heard of
 enum HeaderId { HEAD__NOT_COMPUTE=-14, HEAD__PROBLEMATIC=-12, HEAD__NOT_PRESENT=-11, HEAD__OTHER=1,
     HEAD_CACHE_CONTROL, HEAD_CONNECTION, HEAD_DATE, HEAD_PRAGMA, HEAD_TRAILER, HEAD_COOKIE,
@@ -121,7 +137,7 @@ enum HeaderId { HEAD__NOT_COMPUTE=-14, HEAD__PROBLEMATIC=-12, HEAD__NOT_PRESENT=
     HEAD_CONTENT_LENGTH, HEAD_CONTENT_LOCATION, HEAD_CONTENT_MD5, HEAD_CONTENT_RANGE,
     HEAD_CONTENT_TYPE, HEAD_EXPIRES, HEAD_LAST_MODIFIED, HEAD_X_FORWARDED_FOR, HEAD_TRUE_CLIENT_IP,
     HEAD_X_WORKING_WITH, HEAD_CONTENT_TRANSFER_ENCODING, HEAD_MIME_VERSION, HEAD_PROXY_AGENT,
-    HEAD_CONTENT_DISPOSITION, HEAD__MAX_VALUE };
+    HEAD_CONTENT_DISPOSITION, HEAD_HTTP2_SETTINGS, HEAD__MAX_VALUE };
 
 // All the infractions we might find while parsing and analyzing a message
 enum Infraction
@@ -246,19 +262,11 @@ enum Infraction
     INF_TRUNCATED_MSG_BODY_CHUNK,
     INF_LONG_SCHEME,
     INF_MULTIPLE_HOST_HDRS,
+    INF_HTTP2_SETTINGS,
+    INF_UPGRADE_HEADER_HTTP2,
     INF__MAX_VALUE
 };
 
-// Types of character for URI scanning
-enum CharAction { CHAR_NORMAL=2, CHAR_PERCENT, CHAR_PATH, CHAR_EIGHTBIT, CHAR_SUBSTIT };
-
-// Content codings
-enum Contentcoding { CONTENTCODE__OTHER=1, CONTENTCODE_GZIP, CONTENTCODE_DEFLATE,
-    CONTENTCODE_COMPRESS, CONTENTCODE_EXI, CONTENTCODE_PACK200_GZIP, CONTENTCODE_X_GZIP,
-    CONTENTCODE_X_COMPRESS, CONTENTCODE_IDENTITY, CONTENTCODE_CHUNKED, CONTENTCODE_BR,
-    CONTENTCODE_BZIP2, CONTENTCODE_LZMA, CONTENTCODE_PEERDIST, CONTENTCODE_SDCH,
-    CONTENTCODE_XPRESS, CONTENTCODE_XZ };
-
 enum EventSid
 {
     EVENT__NONE = -1,
@@ -377,6 +385,8 @@ enum EventSid
     EVENT_TRUNCATED_MSG_BODY_CL = 260,
     EVENT_TRUNCATED_MSG_BODY_CHUNK = 261,
     EVENT_LONG_SCHEME = 262,
+    EVENT_HTTP2_UPGRADE_REQUEST = 263,
+    EVENT_HTTP2_UPGRADE_RESPONSE = 264,
     EVENT__MAX_VALUE
 };
 
index 5741dff73abc53fef1d6e6356b4a3625cfbe13d1..05573be725813f59eb63add304fd0b02b2826a83 100755 (executable)
@@ -51,6 +51,8 @@ public:
     static const StrCode content_code_list[];
     static const StrCode charset_code_list[];
     static const StrCode charset_code_opt_list[];
+    static const StrCode transfer_encoding_list[];
+    static const StrCode upgrade_list[];
 
     // The file_cache_index is used along with the source ip and destination ip to cache file
     // verdicts.
index e7f418588d5911c4a57665eb8d79e591fa921333..20e181e3e57a602ff23ee2ef998eca9ce866efa2 100755 (executable)
@@ -155,6 +155,37 @@ void HttpMsgHeader::gen_events()
         add_infraction(INF_CTE_HEADER);
         create_event(EVENT_CTE_HEADER);
     }
+
+    // We don't support HTTP/1 to HTTP/2 upgrade and we alert on any attempt to do it
+    if (get_header_count(HEAD_HTTP2_SETTINGS) > 0)
+    {
+        add_infraction(INF_HTTP2_SETTINGS);
+        if (source_id == SRC_CLIENT)
+            create_event(EVENT_HTTP2_UPGRADE_REQUEST);
+        else
+            create_event(EVENT_HTTP2_UPGRADE_RESPONSE);
+    }
+    if ((get_header_count(HEAD_UPGRADE) > 0) &&
+        ((source_id == SRC_CLIENT) || (status_code_num == 101)))
+    {
+        const Field& up_header = get_header_value_norm(HEAD_UPGRADE);
+        int32_t consumed = 0;
+        do
+        {
+            const int32_t upgrade = get_code_from_token_list(up_header.start(), up_header.length(),
+                consumed, upgrade_list);
+            if ((upgrade == UP_H2C) || (upgrade == UP_H2) || (upgrade == UP_HTTP20))
+            {
+                add_infraction(INF_UPGRADE_HEADER_HTTP2);
+                if (source_id == SRC_CLIENT)
+                    create_event(EVENT_HTTP2_UPGRADE_REQUEST);
+                else
+                    create_event(EVENT_HTTP2_UPGRADE_RESPONSE);
+                break;
+            }
+        }
+        while (consumed != -1);
+    }
 }
 
 void HttpMsgHeader::update_flow()
@@ -253,12 +284,15 @@ void HttpMsgHeader::update_flow()
     if (session_data->for_http2)
     {
         // The only transfer-encoding header we should see for HTTP/2 traffic is "identity"
-        const int IDENTITY_SIZE = 8;
-        if ((te_header.length() > 0) && ( (te_header.length() != IDENTITY_SIZE) ||
-            memcmp(te_header.start(), "identity", IDENTITY_SIZE) != 0))
+        if (te_header.length() > 0)
         {
-            add_infraction(INF_H2_NON_IDENTITY_TE);
-            create_event(EVENT_H2_NON_IDENTITY_TE);
+            int32_t consumed = 0;
+            if ((get_code_from_token_list(te_header.start(), te_header.length(), consumed,
+                transfer_encoding_list) != TE_IDENTITY) || (consumed != -1))
+            {
+                add_infraction(INF_H2_NON_IDENTITY_TE);
+                create_event(EVENT_H2_NON_IDENTITY_TE);
+            }
         }
         if (get_header_value_norm(HEAD_CONTENT_LENGTH).length() > 0)
         {
@@ -293,35 +327,36 @@ void HttpMsgHeader::update_flow()
         // If there is a Transfer-Encoding header, it should be "chunked" without any other
         // encodings being listed. The RFC allows other encodings to come before chunked but
         // no one does this in real life.
-        const int CHUNKED_SIZE = 7;
-        bool is_chunked = false;
-
-        if ((te_header.length() == CHUNKED_SIZE) &&
-            !memcmp(te_header.start(), "chunked", CHUNKED_SIZE))
+        unsigned token_count = 0;
+        int32_t consumed = 0;
+        int32_t transfer_encoding;
+        do
         {
-            is_chunked = true;
+            transfer_encoding = get_code_from_token_list(te_header.start(), te_header.length(),
+                consumed, transfer_encoding_list);
+            token_count++;
         }
-        else if ((te_header.length() > CHUNKED_SIZE) &&
-            !memcmp(te_header.start() + (te_header.length() - (CHUNKED_SIZE+1)),
-                ",chunked", CHUNKED_SIZE+1))
+        while (consumed != -1);
+
+        if (transfer_encoding != TE_CHUNKED)
         {
-            add_infraction(INF_PADDED_TE_HEADER);
-            create_event(EVENT_PADDED_TE_HEADER);
-            is_chunked = true;
+            add_infraction(INF_BAD_TE_HEADER);
+            create_event(EVENT_BAD_TE_HEADER);
         }
-
-        if (is_chunked)
+        else
         {
+            // Last Transfer-Encoding is chunked ...
+            if (token_count > 1)
+            {
+                // ... but there were others before it
+                add_infraction(INF_PADDED_TE_HEADER);
+                create_event(EVENT_PADDED_TE_HEADER);
+            }
             session_data->type_expected[source_id] = SEC_BODY_CHUNK;
             HttpModule::increment_peg_counts(PEG_CHUNKED);
             prepare_body();
             return;
         }
-        else
-        {
-            add_infraction(INF_BAD_TE_HEADER);
-            create_event(EVENT_BAD_TE_HEADER);
-        }
     }
 
     // else because Transfer-Encoding header negates Content-Length header even if something was
index 230ec5972a79f34c63bb47289db08a0820a5b029..c1360ca5a6009c4a6f73d9fc532084a96bb226f4 100755 (executable)
@@ -50,7 +50,8 @@ int32_t substr_to_code(const uint8_t* text, const int32_t text_len, const StrCod
 {
     for (int32_t k=0; table[k].name != nullptr; k++)
     {
-        int32_t len =  (text_len <= (int)strlen(table[k].name) ) ? text_len : (int)strlen(table[k].name);
+        int32_t len = (text_len <= (int)strlen(table[k].name) ) ? text_len :
+            (int)strlen(table[k].name);
 
         if (memcmp(text, table[k].name, len) == 0)
         {
@@ -60,3 +61,13 @@ int32_t substr_to_code(const uint8_t* text, const int32_t text_len, const StrCod
     return HttpCommon::STAT_OTHER;
 }
 
+int32_t get_code_from_token_list(const uint8_t* token_list, const int32_t text_len,
+    int32_t& consumed, const StrCode table[])
+{
+    int32_t k;
+    for (k = consumed; (k < text_len) and (token_list[k] != ','); k++);
+    const int32_t code = str_to_code(token_list + consumed, k - consumed, table);
+    consumed = (k < text_len) ? k + 1 : -1;
+    return code;
+}
+
index 8e783ad86ab0c313a5197f1d1e7c5e6d0cd42829..3a3f0c5dbeb8b8aeaf3156d557fc96fd501871af 100755 (executable)
@@ -32,5 +32,10 @@ int32_t str_to_code(const char* text, const StrCode table[]);
 int32_t str_to_code(const uint8_t* text, const int32_t text_len, const StrCode table[]);
 int32_t substr_to_code(const uint8_t* text, const int32_t text_len, const StrCode table[]);
 
+// Convert the first value in a comma-separated list into a code. consumed is the number of bytes
+// used from the list or -1 if there are no more list entries.
+int32_t get_code_from_token_list(const uint8_t* token_list, const int32_t text_len,
+    int32_t& bytes_consumed, const StrCode table[]);
+
 #endif
 
index 6125af834df0531d4fda232c944d01a37183459e..45b897ced331fca8a9c8d72e66366304d899120e 100755 (executable)
@@ -139,6 +139,7 @@ const StrCode HttpMsgHeadShared::header_list[] =
     { HEAD_MIME_VERSION,              "mime-version" },
     { HEAD_PROXY_AGENT,               "proxy-agent" },
     { HEAD_CONTENT_DISPOSITION,       "content-disposition" },
+    { HEAD_HTTP2_SETTINGS,            "http2-settings" },
     { 0,                              nullptr }
 };
 
@@ -181,6 +182,21 @@ const StrCode HttpMsgHeadShared::charset_code_opt_list[] =
     { 0,                     nullptr }
 };
 
+const StrCode HttpMsgHeadShared::upgrade_list[] =
+{
+    { UP_H2C,                "h2c" },
+    { UP_H2,                 "h2" },
+    { UP_HTTP20,             "http/2.0" },
+    { 0,                     nullptr }
+};
+
+const StrCode HttpMsgHeadShared::transfer_encoding_list[] =
+{
+    { TE_CHUNKED,            "chunked" },
+    { TE_IDENTITY,           "identity" },
+    { 0,                     nullptr }
+};
+
 const HeaderNormalizer HttpMsgHeadShared::NORMALIZER_BASIC
     { EVENT__NONE, INF__NONE, false, nullptr, nullptr, nullptr };
 
@@ -224,10 +240,10 @@ const HeaderNormalizer* const HttpMsgHeadShared::header_norms[HEAD__MAX_VALUE +
     &NORMALIZER_DATE,       // HEAD_DATE
     &NORMALIZER_TOKEN_LIST, // HEAD_PRAGMA
     &NORMALIZER_TOKEN_LIST, // HEAD_TRAILER
-    &NORMALIZER_BASIC,      //HEAD_COOKIE
-    &NORMALIZER_BASIC,      //HEAD_SET_COOKIE
+    &NORMALIZER_BASIC,      // HEAD_COOKIE
+    &NORMALIZER_BASIC,      // HEAD_SET_COOKIE
     &NORMALIZER_TOKEN_LIST, // HEAD_TRANSFER_ENCODING
-    &NORMALIZER_BASIC,      // HEAD_UPGRADE
+    &NORMALIZER_TOKEN_LIST, // HEAD_UPGRADE
     &NORMALIZER_BASIC,      // HEAD_VIA
     &NORMALIZER_BASIC,      // HEAD_WARNING
     &NORMALIZER_TOKEN_LIST, // HEAD_ACCEPT
@@ -275,6 +291,7 @@ const HeaderNormalizer* const HttpMsgHeadShared::header_norms[HEAD__MAX_VALUE +
     &NORMALIZER_BASIC,      // HEAD_MIME_VERSION
     &NORMALIZER_BASIC,      // HEAD_PROXY_AGENT
     &NORMALIZER_BASIC,      // HEAD_CONTENT_DISPOSITION
+    &NORMALIZER_TOKEN_LIST, // HEAD_HTTP2_SETTINGS
     &NORMALIZER_BASIC,      // HEAD__MAX_VALUE
     &NORMALIZER_BASIC,      // HEAD_CUSTOM_XFF_HEADER
     &NORMALIZER_BASIC,      // HEAD_CUSTOM_XFF_HEADER
@@ -408,6 +425,8 @@ const RuleMap HttpModule::http_events[] =
     { EVENT_TRUNCATED_MSG_BODY_CL,      "HTTP Content-Length message body was truncated" },
     { EVENT_TRUNCATED_MSG_BODY_CHUNK,   "HTTP chunked message body was truncated" },
     { 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" },
     { 0, nullptr }
 };