The HTTP version in the start line has a valid format but the version is higher than 1. This alert
does not apply to HTTP/2 or HTTP/3 traffic.
+119:277
+
+The HTTP message body is gzip encoded and the FEXTRA flag is set in the gzip header.
+
121:1
Invalid flag set on HTTP/2 frame header
static const uint32_t HTTP_GID = 119;
static const int GZIP_WINDOW_BITS = 31;
+static const uint8_t GZIP_HEADER_FLAG_OFFSET = 3;
+static const uint8_t GZIP_FLAG_FEXTRA = 0x4;
static const int DEFLATE_WINDOW_BITS = 15;
static const int MAX_FIELD_NAME_LENGTH = 100;
// Plan to support max 8 xff headers
// Body compression types
enum CompressId { CMP_NONE=2, CMP_GZIP, CMP_DEFLATE };
+// GZIP magic verification state
+enum GzipVerificationState { GZIP_TBD, GZIP_MAGIC_BAD, GZIP_MAGIC_GOOD, GZIP_FLAGS_PROCESSED };
+
// Message section in which an IPS option provides the buffer
enum InspectSection { IS_NONE, IS_HEADER, IS_FLEX_HEADER, IS_FIRST_BODY, IS_BODY, IS_TRAILER };
INF_JS_SCOPE_NEST_OVERFLOW = 132,
INF_INVALID_SUBVERSION = 133,
INF_VERSION_0 = 134,
+ INF_GZIP_FEXTRA = 135,
INF__MAX_VALUE
};
EVENT_INVALID_SUBVERSION = 275,
EVENT_VERSION_0 = 276,
EVENT_VERSION_HIGHER_THAN_1 = 277,
+ EVENT_GZIP_FEXTRA = 278,
EVENT__MAX_VALUE
};
detection_status[source_id] = DET_REACTIVATING;
compression[source_id] = CMP_NONE;
+ gzip_state[source_id] = GZIP_TBD;
+ gzip_header_bytes_processed[source_id] = 0;
if (compress_stream[source_id] != nullptr)
{
inflateEnd(compress_stream[source_id]);
uint32_t partial_raw_bytes[2] = { 0, 0 };
uint8_t* partial_buffer[2] = { nullptr, nullptr };
uint32_t partial_buffer_length[2] = { 0, 0 };
+ uint32_t gzip_header_bytes_processed[2] = { 0, 0 };
+ HttpEnums::GzipVerificationState gzip_state[2] = { HttpEnums::GZIP_TBD, HttpEnums::GZIP_TBD };
+ bool gzip_header_check_done();
// *** StreamSplitter internal data - scan() => reassemble()
uint32_t num_excess[2] = { 0, 0 };
HttpCutter* get_cutter(HttpEnums::SectionType type, HttpFlowData* session) const;
void chunk_spray(HttpFlowData* session_data, uint8_t* buffer, const uint8_t* data,
unsigned length) const;
- static void decompress_copy(uint8_t* buffer, uint32_t& offset, const uint8_t* data,
+ void decompress_copy(uint8_t* buffer, uint32_t& offset, const uint8_t* data,
uint32_t length, HttpEnums::CompressId& compression, z_stream*& compress_stream,
bool at_start, HttpInfractions* infractions, HttpEventGen* events,
- HttpFlowData* session_data);
+ HttpFlowData* session_data) const;
+ void process_gzip_header(const uint8_t* data,
+ uint32_t length, HttpFlowData* session_data) const;
+ bool gzip_header_check_done(HttpFlowData* session_data) const;
HttpInspect* const my_inspector;
const HttpCommon::SourceId source_id;
}
}
+void HttpStreamSplitter::process_gzip_header(const uint8_t* data,
+ uint32_t length, HttpFlowData* session_data) const
+{
+ uint32_t& header_bytes_processed = session_data->gzip_header_bytes_processed[source_id];
+ uint32_t input_bytes_processed = 0;
+ if (session_data->gzip_state[source_id] == GZIP_TBD)
+ {
+ static const uint8_t gzip_magic[] = {0x1f, 0x8b, 0x08};
+ static const uint8_t magic_length = 3;
+ const uint32_t magic_cmp_len = (magic_length - header_bytes_processed) < length ?
+ (magic_length - header_bytes_processed) : length;
+
+ if (memcmp(data, gzip_magic + header_bytes_processed, magic_cmp_len))
+ session_data->gzip_state[source_id] = GZIP_MAGIC_BAD;
+ else if (header_bytes_processed + length >= magic_length)
+ session_data->gzip_state[source_id] = GZIP_MAGIC_GOOD;
+ header_bytes_processed += magic_cmp_len;
+ input_bytes_processed += magic_cmp_len;
+ }
+ if (session_data->gzip_state[source_id] == GZIP_MAGIC_GOOD and length > input_bytes_processed)
+ {
+ const uint8_t gzip_flags = data[input_bytes_processed];
+ if (gzip_flags & GZIP_FLAG_FEXTRA)
+ {
+ *session_data->get_infractions(source_id) += INF_GZIP_FEXTRA;
+ session_data->events[source_id]->create_event(EVENT_GZIP_FEXTRA);
+ }
+ header_bytes_processed++;
+ session_data->gzip_state[source_id] = GZIP_FLAGS_PROCESSED;
+ }
+}
+
+bool HttpStreamSplitter::gzip_header_check_done(HttpFlowData* session_data) const
+{
+ return session_data->gzip_state[source_id] == HttpEnums::GZIP_MAGIC_BAD or
+ session_data->gzip_state[source_id] == HttpEnums::GZIP_FLAGS_PROCESSED;
+}
+
void HttpStreamSplitter::decompress_copy(uint8_t* buffer, uint32_t& offset, const uint8_t* data,
uint32_t length, HttpEnums::CompressId& compression, z_stream*& compress_stream,
- bool at_start, HttpInfractions* infractions, HttpEventGen* events, HttpFlowData* session_data)
+ bool at_start, HttpInfractions* infractions, HttpEventGen* events, HttpFlowData* session_data) const
{
if ((compression == CMP_GZIP) || (compression == CMP_DEFLATE))
{
+ if (compression == CMP_GZIP and !gzip_header_check_done(session_data))
+ process_gzip_header(data, length, session_data);
+
compress_stream->next_in = const_cast<Bytef*>(data);
compress_stream->avail_in = length;
compress_stream->next_out = buffer + offset;
inflateEnd(compress_stream);
delete compress_stream;
compress_stream = nullptr;
+ // FIXIT-E - Will need to clear gzip header processing state here when we implement
+ // processing multiple gzip members in a message section
}
return;
}
{ EVENT_INVALID_SUBVERSION, "HTTP/1 version other than 1.0 or 1.1" },
{ EVENT_VERSION_0, "HTTP version in start line is 0" },
{ EVENT_VERSION_HIGHER_THAN_1, "HTTP version in start line is higher than 1" },
+ { EVENT_GZIP_FEXTRA, "HTTP gzip body with the FEXTRA flag set" },
{ 0, nullptr }
};