complicated about that, but suppose we use more than one rule option:
alert tcp any any -> any any ( msg:"combined example"; flow:established,
- to_server; http_uri; content:"chocolate"; file_data;
- content:"sinister POST data"; sid:5; rev:1; )
-
-This rule requires both the URI and the request message body. That sounds
-simple until one considers that the message body may be millions of bytes
-long. The headers with the URI may be long gone by that time.
-
-Is this rule going to work or do we need to do something different?
-
-It is helpful to understand when things happen. All the message headers and
-the first few thousand bytes of the body go through detection at the same
-time. Commonly this is about 16K bytes but there are several exceptions and
-there is no guaranteed minimum amount.
-
-That may be all you need. In many cases that will be the entire message. Or
-it may be more than your request_depth/response_depth. Or this rule may
-simply not care what happens after that in a very long message body.
-
-Beyond that the message body will continue to be subdivided into roughly
-16K-byte sections and inspected. But the previous rule will not be able to
-see the URI and hence will not work unless we rewrite it:
-
- alert tcp any any -> any any ( msg:"URI with_body"; flow:established,
to_server; http_uri: with_body; content:"chocolate"; file_data;
- content:"sinister POST data"; sid:5; rev:2; )
+ content:"sinister POST data"; sid:5; rev:1; )
The with_body option to http_uri causes the URI to be made available with
-every body section, not just the first one. These extra inspections have a
-performance cost which is why they are not done automatically. with_body is
-an option to be used when you actually need it.
+the message body. Use with_body for header-related rule options in rules
+that also examine the message body.
The with_trailer option is analogous and causes an earlier message element
-to be made available at the end of the message when the trailers following a
-chunked body arrive.
+to be made available at the end of the message when the trailers following
+a chunked body arrive.
alert tcp any any -> any any ( msg:"double content-language";
flow:established, to_client; http_header: with_trailer, field
Let's put all of this together. There are six opportunities to do
detection:
-1. When the first part of the request message body arrives. The request
-line, all of the headers, and the first part of the body all go through
-detection at the same time. Of course most requests don't have a body. In
-that case the request line and the headers are the whole message and get
-done at the same time.
+1. When the the request headers arrive. The request line and all of the
+headers go through detection at the same time.
-2. When subsequent sections of the request message body arrive. If you want
-to combine this with something from the request line or headers you must
-use the with_body option.
+2. When sections of the request message body arrive. If you want to combine
+this with something from the request line or headers you must use the
+with_body option.
3. When the request trailers arrive. If you want to combine this with
something from the request line or headers you must use the with_trailer
option.
-4. When the first part of the response message body arrives. The status
-line, all of the headers, and the first part of the body all go through
-detection at the same time. These may be combined with elements from the
-request line, request headers, or request trailers. Where ambiguity arises
-use the request option.
+4. When the response headers arrive. The status line and all of the headers
+go through detection at the same time. These may be combined with elements
+from the request line, request headers, or request trailers. Where
+ambiguity arises use the request option.
-5. When subsequent sections of the response message body arrive. These may
-be combined with the status line, response headers, request line, request
-headers, or request trailers as described above.
+5. When sections of the response message body arrive. These may be combined
+with the status line, response headers, request line, request headers, or
+request trailers as described above.
6. When the response trailers arrive. Again these may be combined as
described above.
-Message body data can only go through detection at the time it is received.
-Headers may be combined with later items but the body cannot.
+Message body sections can only go through detection at the time they are
+received. Headers may be combined with later items but the body cannot.
Step 1: Identify the type of URI.
-NHI recognizes four types of URI:
+HI recognizes four types of URI:
1. Asterisk: a lone ‘*’ character signifying that the request does not refer to any resource in
particular. Often used with the OPTIONS method. This is not normalized.
The procedures for normalizing the individual pieces are mostly identical to 2.X. Some points
warrant mention:
-1. NHI considers it to be normal for reserved characters to be percent encoded and does not
+1. HI considers it to be normal for reserved characters to be percent encoded and does not
generate an alert. The 119/1 alert is used only for unreserved characters that are found to be
percent encoded. The ignore_unreserved configuration option allows the user to specify a list of
unreserved characters that are exempt from this alert.
This test tool does not implement the feature of being hardened against bad input. If you write a
badly formatted or improper test case the program may assert or crash. The responsibility is on the
-developer to get it right. Currently that is the best use of resources.
+developer to get it right.
Test input is currently designed for single-threaded operation only.
enum CompressId { CMP_NONE=2, CMP_GZIP, CMP_DEFLATE };
// Message section in which an IPS option provides the buffer
-enum InspectSection { IS_NONE, IS_DETECTION, IS_BODY, IS_TRAILER };
+enum InspectSection { IS_NONE, IS_HEADER, IS_FLEX_HEADER, IS_FIRST_BODY, IS_BODY, IS_TRAILER };
// Part of the URI to be provided
enum UriComponent { UC_SCHEME = 1, UC_HOST, UC_PORT, UC_PATH, UC_QUERY, UC_FRAGMENT };
if (current_section == nullptr)
return HttpEnums::IS_NONE;
+ // FIXIT-L revisit why we need this check. We should not be getting a current section back
+ // for a raw packet but one of the test cases did exactly that.
+ if (!(p->packet_flags & PKT_PSEUDO))
+ return HttpEnums::IS_NONE;
+
return current_section->get_inspection_section();
}
bool HttpInspect::get_fp_buf(InspectionBuffer::Type ibt, Packet* p, InspectionBuffer& b)
{
+ if (get_latest_is(p) == IS_NONE)
+ return false;
+
// Fast pattern buffers only supplied at specific times
switch (ibt)
{
case InspectionBuffer::IBT_KEY:
- if ((get_latest_is(p) != IS_DETECTION) || (get_latest_src(p) != SRC_CLIENT))
+ // Many rules targeting POST feature http_uri fast pattern with http_client_body. We
+ // accept the performance hit of rerunning http_uri fast pattern with request body message
+ // sections
+ if (get_latest_src(p) != SRC_CLIENT)
return false;
break;
case InspectionBuffer::IBT_HEADER:
- if ((get_latest_is(p) != IS_DETECTION) && (get_latest_is(p) != IS_TRAILER))
+ // http_header fast patterns for response bodies limited to first section
+ if ((get_latest_src(p) == SRC_SERVER) && (get_latest_is(p) == IS_BODY))
return false;
break;
case InspectionBuffer::IBT_BODY:
- if ((get_latest_is(p) != IS_DETECTION) && (get_latest_is(p) != IS_BODY))
+ if ((get_latest_is(p) != IS_FIRST_BODY) && (get_latest_is(p) != IS_BODY))
return false;
break;
default:
if (session_data->section_type[source_id] == SEC__NOT_COMPUTE)
return;
+ // Don't make pkt_data for headers available to detection
+ // FIXIT-M One byte to avoid potential problems with zero
+ if ((session_data->section_type[source_id] == SEC_HEADER) ||
+ (session_data->section_type[source_id] == SEC_TRAILER))
+ {
+ p->set_detect_limit(1);
+ }
+
// Limit alt_dsize of message body sections to request/response depth
if ((session_data->detect_depth_remaining[source_id] > 0) &&
(session_data->detect_depth_remaining[source_id] < p->dsize))
return new HttpStreamSplitter(is_client_to_server, this);
}
static HttpEnums::InspectSection get_latest_is(const snort::Packet* p);
+ static HttpEnums::SourceId get_latest_src(const snort::Packet* p);
// Callbacks that provide "extra data"
static int get_xtra_trueip(snort::Flow*, uint8_t**, uint32_t*, uint32_t*);
bool process(const uint8_t* data, const uint16_t dsize, snort::Flow* const flow,
HttpEnums::SourceId source_id_, bool buf_owner) const;
- static HttpEnums::SourceId get_latest_src(const snort::Packet* p);
const HttpParaList* const params;
const HttpParaList* params_) :
HttpMsgSection(buffer, buf_size, session_data_, source_id_, buf_owner, flow_, params_),
body_octets(session_data->body_octets[source_id]),
- detection_section((body_octets == 0) && (session_data->detect_depth_remaining[source_id] > 0))
+ first_body(session_data->body_octets[source_id] == 0)
{
transaction->set_body(this);
get_related_sections();
body_octets += msg_text.length();
}
-bool HttpMsgBody::detection_required() const
-{
- return (detect_data.length() > 0) || (get_inspection_section() == IS_DETECTION);
-}
-
void HttpMsgBody::do_utf_decoding(const Field& input, Field& output)
{
if ((source_id == SRC_CLIENT) || (session_data->utf_state == nullptr) || (input.length() == 0))
public:
void analyze() override;
HttpEnums::InspectSection get_inspection_section() const override
- { return detection_section ? HttpEnums::IS_DETECTION : HttpEnums::IS_BODY; }
- bool detection_required() const override;
+ { return first_body ? HttpEnums::IS_FIRST_BODY : HttpEnums::IS_BODY; }
+ bool detection_required() const override { return (detect_data.length() > 0); }
HttpMsgBody* get_body() override { return this; }
const Field& get_classic_client_body();
const Field& get_detect_data() { return detect_data; }
const HttpParaList* params_);
int64_t body_octets;
+ bool first_body;
#ifdef REG_TEST
void print_body_section(FILE* output);
Field decoded_body;
Field decompressed_file_body;
Field js_norm_body;
- const bool detection_section;
};
#endif
const int64_t& depth = (source_id == SRC_CLIENT) ? params->request_depth :
params->response_depth;
session_data->detect_depth_remaining[source_id] = (depth != -1) ? depth : INT64_MAX;
- if (session_data->detect_depth_remaining[source_id] > 0)
- {
- // Depth must be positive because first body section must actually go to detection in order
- // to be the detection section
- detection_section = false;
- }
setup_file_processing();
setup_encoding_decompression();
setup_utf_decoding();
HttpEnums::SourceId source_id_, bool buf_owner, snort::Flow* flow_,
const HttpParaList* params_);
HttpEnums::InspectSection get_inspection_section() const override
- { return detection_section ? HttpEnums::IS_DETECTION : HttpEnums::IS_NONE; }
+ { return HttpEnums::IS_HEADER; }
+ bool detection_required() const override { return true; }
void update_flow() override;
void gen_events() override;
void publish() override;
Field true_ip;
Field true_ip_addr;
- bool detection_section = true;
-
#ifdef REG_TEST
void print_section(FILE* output) override;
#endif
HttpContextData::save_snapshot(this);
}
-bool HttpMsgSection::detection_required() const
-{
- return ((msg_text.length() > 0) && (get_inspection_section() != IS_NONE)) ||
- (get_inspection_section() == IS_DETECTION);
-}
-
void HttpMsgSection::add_infraction(int infraction)
{
*transaction->get_infractions(source_id) += infraction;
virtual ~HttpMsgSection() = default;
virtual HttpEnums::InspectSection get_inspection_section() const
{ return HttpEnums::IS_NONE; }
- virtual bool detection_required() const;
+ virtual bool detection_required() const = 0;
HttpEnums::SourceId get_source_id() const { return source_id; }
HttpTransaction* get_transaction() const { return transaction; }
const HttpParaList* get_params() const { return params; }
{
public:
void analyze() override;
+ bool detection_required() const override { return false; }
const Field& get_version() const { return version; }
protected:
const HttpParaList* params_);
HttpEnums::InspectSection get_inspection_section() const override
{ return HttpEnums::IS_TRAILER; }
+ bool detection_required() const override { return (msg_text.length() > 0); }
void gen_events() override;
void update_flow() override;
form = 0;
switch (buffer_index)
{
+ case HTTP_BUFFER_RAW_STATUS:
+ case HTTP_BUFFER_STAT_CODE:
+ case HTTP_BUFFER_STAT_MSG:
+ inspect_section = IS_HEADER;
+ break;
case HTTP_BUFFER_COOKIE:
case HTTP_BUFFER_HEADER:
case HTTP_BUFFER_METHOD:
case HTTP_BUFFER_RAW_COOKIE:
case HTTP_BUFFER_RAW_HEADER:
case HTTP_BUFFER_RAW_REQUEST:
- case HTTP_BUFFER_RAW_STATUS:
case HTTP_BUFFER_RAW_URI:
- case HTTP_BUFFER_STAT_CODE:
- case HTTP_BUFFER_STAT_MSG:
case HTTP_BUFFER_TRUE_IP:
case HTTP_BUFFER_URI:
case HTTP_BUFFER_VERSION:
- inspect_section = IS_DETECTION;
+ inspect_section = IS_FLEX_HEADER;
break;
case HTTP_BUFFER_CLIENT_BODY:
case HTTP_BUFFER_RAW_BODY:
else if (v.is("with_header"))
{
para_list.with_header = true;
- inspect_section = IS_DETECTION;
+ inspect_section = IS_HEADER;
}
else if (v.is("with_body"))
{
{
RuleProfile profile(HttpCursorModule::http_ps[psi]);
- if (!p->flow || !p->flow->gadget)
+ if (!p->flow || !p->flow->gadget || (HttpInspect::get_latest_is(p) == IS_NONE))
return NO_MATCH;
- if (HttpInspect::get_latest_is(p) != inspect_section)
- {
- // It is OK to provide a body buffer during the detection section. If there actually is
- // a body buffer available then the detection section must also be the first body section.
- if (! ((inspect_section == IS_BODY) && (HttpInspect::get_latest_is(p) == IS_DETECTION)) )
- return NO_MATCH;
- }
+ const bool section_match =
+ (HttpInspect::get_latest_is(p) == inspect_section) ||
+ ((HttpInspect::get_latest_is(p) == IS_HEADER) && (inspect_section == IS_FLEX_HEADER)) ||
+ ((HttpInspect::get_latest_is(p) == IS_FIRST_BODY) && (inspect_section == IS_BODY)) ||
+ ((HttpInspect::get_latest_src(p) == SRC_CLIENT) && (inspect_section == IS_FLEX_HEADER));
+ if (!section_match)
+ return NO_MATCH;
InspectionBuffer hb;
{
{ "request", Parameter::PT_IMPLIED, nullptr, nullptr,
"match against the cookie from the request message even when examining the response" },
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "this rule is limited to examining HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
"restrict to given header. Header name is case insensitive." },
{ "request", Parameter::PT_IMPLIED, nullptr, nullptr,
"match against the headers from the request message even when examining the response" },
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "this rule is limited to examining HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_method_params[] =
{
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "this rule is limited to examining HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
{
{ "request", Parameter::PT_IMPLIED, nullptr, nullptr,
"match against the cookie from the request message even when examining the response" },
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "this rule is limited to examining HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
{
{ "request", Parameter::PT_IMPLIED, nullptr, nullptr,
"match against the headers from the request message even when examining the response" },
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "this rule is limited to examining HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_raw_request_params[] =
{
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "this rule is limited to examining HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_raw_uri_params[] =
{
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "this rule is limited to examining HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_true_ip_params[] =
{
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "this rule is limited to examining HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
static const Parameter http_uri_params[] =
{
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "this rule is limited to examining HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,
{
{ "request", Parameter::PT_IMPLIED, nullptr, nullptr,
"match against the version from the request message even when examining the response" },
+ { "with_header", Parameter::PT_IMPLIED, nullptr, nullptr,
+ "this rule is limited to examining HTTP message headers" },
{ "with_body", Parameter::PT_IMPLIED, nullptr, nullptr,
"parts of this rule examine HTTP message body" },
{ "with_trailer", Parameter::PT_IMPLIED, nullptr, nullptr,