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__NOTPRESENT=-1, METH__OTHER=1, METH_GET, METH_HEAD, METH_POST, METH_PUT, METH_DELETE, METH_TRACE,
- METH_CONNECT, METH_PROPFIND,
+typedef enum { METH__NOTCOMPUTE=-4, METH__INSUFMEMORY=-3, METH__PROBLEMATIC=-2, METH__NOTPRESENT=-1, METH__OTHER=1, METH_OPTIONS, 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;
+// URI formats
+typedef enum { URI__NOTCOMPUTE=-4, URI__PROBLEMATIC=-2, URI__NOTPRESENT=-1, URI_ASTERISK = 2, URI_AUTHORITY, URI_ABSPATH, URI_ABSOLUTE } UriType;
+
+// URI schemes
+typedef enum { SCH__NOTCOMPUTE=-4, SCH__INSUFMEMORY=-3, SCH__NOTPRESENT=-1, SCH_OTHER = 1, SCH_HTTP, SCH_HTTPS, SCH_FTP, SCH_GOPHER, SCH_FILE } SchemeId;
+
// 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,
// All the infractions we might find while parsing and analyzing a message
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;
+ INF_BROKENCHUNK=0x2000, INF_BADCHUNKSIZE=0x4000, INF_BADPHRASE=0x8000, INF_BADURI=0x10000, INF_BADPORT=0x20000 } Infraction;
// Formats for output from a header normalization function
typedef enum { NORM_NULL, NORM_FIELD, NORM_INT64, NORM_ENUM64, NORM_ENUM64LIST } NormFormat;
} // 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;
+// Length values <= 0 are StatusCode values and imply that the start pointer is meaningless.
+// Never use the start pointer without verifying that length > 0.
+struct field {
+public:
+ const uint8_t *start;
+ int32_t length;
+};
typedef enum
{
chunkOctets[sourceId] = STAT_NOTPRESENT;
versionId[sourceId] = VERS__NOTPRESENT;
- if (sourceId == SRC_CLIENT) methodId = METH__NOTPRESENT;
- else statusCodeNum = STAT_NOTPRESENT;
+ methodId[sourceId] = METH__NOTPRESENT;
+ schemeId[sourceId] = SCH__NOTPRESENT;
+ statusCodeNum[sourceId] = STAT_NOTPRESENT;
}
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
+ // Some items don't apply in both directions. Have two copies anyway just to simplify code and minimize hard-to-find bugs
+ NHttpEnums::VersionId versionId[2] = { NHttpEnums::VERS__NOTPRESENT, NHttpEnums::VERS__NOTPRESENT };
+ NHttpEnums::MethodId methodId[2] = { NHttpEnums::METH__NOTPRESENT, NHttpEnums::METH__NOTPRESENT };
+ NHttpEnums::SchemeId schemeId[2] = { NHttpEnums::SCH__NOTPRESENT, NHttpEnums::SCH__NOTPRESENT };
+ int32_t statusCodeNum[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT };
+
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
-
- NHttpEnums::VersionId versionId[2] = { NHttpEnums::VERS__NOTPRESENT, NHttpEnums::VERS__NOTPRESENT };
- NHttpEnums::MethodId methodId = NHttpEnums::METH__NOTPRESENT;
- int32_t statusCodeNum = NHttpEnums::STAT_NOTPRESENT;
};
#endif
}
void NHttpMsgHeadShared::deriveHeaderNameId(int index) {
- if (headerName[index].length <= 0) return;
+ 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) {
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);
+ norm2Lower(headerName[index].start, headerName[index].length, lowerName, infractions, nullptr);
+ headerNameId[index] = (HeaderId) strToCode(lowerName, headerName[index].length, headerList);
}
void NHttpMsgHeadShared::genEvents() {
#include "snort.h"
#include "nhttp_enum.h"
+#include "nhttp_head_norm.h"
#include "nhttp_msg_request.h"
using namespace NHttpEnums;
NHttpMsgStart::initSection();
method.length = STAT_NOTCOMPUTE;
uri.length = STAT_NOTCOMPUTE;
+ uriLegacyNorm.length = STAT_NOTCOMPUTE;
+ uriType = URI__NOTCOMPUTE;
+ scheme.length = STAT_NOTCOMPUTE;
+ schemeId = SCH__NOTCOMPUTE;
+ host.length = STAT_NOTCOMPUTE;
+ hostNorm.length = STAT_NOTCOMPUTE;
+ port.length = STAT_NOTCOMPUTE;
+ portValue = STAT_NOTCOMPUTE;
+ path.length = STAT_NOTCOMPUTE;
+ pathNorm.length = STAT_NOTCOMPUTE;
+ query.length = STAT_NOTCOMPUTE;
+ queryNorm.length = STAT_NOTCOMPUTE;
+ fragment.length = STAT_NOTCOMPUTE;
+ fragmentNorm.length = STAT_NOTCOMPUTE;
}
// All the processing that is done for every message (i.e. not just-in-time) is done here.
void NHttpMsgRequest::analyze() {
NHttpMsgStart::analyze();
deriveMethodId();
+ parseUri();
+ deriveSchemeId();
+ parseAuthority();
+ derivePortValue();
+ parseAbsPath();
}
void NHttpMsgRequest::parseStartLine() {
methodId = (MethodId) strToCode(method.start, method.length, methodList);
}
+void NHttpMsgRequest::parseUri() {
+ if (uriType != URI__NOTCOMPUTE) return;
+ if (uri.length <= 0) {
+ uriType = URI__NOTPRESENT;
+ return;
+ }
+
+ // Four basic types of HTTP URI
+ // "*" means request does not apply to any specific resource
+ if ((uri.length == 1) && (uri.start[0] == '*')) {
+ uriType = URI_ASTERISK;
+ scheme.length = STAT_NOTPRESENT;
+ authority.length = STAT_NOTPRESENT;
+ absPath.length = STAT_NOTPRESENT;
+ }
+ // CONNECT method uses an authority
+ else if (methodId == METH_CONNECT) {
+ uriType = URI_AUTHORITY;
+ scheme.length = STAT_NOTPRESENT;
+ authority.length = uri.length;
+ authority.start = uri.start;
+ absPath.length = STAT_NOTPRESENT;
+ }
+ // Absolute path is a path but no scheme or authority
+ else if (uri.start[0] == '/') {
+ uriType = URI_ABSPATH;
+ scheme.length = STAT_NOTPRESENT;
+ authority.length = STAT_NOTPRESENT;
+ absPath.length = uri.length;
+ absPath.start = uri.start;
+ }
+ // Absolute URI includes scheme, authority, and path
+ else {
+ // Find the "://" and then the "/"
+ int j;
+ int k;
+ for (j = 0; (uri.start[j] != ':') && (j < uri.length); j++);
+ for (k = j+3; (uri.start[k] != '/') && (k < uri.length); k++);
+ if ((k < uri.length) && (uri.start[j+1] == '/') && (uri.start[j+2] == '/')) {
+ uriType = URI_ABSOLUTE;
+ scheme.length = j;
+ scheme.start = uri.start;
+ authority.length = k - j - 3;
+ authority.start = uri.start + j + 3;
+ absPath.length = uri.length - k;
+ absPath.start = uri.start + k;
+ }
+ else {
+ infractions |= INF_BADURI;
+ uriType = URI__PROBLEMATIC;
+ scheme.length = STAT_PROBLEMATIC;
+ authority.length = STAT_PROBLEMATIC;
+ absPath.length = STAT_PROBLEMATIC;
+ }
+ }
+}
+
+void NHttpMsgRequest::deriveSchemeId() {
+ if (schemeId != SCH__NOTCOMPUTE) return;
+ if (scheme.length <= 0) return;
+
+ // Normalize scheme name to lower case for matching purposes
+ uint8_t *lowerScheme;
+ if ((lowerScheme = scratchPad.request(scheme.length)) == nullptr) {
+ infractions |= INF_NOSCRATCH;
+ schemeId = SCH__INSUFMEMORY;
+ return;
+ }
+ norm2Lower(scheme.start, scheme.length, lowerScheme, infractions, nullptr);
+ schemeId = (SchemeId) strToCode(lowerScheme, scheme.length, schemeList);
+}
+
+void NHttpMsgRequest::parseAuthority() {
+ if (host.length != STAT_NOTCOMPUTE) return;
+ if (authority.length <= 0) return;
+ host.start = authority.start;
+ for (host.length = 0; (authority.start[host.length] != ':') && (host.length < authority.length); host.length++);
+ if (host.length < authority.length) {
+ port.length = authority.length - host.length - 1;
+ port.start = authority.start + host.length + 1;
+ }
+ else port.length = STAT_NOTPRESENT;
+}
+
+void NHttpMsgRequest::derivePortValue() {
+ if (portValue != SCH__NOTCOMPUTE) return;
+ if (port.length <= 0) return;
+ portValue = 0;
+ for (int k = 0; k < port.length; k++) {
+ portValue = portValue * 10 + (port.start[k] - '0');
+ if ((port.start[k] < '0') || (port.start[k] > '9') || (portValue > 65535))
+ {
+ infractions |= INF_BADURI;
+ portValue = STAT_PROBLEMATIC;
+ break;
+ }
+ }
+}
+
+void NHttpMsgRequest::parseAbsPath() {
+ if (path.length != STAT_NOTCOMPUTE) return;
+ if (absPath.length <= 0) return;
+ path.start = absPath.start;
+ for (path.length = 0; (absPath.start[path.length] != '?') && (absPath.start[path.length] != '#') && (path.length < absPath.length); path.length++);
+ if (path.length == absPath.length) {
+ query.length = STAT_NOTPRESENT;
+ fragment.length = STAT_NOTPRESENT;
+ return;
+ }
+ if (absPath.start[path.length] == '?') {
+ query.start = absPath.start + path.length + 1;
+ for (query.length = 0; (query.start[query.length] != '#') && (query.length < absPath.length - path.length - 1); query.length++);
+ fragment.start = query.start + query.length + 1;
+ fragment.length = absPath.length - path.length - 1 - query.length - 1;
+ }
+ else {
+ query.length = STAT_NOTPRESENT;
+ fragment.start = absPath.start + path.length + 1;
+ fragment.length = absPath.length - path.length - 1;
+ }
+}
+
void NHttpMsgRequest::genEvents() {
if (infractions != 0) SnortEventqAdd(NHTTP_GID, EVENT_ASCII); // I'm just an example event
}
if (versionId != VERS__NOTCOMPUTE) fprintf(output, "Version Id: %d\n", versionId);
if (methodId != METH__NOTCOMPUTE) fprintf(output, "Method Id: %d\n", methodId);
printInterval(output, "URI", uri.start, uri.length);
+ if (uriType != URI__NOTCOMPUTE) fprintf(output, "URI Type: %d\n", uriType);
+ printInterval(output, "Scheme", scheme.start, scheme.length);
+ if (schemeId != SCH__NOTCOMPUTE) fprintf(output, "Scheme Id: %d\n", schemeId);
+ printInterval(output, "Authority", authority.start, authority.length);
+ printInterval(output, "Host Name", host.start, host.length);
+ printInterval(output, "Normalized Host Name", hostNorm.start, hostNorm.length);
+ printInterval(output, "Port", port.start, port.length);
+ if (portValue != STAT_NOTCOMPUTE) fprintf(output, "Port Value: %d\n", portValue);
+ printInterval(output, "Absolute Path", absPath.start, absPath.length);
+ printInterval(output, "Path", path.start, path.length);
+ printInterval(output, "Normalized Path", pathNorm.start, pathNorm.length);
+ printInterval(output, "Query", query.start, query.length);
+ printInterval(output, "Normalized Query", queryNorm.start, queryNorm.length);
+ printInterval(output, "Fragment", fragment.start, fragment.length);
+ printInterval(output, "Normalized Fragment", fragmentNorm.start, fragmentNorm.length);
NHttpMsgSection::printMessageWrapup(output);
}
else {
sessionData->typeExpected[sourceId] = SEC_HEADER;
sessionData->versionId[sourceId] = versionId;
- sessionData->methodId = methodId;
+ sessionData->methodId[sourceId] = methodId;
+ sessionData->schemeId[sourceId] = schemeId;
}
}
void NHttpMsgRequest::legacyClients() const {
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 (uriLegacyNorm.length > 0) SetHttpBuffer(HTTP_BUFFER_URI, uriLegacyNorm.start, (unsigned)uriLegacyNorm.length);
}
private:
// Code conversion tables are for turning token strings into enums.
static const StrCode methodList[];
+ static const StrCode schemeList[];
// "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 parseStartLine();
void deriveMethodId();
+ void parseUri();
+ void deriveSchemeId();
+ void parseAuthority();
+ void derivePortValue();
+ void parseAbsPath();
// 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
+ // Note that these are all scalars, buffer pointers, and buffer sizes. The actual buffers are in the message buffer (raw pieces) or the
// scratchPad (normalized pieces).
field method;
+
+ // URI stuff
field uri;
+ field uriLegacyNorm;
+ NHttpEnums::UriType uriType;
+ field scheme;
+ field authority;
+ field host;
+ field hostNorm;
+ field port;
+ int32_t portValue;
+ field absPath;
+ field path;
+ field pathNorm;
+ field query;
+ field queryNorm;
+ field fragment;
+ field fragmentNorm;
};
#endif
sourceId = sessionData->sourceId;
tcpClose = sessionData->tcpClose;
versionId = sessionData->versionId[sourceId];
- methodId = sessionData->methodId;
- statusCodeNum = sessionData->statusCodeNum;
+ methodId = sessionData->methodId[sourceId];
+ schemeId = sessionData->schemeId[sourceId];
+ statusCodeNum = sessionData->statusCodeNum[sourceId];
scratchPad.reinit();
}
NHttpEnums::SourceId sourceId;
NHttpEnums::VersionId versionId;
NHttpEnums::MethodId methodId;
+ NHttpEnums::SchemeId schemeId;
int32_t statusCodeNum;
};
else {
sessionData->typeExpected[sourceId] = SEC_HEADER;
sessionData->versionId[sourceId] = versionId;
- sessionData->statusCodeNum = statusCodeNum;
+ sessionData->statusCodeNum[sourceId] = statusCodeNum;
}
}
using namespace NHttpEnums;
const StrCode NHttpMsgRequest::methodList[] =
- {{ METH_GET, "GET"},
+ {{ METH_OPTIONS, "OPTIONS"},
+ { METH_GET, "GET"},
{ METH_HEAD, "HEAD"},
{ METH_POST, "POST"},
{ METH_PUT, "PUT"},
{ METH_UPDATEREDIRECTREF, "UPDATEREDIRECTREF"},
{ 0, nullptr} };
+const StrCode NHttpMsgRequest::schemeList[] =
+ {{ SCH_HTTP, "http"},
+ { SCH_HTTPS, "https"},
+ { SCH_FTP, "ftp"},
+ { SCH_GOPHER, "gopher"},
+ { SCH_FILE, "file"},
+ { 0, nullptr} };
+
const StrCode NHttpMsgHeadShared::headerList[] =
{{ HEAD_CACHE_CONTROL, "cache-control"},
{ HEAD_CONNECTION, "connection"},
@3002
@break
@request
-BIND 1234567890?abcdef HTTP/1.1\r\n\r\n
+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
+@3004
+@break
+@request
+GET /URI/Absolute/Path/Example HTTP/1.1\r\n\r\n
+@3005
+@break
+@request
+GET /uri/aBSOLUTE/pATH/eXAMPLE?with-query HTTP/1.1\r\n\r\n
+
+@3006
+@break
+@request
+MKWORKSPACE /URI/Absolute/Path/Example#with-fragment HTTP/1.1\r\n\r\n
+
+@3007
+@break
+@request
+BPROPFIND /URI/Absolute/Path/Example?with_QUERY#AndAFragmentToo HTTP/1.1\r\n\r\n
+
+@3008
+@break
+@request
+GET /?with-query HTTP/1.1\r\n\r\n
+
+@3009
+@break
+@request
+ORDERPATCH /#with-fragment HTTP/1.1\r\n\r\n
+
+@3010
+@break
+@request
+UPDATEREDIRECTREF /?with_QUERY#AndAFragmentToo HTTP/1.1\r\n\r\n
+
+@3011
+@break
+@request
+VERYLONGUNKNOWNMETHODTEST /?# HTTP/1.1\r\n\r\n
+
+@3012
+@break
+@request
+LABEL /stuff?# HTTP/1.1\r\n\r\n
+
+@3013
+@break
+@request
+MKCALENDAR /*stufflotsandlotslotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsofstuff?1# HTTP/1.1\r\n\r\n
+
+@3014
+@break
+@request
+VERSION-CONTROL /*stufflotsandlotslotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsandlotsofstuff?#A HTTP/1.1\r\n\r\n
+
+@3015
+@break
+@request
+OPTIONS * HTTP/1.1\r\n\r\n
+
+@3016
+@break
+@request
+OPTIONS /*options/may/have?a*resource HTTP/1.1\r\n\r\n
+
+@3017
+@break
+@request
+CONNECT this.is.an.authority.com HTTP/1.1\r\n\r\n
+
+@3018
+@break
+@request
+CONNECT this.is.an.authority.with.a.port.com:8739 HTTP/1.1\r\n\r\n
+
+@3019
+@break
+@request
+GET http://iamahost.com/simple/example/of/a/path/ HTTP/1.1\r\n\r\n
+
+@3020
+@break
+@request
+GET HtTpS://1.2.3.4.5.a:6/abcdef/ghijklmnop/qrstuvwxyz/?thequery?fieldcontinues?until#afragme#arrives#1234 HTTP/1.1\r\n\r\n
# ***********************************************************************************************
# Invalid request start lines