namespace NHttpEnums
{
-static const int MAXOCTETS = 63780;
-static const int DATABLOCKSIZE = 16384;
-static const int FINALBLOCKSIZE = 24576;
+static const int MAX_OCTETS = 63780;
+static const int DATA_BLOCK_SIZE = 16384;
+static const int FINAL_BLOCK_SIZE = 24576;
static const uint32_t NHTTP_GID = 219;
// Field status codes for when no valid value is present in length or integer value. Positive
INF_URI_SLASH_DOT_DOT,
INF_URI_ROOT_TRAV,
INF_TOO_MUCH_LEADING_WS,
+ INF_WS_BETWEEN_MSGS,
INF_ENDLESS_HEADER,
INF_LF_WITHOUT_CR,
+ INF_NOT_HTTP,
};
// Formats for output from a header normalization function
EVENT_PDF_CASC_COMP,
EVENT_PDF_PARSE_FAILURE,
EVENT_LOSS_OF_SYNC,
+ EVENT_NOT_HTTP,
+ EVENT_WS_BETWEEN_MSGS,
EVENT_MAXVALUE
};
extern const int8_t as_hex[256];
+extern const bool token_char[256];
} // end namespace NHttpEnums
#endif
}
}
-THREAD_LOCAL uint8_t NHttpInspect::body_buffer[MAXOCTETS];
+THREAD_LOCAL uint8_t NHttpInspect::body_buffer[MAX_OCTETS];
THREAD_LOCAL NHttpMsgSection* NHttpInspect::latest_section = nullptr;
class NHttpInspect : public Inspector
{
public:
- static THREAD_LOCAL uint8_t body_buffer[NHttpEnums::MAXOCTETS];
+ static THREAD_LOCAL uint8_t body_buffer[NHttpEnums::MAX_OCTETS];
NHttpInspect(bool test_input, bool test_output);
{
if ((buffer[k] == 32) || ((buffer[k] >= 9) && (buffer[k] <= 13)))
{
+ if ((buffer[k] != 10) && (buffer[k] != 13))
+ {
+ // tab, VT, FF, or space between messages
+ infractions += INF_WS_BETWEEN_MSGS;
+ events.create_event(EVENT_WS_BETWEEN_MSGS);
+ }
if (num_crlf < MAX_LEADING_WHITESPACE)
{
num_crlf++;
// If we get this far then the leading white space issue is behind us and num_crlf was
// reset to zero
+ if (!validated)
+ {
+ switch (validate(buffer[k]))
+ {
+ case V_GOOD:
+ validated = true;
+ break;
+ case V_BAD:
+ infractions += INF_NOT_HTTP;
+ events.create_event(EVENT_NOT_HTTP);
+ return SCAN_ABORT;
+ case V_TBD:
+ break;
+ }
+ }
if (buffer[k] == '\n')
{
num_crlf++;
return SCAN_NOTFOUND;
}
+NHttpStartSplitter::ValidationResult NHttpRequestSplitter::validate(uint8_t octet)
+{
+ static const int max_method_length = 80;
+
+ if ((octet == ' ') || (octet == '\t'))
+ return V_GOOD;
+ if (!token_char[octet] || ++octets_checked > max_method_length)
+ return V_BAD;
+ return V_TBD;
+}
+
+NHttpStartSplitter::ValidationResult NHttpStatusSplitter::validate(uint8_t octet)
+{
+ static const int match_size = 5;
+ static const uint8_t match[match_size] = { 'H', 'T', 'T', 'P', '/' };
+
+ if (octet != match[octets_checked++])
+ return V_BAD;
+ if (octets_checked >= match_size)
+ return V_GOOD;
+ return V_TBD;
+}
+
ScanResult NHttpHeaderSplitter::split(const uint8_t* buffer, uint32_t length,
NHttpInfractions& infractions, NHttpEventGen& events)
{
// The normal body section size is about 16K. But if there are only 24K or less remaining we
// take the whole thing rather than leave a small final section.
- if (remaining <= FINALBLOCKSIZE)
+ if (remaining <= FINAL_BLOCK_SIZE)
{
num_flush = remaining;
remaining = 0;
else
{
// FIXIT-M need to implement random increments
- num_flush = DATABLOCKSIZE;
+ num_flush = DATA_BLOCK_SIZE;
remaining -= num_flush;
return SCAN_FOUND_PIECE;
}
case CHUNK_DATA:
{
uint32_t skip_amount = (length-k <= expected) ? length-k : expected;
- skip_amount = (skip_amount <= DATABLOCKSIZE-data_seen) ? skip_amount :
- DATABLOCKSIZE-data_seen;
+ skip_amount = (skip_amount <= DATA_BLOCK_SIZE-data_seen) ? skip_amount :
+ DATA_BLOCK_SIZE-data_seen;
k += skip_amount - 1;
if ((expected -= skip_amount) == 0)
{
curr_state = CHUNK_DCRLF1;
}
- if ((data_seen += skip_amount) == DATABLOCKSIZE)
+ if ((data_seen += skip_amount) == DATA_BLOCK_SIZE)
{
// FIXIT-M need to randomize slice point
data_seen = 0;
uint32_t get_octets_seen() const { return octets_seen; }
virtual uint32_t get_num_excess() const { return 0; }
virtual uint32_t get_num_head_lines() const { return 0; }
+ virtual bool valid() const { return true; }
protected:
// number of octets processed by previous split() calls that returned NOTFOUND
NHttpEnums::ScanResult split(const uint8_t* buffer, uint32_t length,
NHttpInfractions& infractions, NHttpEventGen& events) override;
uint32_t get_num_excess() const override { return (num_flush > 0) ? num_crlf : 0; }
+ bool valid() const override { return validated; }
+
+protected:
+ enum ValidationResult { V_GOOD, V_BAD, V_TBD };
private:
static const int MAX_LEADING_WHITESPACE = 20;
+ virtual ValidationResult validate(uint8_t octet) = 0;
+ bool validated = false;
+};
+
+class NHttpRequestSplitter : public NHttpStartSplitter
+{
+private:
+ uint32_t octets_checked = 0;
+ ValidationResult validate(uint8_t octet) override;
+};
+
+class NHttpStatusSplitter : public NHttpStartSplitter
+{
+private:
+ uint32_t octets_checked = 0;
+ ValidationResult validate(uint8_t octet) override;
};
class NHttpHeaderSplitter : public NHttpSplitter
{
switch (type)
{
- case SEC_REQUEST:
- case SEC_STATUS: return (NHttpSplitter*)new NHttpStartSplitter;
+ case SEC_REQUEST: return (NHttpSplitter*)new NHttpRequestSplitter;
+ case SEC_STATUS: return (NHttpSplitter*)new NHttpStatusSplitter;
case SEC_HEADER:
case SEC_TRAILER: return (NHttpSplitter*)new NHttpHeaderSplitter;
case SEC_BODY: return (NHttpSplitter*)new NHttpBodySplitter(
StreamSplitter::Status NHttpStreamSplitter::scan(Flow* flow, const uint8_t* data, uint32_t length,
uint32_t, uint32_t* flush_offset)
{
- assert(length <= MAXOCTETS);
+ assert(length <= MAX_OCTETS);
/* FIXIT-L Temporary printf while we shake out stream interface */
if (!NHttpTestManager::use_test_input() && NHttpTestManager::use_test_output())
}
else if (NHttpTestManager::use_test_output())
{
- printf("Scan from flow data %p direction %d\n", (void*)session_data, source_id);
+ printf("Scan from flow data %p direction %d length %u\n", (void*)session_data, source_id,
+ length);
fflush(stdout);
}
splitter = get_splitter(type, session_data);
assert(splitter != nullptr);
}
- const uint32_t max_length = MAXOCTETS - splitter->get_octets_seen();
+ const uint32_t max_length = MAX_OCTETS - splitter->get_octets_seen();
const ScanResult split_result = splitter->split(data, (length <= max_length) ? length :
max_length, session_data->infractions[source_id], session_data->events[source_id]);
switch (split_result)
{
case SCAN_NOTFOUND:
- if (splitter->get_octets_seen() == MAXOCTETS)
+ if (splitter->get_octets_seen() == MAX_OCTETS)
{
session_data->infractions[source_id] += INF_ENDLESS_HEADER;
session_data->events[source_id].create_event(EVENT_LOSS_OF_SYNC);
copied = len;
// FIXIT-M temporary workaround for high total values
- if (total > MAXOCTETS)
+ if (total > MAX_OCTETS)
return nullptr;
assert(total >= offset + len);
- assert(total <= MAXOCTETS);
+ assert(total <= MAX_OCTETS);
/* FIXIT-L Temporary printf while we shake out stream interface */
if (!NHttpTestManager::use_test_input() && NHttpTestManager::use_test_output())
{
nhttp_buf.data = buffer;
nhttp_buf.length = section_length;
- assert((nhttp_buf.length <= MAXOCTETS) && (nhttp_buf.length != 0));
+ assert((nhttp_buf.length <= MAX_OCTETS) && (nhttp_buf.length != 0));
buffer = nullptr;
if (NHttpTestManager::use_test_output())
{
(session_data->splitter[source_id]->get_octets_seen() > 0) &&
(session_data->type_expected[source_id] != SEC_ABORT))
{
+ if (!session_data->splitter[source_id]->valid())
+ {
+ return false;
+ }
session_data->section_type[source_id] = session_data->type_expected[source_id];
session_data->num_excess[source_id] = 0;
session_data->num_head_lines[source_id] =
uint8_t* data, unsigned len, uint32_t flags, unsigned& copied) override;
bool finish(Flow* flow) override;
bool is_paf() override { return true; }
- unsigned max(Flow*) override { return NHttpEnums::MAXOCTETS; }
+ unsigned max(Flow*) override { return NHttpEnums::MAX_OCTETS; }
private:
void prepare_flush(NHttpFlowData* session_data, uint32_t* flush_offset, NHttpEnums::SectionType
{ EVENT_PDF_CASC_COMP, "PDF file cascaded compression" },
{ EVENT_PDF_PARSE_FAILURE, "PDF file parse failure" },
{ EVENT_LOSS_OF_SYNC, "HTTP misformatted or not really HTTP" },
+ { EVENT_NOT_HTTP, "Input apparently not HTTP" },
+ { EVENT_WS_BETWEEN_MSGS, "White space before or between messages" },
{ 0, nullptr }
};
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
+
+const bool NHttpEnums::token_char[256] =
+{
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+
+ false, true, false, true, true, true, true, true, false, false, true, true, false, true, true, false,
+ true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false,
+
+ false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true, true, true, true, false, false, false, true, true,
+
+ true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true, true, true, true, false, true, false, true, false,
+
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false
+};
+
private:
FILE* test_data_file;
- uint8_t msg_buf[2 * NHttpEnums::MAXOCTETS];
+ uint8_t msg_buf[2 * NHttpEnums::MAX_OCTETS];
// data has been flushed and must be sent by reassemble() before more data may be given to
// scan()
rtyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ
\r\n\r\n
+@1011
+@break
+@response
+ HTTP/1.1 200 OK\r\n\r\n
+
+@1012
+@break
+@response
+ HTTP/1.1 200 OK\r\n\r\n
+
+@1013
+@break
+@response
+\tHTTP/1.1 200 OK\r\n\r\n
# ***********************************************************************************************
# Invalid response start lines
@response
HTTP/1.0 401 \x08illegal nontext character in reason phrase backspace\r\n\r\n
-
# ***********************************************************************************************
# Valid request start lines
@3001
@request
GET HtTpS://1.2.3.4.5.a:6/abcdef/ghijklmnop/qrstuvwxyz/?thequery?fieldcontinues?until#afragme#arrives#1234 HTTP/1.1\r\n\r\n
+@3021
+@break
+@request
+ MKREDIRECTREF / HTTP/2.0\r\n\r\n
+
+@3022
+@break
+@request
+\t\t\t\tBIND /1234567890?abcdef HTTP/1.1\r\n\r\n
+
# ***********************************************************************************************
# Invalid request start lines
@4001
@4013
@break
@request
-GET http://hostname.com/?# HTTP/1.1\r\n\r\n
+GET123456789012345678901234567890123456789012345678901234567890123456789012345678 http://hostname.com/?# HTTP/1.1\r\n\r\n
@4014
@break
@request
-GET http://hostname.com/?# HTTP/1.1\r\n\r\n
+GE;T http://hostname.com/?# HTTP/1.1\r\n\r\n
@4015
@break
@request
-GET http://hostname.com/?# HTTP/1.1\r\n\r\n
-
+GET\xAA http://hostname.com/?# HTTP/1.1\r\n\r\n
# ***********************************************************************************************
# Valid headers without body