InspectionBuffer hb;
if (! ((NHttpInspect*)(p->flow->gadget))->
- get_buf((unsigned)buffer_index, sub_id, form, nullptr, hb))
+ nhttp_get_buf((unsigned)buffer_index, sub_id, form, nullptr, hb))
return DETECTION_OPTION_NO_MATCH;
c.set(key, hb.data, hb.len);
class NHttpEventGen
{
public:
+ virtual ~NHttpEventGen() = default;
void reset() { events_generated = 0; }
- void create_event(NHttpEnums::EventSid sid)
+ virtual void create_event(NHttpEnums::EventSid sid)
{
assert(((int)sid > 0) && ((int)sid <= MAX));
if (!events_generated[sid-1])
length = stat_code;
}
+void Field::set(const Field& f)
+{
+ assert(length == STAT_NOT_COMPUTE);
+ assert(start == nullptr);
+ start = f.start;
+ length = f.length;
+}
#ifdef REG_TEST
void Field::print(FILE* output, const char* name) const
explicit Field(int32_t length_) : length(length_) { assert(length<=0); }
Field() = default;
void set(int32_t length_, const uint8_t* start_);
+ void set(const Field& f);
void set(NHttpEnums::StatusCode stat_code);
void set(int32_t length) { set(static_cast<NHttpEnums::StatusCode>(length)); }
+ // Only call this method if the field owns the dynamically allocated buffer you are deleting.
+ // This method is a convenience but you still must know where the buffer came from. Many fields
+ // refer to static buffers or a subfield of someone else's buffer.
+ void delete_buffer() { if (length >= 0) delete[] start; };
#ifdef REG_TEST
void print(FILE* output, const char* name) const;
switch (ibt)
{
case InspectionBuffer::IBT_KEY:
- return get_buf(NHTTP_BUFFER_URI, 0, 0, nullptr, b);
+ return nhttp_get_buf(NHTTP_BUFFER_URI, 0, 0, nullptr, b);
case InspectionBuffer::IBT_HEADER:
if (get_latest_is() == IS_TRAILER)
- return get_buf(NHTTP_BUFFER_TRAILER, 0, 0, nullptr, b);
+ return nhttp_get_buf(NHTTP_BUFFER_TRAILER, 0, 0, nullptr, b);
else
- return get_buf(NHTTP_BUFFER_HEADER, 0, 0, nullptr, b);
+ return nhttp_get_buf(NHTTP_BUFFER_HEADER, 0, 0, nullptr, b);
case InspectionBuffer::IBT_BODY:
- return get_buf(NHTTP_BUFFER_CLIENT_BODY, 0, 0, nullptr, b);
+ return nhttp_get_buf(NHTTP_BUFFER_CLIENT_BODY, 0, 0, nullptr, b);
default:
return false;
}
}
-SO_PUBLIC bool NHttpInspect::get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet*,
+SO_PUBLIC bool NHttpInspect::nhttp_get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet*,
InspectionBuffer& b)
{
if (latest_section == nullptr)
NHttpInspect(NHttpParaList params_);
bool get_buf(InspectionBuffer::Type ibt, Packet*, InspectionBuffer& b) override;
- bool get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet*, InspectionBuffer& b);
+ bool nhttp_get_buf(unsigned id, uint64_t sub_id, uint64_t form, Packet*, InspectionBuffer& b);
bool get_fp_buf(InspectionBuffer::Type ibt, Packet*, InspectionBuffer& b) override;
bool configure(SnortConfig*) override { return true; }
void show(SnortConfig*) override { LogMessage("NHttpInspect\n"); }
transaction->set_body(this);
}
+NHttpMsgBody::~NHttpMsgBody()
+{
+ if (classic_client_body_alloc)
+ classic_client_body.delete_buffer();
+}
+
void NHttpMsgBody::analyze()
{
detect_data.length = (msg_text.length <= session_data->detect_depth_remaining[source_id]) ?
}
}
+const Field& NHttpMsgBody::get_classic_client_body()
+{
+ return classic_normalize(detect_data, classic_client_body, classic_client_body_alloc);
+}
+
#ifdef REG_TEST
// Common elements of print_section() for body sections
void NHttpMsgBody::print_body_section(FILE* output)
class NHttpMsgBody : public NHttpMsgSection
{
public:
+ virtual ~NHttpMsgBody();
void analyze() override;
const Field& get_detect_buf() const override { return detect_data; }
NHttpEnums::InspectSection get_inspection_section() const override
{ return detection_section ? NHttpEnums::IS_DETECTION : NHttpEnums::IS_BODY; }
+ const Field& get_classic_client_body();
protected:
NHttpMsgBody(const uint8_t* buffer, const uint16_t buf_size, NHttpFlowData* session_data_,
NHttpEnums::SourceId source_id_, bool buf_owner, Flow* flow_,
const NHttpParaList* params_);
- void do_file_processing();
int64_t body_octets;
- Field detect_data;
- Field file_data;
- const bool detection_section;
#ifdef REG_TEST
void print_body_section(FILE* output);
#endif
+
+private:
+ void do_file_processing();
+
+ Field detect_data;
+ Field file_data;
+ const bool detection_section;
+ Field classic_client_body; // URI normalization applied
+ bool classic_client_body_alloc = false;
};
#endif
#include "nhttp_enum.h"
#include "nhttp_normalizers.h"
+#include "nhttp_uri_norm.h"
#include "nhttp_msg_head_shared.h"
using namespace NHttpEnums;
{
NormalizedHeader* temp_ptr = list_ptr;
list_ptr = list_ptr->next;
- if (temp_ptr->norm.length >= 0)
- delete[] temp_ptr->norm.start;
- delete temp_ptr;
+ temp_ptr->norm.delete_buffer();
}
+ if (classic_raw_header_alloc)
+ classic_raw_header.delete_buffer();
+ if (classic_norm_header_alloc)
+ classic_norm_header.delete_buffer();
+ if (classic_norm_cookie_alloc)
+ classic_norm_cookie.delete_buffer();
}
// All the header processing that is done for every message (i.e. not just-in-time) is done here.
return (node != nullptr) ? node->count : 0;
}
+const Field& NHttpMsgHeadShared::get_classic_raw_header()
+{
+ if (classic_raw_header.length != STAT_NOT_COMPUTE)
+ return classic_raw_header;
+ const HeaderId cookie_head = (source_id == SRC_CLIENT) ? HEAD_COOKIE : HEAD_SET_COOKIE;
+ if (!headers_present[cookie_head])
+ {
+ // There are no cookies so the classic headers are the whole thing
+ classic_raw_header.set(msg_text);
+ return classic_raw_header;
+ }
+
+ // Figure out how much space to allocate by stepping through the headers in advance
+ int32_t length = 0;
+ for (int k = 0; k < num_headers; k++)
+ {
+ if (header_name_id[k] == cookie_head)
+ continue;
+ // All header line Fields point into the buffer holding the entire message section.
+ // Calculation must account for separators between header lines, but there are none
+ // following the final header line.
+ const int32_t head_len = (k == num_headers-1) ? header_line[k].length :
+ header_line[k+1].start - header_line[k].start;
+ length += head_len;
+ }
+
+ // Step through headers again and do the copying this time
+ uint8_t* const buffer = new uint8_t[length];
+ int32_t current = 0;
+ for (int k = 0; k < num_headers; k++)
+ {
+ if (header_name_id[k] == cookie_head)
+ continue;
+ const int32_t head_len = (k == num_headers-1) ? header_line[k].length :
+ header_line[k+1].start - header_line[k].start;
+ memcpy(buffer + current, header_line[k].start, head_len);
+ current += head_len;
+ }
+ assert(current == length);
+
+ classic_raw_header.set(length, buffer);
+ classic_raw_header_alloc = true;
+ return classic_raw_header;
+}
+
+const Field& NHttpMsgHeadShared::get_classic_norm_header()
+{
+ return classic_normalize(get_classic_raw_header(), classic_norm_header,
+ classic_norm_header_alloc);
+}
+
+const Field& NHttpMsgHeadShared::get_classic_raw_cookie()
+{
+ HeaderId cookie_head = (source_id == SRC_CLIENT) ? HEAD_COOKIE : HEAD_SET_COOKIE;
+ return get_header_value_norm(cookie_head);
+}
+
+const Field& NHttpMsgHeadShared::get_classic_norm_cookie()
+{
+ return classic_normalize(get_classic_raw_cookie(), classic_norm_cookie,
+ classic_norm_cookie_alloc);
+}
+
const Field& NHttpMsgHeadShared::get_header_value_norm(HeaderId header_id)
{
NormalizedHeader* node = get_header_node(header_id);
void analyze() override;
int32_t get_num_headers() const { return num_headers; }
- const Field& get_headers() const { return msg_text; }
+ const Field& get_classic_raw_header();
+ const Field& get_classic_raw_cookie();
+ const Field& get_classic_norm_header();
+ const Field& get_classic_norm_cookie();
const Field& get_header_line(int k) const { return header_line[k]; }
const Field& get_header_name(int k) const { return header_name[k]; }
const Field& get_header_value(int k) const { return header_value[k]; }
{ }
~NHttpMsgHeadShared();
+#ifdef REG_TEST
+ void print_headers(FILE* output);
+#endif
+
+private:
+ static const int MAX = NHttpEnums::HEAD__MAX_VALUE;
+
// Header normalization strategies. There should be one defined for every different way we can
// process a header field value.
static const HeaderNormalizer NORMALIZER_BASIC;
// Master table of known header fields and their normalization strategies.
static const HeaderNormalizer* const header_norms[];
+ // All of these are indexed by the relative position of the header field in the message
+ static const int MAX_HEADERS = 200; // I'm an arbitrary number. FIXIT-L
+ static const int MAX_HEADER_LENGTH = 4096; // Based on max cookie size of some browsers
+
void parse_header_block();
uint32_t find_header_end(const uint8_t* buffer, int32_t length, int& num_seps);
void parse_header_lines();
void create_norm_head_list();
void derive_header_name_id(int index);
- // All of these are indexed by the relative position of the header field in the message
- static const int MAX_HEADERS = 200; // I'm an arbitrary number. FIXIT-L
- static const int MAX_HEADER_LENGTH = 4096; // Based on max cookie size of some browsers
+ std::bitset<MAX> headers_present = 0;
int32_t num_headers = NHttpEnums::STAT_NOT_COMPUTE;
Field* header_line = nullptr;
Field* header_name = nullptr;
NHttpEnums::HeaderId* header_name_id = nullptr;
Field* header_value = nullptr;
-#ifdef REG_TEST
- void print_headers(FILE* output);
-#endif
-
-private:
- static const int MAX = NHttpEnums::HEAD__MAX_VALUE;
- std::bitset<MAX> headers_present = 0;
+ Field classic_raw_header; // raw headers with cookies spliced out
+ bool classic_raw_header_alloc = false;
+ Field classic_norm_header; // URI normalization applied
+ bool classic_norm_header_alloc = false;
+ Field classic_norm_cookie; // URI normalization applied to concatenated cookie values
+ bool classic_norm_cookie_alloc = false;
struct NormalizedHeader
{
}
}
+const Field& NHttpMsgSection::classic_normalize(const Field& raw, Field& norm, bool& norm_alloc)
+{
+ if (norm.length != STAT_NOT_COMPUTE)
+ return norm;
+
+ if ((raw.length <= 0) || !UriNormalizer::need_norm_path(raw))
+ {
+ norm.set(raw);
+ return norm;
+ }
+ uint8_t* buffer = new uint8_t[raw.length + UriNormalizer::URI_NORM_EXPANSION];
+ UriNormalizer::classic_normalize(raw, norm, buffer);
+ norm_alloc = true;
+ return norm;
+}
+
const Field& NHttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, uint64_t form)
{
- // Only use with buffers that support the request option
+ // buffer_side replaces source_id for buffers that support the request option
const SourceId buffer_side = (form & FORM_REQUEST) ? SRC_CLIENT : source_id;
switch (id)
if (source_id != SRC_CLIENT)
return Field::FIELD_NULL;
NHttpMsgBody* body = transaction->get_body();
- return (body != nullptr) ? body->get_detect_buf() : Field::FIELD_NULL;
+ return (body != nullptr) ? body->get_classic_client_body() : Field::FIELD_NULL;
}
case NHTTP_BUFFER_COOKIE:
case NHTTP_BUFFER_RAW_COOKIE:
- // FIXIT-M when real cookie normalization is implemented these need to become separate cases.
- // Currently "normalization" is aggregation of multiple cookies. That is correct for raw
- // cookies and all there is for normalized cookies.
{
NHttpMsgHeader* header = transaction->get_header(buffer_side);
if (header == nullptr)
return Field::FIELD_NULL;
- HeaderId cookie_head = (buffer_side == SRC_CLIENT) ? HEAD_COOKIE : HEAD_SET_COOKIE;
- return header->get_header_value_norm(cookie_head);
+ return (id == NHTTP_BUFFER_COOKIE) ? header->get_classic_norm_cookie() :
+ header->get_classic_raw_cookie();
}
case NHTTP_BUFFER_HEADER:
case NHTTP_BUFFER_TRAILER:
if (header == nullptr)
return Field::FIELD_NULL;
if (sub_id == 0)
- return header->get_headers();
+ return header->get_classic_norm_header();
return header->get_header_value_norm((HeaderId)sub_id);
}
case NHTTP_BUFFER_METHOD:
case NHTTP_BUFFER_RAW_HEADER:
{
NHttpMsgHeader* header = transaction->get_header(buffer_side);
- return (header != nullptr) ? header->get_headers() : Field::FIELD_NULL;
+ return (header != nullptr) ? header->get_classic_raw_header() : Field::FIELD_NULL;
}
case NHTTP_BUFFER_STAT_CODE:
{
case NHTTP_BUFFER_RAW_TRAILER:
{
NHttpMsgTrailer* trailer = transaction->get_trailer(buffer_side);
- return (trailer != nullptr) ? trailer->get_headers() : Field::FIELD_NULL;
+ return (trailer != nullptr) ? trailer->get_classic_raw_header() : Field::FIELD_NULL;
}
default:
assert(false);
NHttpEnums::MethodId method_id;
int32_t status_code_num;
- // Convenience methods
+ // Convenience methods shared by multiple subclasses
void update_depth() const;
+ static const Field& classic_normalize(const Field& raw, Field& norm, bool& norm_alloc);
#ifdef REG_TEST
void print_message_title(FILE* output, const char* title) const;
void print_message_wrapup(FILE* output);
#include <stdio.h>
#include "nhttp_enum.h"
-#include "nhttp_normalizers.h"
#include "nhttp_uri.h"
using namespace NHttpEnums;
int32_t UriNormalizer::norm_path_clean(uint8_t* buf, const int32_t in_length,
NHttpInfractions& infractions, NHttpEventGen& events)
{
+ // This is supposed to be the path portion of a URI. Read NHttpUri::parse_uri() for an
+ // explanation.
+ assert(buf[0] == '/');
+
int32_t length = 0;
// It simplifies the code that handles /./ and /../ to pretend there is an extra '/' after the
// buffer. Avoids making a special case of URIs that end in . or .. That is why the loop steps
return length;
}
+// Provide traditional URI-style normalization for buffers that usually are not URIs
+void UriNormalizer::classic_normalize(const Field& input, Field& result, uint8_t* buffer)
+{
+ // The requirements for generating events related to these normalizations are unclear. It
+ // definitely doesn't seem right to generate standard URI events. For now we won't generate
+ // any events at all because these buffers may well not be URIs so regardless of what we find
+ // it is "normal". Similarly we don't have any reason to track any infractions.
+
+ // We want to reuse all the URI-normalization functions without complicating their event and
+ // infraction logic with legacy problems. The following centralizes all the messiness here so
+ // that we can conveniently modify it as requirements are better understood.
+
+ class NHttpDummyEventGen : public NHttpEventGen
+ {
+ void create_event(NHttpEnums::EventSid) override {}
+ };
+
+ NHttpInfractions unused;
+ NHttpDummyEventGen dummy_ev;
+
+ // Normalize character escape sequences
+ int32_t data_length = norm_char_clean(input.start, input.length, buffer, unused, dummy_ev);
+
+ // Normalize path directory traversals
+ // Find the leading slash if there is one
+ int32_t uri_offset;
+ for (uri_offset = 0; (uri_offset < data_length) && (buffer[uri_offset] != '/'); uri_offset++);
+ if (uri_offset < data_length)
+ {
+ norm_backslash(buffer + uri_offset, data_length - uri_offset, unused, dummy_ev);
+ data_length = uri_offset +
+ norm_path_clean(buffer + uri_offset, data_length - uri_offset, unused, dummy_ev);
+ }
+
+ result.set(data_length, buffer);
+}
+
#include "nhttp_field.h"
#include "nhttp_infractions.h"
#include "nhttp_event_gen.h"
-#include "nhttp_normalizers.h"
class UriNormalizer
{
public:
+ static const unsigned URI_NORM_EXPANSION = 1;
+
static void normalize(const Field& input, Field& result, bool do_path, uint8_t* buffer,
NHttpInfractions& infractions, NHttpEventGen& events);
static bool need_norm_path(const Field& uri_component);
static bool need_norm_no_path(const Field& uri_component);
- static const unsigned URI_NORM_EXPANSION = 1;
+ static void classic_normalize(const Field& input, Field& result, uint8_t* buffer);
private:
static const NHttpEnums::CharAction uri_char[256];
static const bool good_percent[256];
- static NormFunc norm_char_clean;
+ static int32_t norm_char_clean(const uint8_t* in_buf, int32_t in_length, uint8_t* out_buf,
+ NHttpInfractions& infractions, NHttpEventGen& events);
static void norm_backslash(uint8_t* buf, int32_t length, NHttpInfractions& infractions,
NHttpEventGen& events);
static int32_t norm_path_clean(uint8_t* buf, const int32_t in_length,