From: Tom Peters Date: Wed, 4 Jun 2014 16:53:22 +0000 (-0400) Subject: Changes for PAF implementation. File deletes in a future commit. X-Git-Tag: 3.0.0-233~1501^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e693a1cd7683b96c1f5be62895b5ab085cf3d66a;p=thirdparty%2Fsnort3.git Changes for PAF implementation. File deletes in a future commit. --- diff --git a/src/service_inspectors/nhttp_inspect/CMakeLists.txt b/src/service_inspectors/nhttp_inspect/CMakeLists.txt index 3c62e9bca..17cbfae4b 100644 --- a/src/service_inspectors/nhttp_inspect/CMakeLists.txt +++ b/src/service_inspectors/nhttp_inspect/CMakeLists.txt @@ -2,23 +2,36 @@ set (FILE_LIST nhttp_inspect.cc nhttp_inspect.h - nhttp_msgheader.cc - nhttp_msgheader.h - nhttp_headnorm.cc - nhttp_headnorm.h - nhttp_strtocode.cc - nhttp_strtocode.h - nhttp_api.cc - nhttp_api.h + nhttp_msg_section.cc + nhttp_msg_section.h + nhttp_msg_head_shared.cc + nhttp_msg_head_shared.h + nhttp_msg_head.cc + nhttp_msg_head.h + nhttp_msg_body.cc + nhttp_msg_body.h + nhttp_msg_chunk_head.cc + nhttp_msg_chunk_head.h + nhttp_msg_chunk_body.cc + nhttp_msg_chunk_body.h + nhttp_msg_trailer.cc + nhttp_msg_trailer.h + nhttp_head_norm.cc + nhttp_head_norm.h + nhttp_str_to_code.cc + nhttp_str_to_code.h + nhttp_api.cc nhttp_api.h nhttp_tables.cc nhttp_module.cc nhttp_module.h - nhttp_testinput.cc - nhttp_testinput.h - nhttp_flowdata.cc - nhttp_flowdata.h - nhttp_scratchpad.h + nhttp_test_input.cc + nhttp_test_input.h + nhttp_flow_data.cc + nhttp_flow_data.h + nhttp_scratch_pad.h nhttp_enum.h + nhttp_stream_splitter.cc + nhttp_stream_splitter.h ) #if (STATIC_INSPECTORS) diff --git a/src/service_inspectors/nhttp_inspect/Makefile.am b/src/service_inspectors/nhttp_inspect/Makefile.am index db9ffca58..05f0765a6 100644 --- a/src/service_inspectors/nhttp_inspect/Makefile.am +++ b/src/service_inspectors/nhttp_inspect/Makefile.am @@ -2,15 +2,22 @@ AUTOMAKE_OPTIONS=foreign no-dependencies file_list = \ nhttp_inspect.cc nhttp_inspect.h \ -nhttp_msgheader.cc nhttp_msgheader.h \ -nhttp_headnorm.cc nhttp_headnorm.h \ -nhttp_strtocode.cc nhttp_strtocode.h \ +nhttp_msg_section.cc nhttp_msg_section.h \ +nhttp_msg_head_shared.cc nhttp_msg_head_shared.h \ +nhttp_msg_head.cc nhttp_msg_head.h \ +nhttp_msg_body.cc nhttp_msg_body.h \ +nhttp_msg_chunk_head.cc nhttp_msg_chunk_head.h \ +nhttp_msg_chunk_body.cc nhttp_msg_chunk_body.h \ +nhttp_msg_trailer.cc nhttp_msg_trailer.h \ +nhttp_head_norm.cc nhttp_head_norm.h \ +nhttp_str_to_code.cc nhttp_str_to_code.h \ nhttp_api.cc nhttp_api.h \ nhttp_tables.cc \ nhttp_module.cc nhttp_module.h \ -nhttp_testinput.cc nhttp_testinput.h \ -nhttp_flowdata.cc nhttp_flowdata.h \ -nhttp_scratchpad.h nhttp_enum.h +nhttp_test_input.cc nhttp_test_input.h \ +nhttp_flow_data.cc nhttp_flow_data.h \ +nhttp_stream_splitter.cc nhttp_stream_splitter.h \ +nhttp_scratch_pad.h nhttp_enum.h #if STATIC_INSPECTORS diff --git a/src/service_inspectors/nhttp_inspect/nhttp_api.cc b/src/service_inspectors/nhttp_inspect/nhttp_api.cc index 16873b125..d6f6f19fb 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_api.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_api.cc @@ -32,20 +32,13 @@ #include #include "snort.h" -#include "framework/parameter.h" -#include "framework/module.h" -#include "framework/inspector.h" -#include "flow/flow.h" +#include "target_based/sftarget_protocol_reference.h" #include "nhttp_enum.h" -#include "nhttp_flowdata.h" -#include "nhttp_scratchpad.h" #include "nhttp_module.h" -#include "nhttp_strtocode.h" -#include "nhttp_headnorm.h" -#include "nhttp_msgheader.h" -#include "nhttp_testinput.h" -#include "nhttp_api.h" #include "nhttp_inspect.h" +#include "nhttp_api.h" + +int16_t NHttpApi::appProtocolId; Module* NHttpApi::nhttp_mod_ctor() { return new NHttpModule; } @@ -55,53 +48,49 @@ const char* NHttpApi::nhttp_myName = "nhttp_inspect"; void NHttpApi::nhttp_init() { - printf("nhttp_init()\n"); NHttpFlowData::init(); + appProtocolId = AddProtocolReference("nhttp"); } void NHttpApi::nhttp_term() { - printf("nhttp_term()\n"); } Inspector* NHttpApi::nhttp_ctor(Module* mod) { const NHttpModule* nhttpMod = (NHttpModule*) mod; - printf("nhttp_ctor()\n"); return new NHttpInspect(nhttpMod->get_test_mode()); } void NHttpApi::nhttp_dtor(Inspector* p) { - printf("nhttp_dtor()\n"); delete p; } void NHttpApi::nhttp_pinit() { - printf("nhttp_pinit()\n"); NHttpInspect::msgHead = new NHttpMsgHeader; + NHttpInspect::msgBody = new NHttpMsgBody; + NHttpInspect::msgChunkHead = new NHttpMsgChunkHead; + NHttpInspect::msgChunkBody = new NHttpMsgChunkBody; + NHttpInspect::msgTrailer = new NHttpMsgTrailer; } void NHttpApi::nhttp_pterm() { - printf("nhttp_pterm()\n"); delete NHttpInspect::msgHead; } void NHttpApi::nhttp_sum() { - printf("nhttp_sum()\n"); } void NHttpApi::nhttp_stats() { - printf("nhttp_stats()\n"); } void NHttpApi::nhttp_reset() { - printf("nhttp_reset()\n"); } const InspectApi NHttpApi::nhttp_api = @@ -123,7 +112,7 @@ const InspectApi NHttpApi::nhttp_api = NHttpApi::nhttp_dtor, NHttpApi::nhttp_pinit, NHttpApi::nhttp_pterm, - nullptr, // ssn + nullptr, NHttpApi::nhttp_sum, NHttpApi::nhttp_stats, NHttpApi::nhttp_reset diff --git a/src/service_inspectors/nhttp_inspect/nhttp_api.h b/src/service_inspectors/nhttp_inspect/nhttp_api.h index 79f4ec0e9..4335e7902 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_api.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_api.h @@ -29,9 +29,14 @@ #ifndef NHTTP_API_H #define NHTTP_API_H +#include "framework/parameter.h" +#include "framework/module.h" +#include "framework/inspector.h" + class NHttpApi { public: static const InspectApi nhttp_api; + static int16_t appProtocolId; private: NHttpApi() = delete; static Module* nhttp_mod_ctor(); @@ -43,7 +48,6 @@ private: static void nhttp_dtor(Inspector* p); static void nhttp_pinit(); static void nhttp_pterm(); - static void nhttp_purge(); static void nhttp_sum(); static void nhttp_stats(); static void nhttp_reset(); diff --git a/src/service_inspectors/nhttp_inspect/nhttp_enum.h b/src/service_inspectors/nhttp_inspect/nhttp_enum.h index 46355c666..5ee313f3c 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_enum.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_enum.h @@ -29,19 +29,20 @@ #ifndef NHTTP_ENUM_H #define NHTTP_ENUM_H -#define GID_HTTP_CLIENT 119 -#define GID_HTTP_SERVER 120 +#define NHTTP_GID 119 namespace NHttpEnums { +static const uint32_t MAXOCTETS = 63780; + // Field status codes for when no valid value is present in length or integer value. Positive values are actual length or field value. typedef enum { STAT_NOTCONFIGURED=-5, STAT_NOTCOMPUTE=-4, STAT_INSUFMEMORY=-3, STAT_PROBLEMATIC=-2, STAT_NOTPRESENT=-1, STAT_EMPTYSTRING=0, STAT_OTHER=1 } StatusCode; // Message originator--client or server -typedef enum { SRC__NOTCOMPUTE=-4, SRC__PROBLEMATIC=-2, SRC_CLIENT=1, SRC_SERVER } SourceId; +typedef enum { SRC__NOTCOMPUTE=-4, SRC_CLIENT=0, SRC_SERVER=1 } SourceId; // Type of message section -typedef enum { SEC__PROBLEMATIC=-2, SEC_HEADER = 2, SEC_BODY, SEC_CHUNK } SectionType; +typedef enum { SEC__NOTCOMPUTE=-4, SEC__NOTPRESENT=-1, SEC_HEADER = 2, SEC_BODY, SEC_CHUNKHEAD, SEC_CHUNKBODY, SEC_TRAILER, SEC_DISCARD, SEC_CLOSED, SEC_ABORT } SectionType; // List of possible HTTP versions. Version 0.9 omitted because 0.9 predates creation of the HTTP/X.Y token. There would never be a message with "HTTP/0.9" typedef enum { VERS__NOTCOMPUTE=-4, VERS__PROBLEMATIC=-2, VERS__NOTPRESENT=-1, VERS__OTHER=1, VERS_1_0, VERS_1_1, VERS_2_0 } VersionId; @@ -63,11 +64,12 @@ typedef enum { HEAD__NOTCOMPUTE=-4, HEAD__INSUFMEMORY=-3, HEAD__PROBLEMATIC=-2, HEAD_CONTENT_LOCATION, HEAD_CONTENT_MD5, HEAD_CONTENT_RANGE, HEAD_CONTENT_TYPE, HEAD_EXPIRES, HEAD_LAST_MODIFIED, HEAD__MAXVALUE } HeaderId; // All the infractions we might find while parsing and analyzing a message -typedef enum { INF_TRUNCATED=0x1, /*INF_CANTFINDVERS=0x2,*/ /*INF_STARTTOOSHORT=0x4,*/ INF_BADREQLINE=0x8, INF_BADSTATLINE=0x10, INF_TOOMANYHEADERS=0x20, - INF_BADHEADER=0x40, INF_BADSTATCODE=0x80, INF_UNKNOWNVERSION=0x100, INF_BADVERSION=0x200, INF_NOSCRATCH=0x400, INF_BADHEADERREPS=0x800, INF_BADHEADERDATA=0x1000 } Infraction; +typedef enum { INF_TRUNCATED=0x1, INF_HEADTOOLONG=0x2, /*INF_STARTTOOSHORT=0x4,*/ INF_BADREQLINE=0x8, INF_BADSTATLINE=0x10, INF_TOOMANYHEADERS=0x20, + INF_BADHEADER=0x40, INF_BADSTATCODE=0x80, INF_UNKNOWNVERSION=0x100, INF_BADVERSION=0x200, INF_NOSCRATCH=0x400, INF_BADHEADERREPS=0x800, INF_BADHEADERDATA=0x1000, + INF_BROKENCHUNK=0x2000, INF_BADCHUNKSIZE=0x4000, INF_BADPHRASE= 0x8000 } Infraction; // Formats for output from a header normalization function -typedef enum { NORM_NULL, NORM_FIELD, NORM_INTEGER, NORM_ENUM, NORM_ENUMLIST } NormFormat; +typedef enum { NORM_NULL, NORM_FIELD, NORM_INT64, NORM_ENUM64, NORM_ENUM64LIST } NormFormat; // Transfer codings typedef enum { TRANSCODE__OTHER=1, TRANSCODE_CHUNKED, TRANSCODE_IDENTITY, TRANSCODE_GZIP, TRANSCODE_COMPRESS, TRANSCODE_DEFLATE } Transcoding; @@ -81,5 +83,55 @@ typedef struct { int32_t length = -1; } field; +typedef enum +{ + EVENT_ASCII = 1, + EVENT_DOUBLE_DECODE, + EVENT_U_ENCODE, + EVENT_BARE_BYTE, + EVENT_OBSOLETE_1, + EVENT_UTF_8, + EVENT_IIS_UNICODE, + EVENT_MULTI_SLASH, + EVENT_IIS_BACKSLASH, + EVENT_SELF_DIR_TRAV, + EVENT_DIR_TRAV, + EVENT_APACHE_WS, + EVENT_IIS_DELIMITER, + EVENT_NON_RFC_CHAR, + EVENT_OVERSIZE_DIR, + EVENT_LARGE_CHUNK, + EVENT_PROXY_USE, + EVENT_WEBROOT_DIR, + EVENT_LONG_HDR, + EVENT_MAX_HEADERS, + EVENT_MULTIPLE_CONTLEN, + EVENT_CHUNK_SIZE_MISMATCH, + EVENT_INVALID_TRUEIP, + EVENT_MULTIPLE_HOST_HDRS, + EVENT_LONG_HOSTNAME, + EVENT_EXCEEDS_SPACES, + EVENT_CONSECUTIVE_SMALL_CHUNKS, + EVENT_UNBOUNDED_POST, + EVENT_MULTIPLE_TRUEIP_IN_SESSION, + EVENT_BOTH_TRUEIP_XFF_HDRS, + EVENT_UNKNOWN_METHOD, + EVENT_SIMPLE_REQUEST, + EVENT_UNESCAPED_SPACE_URI, + EVENT_PIPELINE_MAX, + EVENT_ANOM_SERVER, + EVENT_INVALID_STATCODE, + EVENT_NO_CONTLEN, + EVENT_UTF_NORM_FAIL, + EVENT_UTF7, + EVENT_DECOMPR_FAILED, + EVENT_CONSECUTIVE_SMALL_CHUNKS_S, + EVENT_MSG_SIZE_EXCEPTION, + EVENT_JS_OBFUSCATION_EXCD, + EVENT_JS_EXCESS_WS, + EVENT_MIXED_ENCODINGS, + EVENT_MAXVALUE +} EventSid; + #endif diff --git a/src/service_inspectors/nhttp_inspect/nhttp_flow_data.cc b/src/service_inspectors/nhttp_inspect/nhttp_flow_data.cc new file mode 100644 index 000000000..e82fd6dbe --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_flow_data.cc @@ -0,0 +1,53 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief Flow Data object used to store session information with Streams +// + +#include +#include +#include +#include "snort.h" +#include "nhttp_enum.h" +#include "nhttp_flow_data.h" + +using namespace NHttpEnums; + +unsigned NHttpFlowData::nhttp_flow_id = 0; + +NHttpFlowData::NHttpFlowData() : FlowData(nhttp_flow_id) {} + +void NHttpFlowData::halfReset(SourceId sourceId) { + assert((sourceId == SRC_CLIENT) || (sourceId == SRC_SERVER)); + dataLength[sourceId] = STAT_NOTPRESENT; + octetsExpected[sourceId] = STAT_NOTPRESENT; + bodySections[sourceId] = STAT_NOTPRESENT; + bodyOctets[sourceId] = STAT_NOTPRESENT; + numChunks[sourceId] = STAT_NOTPRESENT; + chunkSections[sourceId] = STAT_NOTPRESENT; + chunkOctets[sourceId] = STAT_NOTPRESENT; +} + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_flow_data.h b/src/service_inspectors/nhttp_inspect/nhttp_flow_data.h new file mode 100644 index 000000000..50374c37e --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_flow_data.h @@ -0,0 +1,92 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief Converts protocol constant string to enum +// + +#ifndef NHTTP_FLOW_DATA_H +#define NHTTP_FLOW_DATA_H + +#include "stream/stream_api.h" + +class NHttpInspect; +class NHttpMsgSection; +class NHttpMsgHeader; +class NHttpMsgBody; +class NHttpMsgChunkHead; +class NHttpMsgChunkBody; +class NHttpMsgTrailer; +class NHttpTestInput; + +class NHttpFlowData : public FlowData +{ +public: + NHttpFlowData(); + static unsigned nhttp_flow_id; + static void init() { nhttp_flow_id = FlowData::get_flow_id(); }; + + friend class NHttpInspect; + friend class NHttpMsgSection; + friend class NHttpMsgHeader; + friend class NHttpMsgBody; + friend class NHttpMsgChunkHead; + friend class NHttpMsgChunkBody; + friend class NHttpMsgTrailer; + friend class NHttpTestInput; + friend class NHttpStreamSplitter; +private: + void halfReset(NHttpEnums::SourceId sourceId); + + // StreamSplitter => Inspector (facts about the most recent message section) + NHttpEnums::SourceId sourceId = NHttpEnums::SRC__NOTCOMPUTE; + NHttpEnums::SectionType sectionType = NHttpEnums::SEC__NOTCOMPUTE; + bool tcpClose = false; + uint64_t infractions = 0; + + // Inspector => StreamSplitter (facts about the message section that is coming next) + // 0 element refers to client request, 1 element refers to server response + NHttpEnums::SectionType typeExpected[2] = { NHttpEnums::SEC_HEADER, NHttpEnums::SEC_HEADER }; + int64_t octetsExpected[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // expected size of the upcoming body or chunk body section + + // Inspector's internal data about the current message + int64_t dataLength[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // length of the data from Content-Length field or chunk header. + int64_t bodySections[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of body sections seen so far including chunk headers + int64_t bodyOctets[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of user data octets seen so far (either regular body or chunks) + int64_t numChunks[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of chunks seen so far + int64_t chunkSections[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of sections seen so far in the current chunk + int64_t chunkOctets[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of user data octets seen so far in the current chunk including terminating CRLF +}; + +#endif + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_head_norm.cc b/src/service_inspectors/nhttp_inspect/nhttp_head_norm.cc new file mode 100644 index 000000000..57d5236f1 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_head_norm.cc @@ -0,0 +1,203 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief HeaderNormalizer class +// + + +#include +#include +#include + +#include "snort.h" +#include "snort_types.h" + +#include "nhttp_enum.h" +#include "nhttp_str_to_code.h" +#include "nhttp_head_norm.h" + +using namespace NHttpEnums; + +// This derivation removes embedded CRLFs (wrapping), omits leading and trailing linear white space, and replaces internal strings of and with a single +int32_t HeaderNormalizer::deriveHeaderContent(const uint8_t *value, int32_t length, uint8_t *buffer) { + int32_t outLength = 0; + bool lastWhite = true; + for (int32_t k=0; k < length; k++) { + if ((value[k] == '\r') && (k+1 < length) && (value[k+1] == '\n')) k++; + else if ((value[k] != ' ') && (value[k] != '\t')) { + lastWhite = false; + buffer[outLength++] = value[k]; + } + else if (!lastWhite) { + lastWhite = true; + buffer[outLength++] = ' '; + } + } + if ((outLength > 0) && (buffer[outLength - 1] == ' ')) outLength--; + return outLength; +} + +void HeaderNormalizer::normalize(ScratchPad &scratchPad, uint64_t &infractions, HeaderId headId, const HeaderId headerNameId[], const field headerValue[], int32_t numHeaders, + field &resultField) const { + // This method normalizes the header field value for headId. + if (format == NORM_NULL) { + resultField.length = STAT_NOTCONFIGURED; + return; + } + + // Search Header IDs from all the headers in this message. A critical issue is whether the header can be present more than once in a message. concatenateRepeats means the + // header can be present more than once. The standard normalization is to concatenate all the repeated field values into a comma-separated list. Otherwise there should not + // be more than one instance of this header. infractRepeats causes us to inspect for improper repeated headers. Regardless of whether we look for these extra values only + // the first value will be normalized. + + int numMatches = 0; + int32_t bufferLength = 0; + int firstMatch = -1; + for (int k=0; k < numHeaders; k++) { + if (headerNameId[k] == HEAD__NOTCOMPUTE) break; + if (headerNameId[k] == headId) { + numMatches++; + if (numMatches == 1) firstMatch = k; + if ((numMatches == 1) || concatenateRepeats) bufferLength += headerValue[k].length; + if (!concatenateRepeats && !infractRepeats) break; + } + } + if (numMatches == 0) { + resultField.length = STAT_NOTPRESENT; + return; + } + if (infractRepeats && (numMatches >= 2)) infractions |= INF_BADHEADERREPS; + + // The scratchPad provides the space to store the normalized value. We are allocating twice as much memory as we need to store the normalized field value. The raw field + // value will be copied into one half of the buffer. Concatenation and white space normalization happen during this step. Next a series of normalization functions will + // transform the value into final form. Each normalization copies the value from one half of the buffer to the other. Based on whether the number of normalization functions + // is odd or even, the initial placement in the buffer is chosen so that the final normalization leaves the field value at the front of the buffer. The buffer space actually + // used is locked down in the scratchPad. The remainder of the first half and all of the second half are returned to the scratchPad for future use. + if (concatenateRepeats) bufferLength += numMatches - 1; // allow space for concatenation commas + // Round up to multiple of eight so that both halves are 64-bit aligned. + // 200 is a "way too big" fudge factor to allow for modest expansion of field size during normalization. Needs improvement. + bufferLength += (8-bufferLength%8)%8 + 200; + uint8_t * const scratch = scratchPad.request(2*bufferLength); + if (scratch == nullptr) { + resultField.length = STAT_INSUFMEMORY; + return; + } + + uint8_t * const frontHalf = scratch; + uint8_t * const backHalf = scratch + bufferLength; + uint8_t *working = (numNormalizers%2 == 0) ? frontHalf : backHalf; + int currMatch = firstMatch; + int32_t dataLength = 0; + for (int j=0; j < numMatches; j++) { + if (j >= 1) { + *working++ = ','; + dataLength++; + while (headerNameId[++currMatch] != headId); + } + int32_t growth = deriveHeaderContent(headerValue[currMatch].start, headerValue[currMatch].length, working); + working += growth; + dataLength += growth; + if (!concatenateRepeats) break; + } + + for (int i=0; i < numNormalizers; i++) { + if (i%2 != numNormalizers%2) dataLength = normalizer[i](backHalf, dataLength, frontHalf, infractions, normArg[i]); + else dataLength = normalizer[i](frontHalf, dataLength, backHalf, infractions, normArg[i]); + if (dataLength <= 0) { + resultField.length = dataLength; + return; + } + } + resultField.start = scratch; + resultField.length = dataLength; + scratchPad.commit(dataLength); +} + +// Collection of stock normalization functions. This will probably grow throughout the life of the software. New functions must follow the standard signature. +// The void* at the end is for any special configuration data the function requires. + +int32_t normDecimalInteger(const uint8_t* inBuf, int32_t inLength, uint8_t* outBuf, uint64_t& infractions, const void *) { + // Limited to 18 decimal digits, not including leading zeros, to fit comfortably into int64_t + int64_t total = 0; + int nonLeadingZeros = 0; + for (int32_t k=0; k < inLength; k++) { + int value = inBuf[k] - '0'; + if (nonLeadingZeros || (value != 0)) nonLeadingZeros++; + if (nonLeadingZeros > 18) { + infractions |= INF_BADHEADERDATA; + return STAT_PROBLEMATIC; + } + if ((value < 0) || (value > 9)) { + infractions |= INF_BADHEADERDATA; + return STAT_PROBLEMATIC; + } + total = total*10 + value; + } + ((int64_t*)outBuf)[0] = total; + return sizeof(int64_t); +} + + +int32_t norm2Lower(const uint8_t* inBuf, int32_t inLength, uint8_t *outBuf, uint64_t&, const void *) { + for (int32_t k=0; k < inLength; k++) { + outBuf[k] = ((inBuf[k] < 'A') || (inBuf[k] > 'Z')) ? inBuf[k] : inBuf[k] - ('A' - 'a'); + } + return inLength; +} + + +int32_t normStrCode(const uint8_t* inBuf, int32_t inLength, uint8_t *outBuf, uint64_t&, const void *table) { + ((int64_t*)outBuf)[0] = strToCode(inBuf, inLength, (const StrCode*)table); + return sizeof(int64_t); +} + +int32_t normSeqStrCode(const uint8_t* inBuf, int32_t inLength, uint8_t *outBuf, uint64_t&, const void *table) { + int32_t numCodes = 0; + const uint8_t* start = inBuf; + while (true) { + int32_t length; + for (length = 0; (start + length < inBuf + inLength) && (start[length] != ','); length++); + if (length == 0) ((uint32_t*)outBuf)[numCodes++] = STAT_EMPTYSTRING; + else ((int64_t*)outBuf)[numCodes++] = strToCode(start, length, (const StrCode*)table); + if (start + length >= inBuf + inLength) break; + start += length + 1; + } + return numCodes * sizeof(int64_t); +} + +// Remove all space and tab characters (known as LWS or linear white space in the RFC) +int32_t normRemoveLws(const uint8_t* inBuf, int32_t inLength, uint8_t *outBuf, uint64_t&, const void *) { + int32_t length = 0; + for (int32_t k = 0; k < inLength; k++) { + if ((inBuf[k] != ' ') && (inBuf[k] != '\t')) outBuf[length++] = inBuf[k]; + } + return length; +} + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_head_norm.h b/src/service_inspectors/nhttp_inspect/nhttp_head_norm.h new file mode 100644 index 000000000..126fe88a3 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_head_norm.h @@ -0,0 +1,80 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief HeaderNormalizer class declaration +// + +#ifndef NHTTP_HEAD_NORM_H +#define NHTTP_HEAD_NORM_H + +#include "nhttp_scratch_pad.h" + +//------------------------------------------------------------------------- +// HeaderNormalizer class +// Strategies for normalizing HTTP header field values +//------------------------------------------------------------------------- + +// Three normalization functions per HeaderNormalizer seems likely to be enough. Nothing subtle will break if you choose to expand it to four or more. Just a whole bunch of +// signatures and initializers to update. +// When defining a HeaderNormalizer don't leave holes in the normalizer list. E.g. if you have two normalizers they must be first and second. If you do first and third +// instead it won't explode but the third one won't be used either. + +class HeaderNormalizer { +public: + constexpr HeaderNormalizer(NHttpEnums::NormFormat _format, bool _concatenateRepeats, bool _infractRepeats, int32_t (*f1)(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void*), + const void *f1Arg, int32_t (*f2)(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void*), const void *f2Arg, int32_t (*f3)(const uint8_t*, int32_t, uint8_t*, uint64_t&, + const void*), const void *f3Arg) : + format(_format), + concatenateRepeats(_concatenateRepeats), + infractRepeats(_infractRepeats), + normalizer { f1, f2, f3 }, + normArg { f1Arg, f2Arg, f3Arg }, + numNormalizers((f1 != nullptr) + (f1 != nullptr)*(f2 != nullptr) + (f1 != nullptr)*(f2 != nullptr)*(f3 != nullptr)) {}; + void normalize(ScratchPad &scratchPad, uint64_t &infractions, NHttpEnums::HeaderId headId, const NHttpEnums::HeaderId headerNameId[], const field headerName[], int32_t numHeaders, + field &resultField) const; + NHttpEnums::NormFormat getFormat() const {return format;}; + +private: + static int32_t deriveHeaderContent(const uint8_t *value, int32_t length, uint8_t *buffer); + + const NHttpEnums::NormFormat format; + const bool concatenateRepeats; + const bool infractRepeats; + int32_t (* const normalizer[3])(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void*); + const void * normArg[3]; + const int numNormalizers; +}; + +// Normalizer functions + +int32_t normDecimalInteger(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void* notUsed); +int32_t norm2Lower(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void* notUsed); +int32_t normStrCode(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void*); +int32_t normSeqStrCode(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void*); +int32_t normRemoveLws(const uint8_t*, int32_t, uint8_t*, uint64_t&, const void* notUsed); + +#endif + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_inspect.cc b/src/service_inspectors/nhttp_inspect/nhttp_inspect.cc index fb6e1966a..2eef1eea8 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_inspect.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_inspect.cc @@ -34,115 +34,126 @@ #include #include "snort.h" -#include "framework/inspector.h" -#include "flow/flow.h" +#include "stream/stream_api.h" #include "nhttp_enum.h" -#include "nhttp_scratchpad.h" -#include "nhttp_strtocode.h" -#include "nhttp_headnorm.h" -#include "nhttp_flowdata.h" -#include "nhttp_msgheader.h" -#include "nhttp_testinput.h" +#include "nhttp_stream_splitter.h" #include "nhttp_api.h" #include "nhttp_inspect.h" -const char* NHttpInspect::testInputFile = "nhttptestmsgs.txt"; -const char* NHttpInspect::testOutputPrefix = "nhttpresults/testcase"; +using namespace NHttpEnums; + THREAD_LOCAL NHttpMsgHeader* NHttpInspect::msgHead; +THREAD_LOCAL NHttpMsgBody* NHttpInspect::msgBody; +THREAD_LOCAL NHttpMsgChunkHead* NHttpInspect::msgChunkHead; +THREAD_LOCAL NHttpMsgChunkBody* NHttpInspect::msgChunkBody; +THREAD_LOCAL NHttpMsgTrailer* NHttpInspect::msgTrailer; -NHttpInspect::NHttpInspect(bool _test_mode) : test_mode(_test_mode) +NHttpInspect::NHttpInspect(bool test_mode) { - printf("NHttpInspect constructor()\n"); - if (test_mode) { - testInput = new NHttpTestInput(testInputFile); + NHttpTestInput::test_mode = test_mode; + if (NHttpTestInput::test_mode) { + NHttpTestInput::testInput = new NHttpTestInput(testInputFile); } } NHttpInspect::~NHttpInspect () { - printf("NHttpInspect destructor()\n"); - if (test_mode) { - delete testInput; + if (NHttpTestInput::test_mode) { + delete NHttpTestInput::testInput; if (testOut) fclose(testOut); } } bool NHttpInspect::enabled () { - printf("NHttpInspect enabled()\n"); return true; } -void NHttpInspect::configure (SnortConfig *sc, const char*, char *args) +bool NHttpInspect::configure (SnortConfig *) { - printf("NHttpInspect configure()\n"); + return true; } -int NHttpInspect::verify(SnortConfig* sc) +int NHttpInspect::verify(SnortConfig*) { - printf("NHttpInspect verify()\n"); return 0; // 0 = good, -1 = bad } void NHttpInspect::pinit() { - printf("NHttpInspect pinit()\n"); } void NHttpInspect::pterm() { - printf("NHttpInspect pterm()\n"); } void NHttpInspect::show(SnortConfig*) { - printf("NHttpInspect show()\n"); LogMessage("NHttpInspect\n"); } void NHttpInspect::eval (Packet* p) { - printf("NHttpInspect eval()\n"); + // Only packets from the StreamSplitter can be processed + if (!PacketHasPAFPayload(p)) return; Flow *flow = p->flow; NHttpFlowData* sessionData = (NHttpFlowData*)flow->get_application_data(NHttpFlowData::nhttp_flow_id); - if (sessionData == nullptr) flow->set_application_data(sessionData = new NHttpFlowData); - - if (!test_mode) msgHead->loadMessage(p->data, p->dsize, sessionData); + assert(sessionData); + + NHttpMsgSection *msgSect = nullptr; + + if (!NHttpTestInput::test_mode) { + switch (sessionData->sectionType) { + case SEC_HEADER: msgSect = msgHead; break; + case SEC_BODY: msgSect = msgBody; break; + case SEC_CHUNKHEAD: msgSect = msgChunkHead; break; + case SEC_CHUNKBODY: msgSect = msgChunkBody; break; + case SEC_TRAILER: msgSect = msgTrailer; break; + case SEC_DISCARD: return; + default: assert(0); return; + } + msgSect->loadSection(p->data, p->dsize, sessionData); + } else { uint8_t *testBuffer; - int32_t testLength; - if ((testLength = testInput->ntiGet(&testBuffer, sessionData, testNumber)) > 0) { - msgHead->loadMessage(testBuffer, testLength, sessionData); + uint16_t testLength; + if ((testLength = NHttpTestInput::testInput->toEval(&testBuffer, testNumber)) > 0) { + switch (sessionData->sectionType) { + case SEC_HEADER: msgSect = msgHead; break; + case SEC_BODY: msgSect = msgBody; break; + case SEC_CHUNKHEAD: msgSect = msgChunkHead; break; + case SEC_CHUNKBODY: msgSect = msgChunkBody; break; + case SEC_TRAILER: msgSect = msgTrailer; break; + case SEC_DISCARD: return; + default: assert(0); return; + } + msgSect->loadSection(testBuffer, testLength, sessionData); } else { - printf("Out of test data.\n"); + printf("Zero length test data.\n"); return; } } + msgSect->initSection(); + msgSect->analyze(); + msgSect->updateFlow(); + msgSect->genEvents(); + msgSect->legacyClients(); - msgHead->analyze(); - - msgHead->genEvents(); - - // Interface to the old Snort clients - msgHead->oldClients(); - - if (!test_mode) msgHead->printMessage(stdout); + if (!NHttpTestInput::test_mode) msgSect->printMessage(stdout); else { if (testNumber != fileTestNumber) { if (testOut) fclose (testOut); fileTestNumber = testNumber; char fileName[100]; - sprintf(fileName, "%s%d.txt", testOutputPrefix, testNumber); + snprintf(fileName, sizeof(fileName), "%s%" PRIi64 ".txt", testOutputPrefix, testNumber); if ((testOut = fopen(fileName, "w+")) == nullptr) throw std::runtime_error("Cannot open test output file"); } - msgHead->printMessage(testOut); + msgSect->printMessage(testOut); } } - - diff --git a/src/service_inspectors/nhttp_inspect/nhttp_inspect.h b/src/service_inspectors/nhttp_inspect/nhttp_inspect.h index 4a1d577e1..8c893d670 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_inspect.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_inspect.h @@ -27,28 +27,42 @@ // NHttpInspect class //------------------------------------------------------------------------- +#include "framework/inspector.h" +#include "nhttp_msg_head.h" +#include "nhttp_msg_body.h" +#include "nhttp_msg_chunk_head.h" +#include "nhttp_msg_chunk_body.h" +#include "nhttp_msg_trailer.h" +#include "nhttp_stream_splitter.h" +#include "nhttp_test_input.h" + +class NHttpApi; + class NHttpInspect : public Inspector { public: NHttpInspect(bool _test_mode); ~NHttpInspect(); - void configure(SnortConfig*, const char*, char *args); + bool configure(SnortConfig*); int verify(SnortConfig*); void show(SnortConfig*); void eval(Packet*); bool enabled(); void pinit(); void pterm(); + NHttpStreamSplitter* get_splitter(bool isClientToServer) { return new NHttpStreamSplitter(isClientToServer); }; private: friend NHttpApi; static THREAD_LOCAL NHttpMsgHeader *msgHead; + static THREAD_LOCAL NHttpMsgBody *msgBody; + static THREAD_LOCAL NHttpMsgChunkHead *msgChunkHead; + static THREAD_LOCAL NHttpMsgChunkBody *msgChunkBody; + static THREAD_LOCAL NHttpMsgTrailer *msgTrailer; // Test mode - bool test_mode; - static const char *testInputFile; - static const char *testOutputPrefix; - NHttpTestInput *testInput = nullptr; + const char *testInputFile = "nhttp_test_msgs.txt"; + const char *testOutputPrefix = "nhttpresults/testcase"; FILE *testOut = nullptr; int64_t testNumber = 0; int64_t fileTestNumber = -1; diff --git a/src/service_inspectors/nhttp_inspect/nhttp_module.cc b/src/service_inspectors/nhttp_inspect/nhttp_module.cc index 81e0e9a50..3ef609a6a 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_module.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_module.cc @@ -26,32 +26,31 @@ // @brief Module class for NHttpInspect // - #include #include #include #include "snort.h" -#include "framework/module.h" #include "nhttp_enum.h" #include "nhttp_module.h" +NHttpModule::NHttpModule() : Module("nhttp_inspect", nhttpParams, nhttpEvents) { +} + + const Parameter NHttpModule::nhttpParams[] = {{ "test_mode", Parameter::PT_BOOL, nullptr, "false", "read HTTP messages from text file" }, { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }}; bool NHttpModule::begin(const char*, int, SnortConfig*) { - printf("NHttpModule begin()\n"); test_mode = false; return true; } bool NHttpModule::end(const char*, int, SnortConfig*) { - printf("NHttpModule end()\n"); return true; } bool NHttpModule::set(const char*, Value &val, SnortConfig*) { - printf("NHttpModule set()\n"); if (val.is("test_mode")) { test_mode = val.get_bool(); return true; @@ -60,6 +59,6 @@ bool NHttpModule::set(const char*, Value &val, SnortConfig*) { } unsigned NHttpModule::get_gid() const { - return GID_HTTP_CLIENT; + return NHTTP_GID; } diff --git a/src/service_inspectors/nhttp_inspect/nhttp_module.h b/src/service_inspectors/nhttp_inspect/nhttp_module.h index 37c55b0ba..e2bead00c 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_module.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_module.h @@ -29,10 +29,12 @@ #ifndef NHTTP_MODULE_H #define NHTTP_MODULE_H +#include "framework/module.h" + class NHttpModule : public Module { public: - NHttpModule() : Module("nhttp_inspect", nhttpParams) { }; + NHttpModule(); bool begin(const char*, int, SnortConfig*); bool end(const char*, int, SnortConfig*); bool set(const char*, Value&, SnortConfig*); @@ -40,6 +42,7 @@ public: bool get_test_mode() const { return test_mode; }; private: static const Parameter nhttpParams[]; + static const RuleMap nhttpEvents[]; bool test_mode = false; }; diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_body.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_body.cc new file mode 100644 index 000000000..4e426cd19 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_body.cc @@ -0,0 +1,129 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgBody class analyzes individual HTTP message bodies. Message chunks are handled in NHttpMsgChunk. +// + + +#include +#include +#include +#include + +#include "snort.h" +#include "nhttp_enum.h" +#include "nhttp_msg_body.h" + +using namespace NHttpEnums; + +void NHttpMsgBody::loadSection(const uint8_t *buffer, const uint16_t bufSize, NHttpFlowData *sessionData_) { + NHttpMsgSection::loadSection(buffer, bufSize, sessionData_); + + dataLength = sessionData->dataLength[sourceId]; + bodySections = sessionData->bodySections[sourceId]; + bodyOctets = sessionData->bodyOctets[sourceId]; +} + +void NHttpMsgBody::initSection() { + data.length = STAT_NOTCOMPUTE; +} + +void NHttpMsgBody::analyze() { + bodySections++; + bodyOctets += length; + data.start = msgText; + data.length = length; + + // The following statement tests for the case where streams underfulfilled flush due to a TCP connection close + if ((length < 16384) && (bodyOctets < dataLength)) tcpClose = true; + if (tcpClose && (bodyOctets < dataLength)) infractions |= INF_TRUNCATED; +} + +void NHttpMsgBody::genEvents() { + if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event +} + +void NHttpMsgBody::printMessage(FILE *output) const { + NHttpMsgSection::printMessageTitle(output, "body"); + fprintf(output, "Expected data length %" PRIi64 ", sections seen %" PRIi64 ", octets seen %" PRIi64 "\n", dataLength, bodySections, bodyOctets); + printInterval(output, "Data", data.start, data.length); + NHttpMsgSection::printMessageWrapup(output); +} + +void NHttpMsgBody::updateFlow() const { + if (tcpClose) { + sessionData->typeExpected[sourceId] = SEC_CLOSED; + sessionData->halfReset(sourceId); + } + else if (bodyOctets < dataLength) { + // More body coming + sessionData->bodySections[sourceId] = bodySections; + sessionData->bodyOctets[sourceId] = bodyOctets; + } + else { + // End of message + sessionData->typeExpected[sourceId] = SEC_HEADER; + sessionData->halfReset(sourceId); + } +} + + +// Legacy support function. Puts message fields into the buffers used by old Snort. +void NHttpMsgBody::legacyClients() const { + ClearHttpBuffers(); + if (data.length > 0) SetHttpBuffer(HTTP_BUFFER_CLIENT_BODY, data.start, (unsigned)data.length); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_body.h b/src/service_inspectors/nhttp_inspect/nhttp_msg_body.h new file mode 100644 index 000000000..86c919ffa --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_body.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgBody class declaration +// + +#ifndef NHTTP_MSG_BODY_H +#define NHTTP_MSG_BODY_H + +#include "nhttp_msg_section.h" + +//------------------------------------------------------------------------- +// NHttpMsgBody class +//------------------------------------------------------------------------- + +class NHttpMsgBody : public NHttpMsgSection { +public: + NHttpMsgBody() {}; + void loadSection(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_); + void initSection(); + void analyze(); + void printMessage(FILE *output) const; + void genEvents(); + void updateFlow() const; + void legacyClients() const; + +protected: + int64_t dataLength; + int64_t bodySections; + int64_t bodyOctets; + + field data; +}; + +#endif + + + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_body.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_body.cc new file mode 100644 index 000000000..33dfdc727 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_body.cc @@ -0,0 +1,146 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgChunkBody class analyzes data portion (not start line) of an HTTP chunk. +// + + +#include +#include +#include +#include + +#include "snort.h" +#include "nhttp_enum.h" +#include "nhttp_msg_chunk_body.h" + +using namespace NHttpEnums; + +void NHttpMsgChunkBody::loadSection(const uint8_t *buffer, const uint16_t bufSize, NHttpFlowData *sessionData_) { + NHttpMsgBody::loadSection(buffer, bufSize, sessionData_); + + numChunks = sessionData->numChunks[sourceId]; + chunkSections = sessionData->chunkSections[sourceId]; + chunkOctets = sessionData->chunkOctets[sourceId]; +} + +void NHttpMsgChunkBody::initSection() { + NHttpMsgBody::initSection(); +} + +void NHttpMsgChunkBody::analyze() { + bodySections++; + chunkOctets += length; + bodyOctets += length; + int termCrlfBytes = 0; + if (chunkOctets > dataLength) { + // Final are not data and do not belong in octet total or data field + termCrlfBytes = chunkOctets - dataLength; + assert(termCrlfBytes <= 2); + bodyOctets -= termCrlfBytes; + // Check for correct CRLF termination. Beware the section might break just before chunk end. + if ( ! ( ((termCrlfBytes == 2) && (length >= 2) && (msgText[length-2] == '\r') && (msgText[length-1] == '\n')) || + ((termCrlfBytes == 2) && (length == 1) && (msgText[length-1] == '\n')) || + ((termCrlfBytes == 1) && (msgText[length-1] == '\r')) ) ) { + infractions |= INF_BROKENCHUNK; + } + } + + data.start = msgText; + data.length = length - termCrlfBytes; + + chunkSections++; + // The following statement tests for the case where streams underfulfilled flush due to a TCP connection close + if ((length < 16384) && (bodyOctets + termCrlfBytes < dataLength + 2)) tcpClose = true; + if (tcpClose) infractions |= INF_TRUNCATED; +} + +void NHttpMsgChunkBody::genEvents() { + if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event +} + +void NHttpMsgChunkBody::printMessage(FILE *output) const { + NHttpMsgSection::printMessageTitle(output, "chunk body"); + fprintf(output, "Expected chunk length %" PRIi64 ", cumulative sections %" PRIi64 ", cumulative octets %" PRIi64 "\n", dataLength, bodySections, bodyOctets); + fprintf(output, "cumulative chunk sections %" PRIi64 ", cumulative chunk octets %" PRIi64 "\n", chunkSections, chunkOctets); + printInterval(output, "Data", data.start, data.length); + NHttpMsgSection::printMessageWrapup(output); +} + +void NHttpMsgChunkBody::updateFlow() const { + if (tcpClose) { + sessionData->typeExpected[sourceId] = SEC_CLOSED; + sessionData->halfReset(sourceId); + } + else if (chunkOctets < dataLength + 2) { + sessionData->bodySections[sourceId] = bodySections; + sessionData->bodyOctets[sourceId] = bodyOctets; + sessionData->chunkSections[sourceId] = chunkSections; + sessionData->chunkOctets[sourceId] = chunkOctets; + } + else { + sessionData->typeExpected[sourceId] = SEC_CHUNKHEAD; + sessionData->octetsExpected[sourceId] = STAT_NOTPRESENT; + sessionData->dataLength[sourceId] = STAT_NOTPRESENT; + sessionData->bodySections[sourceId] = bodySections; + sessionData->bodyOctets[sourceId] = bodyOctets; + sessionData->chunkSections[sourceId] = STAT_NOTPRESENT; + sessionData->chunkOctets[sourceId] = STAT_NOTPRESENT; + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_body.h b/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_body.h new file mode 100644 index 000000000..6da7b02a9 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_body.h @@ -0,0 +1,71 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgChunkBody class declaration +// + +#ifndef NHTTP_MSG_CHUNK_BODY_H +#define NHTTP_MSG_CHUNK_BODY_H + +#include "nhttp_msg_body.h" + +//------------------------------------------------------------------------- +// NHttpMsgChunkBody class +//------------------------------------------------------------------------- + +class NHttpMsgChunkBody : public NHttpMsgBody { +public: + NHttpMsgChunkBody() {}; + void loadSection(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_); + void initSection(); + void analyze(); + void printMessage(FILE *output) const; + void genEvents(); + void updateFlow() const; + +private: + int64_t numChunks; + int64_t chunkSections; + int64_t chunkOctets; +}; + +#endif + + + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_head.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_head.cc new file mode 100644 index 000000000..edb87d33f --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_head.cc @@ -0,0 +1,157 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgChunkHead class analyzes header line for a chunk. +// + + +#include +#include +#include +#include + +#include "snort.h" +#include "nhttp_enum.h" +#include "nhttp_msg_chunk_head.h" + +using namespace NHttpEnums; + +// Convert the hexadecimal chunk length. +// RFC says that zero may be written with multiple digits "000000". +// Arbitrary limit of 15 hex digits not including leading zeros ensures in a simple way against 64-bit overflow and should be +// vastly bigger than any legitimate chunk. +void NHttpMsgChunkHead::deriveChunkLength() { + if (chunkSize.length <= 0) { + dataLength = STAT_PROBLEMATIC; + infractions |= INF_BADCHUNKSIZE; + return; + } + dataLength = 0; + int nonLeadingZeros = 0; + for (int k=0; k < chunkSize.length; k++) { + if (nonLeadingZeros || (chunkSize.start[k] != '0')) nonLeadingZeros++; + if (nonLeadingZeros > 15) { + dataLength = STAT_PROBLEMATIC; + infractions |= INF_BADCHUNKSIZE; + return; + } + + dataLength *= 16; + if ((chunkSize.start[k] >= '0') && (chunkSize.start[k] <= '9')) dataLength += chunkSize.start[k] - '0'; + else if ((chunkSize.start[k] >= 'A') && (chunkSize.start[k] <= 'F')) dataLength += chunkSize.start[k] - 'A' + 10; + else if ((chunkSize.start[k] >= 'a') && (chunkSize.start[k] <= 'f')) dataLength += chunkSize.start[k] - 'a' + 10; + else { + dataLength = STAT_PROBLEMATIC; + infractions |= INF_BADCHUNKSIZE; + return; + } + } +} + +void NHttpMsgChunkHead::loadSection(const uint8_t *buffer, const uint16_t bufSize, NHttpFlowData *sessionData_) { + NHttpMsgSection::loadSection(buffer, bufSize, sessionData_); + + bodySections = sessionData->bodySections[sourceId]; + numChunks = sessionData->numChunks[sourceId]; +} + +void NHttpMsgChunkHead::initSection() { + startLine.length = STAT_NOTCOMPUTE; + chunkSize.length = STAT_NOTCOMPUTE; + chunkExtensions.length = STAT_NOTCOMPUTE; +} + +void NHttpMsgChunkHead::analyze() { + bodySections++; + // First section in a new chunk is just the start line. + numChunks++; + startLine.start = msgText; + if (!tcpClose) startLine.length = length - 2; + else startLine.length = findCrlf(startLine.start, length, false); + chunkSize.start = msgText; + // Start line format is chunk size in hex followed by optional semicolon and extensions field + for (chunkSize.length = 0; (chunkSize.length < startLine.length) && (startLine.start[chunkSize.length] != ';'); chunkSize.length++); + if (chunkSize.length == startLine.length) { + chunkExtensions.length = STAT_NOTPRESENT; + } + else if (chunkSize.length == startLine.length - 1) { + chunkExtensions.length = STAT_EMPTYSTRING; + } + else { + chunkExtensions.start = msgText + chunkSize.length + 1; + chunkExtensions.length = startLine.length - chunkSize.length - 1; + } + deriveChunkLength(); + if (tcpClose) infractions |= INF_TRUNCATED; +} + +void NHttpMsgChunkHead::genEvents() { + if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event +} + +void NHttpMsgChunkHead::printMessage(FILE *output) const { + NHttpMsgSection::printMessageTitle(output, "chunk header"); + fprintf(output, "Chunk size: %" PRIi64 "\n", dataLength); + printInterval(output, "Chunk extensions", chunkExtensions.start, chunkExtensions.length); + NHttpMsgSection::printMessageWrapup(output); +} + +void NHttpMsgChunkHead::updateFlow() const { + if (tcpClose) { + sessionData->typeExpected[sourceId] = SEC_CLOSED; + sessionData->halfReset(sourceId); + } + else if (dataLength > 0) { + sessionData->typeExpected[sourceId] = SEC_CHUNKBODY; + sessionData->octetsExpected[sourceId] = dataLength+2; + sessionData->bodySections[sourceId] = bodySections; + sessionData->numChunks[sourceId] = numChunks; + sessionData->dataLength[sourceId] = dataLength; + sessionData->chunkSections[sourceId] = 0; + sessionData->chunkOctets[sourceId] = 0; + } + else { + // This was zero-length last chunk, trailer comes next + sessionData->typeExpected[sourceId] = SEC_TRAILER; + sessionData->halfReset(sourceId); + } +} + + +// Legacy support function. Puts message fields into the buffers used by old Snort. +void NHttpMsgChunkHead::legacyClients() const { + ClearHttpBuffers(); + if (startLine.length > 0) SetHttpBuffer(HTTP_BUFFER_CLIENT_BODY, startLine.start, (unsigned)startLine.length); +} + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_head.h b/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_head.h new file mode 100644 index 000000000..a63dc48c9 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_chunk_head.h @@ -0,0 +1,78 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgChunkHead class declaration +// + +#ifndef NHTTP_MSG_CHUNK_HEAD_H +#define NHTTP_MSG_CHUNK_HEAD_H + +#include "nhttp_msg_section.h" + +//------------------------------------------------------------------------- +// NHttpMsgChunkHead class +//------------------------------------------------------------------------- + +class NHttpMsgChunkHead : public NHttpMsgSection { +public: + NHttpMsgChunkHead() {}; + void loadSection(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_); + void initSection(); + void analyze(); + void printMessage(FILE *output) const; + void genEvents(); + void updateFlow() const; + void legacyClients() const; + +private: + void deriveChunkLength(); + + field startLine; + field chunkSize; + field chunkExtensions; + + int64_t dataLength; + int64_t bodySections; + int64_t numChunks; +}; + +#endif + + + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_head.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_head.cc new file mode 100644 index 000000000..df7b13d5c --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_head.cc @@ -0,0 +1,319 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgHeader class analyzes individual HTTP message headers. +// + + +#include +#include +#include +#include + +#include "snort.h" +#include "nhttp_enum.h" +#include "nhttp_test_input.h" // &&& temporary to support TCP close workaround +#include "nhttp_msg_head.h" + +using namespace NHttpEnums; + +// Reinitialize everything derived in preparation for analyzing a new message +void NHttpMsgHeader::initSection() { + NHttpMsgSharedHead::initSection(); + startLine.length = STAT_NOTCOMPUTE; + version.length = STAT_NOTCOMPUTE; + versionId = VERS__NOTCOMPUTE; + method.length = STAT_NOTCOMPUTE; + methodId = METH__NOTCOMPUTE; + uri.length = STAT_NOTCOMPUTE; + statusCode.length = STAT_NOTCOMPUTE; + statusCodeNum = STAT_NOTCOMPUTE; + reasonPhrase.length = STAT_NOTCOMPUTE; +} + +// All the header processing that is done for every message (i.e. not just-in-time) is done here. +void NHttpMsgHeader::analyze() { + NHttpMsgSharedHead::analyze(); + if (sourceId == SRC_CLIENT) { + parseRequestLine(); + deriveMethodId(); + } + else if (sourceId == SRC_SERVER) { + parseStatusLine(); + deriveStatusCodeNum(); + } + deriveVersionId(); +} + +// All we do here is separate the start line from the header fields. +// It is so complicated because 1) there might not be any header fields and 2) the message may have been truncated by a TCP connection close. +// The asserts are very useful in test mode because they pick up bad test case data that we are not designed to handle. Otherwise they should +// never go off unless PAF is broken and feeding us bad stuff. +void NHttpMsgHeader::parseWhole() { + startLine.start = msgText; + startLine.length = findCrlf(startLine.start, length, false); + // findCrtl() guarentees that either the start line is the whole message or there must be at least two more characters and the first two are . + assert((length == startLine.length) || ((length >= startLine.length+2) && !memcmp(msgText + startLine.length, "\r\n", 2))); + +// &&& The else clause is a workaround for the lack of TCP close notification support in the framework. +// &&& Eventually the contents of the if clause should become the only code and the if-statement should go away. +if (NHttpTestInput::test_mode) { + // We trust PAF. !tcpClose guarentees that either there are exactly four more characters or there are at least seven more characters + // with the first two being and the last four being . + assert(tcpClose || + ((length == startLine.length+4) && !memcmp(msgText + startLine.length, "\r\n\r\n", 4)) || + ((length >= startLine.length+7) && !memcmp(msgText + startLine.length, "\r\n", 2) && !memcmp(msgText + length - 4, "\r\n\r\n", 4))); +} +else { + // We trust PAF. Therefore if the invariants don't hold it must be because we got the leftovers after a TCP close. + // So we kludge the close notification as a workaround. + if ( ! ( ((length == startLine.length+4) && !memcmp(msgText + startLine.length, "\r\n\r\n", 4)) || + ((length >= startLine.length+7) && !memcmp(msgText + startLine.length, "\r\n", 2) && !memcmp(msgText + length - 4, "\r\n\r\n", 4)) ) ) tcpClose = true; +} + + // The following if-else ladder puts the extremely common normal cases at the beginning and the rare pathological cases at the end + // Normal case with header fields + if (!tcpClose && (length >= startLine.length+7)) { + headers.start = msgText + startLine.length + 2; + headers.length = length - startLine.length - 6; + } + // Normal case no header fields (only a start line) + else if (!tcpClose) { + headers.length = STAT_NOTPRESENT; + } + // Normal case with header fields and TCP connection close + else if ((length >= startLine.length+7) && !memcmp(msgText+length-4, "\r\n\r\n", 4)) { + headers.start = msgText + startLine.length + 2; + headers.length = length - startLine.length - 6; + } + // Normal case no header fields and TCP connection close + else if ((length == startLine.length + 4) && !memcmp(msgText+length-2, "\r\n", 2)) { + headers.length = STAT_NOTPRESENT; + } + // Abnormal cases truncated by TCP connection close + else { + infractions |= INF_TRUNCATED; + // Either start line incomplete or start line complete but no leftover octets for anything else + if (length <= startLine.length+2) { + headers.length = STAT_NOTPRESENT; + } + // Start line complete followed by lone + else if ((length == startLine.length+3) && (msgText[length-1] == '\r')) { + headers.length = STAT_NOTPRESENT; + } + // Truncation occurred somewhere in the header fields + else { + headers.start = msgText + startLine.length + 2; + headers.length = length - startLine.length - 2; + // When present, remove partial sequence from the very end + if ((length > startLine.length+6) && !memcmp(msgText+length-3, "\r\n\r", 3)) headers.length -= 3; + else if ((length > startLine.length+5) && !memcmp(msgText+length-2, "\r\n", 2)) headers.length -= 2; + else if ((length > startLine.length+4) && (msgText[length-1] == '\r')) headers.length -= 1; + } + } +} + +void NHttpMsgHeader::parseRequestLine() { + // There should be exactly two spaces. One following the method and one before "HTTP/". + // Eventually we may need to cater to certain format errors, but for now exact match or treat as error. + // HTTP/X.Y + if (startLine.start[startLine.length-9] != ' ') { + // space before "HTTP" missing or in wrong place + infractions |= INF_BADREQLINE; + return; + } + + int space = -1; + for (int32_t k=0; k < startLine.length-9; k++) { + if (startLine.start[k] == ' ') { + if (space == -1) space = k; + else { + // too many spaces + infractions |= INF_BADREQLINE; + return; + } + } + } + if ((space <= 0)) { + // no first space or a leading space + infractions |= INF_BADREQLINE; + return; + } + + method.start = startLine.start; + method.length = space; + uri.start = startLine.start + method.length + 1; + uri.length = startLine.length - method.length - 10; + version.start = startLine.start + (startLine.length - 8); + version.length = 8; + assert (startLine.length == method.length + uri.length + version.length + 2); +} + +void NHttpMsgHeader::parseStatusLine() { + // Eventually we may need to cater to certain format errors, but for now exact match or treat as error. + // HTTP/X.Y### + if ((startLine.length < 13) || (startLine.start[8] != ' ') || (startLine.start[12] != ' ')) { + infractions |= INF_BADSTATLINE; + return; + } + version.start = startLine.start; + version.length = 8; + statusCode.start = startLine.start + 9; + statusCode.length = 3; + reasonPhrase.start = startLine.start + 13; + reasonPhrase.length = startLine.length - 13; + for (int32_t k = 0; k < reasonPhrase.length; k++) { + if ((reasonPhrase.start[k] <= 31) || (reasonPhrase.start[k] >= 127)) { + // Illegal character in reason phrase + infractions |= INF_BADPHRASE; + break; + } + } + assert (startLine.length == version.length + statusCode.length + reasonPhrase.length + 2); +} + +void NHttpMsgHeader::deriveStatusCodeNum() { + if (statusCode.length != 3) { + statusCodeNum = STAT_PROBLEMATIC; + return; + } + if ((statusCode.start[0] < '0') || (statusCode.start[0] > '9') || (statusCode.start[1] < '0') || (statusCode.start[1] > '9') || + (statusCode.start[2] < '0') || (statusCode.start[2] > '9')) { + infractions |= INF_BADSTATCODE; + statusCodeNum = STAT_PROBLEMATIC; + return; + } + statusCodeNum = (statusCode.start[0] - '0') * 100 + (statusCode.start[1] - '0') * 10 + (statusCode.start[2] - '0'); + if ((statusCodeNum < 100) || (statusCodeNum > 599)) { + infractions |= INF_BADSTATCODE; + } +} + +void NHttpMsgHeader::deriveVersionId() { + if (version.length != 8) { + versionId = VERS__PROBLEMATIC; + return; + } + if (memcmp(version.start, "HTTP/", 5) || (version.start[6] != '.')) { + versionId = VERS__PROBLEMATIC; + infractions |= INF_BADVERSION; + } + else if ((version.start[5] == '1') && (version.start[7] == '1')) { + versionId = VERS_1_1; + } + else if ((version.start[5] == '1') && (version.start[7] == '0')) { + versionId = VERS_1_0; + } + else if ((version.start[5] == '2') && (version.start[7] == '0')) { + versionId = VERS_2_0; + } + else if ((version.start[5] >= '0') && (version.start[5] <= '9') && (version.start[7] >= '0') && (version.start[7] <= '9')) { + versionId = VERS__OTHER; + infractions |= INF_UNKNOWNVERSION; + } + else { + versionId = VERS__PROBLEMATIC; + infractions |= INF_BADVERSION; + } +} + +void NHttpMsgHeader::deriveMethodId() { + methodId = (MethodId) strToCode(method.start, method.length, methodList); +} + +void NHttpMsgHeader::genEvents() { + if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event +} + +void NHttpMsgHeader::printMessage(FILE *output) const { + NHttpMsgSection::printMessageTitle(output, "header"); + + if (sourceId != SRC__NOTCOMPUTE) fprintf(output, "Source Id: %d\n", sourceId); + if (versionId != VERS__NOTCOMPUTE) fprintf(output, "Version Id: %d\n", versionId); + if (methodId != METH__NOTCOMPUTE) fprintf(output, "Method Id: %d\n", methodId); + if (statusCodeNum != STAT_NOTCOMPUTE) fprintf(output, "Status Code Num: %d\n", statusCodeNum); + printInterval(output, "Reason Phrase", reasonPhrase.start, reasonPhrase.length); + printInterval(output, "URI", uri.start, uri.length); + + NHttpMsgSharedHead::printMessageHead(output); + NHttpMsgSection::printMessageWrapup(output); +} + + +void NHttpMsgHeader::updateFlow() const { + const uint64_t disasterMask = INF_BADREQLINE | INF_BADSTATLINE | INF_BROKENCHUNK | INF_BADCHUNKSIZE; + + // The following logic to determine body type is by no means the last word on this topic. + if (tcpClose) { + sessionData->typeExpected[sourceId] = SEC_CLOSED; + sessionData->halfReset(sourceId); + } + else if (infractions & disasterMask) { + sessionData->typeExpected[sourceId] = SEC_ABORT; + sessionData->halfReset(sourceId); + } + else if ((sourceId == SRC_SERVER) && ((statusCodeNum <= 199) || (statusCodeNum == 204) || (statusCodeNum == 304))) { + // No body allowed by RFC for these response codes + sessionData->typeExpected[sourceId] = SEC_HEADER; + sessionData->halfReset(sourceId); + } + // If there is a Transfer-Encoding header, see if the last of the encoded values is "chunked". + else if ( (headerValueNorm[HEAD_TRANSFER_ENCODING].length > 0) && + ((*(int64_t *)(headerValueNorm[HEAD_TRANSFER_ENCODING].start + (headerValueNorm[HEAD_TRANSFER_ENCODING].length - 8))) == TRANSCODE_CHUNKED) ) { + // Chunked body + sessionData->typeExpected[sourceId] = SEC_CHUNKHEAD; + sessionData->bodySections[sourceId] = 0; + sessionData->bodyOctets[sourceId] = 0; + sessionData->numChunks[sourceId] = 0; + } + else if ((headerValueNorm[HEAD_CONTENT_LENGTH].length > 0) && (*(int64_t*)headerValueNorm[HEAD_CONTENT_LENGTH].start > 0)) { + // Regular body + sessionData->typeExpected[sourceId] = SEC_BODY; + sessionData->octetsExpected[sourceId] = *(int64_t*)headerValueNorm[HEAD_CONTENT_LENGTH].start; + sessionData->dataLength[sourceId] = *(int64_t*)headerValueNorm[HEAD_CONTENT_LENGTH].start; + sessionData->bodySections[sourceId] = 0; + sessionData->bodyOctets[sourceId] = 0; + } + else { + // No body + sessionData->typeExpected[sourceId] = SEC_HEADER; + sessionData->halfReset(sourceId); + } +} + +// Legacy support function. Puts message fields into the buffers used by old Snort. +void NHttpMsgHeader::legacyClients() const { + NHttpMsgSharedHead::legacyClients(); + if (method.length > 0) SetHttpBuffer(HTTP_BUFFER_METHOD, method.start, (unsigned)method.length); + if (uri.length > 0) SetHttpBuffer(HTTP_BUFFER_RAW_URI, uri.start, (unsigned)uri.length); + if (uri.length > 0) SetHttpBuffer(HTTP_BUFFER_URI, uri.start, (unsigned)uri.length); + if (statusCode.length > 0) SetHttpBuffer(HTTP_BUFFER_STAT_CODE, statusCode.start, (unsigned)statusCode.length); + if (reasonPhrase.length > 0) SetHttpBuffer(HTTP_BUFFER_STAT_MSG, reasonPhrase.start, (unsigned)reasonPhrase.length); +} + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_head.h b/src/service_inspectors/nhttp_inspect/nhttp_msg_head.h new file mode 100644 index 000000000..fbab3ee1b --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_head.h @@ -0,0 +1,94 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgHeader class declaration +// + +#ifndef NHTTP_MSG_HEAD_H +#define NHTTP_MSG_HEAD_H + +#include "nhttp_msg_head_shared.h" + +//------------------------------------------------------------------------- +// NHttpMsgHeader class +//------------------------------------------------------------------------- + +class NHttpMsgHeader: public NHttpMsgSharedHead { +public: + NHttpMsgHeader() {}; + void initSection(); + void analyze(); + void printMessage(FILE *output) const; + void genEvents(); + void updateFlow() const; + void legacyClients() const; + +private: + // Code conversion tables are for turning token strings into enums. + static const StrCode methodList[]; + + // "Parse" methods cut things into pieces. "Derive" methods convert things into a new format such as an integer or enum token. "Normalize" methods convert + // things into a standard form without changing the underlying format. + void parseWhole(); + void parseRequestLine(); + void parseStatusLine(); + void deriveStatusCodeNum(); + void deriveVersionId(); + void deriveMethodId(); + + // This is where all the derived values, extracted message parts, and normalized values are. + // Note that this is all scalars, buffer pointers, and buffer sizes. The actual buffers are in the message buffer (raw pieces) or the + // scratchPad (normalized pieces). + field startLine; + field version; + NHttpEnums::VersionId versionId; + field method; + NHttpEnums::MethodId methodId; + field uri; + field statusCode; + int32_t statusCodeNum; + field reasonPhrase; +}; + +#endif + + + + + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.cc new file mode 100644 index 000000000..a2279c89b --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.cc @@ -0,0 +1,166 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgSharedHead virtual class rolls up all the common elements of header processing and trailer processing. +// + +#include +#include +#include +#include + +#include "snort.h" +#include "nhttp_enum.h" +#include "nhttp_msg_head_shared.h" + +using namespace NHttpEnums; + +// Reinitialize everything derived in preparation for analyzing a new message +void NHttpMsgSharedHead::initSection() { + headers.length = STAT_NOTCOMPUTE; + numHeaders = STAT_NOTCOMPUTE; + for(int k = 0; k < MAXHEADERS; k++) { + headerLine[k].length = STAT_NOTCOMPUTE; + headerName[k].length = STAT_NOTCOMPUTE; + headerNameId[k] = HEAD__NOTCOMPUTE; + headerValue[k].length = STAT_NOTCOMPUTE; + } + for (int k = 1; k < HEAD__MAXVALUE; k++) { + headerValueNorm[k].length = STAT_NOTCOMPUTE; + } +} + +// All the header processing that is done for every message (i.e. not just-in-time) is done here. +void NHttpMsgSharedHead::analyze() { + parseWhole(); + parseHeaderBlock(); + parseHeaderLines(); + for (int j=0; j < MAXHEADERS; j++) { + if (headerName[j].length <= 0) break; + deriveHeaderNameId(j); + } + for (int k=1; k <= numNorms; k++) { + headerNorms[k]->normalize(scratchPad, infractions, (HeaderId)k, headerNameId, headerValue, MAXHEADERS, headerValueNorm[k]); + } +} + +// Divide up the block of header fields into individual header field lines. +void NHttpMsgSharedHead::parseHeaderBlock() { + if (headers.length <= 0) return; + int32_t bytesused = 0; + numHeaders = 0; + while (bytesused < headers.length) { + headerLine[numHeaders].start = headers.start + bytesused; + headerLine[numHeaders].length = findCrlf(headerLine[numHeaders].start, headers.length - bytesused, true); + bytesused += headerLine[numHeaders++].length + 2; + if (numHeaders >= MAXHEADERS) { + break; + } + } + if (bytesused < headers.length) { + infractions |= INF_TOOMANYHEADERS; + } +} + +// Divide header field lines into field name and field value +void NHttpMsgSharedHead::parseHeaderLines() { + int colon; + for (int k=0; k < numHeaders; k++) { + for (colon=0; colon < headerLine[k].length; colon++) { + if (headerLine[k].start[colon] == ':') break; + } + if (colon < headerLine[k].length) { + headerName[k].start = headerLine[k].start; + headerName[k].length = colon; + headerValue[k].start = headerLine[k].start + colon + 1; + headerValue[k].length = headerLine[k].length - colon - 1; + } + else { + infractions |= INF_BADHEADER; + } + } +} + +void NHttpMsgSharedHead::deriveHeaderNameId(int index) { + if (headerName[index].length <= 0) return; + // Normalize header field name to lower case for matching purposes + uint8_t *lowerName; + if ((lowerName = scratchPad.request(headerName[index].length)) == nullptr) { + infractions |= INF_NOSCRATCH; + headerNameId[index] = HEAD__INSUFMEMORY; + return; + } + int32_t lowerLength = norm2Lower(headerName[index].start, headerName[index].length, lowerName, infractions, nullptr); + headerNameId[index] = (HeaderId) strToCode(lowerName, lowerLength, headerList); +} + +void NHttpMsgSharedHead::genEvents() { + if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event +} + +// Legacy support function. Puts message fields into the buffers used by old Snort. +void NHttpMsgSharedHead::legacyClients() const { + ClearHttpBuffers(); + + if (headers.length > 0) SetHttpBuffer(HTTP_BUFFER_RAW_HEADER, headers.start, (unsigned)headers.length); + if (headers.length > 0) SetHttpBuffer(HTTP_BUFFER_HEADER, headers.start, (unsigned)headers.length); + + for (int k=0; (headerNameId[k] != HEAD__NOTCOMPUTE) && (k < MAXHEADERS); k++) { + if (((headerNameId[k] == HEAD_COOKIE) && (sourceId == SRC_CLIENT)) || ((headerNameId[k] == HEAD_SET_COOKIE) && (sourceId == SRC_SERVER))) { + if (headerValue[k].length > 0) SetHttpBuffer(HTTP_BUFFER_RAW_COOKIE, headerValue[k].start, (unsigned)headerValue[k].length); + break; + } + } + + if ((sourceId == SRC_CLIENT) && (headerValueNorm[HEAD_COOKIE].length > 0)) + SetHttpBuffer(HTTP_BUFFER_COOKIE, headerValueNorm[HEAD_COOKIE].start, (unsigned)headerValueNorm[HEAD_COOKIE].length); + else if ((sourceId == SRC_SERVER) && (headerValueNorm[HEAD_SET_COOKIE].length > 0)) + SetHttpBuffer(HTTP_BUFFER_COOKIE, headerValueNorm[HEAD_SET_COOKIE].start, (unsigned)headerValueNorm[HEAD_SET_COOKIE].length); +} + +void NHttpMsgSharedHead::printMessageHead(FILE *output) const { + char titleBuf[100]; + if (numHeaders != STAT_NOTCOMPUTE) fprintf(output, "Number of headers: %d\n", numHeaders); + for (int j=0; j < numHeaders && j < 200; j++) { + snprintf(titleBuf, sizeof(titleBuf), "Header ID %d", headerNameId[j]); + printInterval(output, titleBuf, headerValue[j].start, headerValue[j].length); + } + for (int k=1; k <= numNorms; k++) { + if (headerValueNorm[k].length != STAT_NOTPRESENT) { + snprintf(titleBuf, sizeof(titleBuf), "Normalized header %d", k); + printInterval(output, titleBuf, headerValueNorm[k].start, headerValueNorm[k].length, true); + } + } +} + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.h b/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.h new file mode 100644 index 000000000..071656434 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_head_shared.h @@ -0,0 +1,105 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgSharedHead class declaration +// + +#ifndef NHTTP_MSG_HEAD_SHARED_H +#define NHTTP_MSG_HEAD_SHARED_H + +#include "nhttp_str_to_code.h" +#include "nhttp_head_norm.h" +#include "nhttp_msg_section.h" + +//------------------------------------------------------------------------- +// NHttpMsgSharedHead class +//------------------------------------------------------------------------- + +class NHttpMsgSharedHead: public NHttpMsgSection { +public: + void initSection(); + void analyze(); + void genEvents(); + void legacyClients() const; + +protected: + // Header normalization. There should be one of these for every different way we can process a header field value. + static const HeaderNormalizer NORMALIZER_NIL; + static const HeaderNormalizer NORMALIZER_BASIC; + static const HeaderNormalizer NORMALIZER_CAT; + static const HeaderNormalizer NORMALIZER_NOREPEAT; + static const HeaderNormalizer NORMALIZER_DECIMAL; + static const HeaderNormalizer NORMALIZER_TRANSCODE; + + // Master table of known header fields and their normalization strategies. + static const HeaderNormalizer* const headerNorms[]; + static const int32_t numNorms; + + // Code conversion tables are for turning token strings into enums. + static const StrCode headerList[]; + static const StrCode transCodeList[]; + + // "Parse" methods cut things into pieces. "Derive" methods convert things into a new format such as an integer or enum token. "Normalize" methods convert + // things into a standard form without changing the underlying format. + virtual void parseWhole() = 0; + void parseHeaderBlock(); + void parseHeaderLines(); + void deriveHeaderNameId(int index); + + void printMessageHead(FILE *output) const; + + // This is where all the derived values, extracted message parts, and normalized values are. + // Note that this is all scalars, buffer pointers, and buffer sizes. The actual buffers are in the message buffer (raw pieces) or the + // scratchPad (normalized pieces). + field headers; + static const int MAXHEADERS = 200; // I'm an arbitrary number. Need to revisit. + int32_t numHeaders; + field headerLine[MAXHEADERS]; + field headerName[MAXHEADERS]; + NHttpEnums::HeaderId headerNameId[MAXHEADERS]; + field headerValue[MAXHEADERS]; + field headerValueNorm[NHttpEnums::HEAD__MAXVALUE]; +}; + +#endif + + + + + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc new file mode 100644 index 000000000..255b4423d --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc @@ -0,0 +1,107 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgSection class is virtual parent for classes that analyze individual HTTP message sections. +// + +#include +#include +#include +#include + +#include "snort.h" +#include "nhttp_enum.h" +#include "nhttp_msg_section.h" + +using namespace NHttpEnums; + +// Return the number of octets before the first CRLF. Return length if CRLF not present. +// +// wrappable: CRLF does not count in a header field when immediately followed by or . These whitespace characters +// at the beginning of the next line indicate that the previous header has wrapped and is continuing on the next line. +uint32_t NHttpMsgSection::findCrlf(const uint8_t* buffer, int32_t length, bool wrappable) { + for (int32_t k=0; k < length-1; k++) { + if ((buffer[k] == '\r') && (buffer[k+1] == '\n')) + if (!wrappable || (k+2 >= length) || ((buffer[k+2] != ' ') && (buffer[k+2] != '\t'))) return k; + } + return length; +} + +// Load a new message section +void NHttpMsgSection::loadSection(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_) { + length = bufsize; + memcpy(rawBuf, buffer, length); + + sessionData = sessionData_; + infractions = sessionData->infractions; + sourceId = sessionData->sourceId; + tcpClose = sessionData->tcpClose; + + scratchPad.reinit(); +} + +void NHttpMsgSection::printInterval(FILE *output, const char* name, const uint8_t *text, int32_t length, bool intVals) { + if ((length == STAT_NOTPRESENT) || (length == STAT_NOTCOMPUTE)) return; + int outCount = fprintf(output, "%s, length = %d, ", name, length); + if (length <= 0) { + fprintf(output, "\n"); + return; + } + if (text == nullptr) { + fprintf(output, "nullptr\n"); + return; + } + if (length > 1000) length = 1000; // Limit the amount of data printed + for (int k=0; k < length; k++) { + if ((text[k] >= 0x20) && (text[k] <= 0x7E)) fprintf(output, "%c", (char)text[k]); + else if (text[k] == 0xD) fprintf(output, "~"); + else if (text[k] == 0xA) fprintf(output, "^"); + else fprintf(output, "*"); + if ((k%120 == (119 - outCount)) && (k+1 < length)) fprintf(output, "\n"); + } + + if (intVals && (length%8 == 0)) { + fprintf(output, "\nInteger values ="); + for (int j=0; j < length; j+=8) { + fprintf(output, " %" PRIu64 , *((const uint64_t*)(text+j))); + } + } + fprintf(output, "\n"); +} + +void NHttpMsgSection::printMessageTitle(FILE *output, const char *title) const { + fprintf(output, "HTTP message %s:\n", title); + printInterval(output, "Input", msgText, length); +} + +void NHttpMsgSection::printMessageWrapup(FILE *output) const { + fprintf(output, "Infractions: %lx, TCP Close: %s\n", infractions, tcpClose ? "True" : "False"); + fprintf(output, "Interface to old clients. http_mask = %x.\n", http_mask); + for (int i=0; i < HTTP_BUFFER_MAX; i++) { + if ((1 << i) & http_mask) printInterval(output, http_buffer_name[i], http_buffer[i].buf, http_buffer[i].length); + } + fprintf(output, "\n"); +} + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_section.h b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.h new file mode 100644 index 000000000..783087319 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.h @@ -0,0 +1,99 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgSection class declaration +// + +#ifndef NHTTP_MSG_SECTION_H +#define NHTTP_MSG_SECTION_H + +#include "detection/detection_util.h" +#include "nhttp_scratch_pad.h" +#include "nhttp_flow_data.h" + +//------------------------------------------------------------------------- +// NHttpMsgSection class +//------------------------------------------------------------------------- + +class NHttpMsgSection { +public: + virtual void loadSection(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_); + virtual ~NHttpMsgSection() = default; + virtual void initSection() = 0; + virtual void analyze() = 0; + virtual void printMessage(FILE *output) const = 0; + virtual void genEvents() = 0; + virtual void updateFlow() const = 0; + virtual void legacyClients() const = 0; + +protected: + // Convenience methods + static uint32_t findCrlf(const uint8_t* buffer, int32_t length, bool wrappable); + static void printInterval(FILE *output, const char* name, const uint8_t *text, int32_t length, bool intVals = false); + void printMessageTitle(FILE *output, const char *title) const; + void printMessageWrapup(FILE *output) const; + + // The current strategy is to copy the entire raw message section into this object. Here it is. + int32_t length; // Length of the original message section in octets + uint8_t rawBuf[NHttpEnums::MAXOCTETS]; // The original HTTP message section octets + // This pointer is the handle for working with the original message data. It makes it simple to later replace rawBuf with some other form of storage + // such as the buffer in the packet structure or something dynamic. Const x 2 because this pointer should never change and people working with the + // original message should not be changing it. Only loading a completely new message into rawBuf should do that. + const uint8_t * const msgText = rawBuf; + + // Working space and storage for all the derived fields. See scratchPad.h for usage instructions. + // Allocation size may be complete overkill. Need to revisit this. + uint64_t derivedBuf[NHttpEnums::MAXOCTETS/8]; + NHttpFlowData* sessionData; + ScratchPad scratchPad {derivedBuf, NHttpEnums::MAXOCTETS/8}; + + // This is where all the derived values, extracted message parts, and normalized values are. + // Note that this is all scalars, buffer pointers, and buffer sizes. The actual buffers are in message buffer (raw pieces) or the + // scratchPad (normalized pieces). + uint64_t infractions; + bool tcpClose; + NHttpEnums::SourceId sourceId; +}; + +#endif + + + + + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_trailer.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_trailer.cc new file mode 100644 index 000000000..a1770b359 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_trailer.cc @@ -0,0 +1,123 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgTrailer class analyzes HTTP chunked message trailers. +// + +#include +#include +#include +#include + +#include "snort.h" +#include "nhttp_enum.h" +#include "nhttp_msg_trailer.h" + +using namespace NHttpEnums; + +void NHttpMsgTrailer::parseWhole() { + // The following if-else ladder puts the extremely common normal cases at the beginning and the rare pathological cases at the end + // Normal case with no trailer fields + if (!tcpClose && (length == 2)) { + headers.length = STAT_NOTPRESENT; + } + // Normal case with trailer fields + else if (!tcpClose) { + headers.start = msgText; + headers.length = length - 4; + } + // Normal case no trailer fields and TCP connection close + else if ((length == 2) && !memcmp(msgText, "\r\n", 2)) { + headers.length = STAT_NOTPRESENT; + } + // Normal case with trailer fields and TCP connection close + else if ((length >= 5) && !memcmp(msgText+length-4, "\r\n\r\n", 4)) { + headers.start = msgText; + headers.length = length - 4; + } + // Abnormal cases truncated by TCP connection close + else { + infractions |= INF_TRUNCATED; + + // Lone + if ((length == 1) && (msgText[0] == '\r')) { + headers.length = STAT_NOTPRESENT; + } + // Truncation occurred somewhere in the trailer fields + else { + headers.start = msgText; + headers.length = length; + // When present, remove partial sequence from the very end + if ((length > 4) && !memcmp(msgText+length-3, "\r\n\r", 3)) headers.length -= 3; + else if ((length > 3) && !memcmp(msgText+length-2, "\r\n", 2)) headers.length -= 2; + else if ((length > 2) && (msgText[length-1] == '\r')) headers.length -= 1; + } + } +} + +void NHttpMsgTrailer::genEvents() { + if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event +} + +void NHttpMsgTrailer::printMessage(FILE *output) const { + NHttpMsgSection::printMessageTitle(output, "trailer"); + NHttpMsgSharedHead::printMessageHead(output); + NHttpMsgSection::printMessageWrapup(output); +} + + +void NHttpMsgTrailer::updateFlow() const { + if (tcpClose) { + sessionData->typeExpected[sourceId] = SEC_CLOSED; + sessionData->halfReset(sourceId); + } + else { + sessionData->typeExpected[sourceId] = SEC_HEADER; + sessionData->halfReset(sourceId); + } +} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_trailer.h b/src/service_inspectors/nhttp_inspect/nhttp_msg_trailer.h new file mode 100644 index 000000000..282421db2 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_trailer.h @@ -0,0 +1,68 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHttpMsgTrailer class declaration +// + +#ifndef NHTTP_MSG_TRAILER_H +#define NHTTP_MSG_TRAILER_H + +#include "nhttp_msg_head_shared.h" + +//------------------------------------------------------------------------- +// NHttpMsgTrailer class +//------------------------------------------------------------------------- + +class NHttpMsgTrailer: public NHttpMsgSharedHead { +public: + NHttpMsgTrailer() {}; + void printMessage(FILE *output) const; + void genEvents(); + void updateFlow() const; + +private: + void parseWhole(); +}; + +#endif + + + + + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_scratch_pad.h b/src/service_inspectors/nhttp_inspect/nhttp_scratch_pad.h new file mode 100644 index 000000000..76e324321 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_scratch_pad.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief ScratchPad class declaration +// + +#ifndef NHTTP_SCRATCH_PAD_H +#define NHTTP_SCRATCH_PAD_H + + +//------------------------------------------------------------------------- +// ScratchPad class +// Memory management for NHttpMsgHeader class +//------------------------------------------------------------------------- + +// Working space and storage for all the derived fields +// Return value of request is 64-bit aligned and may be freely cast to uint64_t* +// 1. request the maximum number of bytes you might need +// 2. use what you need +// 3. commit() what you actually used if you want to keep it +// Anything you do not commit will be reused by the next request. + +class ScratchPad { +public: + ScratchPad(uint64_t *buff, uint32_t length) : buffer(buff), capacity(length*8), used(0) {}; // Careful: length must be number of uint64_ts provided, not octets. + void reinit() {used = 0;}; + uint8_t *request(uint32_t needed) const {return (needed <= capacity-used) ? (uint8_t*)(buffer+used) : nullptr;}; + void commit(uint32_t taken) { used += taken + (8-(taken%8))%8; }; // round up to multiple of 8 to preserve alignment + +private: + uint64_t *buffer; + uint32_t capacity; + uint32_t used; +}; + +#endif + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_str_to_code.cc b/src/service_inspectors/nhttp_inspect/nhttp_str_to_code.cc new file mode 100644 index 000000000..c8c33373f --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_str_to_code.cc @@ -0,0 +1,47 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief Converts token strings to enum codes +// + + +#include +#include +#include +#include "snort.h" +#include "nhttp_enum.h" +#include "nhttp_str_to_code.h" + +// Need to replace this simple algorithm for better performance +int32_t strToCode(const uint8_t *text, int32_t textLen, const StrCode table[]) { + if (textLen <= 0) return NHttpEnums::STAT_PROBLEMATIC; + for (int32_t k=0; table[k].name != nullptr; k++) { + if ((textLen == (int) strlen(table[k].name)) && (memcmp(text, table[k].name, textLen) == 0)) { + return table[k].code; + } + } + return NHttpEnums::STAT_OTHER; +} + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_str_to_code.h b/src/service_inspectors/nhttp_inspect/nhttp_str_to_code.h new file mode 100644 index 000000000..c3e278d49 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_str_to_code.h @@ -0,0 +1,40 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief Converts protocol constant string to enum +// + +#ifndef NHTTP_STR_TO_CODE_H +#define NHTTP_STR_TO_CODE_H + +struct StrCode { + int32_t code; + const char *name; +}; + +int32_t strToCode(const uint8_t *text, int32_t textLen, const StrCode table[]); + +#endif + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.cc b/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.cc new file mode 100644 index 000000000..3974450be --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.cc @@ -0,0 +1,159 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief HTTP Stream Splitter Class +// + +#include +#include +#include +#include "snort.h" +#include "nhttp_enum.h" +#include "nhttp_test_input.h" +#include "nhttp_stream_splitter.h" + +using namespace NHttpEnums; + +// Convenience function. All the housekeeping that must be done before we can return PAF_FLUSH to stream. +void NHttpStreamSplitter::prepareFlush(NHttpFlowData* sessionData, uint32_t* flushOffset, SourceId sourceId, SectionType sectionType, bool tcpClose, + uint64_t infractions, uint32_t numOctets) { + sessionData->sourceId = sourceId; + sessionData->sectionType = sectionType; + sessionData->tcpClose = tcpClose; + sessionData->infractions = infractions; + if (tcpClose) sessionData->typeExpected[sourceId] = SEC_CLOSED; + if (!NHttpTestInput::test_mode) *flushOffset = numOctets; + else NHttpTestInput::testInput->pafFlush(numOctets); + octetsSeen = 0; + numCrlf = 0; +} + +PAF_Status NHttpStreamSplitter::scan (Flow* flow, const uint8_t* data, uint32_t length, uint32_t flags, uint32_t* flushOffset) { + // When the system begins providing TCP connection close information this won't always be false. &&& + bool tcpClose = false; + + // This is the session state information we share with HTTP Inspect and store with stream. A session is defined by a TCP connection. + // Since PAF is the first to see a new TCP connection the new flow data object is created here. + NHttpFlowData* sessionData = (NHttpFlowData*)flow->get_application_data(NHttpFlowData::nhttp_flow_id); + if (sessionData == nullptr) flow->set_application_data(sessionData = new NHttpFlowData); + assert(sessionData != nullptr); + + SourceId sourceId = (flags & PKT_FROM_CLIENT) ? SRC_CLIENT : SRC_SERVER; + + if (NHttpTestInput::test_mode) { + *flushOffset = length; + bool needBreak; + NHttpTestInput::testInput->toPaf((uint8_t*&)data, length, sourceId, tcpClose, needBreak); + if (length == 0) return PAF_FLUSH; + if (needBreak) flow->set_application_data(sessionData = new NHttpFlowData); + } + + switch (SectionType type = sessionData->typeExpected[sourceId]) { + case SEC_HEADER: + case SEC_CHUNKHEAD: + case SEC_TRAILER: + pafMax = 63780; + for (uint32_t k = 0; k < length; k++) { + octetsSeen++; + + // Count the alternating and characters we have seen in a row + if (((data[k] == '\r') && (numCrlf%2 == 0)) || ((data[k] == '\n') && (numCrlf%2 == 1))) numCrlf++; + else numCrlf = 0; + + // Check header for leading CRLF because some 1.0 implementations put extra blank lines between messages. We tolerate this by quietly ignoring them. + // Trailer may also have leading CRLF. That is completely normal and means there is no trailer. + if (((type == SEC_HEADER) || (type == SEC_TRAILER)) && (numCrlf == 2) && (octetsSeen == 2)) { + prepareFlush(sessionData, flushOffset, sourceId, (type == SEC_HEADER) ? SEC_DISCARD : type, tcpClose && (k == length-1), 0, k+1); + return PAF_FLUSH; + } + // The chunk header section always ends with the first + else if ((type == SEC_CHUNKHEAD) && (numCrlf == 2)) { + prepareFlush(sessionData, flushOffset, sourceId, type, tcpClose && (k == length-1), 0, k+1); + return PAF_FLUSH; + } + // The header and trailer sections always end with the first double + else if (numCrlf == 4) { + prepareFlush(sessionData, flushOffset, sourceId, type, tcpClose && (k == length-1), 0, k+1); + return PAF_FLUSH; + } + // We must do this to protect ourself from buffer overrun. + else if (octetsSeen >= 63780) { + prepareFlush(sessionData, flushOffset, sourceId, type, tcpClose && (k == length-1), INF_HEADTOOLONG, k+1); + return PAF_FLUSH; + } + } + // Incomplete headers wait patiently for more data + if (!tcpClose) return PAF_SEARCH; + // Discard the oddball case where the new "message" starts with + else if ((octetsSeen == 1) && (numCrlf == 1)) prepareFlush(sessionData, flushOffset, sourceId, SEC_DISCARD, true, 0, length); + // TCP connection close, flush the partial header + else prepareFlush(sessionData, flushOffset, sourceId, type, true, INF_TRUNCATED, length); + return PAF_FLUSH; + case SEC_BODY: + case SEC_CHUNKBODY: + pafMax = 16384; + prepareFlush(sessionData, flushOffset, sourceId, type, tcpClose && (sessionData->octetsExpected[sourceId] >= length), 0, sessionData->octetsExpected[sourceId]); + return PAF_FLUSH; + case SEC_ABORT: + return PAF_ABORT; + default: + assert(0); + return PAF_ABORT; + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.h b/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.h new file mode 100644 index 000000000..ce6fd74e2 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter.h @@ -0,0 +1,65 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief NHTTP Stream Splitter class +// + +#ifndef NHTTP_STREAM_SPLITTER_H +#define NHTTP_STREAM_SPLITTER_H + +#include "stream/stream_splitter.h" +#include "nhttp_flow_data.h" + +class NHttpStreamSplitter : public StreamSplitter { +public: + NHttpStreamSplitter(bool isClientToServer) : StreamSplitter(isClientToServer) {}; + PAF_Status scan(Flow* flow, const uint8_t* data, uint32_t length, uint32_t flags, uint32_t* flushOffset); + bool is_paf() { return true; }; + uint32_t max() { return pafMax; }; +private: + void prepareFlush(NHttpFlowData* sessionData, uint32_t* flushOffset, NHttpEnums::SourceId sourceId, NHttpEnums::SectionType sectionType, bool tcpClose, + uint64_t infractions, uint32_t numOctets); + + int64_t octetsSeen; + int numCrlf; + uint32_t pafMax = 63780; +}; + +#endif + + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_tables.cc b/src/service_inspectors/nhttp_inspect/nhttp_tables.cc index 38bb8006e..7ad6e7727 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_tables.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_tables.cc @@ -23,21 +23,23 @@ // // @author Tom Peters // -// @brief Static constant tables for converting protocol strings to enum codes. Members of HttpMsgHeader. +// @brief Static constant tables for various conversions and normalizations +// +// Note: protocol correctness is not the decisive criterion for inclusion in the following tables. Just because something is "wrong" per RFC does +// not mean that it cannot appear here. The goal is to recognize and inspect things that actually happen or might happen regardless of +// whether they should happen. // - #include #include #include "snort.h" -#include "flow/flow.h" +#include "framework/module.h" #include "nhttp_enum.h" -#include "nhttp_scratchpad.h" -#include "nhttp_strtocode.h" -#include "nhttp_headnorm.h" -#include "nhttp_flowdata.h" -#include "nhttp_msgheader.h" +#include "nhttp_str_to_code.h" +#include "nhttp_head_norm.h" +#include "nhttp_msg_head.h" +#include "nhttp_module.h" using namespace NHttpEnums; @@ -91,7 +93,7 @@ const StrCode NHttpMsgHeader::methodList[] = { METH_UPDATEREDIRECTREF, "UPDATEREDIRECTREF"}, { 0, nullptr} }; -const StrCode NHttpMsgHeader::headerList[] = +const StrCode NHttpMsgSharedHead::headerList[] = {{ HEAD_CACHE_CONTROL, "cache-control"}, { HEAD_CONNECTION, "connection"}, { HEAD_DATE, "date"}, @@ -143,7 +145,7 @@ const StrCode NHttpMsgHeader::headerList[] = { HEAD_LAST_MODIFIED, "last-modified"}, { 0, nullptr} }; -const StrCode NHttpMsgHeader::transCodeList[] = +const StrCode NHttpMsgSharedHead::transCodeList[] = {{ TRANSCODE_CHUNKED, "chunked"}, { TRANSCODE_IDENTITY, "identity"}, { TRANSCODE_GZIP, "gzip"}, @@ -151,14 +153,14 @@ const StrCode NHttpMsgHeader::transCodeList[] = { TRANSCODE_DEFLATE, "deflate"}, { 0, nullptr} }; -const HeaderNormalizer NHttpMsgHeader::NORMALIZER_NIL {NORM_NULL, false, false, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; -const HeaderNormalizer NHttpMsgHeader::NORMALIZER_BASIC {NORM_FIELD, false, false, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; -const HeaderNormalizer NHttpMsgHeader::NORMALIZER_CAT {NORM_FIELD, true, false, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; -const HeaderNormalizer NHttpMsgHeader::NORMALIZER_NOREPEAT {NORM_FIELD, false, true, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; -const HeaderNormalizer NHttpMsgHeader::NORMALIZER_DECIMAL {NORM_INTEGER, false, true, normDecimalInteger, nullptr, nullptr, nullptr, nullptr, nullptr}; -const HeaderNormalizer NHttpMsgHeader::NORMALIZER_TRANSCODE {NORM_INTEGER, true, false, normSeqStrCode, NHttpMsgHeader::transCodeList, nullptr, nullptr, nullptr, nullptr}; +const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_NIL {NORM_NULL, false, false, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; +const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_BASIC {NORM_FIELD, false, false, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; +const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_CAT {NORM_FIELD, true, false, normRemoveLws, nullptr, nullptr, nullptr, nullptr, nullptr}; +const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_NOREPEAT {NORM_FIELD, false, true, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; +const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_DECIMAL {NORM_INT64, false, true, normDecimalInteger, nullptr, nullptr, nullptr, nullptr, nullptr}; +const HeaderNormalizer NHttpMsgSharedHead::NORMALIZER_TRANSCODE {NORM_ENUM64, true, false, normRemoveLws, nullptr, norm2Lower, nullptr, normSeqStrCode, NHttpMsgSharedHead::transCodeList}; -const HeaderNormalizer* const NHttpMsgHeader::headerNorms[HEAD__MAXVALUE] = { [0] = &NORMALIZER_NIL, +const HeaderNormalizer* const NHttpMsgSharedHead::headerNorms[HEAD__MAXVALUE] = { [0] = &NORMALIZER_NIL, [HEAD__OTHER] = &NORMALIZER_BASIC, [HEAD_CACHE_CONTROL] = &NORMALIZER_BASIC, [HEAD_CONNECTION] = &NORMALIZER_BASIC, @@ -173,8 +175,8 @@ const HeaderNormalizer* const NHttpMsgHeader::headerNorms[HEAD__MAXVALUE] = { [0 [HEAD_WARNING] = &NORMALIZER_BASIC, [HEAD_ACCEPT] = &NORMALIZER_BASIC, [HEAD_ACCEPT_CHARSET] = &NORMALIZER_BASIC, - [HEAD_ACCEPT_ENCODING] = &NORMALIZER_BASIC, - [HEAD_ACCEPT_LANGUAGE] = &NORMALIZER_BASIC, + [HEAD_ACCEPT_ENCODING] = &NORMALIZER_CAT, + [HEAD_ACCEPT_LANGUAGE] = &NORMALIZER_CAT, [HEAD_AUTHORIZATION] = &NORMALIZER_BASIC, [HEAD_EXPECT] = &NORMALIZER_BASIC, [HEAD_FROM] = &NORMALIZER_BASIC, @@ -211,6 +213,55 @@ const HeaderNormalizer* const NHttpMsgHeader::headerNorms[HEAD__MAXVALUE] = { [0 [HEAD_LAST_MODIFIED] = &NORMALIZER_BASIC }; - const int32_t NHttpMsgHeader::numNorms = HEAD__MAXVALUE-1; +const int32_t NHttpMsgSharedHead::numNorms = HEAD__MAXVALUE-1; +const RuleMap NHttpModule::nhttpEvents[] = +{ + { EVENT_ASCII, "(nhttp_inspect) ascii encoding" }, + { EVENT_DOUBLE_DECODE, "(nhttp_inspect) double decoding attack" }, + { EVENT_U_ENCODE, "(nhttp_inspect) u encoding" }, + { EVENT_BARE_BYTE, "(nhttp_inspect) bare byte unicode encoding" }, + { EVENT_OBSOLETE_1, "(nhttp_inspect) obsolete event--should not appear" }, + { EVENT_UTF_8, "(nhttp_inspect) utf-8 encoding" }, + { EVENT_IIS_UNICODE, "(nhttp_inspect) iis unicode codepoint encoding" }, + { EVENT_MULTI_SLASH, "(nhttp_inspect) multi_slash encoding" }, + { EVENT_IIS_BACKSLASH, "(nhttp_inspect) iis backslash evasion" }, + { EVENT_SELF_DIR_TRAV, "(nhttp_inspect) self directory traversal" }, + { EVENT_DIR_TRAV, "(nhttp_inspect) directory traversal" }, + { EVENT_APACHE_WS, "(nhttp_inspect) apache whitespace (tab)" }, + { EVENT_IIS_DELIMITER, "(nhttp_inspect) non-rfc http delimiter" }, + { EVENT_NON_RFC_CHAR, "(nhttp_inspect) non-rfc defined char" }, + { EVENT_OVERSIZE_DIR, "(nhttp_inspect) oversize request-uri directory" }, + { EVENT_LARGE_CHUNK, "(nhttp_inspect) oversize chunk encoding" }, + { EVENT_PROXY_USE, "(nhttp_inspect) unauthorized proxy use detected" }, + { EVENT_WEBROOT_DIR, "(nhttp_inspect) webroot directory traversal" }, + { EVENT_LONG_HDR, "(nhttp_inspect) long header" }, + { EVENT_MAX_HEADERS, "(nhttp_inspect) max header fields" }, + { EVENT_MULTIPLE_CONTLEN, "(nhttp_inspect) multiple content length" }, + { EVENT_CHUNK_SIZE_MISMATCH, "(nhttp_inspect) chunk size mismatch detected" }, + { EVENT_INVALID_TRUEIP, "(nhttp_inspect) invalid ip in true-client-ip/xff header" }, + { EVENT_MULTIPLE_HOST_HDRS, "(nhttp_inspect) multiple host hdrs detected" }, + { EVENT_LONG_HOSTNAME, "(nhttp_inspect) hostname exceeds 255 characters" }, + { EVENT_EXCEEDS_SPACES, "(nhttp_inspect) header parsing space saturation" }, + { EVENT_CONSECUTIVE_SMALL_CHUNKS, "(nhttp_inspect) client consecutive small chunk sizes" }, + { EVENT_UNBOUNDED_POST, "(nhttp_inspect) post w/o content-length or chunks" }, + { EVENT_MULTIPLE_TRUEIP_IN_SESSION, "(nhttp_inspect) multiple true ips in a session" }, + { EVENT_BOTH_TRUEIP_XFF_HDRS, "(nhttp_inspect) both true_client_ip and xff hdrs present" }, + { EVENT_UNKNOWN_METHOD, "(nhttp_inspect) unknown method" }, + { EVENT_SIMPLE_REQUEST, "(nhttp_inspect) simple request" }, + { EVENT_UNESCAPED_SPACE_URI, "(nhttp_inspect) unescaped space in http uri" }, + { EVENT_PIPELINE_MAX, "(nhttp_inspect) too many pipelined requests" }, + { EVENT_ANOM_SERVER, "(nhttp_inspect) anomalous http server on undefined http port" }, + { EVENT_INVALID_STATCODE, "(nhttp_inspect) invalid status code in http response" }, + { EVENT_NO_CONTLEN, "(nhttp_inspect) no content-length or transfer-encoding in http response" }, + { EVENT_UTF_NORM_FAIL, "(nhttp_inspect) http response has utf charset which failed to normalize" }, + { EVENT_UTF7, "(nhttp_inspect) http response has utf-7 charset" }, + { EVENT_DECOMPR_FAILED, "(nhttp_inspect) http response gzip decompression failed" }, + { EVENT_CONSECUTIVE_SMALL_CHUNKS_S, "(nhttp_inspect) server consecutive small chunk sizes" }, + { EVENT_MSG_SIZE_EXCEPTION, "(nhttp_inspect) invalid content-length or chunk size" }, + { EVENT_JS_OBFUSCATION_EXCD, "(nhttp_inspect) javascript obfuscation levels exceeds 1" }, + { EVENT_JS_EXCESS_WS, "(nhttp_inspect) javascript whitespaces exceeds max allowed" }, + { EVENT_MIXED_ENCODINGS, "(nhttp_inspect) multiple encodings within javascript obfuscated data" }, + { 0, nullptr } +}; diff --git a/src/service_inspectors/nhttp_inspect/nhttp_test_input.cc b/src/service_inspectors/nhttp_inspect/nhttp_test_input.cc new file mode 100644 index 000000000..943b806d6 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_test_input.cc @@ -0,0 +1,294 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief Interface to file of test messages +// + + +#include +#include +#include +#include +#include + +#include "nhttp_enum.h" +#include "nhttp_test_input.h" + +using namespace NHttpEnums; + +bool NHttpTestInput::test_mode = false; +NHttpTestInput *NHttpTestInput::testInput = nullptr; + +NHttpTestInput::NHttpTestInput(const char *fileName) { + if ((testDataFile = fopen(fileName, "r")) == nullptr) throw std::runtime_error("Cannot open test input file"); +} + +NHttpTestInput::~NHttpTestInput() { + fclose(testDataFile); +} + +// Read from the test data file and present to PAF. +// In the process we may need to skip comments, execute simple commands, and handle escape sequences. +// The best way to understand this function is to read the comments at the top of the file of test cases. +void NHttpTestInput::toPaf(uint8_t*& data, uint32_t &length, SourceId &sourceId, bool &tcpClose, bool &needBreak) { + // No new data presented to PAF while the last section is still being flushed. + if (flushed) { + length = 0; + return; + } + + sourceId = lastSourceId; + tcpClose = false; + needBreak = false; + + if (justFlushed) { + // PAF just flushed. There may or may not be leftover data in our buffer. + justFlushed = false; + data = msgBuf; + length = endOffset - flushOffset; // this is the leftover data + previousOffset = 0; + endOffset = length; + if (length > 0) { + // Must present unflushed leftovers to PAF again. + // If we don't take this opportunity to left justify our data in the buffer we may "walk" to the right until we run out of buffer space + memmove(msgBuf, msgBuf+flushOffset, length); + tcpClose = tcpAlreadyClosed; + return; + } + // If we reach here then PAF has already flushed all the data we have read so far. + tcpAlreadyClosed = false; + } + else { + // The data we gave PAF last time was not flushed + length = 0; + previousOffset = endOffset; + data = msgBuf + previousOffset; + } + + // Now we need to move forward by reading more data from the file + int newChar; + typedef enum { WAITING, COMMENT, COMMAND, SECTION, ESCAPE, HEXVAL, FILLNUM, BRIDGE } State; + State state = WAITING; + bool ending; + int commandLength; + const int MaxCommand = 100; + char commandValue[MaxCommand]; + uint8_t hexVal; + int numDigits; + uint32_t fillLength; + + while ((newChar = getc(testDataFile)) != EOF) { + switch (state) { + case WAITING: + if (newChar == '#') state = COMMENT; + else if (newChar == '@') { + state = COMMAND; + commandLength = 0; + } + else if (newChar == '\\') { + state = ESCAPE; + ending = false; + } + else if (newChar != '\n') { + state = SECTION; + ending = false; + data[length++] = (uint8_t) newChar; + } + break; + case COMMENT: + if (newChar == '\n') state = WAITING; + break; + case COMMAND: + if (newChar == '\n') { + state = WAITING; + if ((commandLength == strlen("request")) && !memcmp(commandValue, "request", strlen("request"))) sourceId = lastSourceId = SRC_CLIENT; + else if ((commandLength == strlen("response")) && !memcmp(commandValue, "response", strlen("response"))) sourceId = lastSourceId = SRC_SERVER; + else if ((commandLength == strlen("break")) && !memcmp(commandValue, "break", strlen("break"))) needBreak = true; + else if ((commandLength == strlen("bodyend")) && !memcmp(commandValue, "bodyend", strlen("bodyend"))) { + termBytes[0] = 'x'; + termBytes[1] = 'y'; + } + else if ((commandLength == strlen("chunkend")) && !memcmp(commandValue, "chunkend", strlen("chunkend"))) { + termBytes[0] = '\r'; + termBytes[1] = '\n'; + } + else if (commandLength > 0) { + // Look for a test number + bool isNumber = true; + for (int k=0; (k < commandLength) && isNumber; k++) { + isNumber = (commandValue[k] >= '0') && (commandValue[k] <= '9'); + } + if (isNumber) { + testNumber = 0; + for (int j=0; j < commandLength; j++) { + testNumber = testNumber * 10 + (commandValue[j] - '0'); + } + } + } + } + else { + if (commandLength < MaxCommand) commandValue[commandLength++] = newChar; + else assert(0); + } + break; + case SECTION: + if (newChar == '\\') { + state = ESCAPE; + ending = false; + } + else if (newChar == '\n') { + if (ending) { + // Found the blank line that ends the section. + endOffset = previousOffset + length; + return; + } + ending = true; + } + else { + ending = false; + data[length++] = (uint8_t) newChar; + } + break; + case ESCAPE: + switch (newChar) { + case 'n': state = SECTION; data[length++] = '\n'; break; + case 'r': state = SECTION; data[length++] = '\r'; break; + case 't': state = SECTION; data[length++] = '\t'; break; + case 'B': state = BRIDGE; break; + case 'C': endOffset = previousOffset + length; return; + case 'T': tcpClose = tcpAlreadyClosed = true; endOffset = previousOffset + length; return; + case '#': state = SECTION; data[length++] = '#'; break; + case '@': state = SECTION; data[length++] = '@'; break; + case '\\': state = SECTION; data[length++] = '\\'; break; + case 'x': + case 'X': state = HEXVAL; hexVal = 0; numDigits = 0; break; + case '/': state = FILLNUM; fillLength = 0; break; + default: assert(0); state = SECTION; break; + } + break; + case BRIDGE: + if (newChar != '\n') { + state = SECTION; + data[length++] = (uint8_t) newChar; + } + break; + case HEXVAL: + if ((newChar >= '0') && (newChar <= '9')) hexVal = hexVal * 16 + (newChar - '0'); + else if ((newChar >= 'a') && (newChar <= 'f')) hexVal = hexVal * 16 + 10 + (newChar - 'a'); + else if ((newChar >= 'A') && (newChar <= 'F')) hexVal = hexVal * 16 + 10 + (newChar - 'A'); + else assert(0); + if (++numDigits == 2) { + data[length++] = hexVal; + state = SECTION; + } + break; + case FILLNUM: + if (newChar != '/') { + assert((newChar >= '0') && (newChar <= '9')); + fillLength = fillLength * 10 + (newChar - '0'); + assert(fillLength <= sizeof(msgBuf)); + break; + } + else { + bodyData = true; + // Add the specified number of fill characters to the buffer and cut. + // Simulates body data at the end of a header segment or the first segment containing body data + // Don't allow a buffer overrun. + if (previousOffset + length + fillLength > sizeof(msgBuf)) assert(0); + for (uint32_t k=0; k < fillLength; k++) { + data[length++] = 'x'; + } + endOffset = previousOffset + length; + return; + } + } + // If we have reached the configured maximum segment size automatically cut the data. + if (length >= mssLength) { + endOffset = previousOffset + length; + return; + } + // Don't allow a buffer overrun. + if (previousOffset + length >= sizeof(msgBuf)) assert(0); + } + // End-of-file. Return everything we have so far. + endOffset = previousOffset + length; + return; +} + +void NHttpTestInput::pafFlush(uint32_t length) { + assert(!flushed); + flushed = true; + if (bodyData && (previousOffset + length >= endOffset)) { + fillOctets = length; + bodyData = false; + previousOffset = 0; + endOffset = 0; + flushOffset = 0; + } + else { + flushOffset = previousOffset + length; + } +} + + +uint16_t NHttpTestInput::toEval(uint8_t **buffer, int64_t &testNumber_) { + if (!flushed) return 0; + testNumber_ = testNumber; + *buffer = msgBuf; + if (fillOctets > 0) { + uint32_t fillOut = (fillOctets <= 16384) ? fillOctets : 16384; + for (uint32_t k = 0; k < fillOut; k++) { + msgBuf[k] = 'A' + k % 26; + } + fillOctets -= fillOut; + if (fillOctets == 0) { + if (fillOut > 1) msgBuf[fillOut-2] = termBytes[0]; + msgBuf[fillOut-1] = termBytes[1]; + flushed = false; + justFlushed = true; + } + else if (fillOctets == 1) { + msgBuf[fillOut-1] = termBytes[0]; + } + return (uint16_t)fillOut; + } + flushed = false; + justFlushed = true; + return (uint16_t)flushOffset; +} + + + + + + + + + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_test_input.h b/src/service_inspectors/nhttp_inspect/nhttp_test_input.h new file mode 100644 index 000000000..d7fe9e556 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_test_input.h @@ -0,0 +1,61 @@ +/**************************************************************************** + * +** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2003-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +// +// @author Tom Peters +// +// @brief Read test file and present test input to PAF +// + +#ifndef NHTTP_TEST_INPUT_H +#define NHTTP_TEST_INPUT_H + +class NHttpTestInput { +public: + NHttpTestInput(const char *fileName); + ~NHttpTestInput(); + void toPaf(uint8_t*& data, uint32_t &length, NHttpEnums::SourceId &sourceId, bool &tcpClose, bool &needBreak); + void pafFlush(uint32_t length); + uint16_t toEval(uint8_t **buffer, int64_t &testNumber); + + // Hard for NHttpInspect and PAF to share these without making them "global". This is as good a place as any for them to live. + static bool test_mode; + static NHttpTestInput *testInput; +private: + FILE *testDataFile; + uint8_t msgBuf[2 * NHttpEnums::MAXOCTETS]; + bool flushed = false; // verifies alternation between PAF section flushing and eval() section processing + bool justFlushed = true; // toPaf() needs to do special post-flush processing before it resumes reading the file + bool tcpAlreadyClosed = false; // so we can keep presenting a TCP close to PAF until all the remaining octets are consumed and flushed + uint32_t flushOffset = 0; // last character in buffer that has been flushed by PAF and must go to eval(). + uint32_t previousOffset = 0; // last character in the buffer shown to PAF but not flushed yet + uint32_t endOffset = 0; // last read character in the buffer + bool bodyData = false; // pending output of fill data + uint32_t fillOctets = 0; // remaining fill data + int64_t testNumber = 0; // for numbering test output files + uint32_t mssLength = 1460; // Maximum Segment Size. Needs to be enhanced to be set through a command. + NHttpEnums::SourceId lastSourceId = NHttpEnums::SRC_CLIENT; // current direction of traffic flow. Toggled by commands in file. + uint8_t termBytes[2] = { 'x', 'y' }; +}; + +#endif + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_test_msgs.txt b/src/service_inspectors/nhttp_inspect/nhttp_test_msgs.txt new file mode 100644 index 000000000..24cc21170 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_test_msgs.txt @@ -0,0 +1,530 @@ +# Enter HTTP test message text as you want it to be presented to PAF. +# +# The easiest way to format is to put a blank line between message sections so that each message section is its own "paragraph". +# Within a paragraph the placement of new lines does not have any effect. Format a paragraph any way you are comfortable. Extra blank lines +# between paragraphs also do not have any effect. +# +# Lines beginning with # are comments. Lines beginning with @ are commands. This does not apply to lines in the middle of a paragraph. +# +# Command lines are left justified, lower case, with no whitespace: +# @break resets HTTP Inspect data structures and begins a new test. Use it liberally to prevent unrelated tests from interfering with each other. +# @request and @response set the message direction. Applies to subsequent sections until changed. +# @bodyend and @chunkend set the final two characters of a body or chunk to "xy" or "\r\n". +# @ sets the test number and hence the test output file name. Applies to subsequent sections until changed. Don't reuse numbers. +# +# Escape sequences begin with '\'. They may be used within a paragraph or to begin a paragraph. +# \r - carriage return +# \n - linefeed +# \t - tab +# \\ - backslash +# \# - # +# \@ - @ +# \xnn or \Xnn - where nn is a two-digit hexadecimal number. Insert an arbitrary 8-bit number as the next character. a-f and A-F are both acceptable. +# \/nnn/ - where nnn is a variable-length decimal number. Insert nnn octets of fill data. The fill characters are 'A' - 'Z' with the final two bytes +# "xy" for a normal body and "\r\n" for a chunk. +# \B, \C, and \T - bridge, cut, and TCP close. See below. + +# The default procedure for separating data into segments for presentation to PAF is 1) whenever a paragraph ends (blank line) and 2) every MSS octets, +# where MSS is currently a source code constant set to 1460. The \C escape forces an immediate separation point even in the middle of a paragraph. It is +# useful for testing PAF where message segments often arrive in pieces. \B causes prevents the immediately following blank line(s) from causing a +# separation point. Data resumes with the next non-newline character as if there was no gap. This allows PAF to be tested with the end of a section and +# the beginning of the next section or message in the same packet. Commands and comments following \B are not allowed. Data must resume immediately +# following the blank lines. +# +# \T functions as \C but causes the TCP connection closed indication to be set. It only makes sense at the end of a paragraph. \C\T simulates a data +# packet subsequently followed by a close with no more data. + + +# *********************************************************************************************** +# Valid response start lines +@1001 +@break +@response +HTTP/1.1 200 OK\r\n\r\n + +@1002 +@break +@response +HTTP/1.0 315 An example reason phrase with spaces\r\n\r\n + +@1003 +@break +@response +HTTP/2.0 100 minimum valid status code\r\n\r\n + +@1004 +@break +@response +HTTP/1.1 599 MAX\r\n\r\n + +@1005 +@break +@response +HTTP/1.1 502 I'm very long and contain punctuationabcdefghijklmnopqrstuvwxyz!@#$%^&*()-_=+`~![]{}1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghij +|\\;:'",<.>/?klmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstu vwxyz123 4567890abcdefghijklmnopqrstuvwxyz1234567890abcd +efghijklmnopqrstuvwxyz1234567890\r\n\r\n + +@1006 +@break +@response +HTTP/1.0 200 1\r\n\r\n + +@1007 +@break +@response +HTTP/1.1 301 \r\n\r\n + +@1008 +@break +@response +HTTP/1.1 560 \r\n\r\n + +@1010 +@break +@response +HTTP/1.1 111 \r\n\r\n + +@1011 +@break +@response +HTTP/1.1 234 qwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ qwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDE{}[] +FGHIJKLMNOPQRSTUVWXYZ qwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ qwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ qwertyuiopasdfghjklzxcv +bnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklz +xcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghj +klzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdf +ghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ,.;'<>:"qwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwe +rtyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZqwertyuiopasdfghjklzxcvbnmABCDEFGHIJKLMNOPQRSTUVWXYZ +\r\n\r\n + + +# *********************************************************************************************** +# Invalid response start lines + +@2001 +@break +@response +HTTP/2.0 099 status-code too small\r\n\r\n + +@2002 +@break +@response +HTTP/1.1 600 status-code too large\r\n\r\n + +@2003 +@break +@response +HTTP/1.1 879 x\r\n\r\n + +@2004 +@break +@response +HTTP/1.1 000 x----------------------------------------------------------s\r\n\r\n + +@2005 +@break +@response +HTTP/1.1 999 \r\n\r\n + +@2006 +@break +@response +HTTP/0.9 401 aaa\r\n\r\n + +@2007 +@break +@response +HTTP/1.2 344 \r\n\r\n + +@2008 +@break +@response +HTTP/2.1 200 400\r\n\r\n + +@2009 +@break +@response +HTTP/8.0 500 HTTP/1.1\r\n\r\n + +@2010 +@break +@response +HTOP/1.1 378 :"<>;'m,.\r\n\r\n + +@2011 +@break +@response +hTTP/1.1 400 OK\r\n\r\n + +@2012 +@break +@response +HTT5/1.1 200 OK\r\n\r\n + +@2013 +@break +@response +H TP/1.1 588 OK\r\n\r\n + +@2014 +@break +@response +HTTP\\1.1 101 OK\r\n\r\n + +@2015 +@break +@response +HTTP/1.A 700 OK\r\n\r\n + +@2016 +@break +@response +HTTP/1.a 303 OK \r\n\r\n + +@2017 +@break +@response +HTTP/k.1 400 OK\tok\r\n\r\n + +@2018 +@break +@response +HTTP/1;0 400 O K\r\n\r\n + +@2019 +@break +@response +HTTP/1.0 444 O\rK\r\n\r\n + +@2020 +@break +@response +HTTP/1.1 444 O\n K1234567890\r\n\r\n + +@2021 +@break +@response +HTTP/1.1 46 status code too short\r\n\r\n + +@2022 +@break +@response +HTTP/1.1 2001 status code too \x31 long\r\n\r\n + +@2023 +@break +@response +HTTP/1.1 +88 bad character in status code I\r\n\r\n + +@2024 +@break +@response +HTTP/1.1 5E8 bad character in status code\x20II\r\n\r\n + +@2025 +@break +@response +HTTP/1.1 56a bad character in status code III\r\n\r\n + +@2026 +@break +@response +HTTP/1.0 401 illegal nontext char\x00acter in reason phrase null\r\n\r\n + +@2027 +@break +@response +HTTP/1.0 401 illegal nontext character in reason\xffphrase delete\r\n\r\n + +@2028 +@break +@response +HTTP/1.0 401 \x08illegal nontext character in reason phrase backspace\r\n\r\n + + +# *********************************************************************************************** +# Valid request start lines +@3001 +@break +@request +MKREDIRECTREF / HTTP/2.0\r\n\r\n + +@3002 +@break +@request +BIND 1234567890?abcdef HTTP/1.1\r\n\r\n + +@3003 +@break +@request +GET /test/hi-there.txt HTTP/1.0\r\n\r\n + + + +# *********************************************************************************************** +# Invalid request start lines +@4001 +@break +@request + + +# *********************************************************************************************** +# Valid headers without body +@5001 +@break +@request +GET /test/hi-there.htm HTTP/1.1\r\nAccept: text/*\r\nAccept-Language: en,fr\r\n\r\n + +# Base example followed by whitespace variations before and after header field values. +# Varying whitespace in comma-separated fields. +@5002 +@break +@request +GET / HTTP/1.1\r\nAccept: */*\r\nAccept-Language: en,en-us,de,is\r\nAccept-Encoding: gzip, deflate\r\nUser-Agent: Mozilla/4.0\r\n\t(compatible: MSIE 5.5; + Windows NT 5.0)\r\nHost: www.ft.com\r\nConnection: Keep-Alive\r\n\r\n + +@5003 +# Whitespace variations before header values +@break +@request +GET / HTTP/1.1\r\nAccept:*/*\r\nAccept-Language: en, en-us,\tde, is\r\nAccept-Encoding:\tgzip,deflate\r\nUser-Agent:\t\t\t\t\tMozilla/4.0\r\n (compatible: MSIE 5.5; + Windows NT 5.0)\r\nHost:\t \t \t \t\twww.ft.com\r\nConnection: \t Keep-Alive\r\n\r\n + +@5004 +# Whitespace variations after header values +@break +@request +GET / HTTP/1.1\r\nAccept: */* \r\nAccept-Language: en,\t\ten-us,\t de,\r\n is\t\r\nAccept-Encoding: gzip,\tdeflate \r\nUser-Agent: Mozilla/4.0\r\n\t \r\n\t(compatible: MSIE 5.5; + Windows NT 5.0)\t\t\t \t\t\r\nHost: www.ft.com \t\r\n\t\t\r\nConnection: Keep-Alive \t \t \t\r\n\r\n + +@5005 +@break +@response +HTTP/2.0 200 OK\r\nContent-type: text/plain\r\nTransfer-Encoding: gzip,identity,compress,deflate,foo,chunked\r\n\r\n + +@5006 +@break +@response +HTTP/2.0 200 OK\r\nContent-type: text/plain\r\nTransfer-Encoding: gzip, identity, compress, deflate, foo, chunked\r\n\r\n + +@5007 +@break +@response +HTTP/2.0 200 OK\r\nContent-type: text/plain\r\nTransfer-Encoding: gzip\r\nTransfer-Encoding: identity\r\nTransfer-Encoding: compress\r\nTransfer-Encoding: deflate +\r\nTransfer-Encoding: foo\r\nTransfer-Encoding: chunked\r\n\r\n + + +# *********************************************************************************************** +# Invalid headers without body +@6001 +@break +@request + + +# *********************************************************************************************** +# Valid Content-Length and body +@7001 +@break +@bodyend +@response +HTTP/1.1 200 OK\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 19\r\n\r\n +\/19/ + +@7002 +@break +@bodyend +@response +HTTP/1.1 200 OK\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 19\r\n\r\n +\/10/ + +@7003 +@break +@bodyend +@response +HTTP/1.1 200 OK\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 19\r\n\r\n + +\/10/ + +@7004 +@break +@bodyend +@response +HTTP/1.1 200 OK\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 19\r\n\r\n + +\/19/ + +@7005 +@break +@bodyend +@response +HTTP/1.1 200 OK\r\n +Transfer-Encoding: identity\r\n +CoNtEnT-lEnGtH:16382\r\n +Content-type: text/plain\r\n +\r\n +\/800/ + +@7006 +@break +@bodyend +@request +POST /body/in/a/request/ HTTP/1.1\r\n +Content-Length:16383\r\n +\r\n + +\/1300/ + +@7007 +@break +@bodyend +@request +POST /body/in/a/request/ HTTP/1.1\r\n +Content-Length:16384\r\n +\r\n +\/1/ + +@7008 +@break +@bodyend +@response +HTTP/1.0 408 barely too big for one section\r\n +Content-Length:16385\r\n +\r\n + +\/1/ + +@7009 +@break +@bodyend +@response +HTTP/1.1 408 barely too big for two sections\r\n +Content-Length:32772\r\n +\r\n +\/2/ + +@7010 +@break +@bodyend +@response +HTTP/1.1 408 more than eight sections\r\n +Content-Length:133072\r\n +\r\n + +\/2/ + +# *********************************************************************************************** +# Invalid Content-Length +@8001 +@break +@response +HTTP/2.0 200 Illegal zero length\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 0\r\n\r\n + +@8002 +@break +@response +HTTP/1.1 200 Silly gigantic length\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 12345678901 +23456789\r\n\r\n + + +# *********************************************************************************************** +# Valid chunks +# Remember chunk lengths are required to be specified in hex +@9001 +@break +@chunkend +@response +HTTP/1.1 208 Example with chunks\r\nTransfer-Encoding: chunked\r\n\r\n + +64\r\n +\/100/ + +8A\r\n +\/60/ + +e6\r\n + +\/1/ + +0\r\n + +\r\n + +@9002 +@break +@chunkend +@request +POST /request/with/chunks HTTP/1.1\r\n +Content-Type: text/plain\r\n +Transfer-Encoding: compress\r\n +Via: 1.1 proxy3.company.com\r\n +tRaNsFeR-eNcO\CdInG: cHuNkEd \r\n +Accept: *\r\n +\r\n + +1C320\r\n +\/1400/ + +1F40; testing-the-extension-feature\r\n +\/2/ + +13FFE; \r\n + +\/1460/ + +4000;\r\n +\/1460/ + +3FFF;\r\n + +\/2/ + +3FFE\r\n +\/1454/ + +4001;\r\n +\/1/ + +00000;chunkextension=3\r\n + +date: Sun, 01 Oct 2000 23:25:17 GMT\r\n +X-madeupdate: Mon, 02 Oct 2000 23:25:17 GMT\r\n +Accept-Language: en, de\r\n +Accept-Language: is\r\n +\r\n + +# *********************************************************************************************** +# Invalid chunks +@10001 +@break +@request + + + +# *********************************************************************************************** +# Valid trailers +@11001 +@break +@request + + + +# *********************************************************************************************** +# Invalid trailers +@12001 +@break +@request + + + +# *********************************************************************************************** +# Valid request-response pairs +@13001 +@break +@request + + + + + + + +