From: Tom Peters Date: Fri, 2 May 2014 20:12:56 +0000 (-0400) Subject: Initial commit for NHttpInspect. X-Git-Tag: 3.0.0-233~1529^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=df3d133481faeb208ee139b61627430aceb8d2b6;p=thirdparty%2Fsnort3.git Initial commit for NHttpInspect. --- diff --git a/configure.ac b/configure.ac index 97883fa14..ba983c9c6 100644 --- a/configure.ac +++ b/configure.ac @@ -893,6 +893,7 @@ src/service_inspectors/Makefile \ src/service_inspectors/back_orifice/Makefile \ src/service_inspectors/ftp_telnet/Makefile \ src/service_inspectors/http_inspect/Makefile \ +src/service_inspectors/nhttp_inspect/Makefile \ src/service_inspectors/rpc_decode/Makefile \ src/protocols/Makefile \ src/search_engines/Makefile \ diff --git a/src/Makefile.am b/src/Makefile.am index 0ce392a1c..aa42adca6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,7 +12,8 @@ network_inspectors/arp_spoof/libarp_spoof.a \ network_inspectors/port_scan/libport_scan.a \ service_inspectors/back_orifice/libback_orifice.a \ service_inspectors/ftp_telnet/libftp_telnet.a \ -service_inspectors/rpc_decode/librpc_decode.a +service_inspectors/rpc_decode/librpc_decode.a \ +service_inspectors/nhttp_inspect/libnhttp_inspect.a endif # order libs to avoid undefined symbols diff --git a/src/service_inspectors/Makefile.am b/src/service_inspectors/Makefile.am index 0fa1b8904..b5ad88cf0 100644 --- a/src/service_inspectors/Makefile.am +++ b/src/service_inspectors/Makefile.am @@ -18,6 +18,7 @@ SUBDIRS = \ back_orifice \ ftp_telnet \ http_inspect \ +nhttp_inspect \ rpc_decode AM_CXXFLAGS = @AM_CXXFLAGS@ diff --git a/src/service_inspectors/nhttp_inspect/Makefile.am b/src/service_inspectors/nhttp_inspect/Makefile.am new file mode 100644 index 000000000..db9ffca58 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/Makefile.am @@ -0,0 +1,29 @@ +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_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 + + +#if STATIC_INSPECTORS +noinst_LIBRARIES = libnhttp_inspect.a +libnhttp_inspect_a_SOURCES = $(file_list) +#else +# need to fix undefineds first :( +#shlibdir = $(pkglibdir)/inspectors +#shlib_LTLIBRARIES = libnhttp_inspect.la +#libnhttp_inspect_la_CXXFLAGS = $(AM_CXXFLAGS) -DBUILDING_SO +#libnhttp_inspect_la_LDFLAGS = -export-dynamic -shared +#libnhttp_inspect_la_SOURCES = $(file_list) +#endif + +AM_CXXFLAGS = @AM_CXXFLAGS@ + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_api.cc b/src/service_inspectors/nhttp_inspect/nhttp_api.cc new file mode 100644 index 000000000..6c42d7356 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_api.cc @@ -0,0 +1,145 @@ +/**************************************************************************** + * +** 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 API for NHttpInspect +// + + +#include +#include +#include + +#include "snort.h" +#include "framework/parameter.h" +#include "framework/module.h" +#include "framework/inspector.h" +#include "flow/flow.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" + +Module* NHttpApi::nhttp_mod_ctor() { return new NHttpModule; } + +void NHttpApi::nhttp_mod_dtor(Module* m) { delete m; } + +const char* NHttpApi::nhttp_myName = "nhttp_inspect"; + +void NHttpApi::nhttp_init() +{ + printf("nhttp_init()\n"); + NHttpFlowData::init(); +} + +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; +} + +void NHttpApi::nhttp_pterm() +{ + printf("nhttp_pterm()\n"); + delete NHttpInspect::msgHead; +} + +void NHttpApi::nhttp_purge() +{ + printf("nhttp_purge()\n"); +} + +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 = +{ + { + PT_INSPECTOR, + NHttpApi::nhttp_myName, + INSAPI_PLUGIN_V0, + 0, + NHttpApi::nhttp_mod_ctor, + NHttpApi::nhttp_mod_dtor + }, + PRIORITY_APPLICATION, + PROTO_BIT__TCP, + NHttpApi::nhttp_init, + NHttpApi::nhttp_term, + NHttpApi::nhttp_ctor, + NHttpApi::nhttp_dtor, + NHttpApi::nhttp_pinit, + NHttpApi::nhttp_pterm, + NHttpApi::nhttp_purge, + NHttpApi::nhttp_sum, + NHttpApi::nhttp_stats, + NHttpApi::nhttp_reset +}; + +#ifdef BUILDING_SO +SO_PUBLIC const BaseApi* snort_plugins[] = +{ + &NHttpApi::nhttp_api.base, + nullptr +}; +#else +const BaseApi* sin_nhttp = &NHttpApi::nhttp_api.base; +#endif + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_api.h b/src/service_inspectors/nhttp_inspect/nhttp_api.h new file mode 100644 index 000000000..79f4ec0e9 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_api.h @@ -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 Noninstantiated class to collect static framework API functions and facilitate friendships. +// + +#ifndef NHTTP_API_H +#define NHTTP_API_H + +class NHttpApi { +public: + static const InspectApi nhttp_api; +private: + NHttpApi() = delete; + static Module* nhttp_mod_ctor(); + static void nhttp_mod_dtor(Module* m); + static const char* nhttp_myName; + static void nhttp_init(); + static void nhttp_term(); + static Inspector* nhttp_ctor(Module* mod); + 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(); +}; + +#endif + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_enum.h b/src/service_inspectors/nhttp_inspect/nhttp_enum.h new file mode 100644 index 000000000..46355c666 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_enum.h @@ -0,0 +1,85 @@ +/**************************************************************************** + * +** 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 enumerations +// + +#ifndef NHTTP_ENUM_H +#define NHTTP_ENUM_H + +#define GID_HTTP_CLIENT 119 +#define GID_HTTP_SERVER 120 + +namespace NHttpEnums { + +// 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; + +// Type of message section +typedef enum { SEC__PROBLEMATIC=-2, SEC_HEADER = 2, SEC_BODY, SEC_CHUNK } 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; + +// Every request method we have ever heard of +typedef enum { METH__NOTCOMPUTE=-4, METH__INSUFMEMORY=-3, METH__PROBLEMATIC=-2, METH__OTHER=1, METH_GET, METH_HEAD, METH_POST, METH_PUT, METH_DELETE, METH_TRACE, METH_CONNECT, METH_PROPFIND, + METH_PROPPATCH, METH_MKCOL, METH_COPY, METH_MOVE, METH_LOCK, METH_UNLOCK, METH_VERSION_CONTROL, METH_REPORT, METH_CHECKOUT, METH_CHECKIN, METH_UNCHECKOUT, + METH_MKWORKSPACE, METH_UPDATE, METH_LABEL, METH_MERGE, METH_BASELINE_CONTROL, METH_MKACTIVITY, METH_ORDERPATCH, METH_ACL, METH_PATCH, METH_SEARCH, METH_BCOPY, + METH_BDELETE, METH_BMOVE, METH_BPROPFIND, METH_BPROPPATCH, METH_NOTIFY, METH_POLL, METH_SUBSCRIBE, METH_UNSUBSCRIBE, METH_X_MS_ENUMATTS, METH_BIND, METH_LINK, + METH_MKCALENDAR, METH_MKREDIRECTREF, METH_REBIND, METH_UNBIND, METH_UNLINK, METH_UPDATEREDIRECTREF } MethodId; + +// Every header we have ever heard of +typedef enum { HEAD__NOTCOMPUTE=-4, HEAD__INSUFMEMORY=-3, HEAD__PROBLEMATIC=-2, HEAD__NOTPRESENT=-1, HEAD__OTHER=1, HEAD_CACHE_CONTROL, HEAD_CONNECTION, HEAD_DATE, + HEAD_PRAGMA, HEAD_TRAILER, HEAD_COOKIE, HEAD_SET_COOKIE, + HEAD_TRANSFER_ENCODING, HEAD_UPGRADE, HEAD_VIA, HEAD_WARNING, HEAD_ACCEPT, HEAD_ACCEPT_CHARSET, HEAD_ACCEPT_ENCODING, HEAD_ACCEPT_LANGUAGE, HEAD_AUTHORIZATION, + HEAD_EXPECT, HEAD_FROM, HEAD_HOST, HEAD_IF_MATCH, HEAD_IF_MODIFIED_SINCE, HEAD_IF_NONE_MATCH, HEAD_IF_RANGE, HEAD_IF_UNMODIFIED_SINCE, HEAD_MAX_FORWARDS, + HEAD_PROXY_AUTHORIZATION, HEAD_RANGE, HEAD_REFERER, HEAD_TE, HEAD_USER_AGENT, HEAD_ACCEPT_RANGES, HEAD_AGE, HEAD_ETAG, HEAD_LOCATION, HEAD_PROXY_AUTHENTICATE, + HEAD_RETRY_AFTER, HEAD_SERVER, HEAD_VARY, HEAD_WWW_AUTHENTICATE, HEAD_ALLOW, HEAD_CONTENT_ENCODING, HEAD_CONTENT_LANGUAGE, HEAD_CONTENT_LENGTH, + 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; + +// Formats for output from a header normalization function +typedef enum { NORM_NULL, NORM_FIELD, NORM_INTEGER, NORM_ENUM, NORM_ENUMLIST } NormFormat; + +// Transfer codings +typedef enum { TRANSCODE__OTHER=1, TRANSCODE_CHUNKED, TRANSCODE_IDENTITY, TRANSCODE_GZIP, TRANSCODE_COMPRESS, TRANSCODE_DEFLATE } Transcoding; + +} // end namespace NHttpEnums + +// Individual pieces of the message found during parsing +// Set length to -1 when field has no value +typedef struct { + const uint8_t *start = nullptr; + int32_t length = -1; +} field; + +#endif + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_flowdata.cc b/src/service_inspectors/nhttp_inspect/nhttp_flowdata.cc new file mode 100644 index 000000000..2ac2a73ef --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_flowdata.cc @@ -0,0 +1,46 @@ +/**************************************************************************** + * +** 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 "flow/flow.h" +#include "nhttp_enum.h" +#include "nhttp_flowdata.h" + +unsigned NHttpFlowData::nhttp_flow_id = 0; + +NHttpFlowData::NHttpFlowData() : FlowData(nhttp_flow_id) +{ +} + +NHttpFlowData::~NHttpFlowData() +{ +} + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_flowdata.h b/src/service_inspectors/nhttp_inspect/nhttp_flowdata.h new file mode 100644 index 000000000..9d865f9ad --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_flowdata.h @@ -0,0 +1,57 @@ +/**************************************************************************** + * +** 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_FLOWDATA_H +#define NHTTP_FLOWDATA_H + +class NHttpFlowData : public FlowData +{ +public: + NHttpFlowData(); + ~NHttpFlowData(); + static unsigned nhttp_flow_id; + static void init() { nhttp_flow_id = FlowData::get_flow_id(); }; + + // PAF data + NHttpEnums::SourceId sourceId = NHttpEnums::SRC_CLIENT; + bool tcpClose = false; + uint64_t infractions = 0; + + // Client message data + NHttpEnums::SectionType clientTypeExpected = NHttpEnums::SEC_HEADER; + int32_t clientOctetsExpected = NHttpEnums::STAT_NOTPRESENT; + + // Server message data + NHttpEnums::SectionType serverTypeExpected = NHttpEnums::SEC_HEADER; + int32_t serverOctetsExpected = NHttpEnums::STAT_NOTPRESENT; + + // Session data +}; + +#endif + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_headnorm.cc b/src/service_inspectors/nhttp_inspect/nhttp_headnorm.cc new file mode 100644 index 000000000..cd5b960f5 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_headnorm.cc @@ -0,0 +1,187 @@ +/**************************************************************************** + * +** 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_scratchpad.h" +#include "nhttp_strtocode.h" +#include "nhttp_headnorm.h" + +using namespace NHttpEnums; + +// This derivation removes embedded CRLFs (wrapping), omits leading and trailing 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) const { + int32_t outLength = 0; + bool lastWhite = true; + for (int 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 + bufferLength += (4-bufferLength%4)%4 + 200; // &&& 200 is a "way too big" fudge factor to allow for modest expansion of field size during normalization. Needs improvement. + uint8_t *scratch; + if ((scratch = scratchPad.request(2*bufferLength)) == 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 growth; + int32_t dataLength = 0; + for (int j=0; j < numMatches; j++) { + if (j >= 1) { + *working++ = ','; + dataLength++; + while (headerNameId[++currMatch] != headId); + } + 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 *notUsed) { + uint32_t total = 0; + int value; + for (int32_t k=0; k < inLength; k++) { + value = inBuf[k] - '0'; + if ((value < 0) || (value > 9)) { + infractions |= INF_BADHEADERDATA; + return STAT_PROBLEMATIC; + } + total = total*10 + value; + } + ((uint32_t*)outBuf)[0] = total; + return sizeof(uint32_t); +} + + +int32_t norm2Lower(const uint8_t* inBuf, int32_t inLength, uint8_t* outBuf, uint64_t& infractions, const void *notUsed) { + 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& infractions, const void *table) { + ((uint32_t*)outBuf)[0] = strToCode(inBuf, inLength, (const StrCode*)table); + return sizeof(uint32_t); +} + +int32_t normSeqStrCode(const uint8_t* inBuf, int32_t inLength, uint8_t* outBuf, uint64_t& infractions, const void *table) { + int32_t numCodes = 0; + const uint8_t* start = inBuf; + int32_t length = 0; + while (true) { + start += length; + for (length = 0; (start + length < inBuf + inLength) && (start[length] != ','); length++); + if (length == 0) ((uint32_t*)outBuf)[numCodes++] = STAT_EMPTYSTRING; + else ((uint32_t*)outBuf)[numCodes++] = strToCode(start, length, (const StrCode*)table); + if (start + length++ >= inBuf + inLength) break; + } + return numCodes * sizeof(uint32_t); +} + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_headnorm.h b/src/service_inspectors/nhttp_inspect/nhttp_headnorm.h new file mode 100644 index 000000000..5a3162660 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_headnorm.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 HeaderNormalizer class declaration +// + +#ifndef NHTTP_HEADNORM_H +#define NHTTP_HEADNORM_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: + int32_t deriveHeaderContent(const uint8_t *value, int32_t length, uint8_t *buffer) const; + + 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*); + +#endif + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_inspect.cc b/src/service_inspectors/nhttp_inspect/nhttp_inspect.cc new file mode 100644 index 000000000..fb6e1966a --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_inspect.cc @@ -0,0 +1,148 @@ +/**************************************************************************** + * +** 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 Inspector class. +// + + +#include +#include +#include +#include +#include + +#include "snort.h" +#include "framework/inspector.h" +#include "flow/flow.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_api.h" +#include "nhttp_inspect.h" + +const char* NHttpInspect::testInputFile = "nhttptestmsgs.txt"; +const char* NHttpInspect::testOutputPrefix = "nhttpresults/testcase"; +THREAD_LOCAL NHttpMsgHeader* NHttpInspect::msgHead; + +NHttpInspect::NHttpInspect(bool _test_mode) : test_mode(_test_mode) +{ + printf("NHttpInspect constructor()\n"); + if (test_mode) { + testInput = new NHttpTestInput(testInputFile); + } +} + +NHttpInspect::~NHttpInspect () +{ + printf("NHttpInspect destructor()\n"); + if (test_mode) { + delete testInput; + if (testOut) fclose(testOut); + } +} + +bool NHttpInspect::enabled () +{ + printf("NHttpInspect enabled()\n"); + return true; +} + +void NHttpInspect::configure (SnortConfig *sc, const char*, char *args) +{ + printf("NHttpInspect configure()\n"); +} + +int NHttpInspect::verify(SnortConfig* sc) +{ + 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"); + + 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); + else { + uint8_t *testBuffer; + int32_t testLength; + if ((testLength = testInput->ntiGet(&testBuffer, sessionData, testNumber)) > 0) { + msgHead->loadMessage(testBuffer, testLength, sessionData); + } + else { + printf("Out of test data.\n"); + return; + } + } + + msgHead->analyze(); + + msgHead->genEvents(); + + // Interface to the old Snort clients + msgHead->oldClients(); + + if (!test_mode) msgHead->printMessage(stdout); + else { + if (testNumber != fileTestNumber) { + if (testOut) fclose (testOut); + fileTestNumber = testNumber; + char fileName[100]; + sprintf(fileName, "%s%d.txt", testOutputPrefix, testNumber); + if ((testOut = fopen(fileName, "w+")) == nullptr) throw std::runtime_error("Cannot open test output file"); + } + msgHead->printMessage(testOut); + } +} + + + + + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_inspect.h b/src/service_inspectors/nhttp_inspect/nhttp_inspect.h new file mode 100644 index 000000000..4a1d577e1 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_inspect.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * +** 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. + * + ****************************************************************************/ + +#ifndef NHTTP_INSPECT_H +#define NHTTP_INSPECT_H + +//------------------------------------------------------------------------- +// NHttpInspect class +//------------------------------------------------------------------------- + +class NHttpInspect : public Inspector { +public: + NHttpInspect(bool _test_mode); + ~NHttpInspect(); + + void configure(SnortConfig*, const char*, char *args); + int verify(SnortConfig*); + void show(SnortConfig*); + void eval(Packet*); + bool enabled(); + void pinit(); + void pterm(); + +private: + friend NHttpApi; + static THREAD_LOCAL NHttpMsgHeader *msgHead; + + // Test mode + bool test_mode; + static const char *testInputFile; + static const char *testOutputPrefix; + NHttpTestInput *testInput = nullptr; + FILE *testOut = nullptr; + int64_t testNumber = 0; + int64_t fileTestNumber = -1; +}; + +#endif + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_module.cc b/src/service_inspectors/nhttp_inspect/nhttp_module.cc new file mode 100644 index 000000000..04ac352e9 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_module.cc @@ -0,0 +1,66 @@ +/**************************************************************************** + * +** 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 Module class for NHttpInspect +// + + +#include +#include +#include +#include "snort.h" +#include "framework/module.h" +#include "nhttp_enum.h" +#include "nhttp_module.h" + +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; + } + return false; +} + +unsigned NHttpModule::get_gid() const { + printf("NHttpModule get_gid()\n"); + return GID_HTTP_CLIENT; +} + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_module.h b/src/service_inspectors/nhttp_inspect/nhttp_module.h new file mode 100644 index 000000000..22f1f8f25 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_module.h @@ -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 NHttpModule +// + +#ifndef NHTTP_MODULE_H +#define NHTTP_MODULE_H + +class NHttpModule : public Module +{ +public: + NHttpModule() : Module("nhttp_inspect", nhttpParams) { printf("NHttpModule constructor()\n"); }; + bool begin(const char*, int, SnortConfig*); + bool end(const char*, int, SnortConfig*); + bool set(const char*, Value&, SnortConfig*); + unsigned get_gid() const; + bool get_test_mode() const { return test_mode; }; +private: + static const Parameter nhttpParams[]; + bool test_mode = false; +}; + +#endif + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msgheader.cc b/src/service_inspectors/nhttp_inspect/nhttp_msgheader.cc new file mode 100644 index 000000000..cbaaff2f7 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msgheader.cc @@ -0,0 +1,412 @@ +/**************************************************************************** + * +** 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 messages. +// + + +#include +#include +#include +#include + +#include "snort.h" +#include "flow/flow.h" +#include "detection/detection_util.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" + +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 NHttpMsgHeader::findCrlf(const uint8_t* buffer, uint32_t length, bool wrappable) { + for (uint32_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; +} + +// Reinitialize everything and load a new message +void NHttpMsgHeader::loadMessage(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_) { + length = (bufsize <= MAXOCTETS) ? bufsize : MAXOCTETS; + memcpy(rawBuf, buffer, length); + + sessionData = sessionData_; + infractions = sessionData->infractions; + sourceId = sessionData->sourceId; + tcpClose = sessionData->tcpClose; + + scratchPad.reinit(); + + 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; + 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 NHttpMsgHeader::analyze() { + parseWhole(); + if (sourceId == SRC_CLIENT) { + parseRequestLine(); + deriveMethodId(); + } + else if (sourceId == SRC_SERVER) { + parseStatusLine(); + deriveStatusCodeNum(); + } + deriveVersionId(); + 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]); + } +} + +// 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))); + // 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))); + + // 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; + assert (startLine.length == version.length + statusCode.length + reasonPhrase.length + 2); +} + +// Divide up the block of header fields into individual header field lines. +void NHttpMsgHeader::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 NHttpMsgHeader::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 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::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 NHttpMsgHeader::genEvents() { + if (infractions != 0) SnortEventqAdd(GID_HTTP_CLIENT, 1); // I'm just an example event (HI_CLIENT_ASCII) +} + +void NHttpMsgHeader::printInterval(FILE *output, const char* name, const uint8_t *text, int32_t length, bool intVals) { + if ((length == STAT_NOTPRESENT) || (length == STAT_NOTCOMPUTE)) return; + fprintf(output, "%s, length = %d\n", name, length); + if (length <= 0) return; + if (text == nullptr) { + fprintf(output, "nullptr\n"); + return; + } + for (int k=0; k < length; k++) { + if ((text[k] >= 0x20) && (text[k] <= 0x7E)) fprintf(output, "%c", (char)text[k]); + else if (text[k] == 0x0) fprintf(output, "~"); + else if (text[k] == 0xD) fprintf(output, "`"); + else if (text[k] == 0xA) fprintf(output, "'"); + else fprintf(output, "*"); + if (k%200 == 199) fprintf(output, "\n"); + } + + if (intVals && (length%4 == 0)) { + fprintf(output, "\nInteger values ="); + for (int j=0; j < length; j+=4) { + fprintf(output, " %u", *((const uint32_t*)(text+j))); + } + } + fprintf(output, "\n"); +} + +void NHttpMsgHeader::printMessage(FILE *output) { + fprintf(output, "Printout of HTTP message structure.\n"); + printInterval(output, "Raw message", msgText, length); + printInterval(output, "Start Line", startLine.start, startLine.length); + if (sourceId != SRC__NOTCOMPUTE) fprintf(output, "Source Id: %d\n", sourceId); + printInterval(output, "Version", version.start, version.length); + if (versionId != VERS__NOTCOMPUTE) fprintf(output, "Version Id: %d\n", versionId); + printInterval(output, "Method", method.start, method.length); + if (methodId != METH__NOTCOMPUTE) fprintf(output, "Method Id: %d\n", methodId); + printInterval(output, "URI", uri.start, uri.length); + printInterval(output, "Status Code", statusCode.start, statusCode.length); + if (statusCodeNum != STAT_NOTCOMPUTE) fprintf(output, "Status Code Num: %d\n", statusCodeNum); + printInterval(output, "Reason Phrase", reasonPhrase.start, reasonPhrase.length); + printInterval(output, "Headers", headers.start, headers.length); + if (numHeaders != STAT_NOTCOMPUTE) fprintf(output, "Number of headers: %d\n", numHeaders); + for (int j=0; j < numHeaders && j < 200; j++) { + printInterval(output, "Header Line", headerLine[j].start, headerLine[j].length); + printInterval(output, "Header Name", headerName[j].start, headerName[j].length); + fprintf(output, "Header name Id: %d\n", headerNameId[j]); + printInterval(output, "Header Value", headerValue[j].start, headerValue[j].length); + } + for (int k=1; k <= numNorms; k++) { + if (headerValueNorm[k].length != STAT_NOTPRESENT) fprintf(output, "Header ID = %d\n", k); + printInterval(output, "Header Value Normalized", headerValueNorm[k].start, headerValueNorm[k].length, true); + } + fprintf(output, "Infractions: %lx\n", infractions); + fprintf(output, "TCP Close: %s\n", 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); + } +} + +// Legacy support function. Puts message fields into the buffers used by old Snort. This should go away. +void NHttpMsgHeader::oldClients() { + ClearHttpBuffers(); + + 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 (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); + 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); + + 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); +} + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msgheader.h b/src/service_inspectors/nhttp_inspect/nhttp_msgheader.h new file mode 100644 index 000000000..adc859f0c --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_msgheader.h @@ -0,0 +1,140 @@ +/**************************************************************************** + * +** 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_MSGHEADER_H +#define NHTTP_MSGHEADER_H + + +//------------------------------------------------------------------------- +// NHttpMsgHeader class +//------------------------------------------------------------------------- + +class NHttpMsgHeader { +public: + NHttpMsgHeader() {}; + void loadMessage(const uint8_t *buffer, const uint16_t bufsize, NHttpFlowData *sessionData_); + void analyze(); + void printMessage(FILE *output); + void genEvents(); + void oldClients(); // I'm a legacy support method and should go away eventually + static const uint32_t MAXOCTETS = 65535; + +private: + // 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 methodList[]; + 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. + void parseWhole(); + void deriveSourceId(); + void parseRequestLine(); + void parseStatusLine(); + void parseHeaderBlock(); + void parseHeaderLines(); + void deriveHeaderNameId(int index); + void deriveStatusCodeNum(); + void deriveVersionId(); + void deriveMethodId(); + + // Convenience methods + uint32_t findCrlf(const uint8_t* buffer, uint32_t length, bool wrappable); + void printInterval(FILE *output, const char* name, const uint8_t *text, int32_t length, bool intVals = false); + + // The current strategy is to copy the entire raw message headers into this object. Here it is. + uint32_t length; // Length of the original message headers in octets + uint8_t rawBuf[MAXOCTETS]; // The original HTTP message header 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. + uint32_t derivedBuf[MAXOCTETS/4]; + NHttpFlowData* sessionData; + ScratchPad scratchPad {derivedBuf, MAXOCTETS/4}; + + // 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 original message buffer (raw pieces) or the + // scratchPad (normalized pieces). + uint64_t infractions; + bool tcpClose; + field startLine; + NHttpEnums::SourceId sourceId; + field version; + NHttpEnums::VersionId versionId; + field method; + NHttpEnums::MethodId methodId; + field uri; + field statusCode; + int32_t statusCodeNum; + field reasonPhrase; + 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_scratchpad.h b/src/service_inspectors/nhttp_inspect/nhttp_scratchpad.h new file mode 100644 index 000000000..fd568f8fe --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_scratchpad.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_SCRATCHPAD_H +#define NHTTP_SCRATCHPAD_H + + +//------------------------------------------------------------------------- +// ScratchPad class +// Memory management for NHttpMsgHeader class +//------------------------------------------------------------------------- + +// Working space and storage for all the derived fields +// Return value of request is 32-bit aligned and may be freely cast to uint32_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(uint32_t *buff, uint32_t length) : buffer(buff), capacity(length*4), used(0) {}; // Careful: length must be number of uint32_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 + (4-(taken%4))%4; }; // round up to multiple of 4 to preserve alignment + +private: + uint32_t *buffer; + uint32_t capacity; + uint32_t used; +}; + +#endif + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_strtocode.cc b/src/service_inspectors/nhttp_inspect/nhttp_strtocode.cc new file mode 100644 index 000000000..5ad95a40c --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_strtocode.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_strtocode.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_strtocode.h b/src/service_inspectors/nhttp_inspect/nhttp_strtocode.h new file mode 100644 index 000000000..5aceece78 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_strtocode.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_STRTOCODE_H +#define NHTTP_STRTOCODE_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_tables.cc b/src/service_inspectors/nhttp_inspect/nhttp_tables.cc new file mode 100644 index 000000000..38bb8006e --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_tables.cc @@ -0,0 +1,216 @@ +/**************************************************************************** + * +** 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 Static constant tables for converting protocol strings to enum codes. Members of HttpMsgHeader. +// + + +#include +#include + +#include "snort.h" +#include "flow/flow.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" + +using namespace NHttpEnums; + +const StrCode NHttpMsgHeader::methodList[] = + {{ METH_GET, "GET"}, + { METH_HEAD, "HEAD"}, + { METH_POST, "POST"}, + { METH_PUT, "PUT"}, + { METH_DELETE, "DELETE"}, + { METH_TRACE, "TRACE"}, + { METH_CONNECT, "CONNECT"}, + { METH_PROPFIND, "PROPFIND"}, + { METH_PROPPATCH, "PROPPATCH"}, + { METH_MKCOL, "MKCOL"}, + { METH_COPY, "COPY"}, + { METH_MOVE, "MOVE"}, + { METH_LOCK, "LOCK"}, + { METH_UNLOCK, "UNLOCK"}, + { METH_VERSION_CONTROL, "VERSION-CONTROL"}, + { METH_REPORT, "REPORT"}, + { METH_CHECKOUT, "CHECKOUT"}, + { METH_CHECKIN, "CHECKIN"}, + { METH_UNCHECKOUT, "UNCHECKOUT"}, + { METH_MKWORKSPACE, "MKWORKSPACE"}, + { METH_UPDATE, "UPDATE"}, + { METH_LABEL, "LABEL"}, + { METH_MERGE, "MERGE"}, + { METH_BASELINE_CONTROL, "BASELINE-CONTROL"}, + { METH_MKACTIVITY, "MKACTIVITY"}, + { METH_ORDERPATCH, "ORDERPATCH"}, + { METH_ACL, "ACL"}, + { METH_PATCH, "PATCH"}, + { METH_SEARCH, "SEARCH"}, + { METH_BCOPY, "BCOPY"}, + { METH_BDELETE, "BDELETE"}, + { METH_BMOVE, "BMOVE"}, + { METH_BPROPFIND, "BPROPFIND"}, + { METH_BPROPPATCH, "BPROPPATCH"}, + { METH_NOTIFY, "NOTIFY"}, + { METH_POLL, "POLL"}, + { METH_SUBSCRIBE, "SUBSCRIBE"}, + { METH_UNSUBSCRIBE, "UNSUBSCRIBE"}, + { METH_X_MS_ENUMATTS, "X-MS-ENUMATTS"}, + { METH_BIND, "BIND"}, + { METH_LINK, "LINK"}, + { METH_MKCALENDAR, "MKCALENDAR"}, + { METH_MKREDIRECTREF, "MKREDIRECTREF"}, + { METH_REBIND, "REBIND"}, + { METH_UNBIND, "UNBIND"}, + { METH_UNLINK, "UNLINK"}, + { METH_UPDATEREDIRECTREF, "UPDATEREDIRECTREF"}, + { 0, nullptr} }; + +const StrCode NHttpMsgHeader::headerList[] = + {{ HEAD_CACHE_CONTROL, "cache-control"}, + { HEAD_CONNECTION, "connection"}, + { HEAD_DATE, "date"}, + { HEAD_PRAGMA, "pragma"}, + { HEAD_TRAILER, "trailer"}, + { HEAD_COOKIE, "cookie"}, + { HEAD_SET_COOKIE, "set-cookie"}, + { HEAD_TRANSFER_ENCODING, "transfer-encoding"}, + { HEAD_UPGRADE, "upgrade"}, + { HEAD_VIA, "via"}, + { HEAD_WARNING, "warning"}, + { HEAD_ACCEPT, "accept"}, + { HEAD_ACCEPT_CHARSET, "accept-charset"}, + { HEAD_ACCEPT_ENCODING, "accept-encoding"}, + { HEAD_ACCEPT_LANGUAGE, "accept-language"}, + { HEAD_AUTHORIZATION, "authorization"}, + { HEAD_EXPECT, "expect"}, + { HEAD_FROM, "from"}, + { HEAD_HOST, "host"}, + { HEAD_IF_MATCH, "if-match"}, + { HEAD_IF_MODIFIED_SINCE, "if-modified-since"}, + { HEAD_IF_NONE_MATCH, "if-none-match"}, + { HEAD_IF_RANGE, "if-range"}, + { HEAD_IF_UNMODIFIED_SINCE, "if-unmodified-since"}, + { HEAD_MAX_FORWARDS, "max-forwards"}, + { HEAD_PROXY_AUTHORIZATION, "proxy-authorization"}, + { HEAD_RANGE, "range"}, + { HEAD_REFERER, "referer"}, + { HEAD_TE, "te"}, + { HEAD_USER_AGENT, "user-agent"}, + { HEAD_ACCEPT_RANGES, "accept-ranges"}, + { HEAD_AGE, "age"}, + { HEAD_ETAG, "etag"}, + { HEAD_LOCATION, "location"}, + { HEAD_PROXY_AUTHENTICATE, "proxy-authenticate"}, + { HEAD_RETRY_AFTER, "retry-after"}, + { HEAD_SERVER, "server"}, + { HEAD_VARY, "vary"}, + { HEAD_WWW_AUTHENTICATE, "www-authenticate"}, + { HEAD_ALLOW, "allow"}, + { HEAD_CONTENT_ENCODING, "content-encoding"}, + { HEAD_CONTENT_LANGUAGE, "content-language"}, + { HEAD_CONTENT_LENGTH, "content-length"}, + { HEAD_CONTENT_LOCATION, "content-location"}, + { HEAD_CONTENT_MD5, "content-md5"}, + { HEAD_CONTENT_RANGE, "content-range"}, + { HEAD_CONTENT_TYPE, "content-type"}, + { HEAD_EXPIRES, "expires"}, + { HEAD_LAST_MODIFIED, "last-modified"}, + { 0, nullptr} }; + +const StrCode NHttpMsgHeader::transCodeList[] = + {{ TRANSCODE_CHUNKED, "chunked"}, + { TRANSCODE_IDENTITY, "identity"}, + { TRANSCODE_GZIP, "gzip"}, + { TRANSCODE_COMPRESS, "compress"}, + { 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* const NHttpMsgHeader::headerNorms[HEAD__MAXVALUE] = { [0] = &NORMALIZER_NIL, + [HEAD__OTHER] = &NORMALIZER_BASIC, + [HEAD_CACHE_CONTROL] = &NORMALIZER_BASIC, + [HEAD_CONNECTION] = &NORMALIZER_BASIC, + [HEAD_DATE] = &NORMALIZER_BASIC, + [HEAD_PRAGMA] = &NORMALIZER_BASIC, + [HEAD_TRAILER] = &NORMALIZER_BASIC, + [HEAD_COOKIE] = &NORMALIZER_BASIC, + [HEAD_SET_COOKIE] = &NORMALIZER_BASIC, + [HEAD_TRANSFER_ENCODING] = &NORMALIZER_TRANSCODE, + [HEAD_UPGRADE] = &NORMALIZER_BASIC, + [HEAD_VIA] = &NORMALIZER_BASIC, + [HEAD_WARNING] = &NORMALIZER_BASIC, + [HEAD_ACCEPT] = &NORMALIZER_BASIC, + [HEAD_ACCEPT_CHARSET] = &NORMALIZER_BASIC, + [HEAD_ACCEPT_ENCODING] = &NORMALIZER_BASIC, + [HEAD_ACCEPT_LANGUAGE] = &NORMALIZER_BASIC, + [HEAD_AUTHORIZATION] = &NORMALIZER_BASIC, + [HEAD_EXPECT] = &NORMALIZER_BASIC, + [HEAD_FROM] = &NORMALIZER_BASIC, + [HEAD_HOST] = &NORMALIZER_BASIC, + [HEAD_IF_MATCH] = &NORMALIZER_BASIC, + [HEAD_IF_MODIFIED_SINCE] = &NORMALIZER_BASIC, + [HEAD_IF_NONE_MATCH] = &NORMALIZER_BASIC, + [HEAD_IF_RANGE] = &NORMALIZER_BASIC, + [HEAD_IF_UNMODIFIED_SINCE] = &NORMALIZER_BASIC, + [HEAD_MAX_FORWARDS] = &NORMALIZER_BASIC, + [HEAD_PROXY_AUTHORIZATION] = &NORMALIZER_BASIC, + [HEAD_RANGE] = &NORMALIZER_BASIC, + [HEAD_REFERER] = &NORMALIZER_BASIC, + [HEAD_TE] = &NORMALIZER_BASIC, + [HEAD_USER_AGENT] = &NORMALIZER_BASIC, + [HEAD_ACCEPT_RANGES] = &NORMALIZER_BASIC, + [HEAD_AGE] = &NORMALIZER_BASIC, + [HEAD_ETAG] = &NORMALIZER_BASIC, + [HEAD_LOCATION] = &NORMALIZER_BASIC, + [HEAD_PROXY_AUTHENTICATE] = &NORMALIZER_BASIC, + [HEAD_RETRY_AFTER] = &NORMALIZER_BASIC, + [HEAD_SERVER] = &NORMALIZER_BASIC, + [HEAD_VARY] = &NORMALIZER_BASIC, + [HEAD_WWW_AUTHENTICATE] = &NORMALIZER_BASIC, + [HEAD_ALLOW] = &NORMALIZER_BASIC, + [HEAD_CONTENT_ENCODING] = &NORMALIZER_BASIC, + [HEAD_CONTENT_LANGUAGE] = &NORMALIZER_BASIC, + [HEAD_CONTENT_LENGTH] = &NORMALIZER_DECIMAL, + [HEAD_CONTENT_LOCATION] = &NORMALIZER_BASIC, + [HEAD_CONTENT_MD5] = &NORMALIZER_BASIC, + [HEAD_CONTENT_RANGE] = &NORMALIZER_BASIC, + [HEAD_CONTENT_TYPE] = &NORMALIZER_BASIC, + [HEAD_EXPIRES] = &NORMALIZER_BASIC, + [HEAD_LAST_MODIFIED] = &NORMALIZER_BASIC +}; + + const int32_t NHttpMsgHeader::numNorms = HEAD__MAXVALUE-1; + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_testinput.cc b/src/service_inspectors/nhttp_inspect/nhttp_testinput.cc new file mode 100644 index 000000000..e264e7f7b --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_testinput.cc @@ -0,0 +1,162 @@ +/**************************************************************************** + * +** 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 test messages +// + + +#include +#include +#include +#include +#include +#include "snort.h" +#include "flow/flow.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" + +NHttpTestInput::NHttpTestInput(const char *fileName) { + if ((msgFile = fopen(fileName, "r")) == nullptr) throw std::runtime_error("Cannot open test input file"); +} + +NHttpTestInput::~NHttpTestInput() { + fclose(msgFile); +} + +// Read the next message section from the test data file. +// In the process we may need to skip comments, execute simple commands, and handle simple escape sequences. +// The best way to understand this function is to read the comments at the top of the file of test cases. +int32_t NHttpTestInput::ntiGet(uint8_t **buffer, NHttpFlowData* sessionData, int64_t &testNumber) { + int32_t length = 0; + *buffer = msgBuf; + int newChar; + typedef enum { WAITING, COMMENT, COMMAND, SECTION, ESCAPE0, ESCAPE1, ESCAPE2 } State; + State state = WAITING; + bool ending; + char escapeNum[] = { 0, 0, '\0' }; + int commandLength = 0; + const int MaxCommand = 100; + char commandValue[MaxCommand]; + + sessionData->tcpClose = false; + sessionData->infractions = 0; + + while ((newChar = getc(msgFile)) != EOF) { + switch (state) { + case WAITING: + if (newChar == '#') state = COMMENT; + else if (newChar == '@') { + state = COMMAND; + commandLength = 0; + } + else if (newChar == '\\') { + state = ESCAPE0; + ending = false; + } + else if (newChar != '\n') { + state = SECTION; + ending = false; + msgBuf[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"))) sessionData->sourceId = NHttpEnums::SRC_CLIENT; + else if ((commandLength == strlen("response")) && !memcmp(commandValue, "response", strlen("response"))) sessionData->sourceId = NHttpEnums::SRC_SERVER; + else if ((commandLength == strlen("tcpclose")) && !memcmp(commandValue, "tcpclose", strlen("tcpclose"))) sessionData->tcpClose = true; + else if ((commandLength == strlen("break")) && !memcmp(commandValue, "break", strlen("break"))) { + // &&& signifies start of new session so wipe "session data" when we define some. See flowdata.h. + } + else if (commandLength > 0) { + // Look for a test number + bool isNumber = true; + for (int k=0; isNumber && (k < commandLength); 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; + } + break; + case SECTION: + if (newChar == '\\') { + state = ESCAPE0; + } + else if (newChar == '\n') { + if (ending) return length; // Found the blank line that ends the section. + ending = true; + } + else { + ending = false; + msgBuf[length++] = (uint8_t) newChar; + } + break; + case ESCAPE0: + if (newChar == 'n') { state = SECTION; ending = false; msgBuf[length++] = '\n'; } + else if (newChar == 'r') { state = SECTION; ending = false; msgBuf[length++] = '\r'; } + else if (newChar == 't') { state = SECTION; ending = false; msgBuf[length++] = '\t'; } + else if (newChar == '#') { state = SECTION; ending = false; msgBuf[length++] = '#'; } + else if (newChar == '@') { state = SECTION; ending = false; msgBuf[length++] = '@'; } + else if (newChar == '\\') { state = SECTION; ending = false; msgBuf[length++] = '\\'; } + else if ((newChar == 'x') || (newChar == 'X')) state = ESCAPE1; + else { state = SECTION; ending = false; } + break; + case ESCAPE1: + state = ESCAPE2; + escapeNum[0] = newChar; + break; + case ESCAPE2: + state = SECTION; + ending = false; + escapeNum[1] = newChar; + if (((escapeNum[0] < '0') || (escapeNum[0] > '9')) && ((escapeNum[0] < 'A') || (escapeNum[0] > 'Z')) && ((escapeNum[0] < 'a') || (escapeNum[0] > 'z'))) break; + if (((escapeNum[1] < '0') || (escapeNum[1] > '9')) && ((escapeNum[1] < 'A') || (escapeNum[1] > 'Z')) && ((escapeNum[1] < 'a') || (escapeNum[1] > 'z'))) break; + msgBuf[length++] = strtoul(escapeNum, nullptr, 16); + break; + } + // Return because the buffer is full. Not a feature just a safety precaution against bad input. + if (length >= sizeof(msgBuf)) return length; + } + // End-of-file. Return everything we have so far. + return length; +} + + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_testinput.h b/src/service_inspectors/nhttp_inspect/nhttp_testinput.h new file mode 100644 index 000000000..2d578bd06 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttp_testinput.h @@ -0,0 +1,45 @@ +/**************************************************************************** + * +** 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_TESTINPUT_H +#define NHTTP_TESTINPUT_H + +// ntiGet() returns the same static buffer each time. +// Do not call it again before you are finished with the previous output. +class NHttpTestInput { +public: + NHttpTestInput(const char *fileName); + ~NHttpTestInput(); + int32_t ntiGet(uint8_t **buffer, NHttpFlowData* sessionData, int64_t &testNumber); +private: + FILE *msgFile; + uint8_t msgBuf[NHttpMsgHeader::MAXOCTETS]; +}; + +#endif + diff --git a/src/service_inspectors/nhttp_inspect/nhttptestmsgs.txt b/src/service_inspectors/nhttp_inspect/nhttptestmsgs.txt new file mode 100644 index 000000000..7595d4d97 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/nhttptestmsgs.txt @@ -0,0 +1,66 @@ +# A message section is the unit of data passed to the inspector and may by headers, body, a chunk, or trailing headers. +# Blank lines separate message sections. Extra blank lines don't have any effect. +# Lines beginning with '@' are control commands. +# You cannot put comments or control commands in the middle of a message section. +# If a message section begins with # or @ (presumably a very rare situation) you must escape it: \# or \@ +# Control commands: +# @request or @response sets the message direction. Applies to subsequent sections until changed. +# @tcpclose specifies that the section ends with a TCP close. Applies only to the very next section. +# @ sets the test number and hence the test output file name. Applies to subsequent sections until changed. Don't reuse numbers. +# Commands must be all lower case with no white space. +# The escape character is \ and it is used only within message sections. +# \# and \@ are only necessary for the very first character of a message section. \r, \n, \t, \x, \X, and \\ may appear anywhere in a section. +# \x and \X are identical and signify the beginning of an arbitrary two-digit hexadecimal escape value. Examples: 0x3A, 0X27, 0xeE. Case a-f or A-F +# does not matter. + +@1 +@response +HTTP/1.1 200 OK\r\n\r\n + +@2 +@response +HTTP/1.0 315 Redirection test with spaces\r\n\r\n + +@3 +@request +MKREDIRECTREF / HTTP/2.0\r\n\r\n + +@4 +@request +BIND 1234567890?abcdef HTTP/3.8\r\n\r\n + +@5 +@response +HTTP/2.0 600 Bogus-status-code\r\n\r\n + +@6 +@response +HTTP/1.9 26 Short?status++co\nde\r\n\r\n + +@7 +@response +HTTP/1.F 502 abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghij +klmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstu +vwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890\r\n\r\n + +@8 +@request +GET /test/hi-there.txt HTTP/1.0\r\n\r\n + +@9 +@request +GET /test/hi-there.htm HTTP/1.1\r\nAccept: text/*\r\nAccept-Language: en,fr\r\n\r\n + +@10 +@request +GET /test/trigger/hi-there.htm HTTP/1.1\r\nAccept: text/*\r\nAccept-Language: en,fr\r\n\r\n + +@11 +@request +@tcpclose +GET /test/trigger/hi-there.htm HTTP/1.1\r\nAccept: text/*\r\nAccept-Language: en,fr\r\n\r\n + +@12 +@response +HTTP/2.0 200 OK\r\nContent-type: \ttext/plain\t\r\nContent-LENGTH: 19\r\n\r\n + diff --git a/src/service_inspectors/service_inspectors.cc b/src/service_inspectors/service_inspectors.cc index 8e0bd5652..4ef91c57b 100644 --- a/src/service_inspectors/service_inspectors.cc +++ b/src/service_inspectors/service_inspectors.cc @@ -34,6 +34,7 @@ extern const BaseApi* sin_ftp_client; extern const BaseApi* sin_ftp_server; extern const BaseApi* sin_rpc_decode; extern const BaseApi* sin_telnet; +extern const BaseApi* sin_nhttp; #endif const BaseApi* service_inspectors[] = @@ -46,6 +47,7 @@ const BaseApi* service_inspectors[] = sin_ftp_server, sin_rpc_decode, sin_telnet, + sin_nhttp, #endif nullptr, };