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
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
// 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,
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
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,
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
};
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.
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()
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)
{
// 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
{
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)
{
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;
+}
+
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
{ HEAD_MIME_VERSION, "mime-version" },
{ HEAD_PROXY_AGENT, "proxy-agent" },
{ HEAD_CONTENT_DISPOSITION, "content-disposition" },
+ { HEAD_HTTP2_SETTINGS, "http2-settings" },
{ 0, nullptr }
};
{ 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 };
&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
&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
{ 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 }
};