From: Russ Combs Date: Sat, 21 Feb 2015 17:42:55 +0000 (-0500) Subject: sync 297 http xff, swf, and pdf updates X-Git-Tag: 3.0.0-233~1043 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c3cc27355ac302bd24ee3e9d613166898ec2be64;p=thirdparty%2Fsnort3.git sync 297 http xff, swf, and pdf updates --- diff --git a/ChangeLog b/ChangeLog index ce0192d88..c900aa8d9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Pending - build 139 + +-- sync 297 http xff, swf, and pdf updates + 15/02/20 - build 138 -- sync ftp with 297; replace stream event callbacks with FlowData virtuals diff --git a/src/service_inspectors/http_inspect/CMakeLists.txt b/src/service_inspectors/http_inspect/CMakeLists.txt index 27e3c4818..f643fd2b1 100644 --- a/src/service_inspectors/http_inspect/CMakeLists.txt +++ b/src/service_inspectors/http_inspect/CMakeLists.txt @@ -9,9 +9,14 @@ set (FILE_LIST hi_client.h hi_client_norm.cc hi_client_norm.h - hi_client_stateful.h hi_cmd_lookup.cc hi_cmd_lookup.h + hi_file_decomp.cc + hi_file_decomp.h + hi_file_decomp_pdf.cc + hi_file_decomp_pdf.h + hi_file_decomp_swf.cc + hi_file_decomp_swf.h hi_include.h hi_mi.cc hi_mi.h diff --git a/src/service_inspectors/http_inspect/Makefile.am b/src/service_inspectors/http_inspect/Makefile.am index e037f072a..135ed08c4 100644 --- a/src/service_inspectors/http_inspect/Makefile.am +++ b/src/service_inspectors/http_inspect/Makefile.am @@ -6,9 +6,11 @@ hi_main.cc hi_main.h \ hi_ad.cc hi_ad.h \ hi_client.cc hi_client.h \ hi_client_norm.cc hi_client_norm.h \ -hi_client_stateful.h \ hi_cmd_lookup.cc hi_cmd_lookup.h \ hi_events.cc hi_events.h \ +hi_file_decomp.cc hi_file_decomp.h \ +hi_file_decomp_pdf.cc hi_file_decomp_pdf.h \ +hi_file_decomp_swf.cc hi_file_decomp_swf.h \ hi_include.h \ hi_mi.cc hi_mi.h \ hi_module.cc hi_module.h \ diff --git a/src/service_inspectors/http_inspect/hi_client.cc b/src/service_inspectors/http_inspect/hi_client.cc index 6cded8df6..a5e922002 100644 --- a/src/service_inspectors/http_inspect/hi_client.cc +++ b/src/service_inspectors/http_inspect/hi_client.cc @@ -70,10 +70,10 @@ #define HEADER_LENGTH__COOKIE 6 #define HEADER_NAME__CONTENT_LENGTH "Content-length" #define HEADER_LENGTH__CONTENT_LENGTH 14 -#define HEADER_NAME__XFF "X-Forwarded-For" -#define HEADER_LENGTH__XFF 15 -#define HEADER_NAME__TRUE_IP "True-Client-IP" -#define HEADER_LENGTH__TRUE_IP 14 +#define HEADER_NAME__XFF HI_UI_CONFIG_XFF_FIELD_NAME +#define HEADER_LENGTH__XFF (sizeof(HEADER_NAME__XFF)-1) +#define HEADER_NAME__TRUE_IP HI_UI_CONFIG_TCI_FIELD_NAME +#define HEADER_LENGTH__TRUE_IP (sizeof(HEADER_NAME__TRUE_IP)-1) #define HEADER_NAME__HOSTNAME "Host" #define HEADER_LENGTH__HOSTNAME 4 #define HEADER_NAME__TRANSFER_ENCODING "Transfer-encoding" @@ -84,6 +84,19 @@ const u_char *proxy_start = NULL; const u_char *proxy_end = NULL; +// FIXIT-L for 2.9.7 code not yet ported in +/*static const char *g_field_names[] = +{ + HEADER_NAME__COOKIE, + HEADER_NAME__CONTENT_LENGTH, + HEADER_NAME__XFF, + HEADER_NAME__TRUE_IP, + HEADER_NAME__HOSTNAME, + HEADER_NAME__TRANSFER_ENCODING, + HEADER_NAME__CONTENT_TYPE, + NULL +};*/ + /** This makes passing function arguments much more readable and easier ** to follow. */ @@ -124,7 +137,7 @@ LOOKUP_FCN lookup_table[256]; */ int CheckChunkEncoding(HI_SESSION *session, const u_char *start, const u_char *end, const u_char **post_end, u_char *iChunkBuf, uint32_t max_size, - uint32_t chunk_remainder, uint32_t *updated_chunk_remainder, uint32_t *chunkRead, HttpsessionData *hsd, + uint32_t chunk_remainder, uint32_t *updated_chunk_remainder, uint32_t *chunkRead, HttpSessionData *hsd, int iInspectMode) { uint32_t iChunkLen = 0; @@ -1371,7 +1384,6 @@ static int SetClientVars(HI_CLIENT *Client, URI_PTR *uri_ptr, u_int dsize) printf("** second_end = %c\n", *uri_ptr->second_sp_end); if(uri_ptr->delimiter) printf("** delimiter = %c\n", *uri_ptr->delimiter); - if(uri_ptr->uri) printf("** uri = %c\n", *uri_ptr->uri); if(uri_ptr->norm) @@ -1404,11 +1416,8 @@ static int SetClientVars(HI_CLIENT *Client, URI_PTR *uri_ptr, u_int dsize) ** This is one of the last checks we do to make sure that we didn't ** mess up or anything. */ - if(Client->request.uri_size < 1 || Client->request.uri_size > dsize) + if(Client->request.uri_size > dsize) { - /* - ** Bad stuff, let's just bail. - */ return HI_NONFATAL_ERR; } @@ -1424,7 +1433,7 @@ static int SetClientVars(HI_CLIENT *Client, URI_PTR *uri_ptr, u_int dsize) static inline int hi_client_extract_post( HI_SESSION *session, HTTPINSPECT_CONF *ServerConf, const u_char *ptr, const u_char *end, URI_PTR *result, - int content_length, bool is_chunked, HttpsessionData *hsd) + int content_length, bool is_chunked, HttpSessionData *hsd) { const u_char *start = ptr; const u_char *post_end = end; @@ -1514,7 +1523,7 @@ static inline int HTTP_CopyExtraDataTosession(const uint8_t *start, int length, static inline void HTTP_CopyUri( HTTPINSPECT_CONF* /*ServerConf*/, const u_char *start, const u_char *end, - HttpsessionData *hsd, int stream_ins) + HttpSessionData *hsd, int stream_ins) { int iRet = 0; const u_char *cur_ptr; @@ -1536,7 +1545,7 @@ static inline void HTTP_CopyUri( } -static inline int unfold_http_uri(HTTPINSPECT_CONF *ServerConf, const u_char *end, URI_PTR *uri_ptr, HttpsessionData *hsd, int stream_ins) +static inline int unfold_http_uri(HTTPINSPECT_CONF *ServerConf, const u_char *end, URI_PTR *uri_ptr, HttpSessionData *hsd, int stream_ins) { uint8_t unfold_buf[DECODE_BLEN]; uint32_t unfold_size =0; @@ -1576,7 +1585,7 @@ static inline int unfold_http_uri(HTTPINSPECT_CONF *ServerConf, const u_char *en static inline int hi_client_extract_uri( HI_SESSION *session, HTTPINSPECT_CONF *ServerConf, HI_CLIENT * Client, const u_char *start, const u_char *end, - const u_char *ptr, URI_PTR *uri_ptr, HttpsessionData *hsd, int stream_ins) + const u_char *ptr, URI_PTR *uri_ptr, HttpSessionData *hsd, int stream_ins) { int iRet = HI_SUCCESS; const u_char *tmp; @@ -1584,7 +1593,6 @@ static inline int hi_client_extract_uri( session->norm_flags &= ~HI_BODY; - /* ** This loop compares each char to an array of functions ** (one for each char) and calling that function if there is one. @@ -1714,6 +1722,9 @@ static inline int hi_client_extract_uri( ptr++; } + /* No uri in this request. We shouldn't process this request */ + if(uri_ptr->uri == uri_ptr->uri_end) + return HI_NONFATAL_ERR; return iRet; } @@ -1796,7 +1807,7 @@ const u_char *extract_http_xff(HI_SESSION *session, const u_char *p, const u_cha if(!true_ip) return p; - if( (hdrs_args->true_clnt_xff & HDRS_BOTH) == HDRS_BOTH) + if( (hdrs_args->true_clnt_xff & (HDRS_BOTH | XFF_HEADERS)) == HDRS_BOTH) { hi_set_event(GID_HTTP_CLIENT, HI_CLIENT_BOTH_TRUEIP_XFF_HDRS); } @@ -1870,6 +1881,29 @@ const u_char *extract_http_xff(HI_SESSION *session, const u_char *p, const u_cha return p; } } + /* At this point we have a new/valid IP from the header being processed. + If we are using custom xff headers, check the precedence ranking. */ + if( (hdrs_args->true_clnt_xff & XFF_HEADERS) != 0 ) + { + /* Have we located any others? */ + if( (hdrs_args->top_precedence > 0) && + (hdrs_args->new_precedence >= hdrs_args->top_precedence) ) + { + sfip_free( tmp ); + free( ipAddr ); + return( p ); + } + + hdrs_args->top_precedence = hdrs_args->new_precedence; + + /* if we find the top precedence, no need to continue + looking so clear the XFF_HEADERS_ACTIVE flag. */ + if( hdrs_args->top_precedence == XFF_TOP_PRECEDENCE ) + hdrs_args->true_clnt_xff &= (~XFF_HEADERS_ACTIVE); + } + + /* If we have already set a 'true_ip' for the session, look to see if the + new IP differs from the current IP. If so, replace it and post an alert. */ if(*true_ip) { if(!sfip_equals(*true_ip, tmp)) @@ -1877,7 +1911,8 @@ const u_char *extract_http_xff(HI_SESSION *session, const u_char *p, const u_cha sfip_free(*true_ip); *true_ip = tmp; - hi_set_event(GID_HTTP_CLIENT, HI_CLIENT_MULTIPLE_TRUEIP_IN_SESSION); + if ((hdrs_args->true_clnt_xff & XFF_HEADERS) == 0) + hi_set_event(GID_HTTP_CLIENT, HI_CLIENT_MULTIPLE_TRUEIP_IN_SESSION); } else sfip_free(tmp); @@ -1901,7 +1936,7 @@ const u_char *extract_http_xff(HI_SESSION *session, const u_char *p, const u_cha const u_char *extract_http_hostname(HI_SESSION *session, const u_char *p, const u_char *start, - const u_char *end, HEADER_PTR *header_ptr, HttpsessionData *hsd) + const u_char *end, HEADER_PTR *header_ptr, HttpSessionData *hsd) { int num_spaces = 0; uint8_t unfold_buf[DECODE_BLEN]; @@ -2166,14 +2201,94 @@ const u_char *extract_http_content_length(HI_SESSION *session, return p; } +static inline bool IsXFFFieldName( HI_CLIENT_HDR_ARGS *hdrs_args, + u_char **pp, const u_char *end, + uint8_t **Field_Names, uint8_t *Field_Length ) +{ + int i; + int len; + uint8_t *header_ptr; + uint8_t *field_ptr; + + i = 0; // index into the list of XFF field names + field_ptr = NULL; // pointer into the active Field_Name entry + header_ptr = *pp; // pointer into the header, will not step past 'end' + len = 0; // len of the matched name entry + + while( true ) + { + /* If we run off the end of the active table, or table is truncated then + we can stop. We didn't locate a match. */ + if( (i >= (HI_UI_CONFIG_MAX_XFF_FIELD_NAMES)) || (Field_Names[i] == NULL) ) + break; + + if( field_ptr == NULL ) // didn't start to match any entry + { + /* If the length doesn't permit a match, move on. */ + if( (end - *pp) < Field_Length[i] ) + { + i += 1; + continue; + } + + if( toupper(*header_ptr) == *Field_Names[i] ) // does the first char match? + { + /* set our working pointer to the field name */ + field_ptr = (Field_Names[i] + 1); + header_ptr += 1; + len = 1; // We matched one character + continue; + } + i += 1; + } + else + { + /* If we are still matching and we get to the end + of the field name, then we've located a name match */ + if( *field_ptr == 0 ) // End of the field name + { + *pp += len; // Step input pointer over what we found + hdrs_args->new_precedence = (i+1); // Precedence started with one + return( true ); + } + else + { + /* check for another matching character */ + if( toupper(*header_ptr) == *field_ptr ) + { + header_ptr += 1; + field_ptr += 1; + len += 1; + } + else + { + header_ptr = *pp; // Back to the start for the name + field_ptr = NULL; // No longer a match + len = 0; + i += 1; + } + } + } + } + + return( false ); +} + static inline const u_char *extractHeaderFieldValues(HI_SESSION *session, HTTPINSPECT_CONF *ServerConf, const u_char *p, const u_char *offset, const u_char *start, const u_char *end, HI_CLIENT_HDR_ARGS *hdrs_args) { - HttpsessionData *hsd; + HttpSessionData *hsd; hsd = hdrs_args->sd; - if (((p - offset) == 0) && ((*p == 'C') || (*p == 'c'))) + if (((p - offset) == 0) && (ServerConf->enable_xff != 0) && + ((hdrs_args->true_clnt_xff & XFF_HEADERS_ACTIVE) != 0) && (hsd) && + IsXFFFieldName(hdrs_args, (u_char **)&p, (const u_char *)end, + ServerConf->xff_headers, ServerConf->xff_header_lengths)) + { + p = extract_http_xff(session, p, start, end, hdrs_args); + } + else if (((p - offset) == 0) && ((*p == 'C') || (*p == 'c'))) { /* Search for 'Cookie' at beginning, starting from current *p */ if ( ServerConf->enable_cookie && @@ -2193,7 +2308,8 @@ static inline const u_char *extractHeaderFieldValues(HI_SESSION *session, } else if (((p - offset) == 0) && ((*p == 'x') || (*p == 'X') || (*p == 't') || (*p == 'T'))) { - if ( (ServerConf->enable_xff) && hsd ) + //* The default/legacy behavior with two builtin XFF field names */ + if ( (ServerConf->enable_xff) && hsd && ((hdrs_args->true_clnt_xff & XFF_HEADERS) == 0) ) { if(IsHeaderFieldName(p, end, HEADER_NAME__XFF, HEADER_LENGTH__XFF)) { @@ -2274,7 +2390,7 @@ static inline const u_char *extractHeaderFieldValues(HI_SESSION *session, static inline const u_char *hi_client_extract_header( HI_SESSION *session, HTTPINSPECT_CONF *ServerConf, HEADER_PTR *header_ptr, const u_char *start, - const u_char *end, HttpsessionData *hsd, int stream_ins) + const u_char *end, HttpSessionData *hsd, int stream_ins) { int iRet = HI_SUCCESS; const u_char *p; @@ -2315,7 +2431,7 @@ static inline const u_char *hi_client_extract_header( hdrs_args.sd = hsd; hdrs_args.strm_ins = stream_ins; hdrs_args.hst_name_hdr = 0; - hdrs_args.true_clnt_xff = 0; + hdrs_args.true_clnt_xff = (ServerConf->xff_headers[0] != NULL) ? XFF_INIT : 0; SkipBlankSpace(start,end,&p); @@ -2505,10 +2621,20 @@ static inline const u_char *hi_client_extract_header( Client->request.header_raw = NULL;\ Client->request.header_raw_size = 0;\ Client->request.header_norm = NULL; \ + Client->request.header_norm_size = 0 ;\ Client->request.cookie.cookie = NULL;\ Client->request.cookie.cookie_end = NULL;\ + if(Client->request.cookie.next) { \ + COOKIE_PTR *cookie = Client->request.cookie.next; \ + do { \ + Client->request.cookie.next = Client->request.cookie.next->next; \ + free(cookie); \ + cookie = Client->request.cookie.next; \ + } while(cookie); \ + }\ Client->request.cookie.next = NULL;\ Client->request.cookie_norm = NULL;\ + Client->request.cookie_norm_size = 0;\ } while(0); #define CLR_METHOD(Client) \ @@ -2567,7 +2693,7 @@ static inline const u_char *hi_client_extract_header( ** @retval HI_SUCCESS URI detected and session pointers updated */ -int StatelessInspection(Packet *p, HI_SESSION *session, HttpsessionData *hsd, int stream_ins) +int StatelessInspection(Packet *p, HI_SESSION *session, HttpSessionData *hsd, int stream_ins) { HTTPINSPECT_CONF *ServerConf; HTTPINSPECT_CONF *ClientConf; @@ -2583,7 +2709,7 @@ int StatelessInspection(Packet *p, HI_SESSION *session, HttpsessionData *hsd, in const u_char *method_end = NULL; int method_len; int iRet=0; - char sans_uri = 0; + bool sans_uri = false; const unsigned char *data = p->data; int dsize = p->dsize; @@ -2710,19 +2836,19 @@ int StatelessInspection(Packet *p, HI_SESSION *session, HttpsessionData *hsd, in if ( !stream_ins ) hi_set_event(GID_HTTP_CLIENT, HI_CLIENT_UNKNOWN_METHOD); Client->request.method = HI_UNKNOWN_METHOD; - sans_uri = 1; + sans_uri = true; } } if (!sans_uri ) { - uri_ptr.uri = ptr; + uri_ptr.uri = method_ptr.uri_end; uri_ptr.uri_end = end; /* This will set up the URI pointers - effectively extracting * the URI. */ iRet = hi_client_extract_uri( - session, ServerConf, Client, start, end, ptr, &uri_ptr, hsd, stream_ins); + session, ServerConf, Client, start, end, uri_ptr.uri, &uri_ptr, hsd, stream_ins); } /* Check if the URI exceeds the max header field length */ @@ -2880,6 +3006,9 @@ int StatelessInspection(Packet *p, HI_SESSION *session, HttpsessionData *hsd, in iRet = SetClientVars(Client, &uri_ptr, dsize); if (iRet) { + CLR_HEADER(Client); + CLR_POST(Client); + CLR_METHOD(Client); return iRet; } /* @@ -2910,36 +3039,21 @@ int StatelessInspection(Packet *p, HI_SESSION *session, HttpsessionData *hsd, in return HI_SUCCESS; } -int hi_client_inspection(Packet *p, void *S, HttpsessionData *hsd, int stream_ins) +int hi_client_inspection(Packet *p, void *S, HttpSessionData *hsd, int stream_ins) { - HI_SESSION *session; - - int iRet; - if(!S || !(p->data) || (p->dsize < 1)) { return HI_INVALID_ARG; } - session = (HI_SESSION *)S; + HI_SESSION* session = (HI_SESSION*) S; if(!session->global_conf) { return HI_INVALID_ARG; } - { - /* - ** Otherwise we assume stateless inspection - */ - iRet = StatelessInspection(p, session, hsd, stream_ins); - if (iRet) - { - return iRet; - } - } - - return HI_SUCCESS; + return StatelessInspection(p, session, hsd, stream_ins); } /* @@ -2950,68 +3064,47 @@ int hi_client_inspection(Packet *p, void *S, HttpsessionData *hsd, int stream_in ** Initializes arrays and search algorithms depending on the type of ** inspection that we are doing. ** -** @param GlobalConf pointer to the global configuration -** -** @return integer -** ** @retval HI_SUCCESS function successful. */ -int hi_client_init(HTTPINSPECT_GLOBAL_CONF*) +int hi_client_init() { int iCtr; + memset(lookup_table, 0x00, sizeof(lookup_table)); + + // Set up the non-ASCII register for processing. + for(iCtr = 0x80; iCtr <= 0xff; iCtr++) { - memset(lookup_table, 0x00, sizeof(lookup_table)); + lookup_table[iCtr] = SetBinaryNorm; + } + lookup_table[0x00] = SetBinaryNorm; - /* - ** Set up the non-ASCII register for processing. - */ - for(iCtr = 0x80; iCtr <= 0xff; iCtr++) - { - lookup_table[iCtr] = SetBinaryNorm; - } - lookup_table[0x00] = SetBinaryNorm; + lookup_table[(uint8_t)' '] = NextNonWhiteSpace; + lookup_table[(uint8_t)'\r'] = find_rfc_delimiter; + lookup_table[(uint8_t)'\n'] = find_non_rfc_delimiter; - lookup_table[(uint8_t)' '] = NextNonWhiteSpace; - lookup_table[(uint8_t)'\r'] = find_rfc_delimiter; - lookup_table[(uint8_t)'\n'] = find_non_rfc_delimiter; + // ASCII encoding + lookup_table[(uint8_t)'%'] = SetPercentNorm; - /* - ** ASCII encoding - */ - lookup_table[(uint8_t)'%'] = SetPercentNorm; + // Looking for multiple slashes + lookup_table[(uint8_t)'/'] = SetSlashNorm; - /* - ** Looking for multiple slashes - */ - lookup_table[(uint8_t)'/'] = SetSlashNorm; + // Looking for backslashs + lookup_table[(uint8_t)'\\'] = SetBackSlashNorm; - /* - ** Looking for backslashs - */ - lookup_table[(uint8_t)'\\'] = SetBackSlashNorm; - - lookup_table[(uint8_t)'+'] = SetPlusNorm; - - - /* - ** Look up parameter field, so we don't alert on long directory - ** strings, when the next slash in the parameter field. - */ - lookup_table[(uint8_t)'?'] = SetParamField; + lookup_table[(uint8_t)'+'] = SetPlusNorm; - /* - ** Look for absolute URI and proxy communication. - */ - lookup_table[(uint8_t)':'] = SetProxy; + // Look up parameter field, so we don't alert on long directory + // strings, when the next slash in the parameter field. + lookup_table[(uint8_t)'?'] = SetParamField; - } + // Look for absolute URI and proxy communication. + lookup_table[(uint8_t)':'] = SetProxy; return HI_SUCCESS; } - /** ** This was just an initial testing program for these functions. */ @@ -3040,7 +3133,7 @@ int main(int argc, char **argv) hi_ui_config_print_config(&GlobalConf); - if((iRet = hi_client_init(&GlobalConf))) + if((iRet = hi_client_init())) { printf("** error client init\n"); return iRet; diff --git a/src/service_inspectors/http_inspect/hi_client.h b/src/service_inspectors/http_inspect/hi_client.h index 70e248f41..842e678cd 100644 --- a/src/service_inspectors/http_inspect/hi_client.h +++ b/src/service_inspectors/http_inspect/hi_client.h @@ -34,13 +34,20 @@ #define URI_END 99 #define POST_END 100 #define NO_URI 101 -typedef enum { - TRUE_CLIENT_IP_HDR = 0x01, - XFF_HDR = 0x02, - HDRS_BOTH = 0x03 -} ActionSFCC; +#define XFF_MODE_MASK (0x000f) +#define XFF_EXFF_MASK (0x000c) +#define TRUE_CLIENT_IP_HDR (0x01) +#define XFF_HDR (0x02) +#define HDRS_BOTH (0x03) +#define XFF_HEADERS (0x04) // Using xff_headers list +#define XFF_HEADERS_ACTIVE (0x08) // Looking for highest precedence xff header +#define XFF_INIT (XFF_HEADERS | XFF_HEADERS_ACTIVE) + +#define XFF_TOP_PRECEDENCE (1) +#define XFF_BOT_PRECEDENCE (255) + typedef struct s_COOKIE_PTR { const u_char *cookie; @@ -116,11 +123,6 @@ typedef struct s_HEADER_PTR typedef struct s_HI_CLIENT_REQ { - /* - u_char *method; - int method_size; - */ - const u_char *uri; const u_char *uri_norm; const u_char *post_raw; @@ -140,23 +142,6 @@ typedef struct s_HI_CLIENT_REQ u_int cookie_norm_size; u_int method_size; - /* - u_char *param; - u_int param_size; - u_int param_norm; - */ - - /* - u_char *ver; - u_int ver_size; - - u_char *hdr; - u_int hdr_size; - - u_char *payload; - u_int payload_size; - */ - const u_char *pipeline_req; u_char method; uint16_t uri_encode_type; @@ -178,14 +163,18 @@ typedef struct s_HI_CLIENT_HDR_ARGS { HEADER_PTR *hdr_ptr; HEADER_FIELD_PTR *hdr_field_ptr; - HttpsessionData *sd; + HttpSessionData *sd; int strm_ins; int hst_name_hdr; - int true_clnt_xff; + uint8_t true_clnt_xff; + uint8_t top_precedence; + uint8_t new_precedence; } HI_CLIENT_HDR_ARGS; -int hi_client_inspection(Packet *p, void *session, HttpsessionData *hsd, int stream_ins); -int hi_client_init(HTTPINSPECT_GLOBAL_CONF *GlobalConf); +int hi_client_inspection(Packet *p, void *session, HttpSessionData *hsd, int stream_ins); +int hi_client_init(); + +char **hi_client_get_field_names(); extern const u_char *proxy_start; extern const u_char *proxy_end; diff --git a/src/service_inspectors/http_inspect/hi_client_stateful.h b/src/service_inspectors/http_inspect/hi_client_stateful.h deleted file mode 100644 index 250d01f03..000000000 --- a/src/service_inspectors/http_inspect/hi_client_stateful.h +++ /dev/null @@ -1,35 +0,0 @@ -//-------------------------------------------------------------------------- -// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. -// Copyright (C) 2005-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. -//-------------------------------------------------------------------------- - -/* -* hi_client_stateful.h: Functions prototypes for client - * stateful processing. -* -* Author: Chris Sherwin -* -*/ -#ifndef HI_CLIENT_STATEFUL_H -#define HI_CLIENT_STATEFUL_H - -#include "hi_include.h" -#include "hi_si.h" - -int StatefulInspection( HI_SESSION* sessionp, unsigned char* datap, int dsize ); - -#endif /* HI_CLIENT_STATEFUL_H */ diff --git a/src/service_inspectors/http_inspect/hi_events.h b/src/service_inspectors/http_inspect/hi_events.h index 67422ce90..2ed704844 100644 --- a/src/service_inspectors/http_inspect/hi_events.h +++ b/src/service_inspectors/http_inspect/hi_events.h @@ -81,8 +81,14 @@ typedef enum _HI_EVENTS HI_SERVER_JS_OBFUSCATION_EXCD, HI_SERVER_JS_EXCESS_WS, HI_SERVER_MIXED_ENCODINGS, + HI_SERVER_SWF_ZLIB_FAILURE, + HI_SERVER_SWF_LZMA_FAILURE, + HI_SERVER_PDF_DEFL_FAILURE, + HI_SERVER_PDF_UNSUP_COMP_TYPE, + HI_SERVER_PDF_CASC_COMP, + HI_SERVER_PDF_PARSE_FAILURE, HI_SERVER_EVENT_NUM -}HI_EVENTS; +} HI_EVENTS; /* ** These defines are the alert names for each event @@ -183,6 +189,18 @@ typedef enum _HI_EVENTS "javascript whitespaces exceeds max allowed" #define HI_SERVER_MIXED_ENCODINGS_STR \ "multiple encodings within javascript obfuscated data" +#define HI_SERVER_SWF_ZLIB_FAILURE_STR \ + "HTTP response SWF file zlib decompression failure" +#define HI_SERVER_SWF_LZMA_FAILURE_STR \ + "HTTP response SWF file LZMA decompression failure" +#define HI_SERVER_PDF_DEFL_FAILURE_STR \ + "HTTP response PDF file deflate decompression failure" +#define HI_SERVER_PDF_UNSUP_COMP_TYPE_STR \ + "HTTP response PDF file unsupported compression type" +#define HI_SERVER_PDF_CASC_COMP_STR \ + "HTTP response PDF file cascaded compression" +#define HI_SERVER_PDF_PARSE_FAILURE_STR \ + "HTTP response PDF file parse failure" void hi_set_event(unsigned gid, unsigned sid); void hi_clear_events(); diff --git a/src/service_inspectors/http_inspect/hi_file_decomp.cc b/src/service_inspectors/http_inspect/hi_file_decomp.cc new file mode 100644 index 000000000..b6a9eda83 --- /dev/null +++ b/src/service_inspectors/http_inspect/hi_file_decomp.cc @@ -0,0 +1,417 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// +// 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. +//-------------------------------------------------------------------------- + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "util.h" +#include "hi_file_decomp.h" +#include "snort_types.h" +#include "detection_util.h" +#include "hi_file_decomp_pdf.h" +#include "hi_file_decomp_swf.h" + +static const char PDF_Sig[5] = { '%', 'P', 'D', 'F', '-' }; +static const char SWF_ZLIB_Sig[3] = { 'C', 'W', 'S' }; +#ifdef LZMA +static const char SWF_LZMA_Sig[3] = { 'Z', 'W', 'S' }; +#endif +static const char SWF_Uncomp_Sig[3] = { 'F', 'W', 'S' }; + +/* Please assure that the following value correlates with the set of sig's */ +#define MAX_SIG_LENGTH (5) + +static struct sig_map_s +{ + const char* Sig; + size_t Sig_Length; + bool Enabled; + file_type_t File_Type; + file_compression_type_t File_Compression_Type; +} Signature_Map[] = +{ + + { PDF_Sig, sizeof(PDF_Sig), false, FILE_TYPE_PDF, FILE_COMPRESSION_TYPE_NONE }, // Compression type is embedded in PDF dictionaries + { SWF_ZLIB_Sig, sizeof(SWF_ZLIB_Sig), false, FILE_TYPE_SWF, FILE_COMPRESSION_TYPE_ZLIB }, +#ifdef LZMA + { SWF_LZMA_Sig, sizeof(SWF_LZMA_Sig), false, FILE_TYPE_SWF, FILE_COMPRESSION_TYPE_LZMA }, +#endif + { NULL, 0, false, FILE_TYPE_NONE, FILE_COMPRESSION_TYPE_NONE } +}; + +/* Define the elements of the Sig_State value (packed for storage efficiency */ +#define SIG_MATCH_ACTIVE (0x80) +#define SIG_SIG_INDEX_MASK (0x70) +#define SIG_SIG_INDEX_SHIFT (4) +#define SIG_CHR_INDEX_MASK (0x07) +#define SIG_CHR_INDEX_SHIFT (0) + +static uint8_t File_Decomp_Buffer[DECODE_BLEN]; + +/* Look for possible sig at the current payload location. + Do NOT beyond the current location (initial Next_In). */ +static fd_status_t Locate_Sig_Here( fd_session_p_t SessionPtr ) +{ + uint64_t Sig_Index, Char_Index; + + /* If there's no new input, we don't change state */ + if( (SessionPtr->Avail_In == 0) || + (SessionPtr->Next_In == NULL) || (SessionPtr->Next_Out == NULL) ) + return( File_Decomp_Error ); + + if( SessionPtr->Avail_Out < MAX_SIG_LENGTH ) + return( File_Decomp_BlockOut ); + + /* Given that we are here, there is at least one input byte to process. + And at least enough room in the output stream for the signature. */ + + /* Have we started down a sig string? */ + if( (SessionPtr->Sig_State & SIG_MATCH_ACTIVE) != 0 ) + { + /* Get the current index into the sig map table (indicating which sig) and + the index into the sig itself. */ + Sig_Index = (SessionPtr->Sig_State & SIG_SIG_INDEX_MASK) >> SIG_SIG_INDEX_SHIFT; + /* Char_Index indicates the sig char that we are looking for now. */ + Char_Index = (SessionPtr->Sig_State & SIG_CHR_INDEX_MASK) >> SIG_CHR_INDEX_SHIFT; + } + else + { + Sig_Index = 0; + Char_Index = 0; + } + + /* There must be more in the input stream for us to look at, else + we indicate that we didn't find the sig yet. */ + if( SessionPtr->Avail_In <= Char_Index ) + return( File_Decomp_BlockIn ); + + /* NOTE: The following code block makes the assumption that there are + at least MAX_SIG_LENGTH bytes in the output buffer. This assumption + is valid for the current implementation where the signature only + occurs at the beginning of the file. For the generic case of the sig + begin embedded with the file, the seach will need to modified.*/ + while( 1 ) + { + /* if we get to the end of the sig table (or the table is empty), + indicate that we didn't match a sig */ + if( Signature_Map[Sig_Index].Sig == NULL ) + return( File_Decomp_NoSig ); + + /* Get next char and see if it matches next char in sig */ + if( (Signature_Map[Sig_Index].Enabled) && + (*(SessionPtr->Next_In+Char_Index) == *(Signature_Map[Sig_Index].Sig+Char_Index)) ) + { + /* Check to see if we are at the end of the sig string. */ + if( Char_Index == (Signature_Map[Sig_Index].Sig_Length-1) ) + { + uint8_t *Sig = (uint8_t *)Signature_Map[Sig_Index].Sig; + uint16_t Len = (uint16_t)Signature_Map[Sig_Index].Sig_Length; + + SessionPtr->File_Type = Signature_Map[Sig_Index].File_Type; + SessionPtr->Decomp_Type = Signature_Map[Sig_Index].File_Compression_Type; + + if( (SessionPtr->File_Type == FILE_TYPE_SWF) && ((SessionPtr->Modes & FILE_REVERT_BIT) != 0) ) + { + Sig = (uint8_t *)SWF_Uncomp_Sig; + Len = (uint16_t)sizeof( SWF_Uncomp_Sig ); + } + /* The following is safe as we can only be here is there are + are least MAX_SIG_LENGTH bytes in the output buffer */ + (void)Put_N(SessionPtr, Sig, Len); + /* Skip the Sig bytes in the input stream */ + SessionPtr->Next_In += Len; + SessionPtr->Avail_In -= Len; + SessionPtr->Total_In += Len; + return( File_Decomp_OK ); + } + + /* check for more available input bytes */ + if( Char_Index < SessionPtr->Avail_In ) + { + /* Set to the next char and keep checking this matching sig */ + Char_Index += 1; + continue; /* goto top of while() loop */ + } + else + { + /* Indicate that we are actively finding a sig, save the char index + and save the sig index. We'll pickup where we left off when more + input is available. */ + SessionPtr->Sig_State = SIG_MATCH_ACTIVE | + ((Sig_Index & SIG_SIG_INDEX_MASK) << SIG_SIG_INDEX_SHIFT) | + ((Char_Index & SIG_CHR_INDEX_MASK) << SIG_CHR_INDEX_SHIFT); + return( File_Decomp_BlockIn ); + } + } + else + { + /* Failed somewhere matching this sig, goto next sig and reset the + Char_Index to the beginning */ + Sig_Index += 1; + Char_Index = 0; + } + } +} + +static fd_status_t Initialize_Decompression( fd_session_p_t SessionPtr ) +{ + fd_status_t Ret_Code = File_Decomp_OK; + + switch( SessionPtr->File_Type ) + { + case( FILE_TYPE_SWF ): + { + Ret_Code = File_Decomp_Init_SWF( SessionPtr ); + break; + } + case( FILE_TYPE_PDF ): + { + Ret_Code = File_Decomp_Init_PDF( SessionPtr ); + break; + } + default: + return( File_Decomp_Error ); + } + + if( Ret_Code == File_Decomp_OK ) + SessionPtr->State = STATE_ACTIVE; + + return( Ret_Code ); +} + +static fd_status_t Process_Decompression( fd_session_p_t SessionPtr ) +{ + fd_status_t Ret_Code = File_Decomp_OK; + + switch( SessionPtr->File_Type ) + { + case( FILE_TYPE_SWF ): + { + Ret_Code = File_Decomp_SWF( SessionPtr ); + break; + } + case( FILE_TYPE_PDF ): + { + Ret_Code = File_Decomp_PDF( SessionPtr ); + break; + } + default: + return( File_Decomp_Error ); + } + + if( Ret_Code == File_Decomp_Complete ) + SessionPtr->State = STATE_COMPLETE; + + return( Ret_Code ); +} + +/* The caller provides Compr_Depth, Decompr_Depth and Modes in the session object. + Based on the requested Modes, gear=up to initialize the potential decompressors. */ +fd_status_t File_Decomp_Init( fd_session_p_t SessionPtr ) +{ + int Sig; + + if( SessionPtr == NULL ) + return( File_Decomp_Error ); + + SessionPtr->State = STATE_READY; + SessionPtr->File_Type = FILE_TYPE_NONE; + SessionPtr->Decomp_Type = FILE_COMPRESSION_TYPE_NONE; + + for( Sig=0; Signature_Map[Sig].Sig != NULL; Sig++ ) + { + if( (Signature_Map[Sig].File_Type == FILE_TYPE_PDF ) && + ((SessionPtr->Modes & FILE_PDF_ANY) != 0) ) + Signature_Map[Sig].Enabled = true; + + if( (Signature_Map[Sig].File_Type == FILE_TYPE_SWF ) && + (Signature_Map[Sig].File_Compression_Type == FILE_COMPRESSION_TYPE_ZLIB) && + ((SessionPtr->Modes & FILE_SWF_ZLIB_BIT) != 0) ) + Signature_Map[Sig].Enabled = true; + +#ifdef LZMA + if( (Signature_Map[Sig].File_Type == FILE_TYPE_SWF ) && + (Signature_Map[Sig].File_Compression_Type == FILE_COMPRESSION_TYPE_LZMA) && + ((SessionPtr->Modes & FILE_SWF_LZMA_BIT) != 0) ) + Signature_Map[Sig].Enabled = true; +#endif + } + + return( File_Decomp_OK ); +} + +/* Setup session to use internal decompression buffer. Set compr/decompr limits */ +fd_status_t File_Decomp_SetBuf( fd_session_p_t SessionPtr ) +{ + if( SessionPtr == NULL ) + return( File_Decomp_Error ); + + SessionPtr->Buffer = File_Decomp_Buffer; + SessionPtr->Buffer_Len = sizeof(File_Decomp_Buffer); + + SessionPtr->Next_Out = File_Decomp_Buffer; + SessionPtr->Avail_Out = sizeof(File_Decomp_Buffer); + + /* If Compr/Decompr limits are set, then enforce then. */ + if( SessionPtr->Decompr_Depth > 0 ) + { + uint32_t remainder; + + if( SessionPtr->Total_Out > SessionPtr->Decompr_Depth ) + return( File_Decomp_Error ); + + /* Calc whats left in allowance */ + remainder = (SessionPtr->Total_Out - SessionPtr->Decompr_Depth); + + /* Use smaller of remainder or value provided */ + SessionPtr->Avail_Out = (remainder < SessionPtr->Avail_Out) ? + remainder : SessionPtr->Avail_Out; + } + + if( SessionPtr->Compr_Depth > 0 ) + { + uint32_t remainder; + + if( SessionPtr->Total_In > SessionPtr->Compr_Depth ) + return( File_Decomp_Error ); + + remainder = (SessionPtr->Total_In - SessionPtr->Compr_Depth); + + SessionPtr->Avail_In = (remainder < SessionPtr->Avail_In) ? + remainder : SessionPtr->Avail_In; + } + + /* SessionPtr->Next_In is set by the caller to File_Decomp() */ + + return( File_Decomp_OK ); +} + +/* Returns a new session object from the MemPool */ +fd_session_p_t File_Decomp_New() +{ + fd_session_p_t New_Session = new fd_session_t; + + New_Session->State = STATE_NEW; + New_Session->Sig_State = 0; + New_Session->Total_In = 0; + New_Session->Total_Out = 0; + New_Session->Avail_In = 0; + New_Session->Next_In = NULL; + New_Session->Avail_Out = 0; + New_Session->Next_Out = NULL; + + return New_Session; +} + +/* Process Decompression. The session Next_In, Avail_In, Next_Out, Avail_Out MUST have been + set by caller. +*/ +fd_status_t File_Decomp( fd_session_p_t SessionPtr ) +{ + fd_status_t Return_Code; + + if( (SessionPtr->State == STATE_NEW) || + (SessionPtr->Next_In == NULL) || (SessionPtr->Next_Out == NULL) ) + return( File_Decomp_Error ); + + /* STATE_NEW: Look for one of the configured file signatures. */ + if( SessionPtr->State == STATE_READY ) + { + /* Look for the signature at the beginning of the payload stream. */ + if( (Return_Code = Locate_Sig_Here( SessionPtr )) == File_Decomp_OK ) + { + /* We now know the file type and decompression type. Setup appropriate state. */ + if( (Return_Code = Initialize_Decompression( SessionPtr )) == File_Decomp_OK ) + { + return( Process_Decompression( SessionPtr ) ); + } + else + return( Return_Code ); + } + else + /* Locate_Sig_Here() might return BlockIn, BlockOut, Error, or NoSig */ + return( Return_Code ); + } + else if( SessionPtr->State == STATE_ACTIVE ) + { + return( Process_Decompression( SessionPtr ) ); + } + else + return( File_Decomp_Error ); +} + + +fd_status_t File_Decomp_End( fd_session_p_t SessionPtr ) +{ + if( SessionPtr == NULL ) + return( File_Decomp_Error ); + + switch( SessionPtr->File_Type ) + { + case( FILE_TYPE_SWF ): + { + return( File_Decomp_End_SWF( SessionPtr ) ); + } + case( FILE_TYPE_PDF ): + { + return( File_Decomp_End_PDF( SessionPtr ) ); + } + default: + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + +fd_status_t File_Decomp_Reset( fd_session_p_t SessionPtr ) +{ + fd_status_t Ret_Code; + + if( SessionPtr == NULL ) + return( File_Decomp_Error ); + + Ret_Code = File_Decomp_End( SessionPtr ); + + SessionPtr->State = STATE_READY; + + return( Ret_Code ); +} + +fd_status_t File_Decomp_StopFree( fd_session_p_t SessionPtr ) +{ + if( SessionPtr == NULL ) + return( File_Decomp_Error ); + + File_Decomp_End( SessionPtr ); + File_Decomp_Free( SessionPtr ); + + return( File_Decomp_OK ); +} + + +void File_Decomp_Free( fd_session_p_t SessionPtr ) +{ + delete SessionPtr; +} + +void File_Decomp_Alert( fd_session_p_t SessionPtr, int Event ) +{ + if( (SessionPtr != NULL) && (SessionPtr->Alert_Callback != NULL) && (SessionPtr->Alert_Context) ) + (SessionPtr->Alert_Callback)(SessionPtr->Alert_Context, Event); +} diff --git a/src/service_inspectors/http_inspect/hi_file_decomp.h b/src/service_inspectors/http_inspect/hi_file_decomp.h new file mode 100644 index 000000000..ea35dc403 --- /dev/null +++ b/src/service_inspectors/http_inspect/hi_file_decomp.h @@ -0,0 +1,272 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 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 HI_FILE_DECOMP_H +#define HI_FILE_DECOMP_H + +// FIXIT-H temporary for development convenience +//#define LZMA 1 + +#include +#include + +/* File_Decomp global typedefs (used in child objects) */ +typedef enum fd_status +{ + File_Decomp_DecompError = -2, /* Error from decompression */ + File_Decomp_Error = -1, /* Error from decompression */ + File_Decomp_OK = 0, + File_Decomp_NoSig = 1, /* No file signature located */ + File_Decomp_Complete = 2, /* Completed */ + File_Decomp_BlockOut = 3, /* Blocked due to lack of output space */ + File_Decomp_BlockIn = 4, /* Blocked due to lack in input data */ + File_Decomp_Eof = 5 /* End of file located */ +} fd_status_t; + +typedef enum file_compression_type +{ + FILE_COMPRESSION_TYPE_NONE, + FILE_COMPRESSION_TYPE_DEFLATE, + FILE_COMPRESSION_TYPE_ZLIB, + FILE_COMPRESSION_TYPE_LZMA, + FILE_COMPRESSION_TYPE_MAX +} file_compression_type_t; + +typedef struct fd_session_s *fd_session_p_t, fd_session_t; + +#include "hi_file_decomp_pdf.h" +#include "hi_file_decomp_swf.h" +#include + +#ifdef LZMA +#include +#endif + +/* Potential decompression modes */ +#define FILE_SWF_LZMA_BIT (0x00000001) +#define FILE_SWF_ZLIB_BIT (0x00000002) +#define FILE_PDF_DEFL_BIT (0x00000004) +#define FILE_FILT_NORM_BIT (0x40000000) /* Normalize the PDF /Filter value string */ +#define FILE_REVERT_BIT (0x80000000) /* Revert to 'uncompressed' state */ + +#define FILE_PDF_ANY (FILE_PDF_DEFL_BIT) +#define FILE_SWF_ANY (FILE_SWF_LZMA_BIT | FILE_SWF_ZLIB_BIT) + +/* Private Types */ +typedef enum file_type +{ + FILE_TYPE_NONE, + FILE_TYPE_SWF, + FILE_TYPE_PDF, + FILE_TYPE_MAX +} file_type_t; + +typedef enum states +{ + STATE_NEW, /* Session created */ + STATE_READY, /* Session created and ready for content, no file/decomp selected */ + STATE_ACTIVE, /* Decompressor inited and ready for content */ + STATE_COMPLETE /* Decompression completed */ +} fd_states_t; + +struct fd_session_s +{ + uint8_t *Next_In; /* next input byte */ + uint32_t Avail_In; /* number of bytes available at next_in */ + uint32_t Total_In; /* total number of input bytes read so far */ + + uint8_t *Next_Out; /* next output byte should be put there */ + uint32_t Avail_Out; /* remaining free space at next_out */ + uint32_t Total_Out; /* total number of bytes output so far */ + + /* Internal buffer setup by _Init(). App can overide. */ + uint8_t *Buffer; /* pointer to decompresiion buffer */ + uint32_t Buffer_Len;/* length of decompression buffer */ + + /* Configuration settings */ + uint32_t Compr_Depth; + uint32_t Decompr_Depth; + uint32_t Modes; /* Bit mapped set of potential file/algo modes */ + + /* Alerting callback */ + void (*Alert_Callback)(void *Context, int Event); + void *Alert_Context; + + /* Internal State */ + uint8_t File_Type; /* Active file type */ + uint8_t Decomp_Type; /* Active decompression type */ + uint8_t Sig_State; /* Sig search state machine */ + uint8_t State; /* main state machine */ + + union + { + fd_PDF_t PDF; + fd_SWF_t SWF; + } Decomp_State; + + /* Specific event indicated by DecomprError return */ + int Error_Event; +}; + +/* Macros */ + +#ifndef SYNC_IN +#define SYNC_IN(dest) \ + dest->next_in = SessionPtr->Next_In; \ + dest->avail_in = SessionPtr->Avail_In; \ + dest->total_in = SessionPtr->Total_In; \ + dest->next_out = SessionPtr->Next_Out; \ + dest->avail_out = SessionPtr->Avail_Out; \ + dest->total_out = SessionPtr->Total_Out; +#endif + +#ifndef SYNC_OUT +#define SYNC_OUT(src) \ + SessionPtr->Next_In = (uint8_t *)src->next_in; \ + SessionPtr->Avail_In = src->avail_in; \ + SessionPtr->Total_In = src->total_in; \ + SessionPtr->Next_Out = (uint8_t *)src->next_out; \ + SessionPtr->Avail_Out = src->avail_out; \ + SessionPtr->Total_Out = src->total_out; +#endif + + +/* Inline Functions */ + +static inline bool Peek_1( fd_session_p_t SessionPtr, uint8_t *c) +{ + if( (SessionPtr->Next_In != NULL) && (SessionPtr->Avail_In > 0) ) + { + *c = *(SessionPtr->Next_In); + return( true ); + } + else + return( false ); +} + +static inline bool Get_1( fd_session_p_t SessionPtr, uint8_t *c ) +{ + if( (SessionPtr->Next_In != NULL) && (SessionPtr->Avail_In > 0) ) + { + *c = *(SessionPtr->Next_In)++; + SessionPtr->Avail_In -= 1; + SessionPtr->Total_In += 1; + return( true ); + } + else + return( false ); +} + +static inline bool Get_N( fd_session_p_t SessionPtr, uint8_t **c, uint16_t N ) +{ + if( (SessionPtr->Next_In != NULL) && (SessionPtr->Avail_In >= N) ) + { + *c = SessionPtr->Next_In; + SessionPtr->Next_In += N; + SessionPtr->Avail_In -= N; + SessionPtr->Total_In += N; + return( true ); + } + else + return( false ); +} + +static inline bool Put_1( fd_session_p_t SessionPtr, uint8_t c ) +{ + if( (SessionPtr->Next_Out != NULL) && (SessionPtr->Avail_Out > 0) ) + { + *(SessionPtr->Next_Out)++ = c; + SessionPtr->Avail_Out -= 1; + SessionPtr->Total_Out += 1; + return( true ); + } + else + return( false ); +} + +static inline bool Put_N( fd_session_p_t SessionPtr, uint8_t *c, uint16_t N ) +{ + if( (SessionPtr->Next_Out != NULL) && (SessionPtr->Avail_Out >= N) ) + { + strncpy( (char *)SessionPtr->Next_Out, (const char *)c, N); + SessionPtr->Next_Out += N; + SessionPtr->Avail_Out -= N; + SessionPtr->Total_Out += N; + return( true ); + } + else + return( false ); +} + +static inline bool Move_1( fd_session_p_t SessionPtr ) +{ + if( (SessionPtr->Next_Out != NULL) && (SessionPtr->Avail_Out > 0) && + (SessionPtr->Next_In != NULL) && (SessionPtr->Avail_In > 0) ) + { + *(SessionPtr->Next_Out) = *(SessionPtr->Next_In); + SessionPtr->Next_Out += 1; + SessionPtr->Next_In += 1; + SessionPtr->Avail_In -= 1; + SessionPtr->Avail_Out -= 1; + SessionPtr->Total_In += 1; + SessionPtr->Total_Out += 1; + return( true ); + } + else + return( false ); +} + +static inline bool Move_N( fd_session_p_t SessionPtr, uint16_t N ) +{ + if( (SessionPtr->Next_Out != NULL) && (SessionPtr->Avail_Out >= N) && + (SessionPtr->Next_In != NULL) && (SessionPtr->Avail_In >= N) ) + { + strncpy( (char *)SessionPtr->Next_Out, (const char *)SessionPtr->Next_In, N); + SessionPtr->Next_Out += N; + SessionPtr->Next_In += N; + SessionPtr->Avail_In -= N; + SessionPtr->Avail_Out -= N; + SessionPtr->Avail_Out -= N; + SessionPtr->Total_Out += N; + return( true ); + } + else + return( false ); +} + +/* API Functions */ + +fd_session_p_t File_Decomp_New(); + +fd_status_t File_Decomp_Init( fd_session_p_t SessionPtr ); + +fd_status_t File_Decomp_SetBuf( fd_session_p_t SessionPtr ); + +fd_status_t File_Decomp( fd_session_p_t SessionPtr ); + +fd_status_t File_Decomp_End( fd_session_p_t SessionPtr ); + +fd_status_t File_Decomp_Reset( fd_session_p_t SessionPtr ); + +fd_status_t File_Decomp_StopFree( fd_session_p_t SessionPtr ); + +void File_Decomp_Free( fd_session_p_t SessionPtr ); + +void File_Decomp_Alert( fd_session_p_t SessionPtr, int Event ); +#endif diff --git a/src/service_inspectors/http_inspect/hi_file_decomp_pdf.cc b/src/service_inspectors/http_inspect/hi_file_decomp_pdf.cc new file mode 100644 index 000000000..f80cebdf2 --- /dev/null +++ b/src/service_inspectors/http_inspect/hi_file_decomp_pdf.cc @@ -0,0 +1,1151 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// +// 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. +//-------------------------------------------------------------------------- + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "hi_file_decomp.h" +#include "hi_file_decomp_pdf.h" +#include "hi_events.h" +#include "mstring.h" + +/* Define characters and tokens in PDF grammar */ +#define TOK_STRM_OPEN "stream" +#define TOK_STRM_CLOSE "endstream" + +#define TOK_OBJ_OPEN "obj" +#define TOK_OBJ_CLOSE "endobj" + +#define TOK_DICT_OPEN "<<" +#define TOK_DICT_CLOSE ">>" +#define TOK_DICT_FILT "Filter" +#define TOK_DICT_FLATE "FlateDecode" +#define TOK_DICT_FLATE_ALT "Fl" +#define TOK_DICT_PARMS "DecodeParms" +#define TOK_DICT_PARMS_ALT "DP" +#define TOK_DICT_LENGTH "Length" +#define TOK_DICT_NULL "null" +#define TOK_DICT_NULL_FILT " null " // Enclose the null object in spaces +#define TOK_XRF_XREF "xref" +#define TOK_XRF_TRAILER "trailer" +#define TOK_XRF_STARTXREF "startxref" +#define TOK_XRF_END "%%EOF" + +#define WHITESPACE_STRING "\011\012\014\015\040" // plus \000 + +#define TOK_EOL_CR "\r" +#define TOK_EOL_LF "\n" +#define TOK_EOL_CRLF "\r\n" + +#define CHR_CR '\r' +#define CHR_LF '\n' + +#define CHR_COMMENT '%' + +#define CHR_ARRAY_OPEN '[' +#define CHR_ARRAY_CLOSE ']' + +#define CHR_ANGLE_OPEN '<' +#define CHR_ANGLE_CLOSE '>' + +#define CHR_SPACE ' ' +#define CHR_NAME_SEP '/' + +#define IS_WHITESPACE(c) ((strchr((char *)WHITESPACE_STRING, (int)c) != NULL) || (c == 0)) +#define IS_EOL(c) ((c == CHR_CR) || (c == CHR_LF)) + +/* Define the parser states */ +typedef enum p_states +{ + P_START = 1, // Ground state, nothing 'open' + P_COMMENT, // inside a comment (initial state of parser) + P_IND_OBJ, // Indirect Object - Sub_State usage + P_XREF, // The combined xref, trailer, startxref top level items + P_DICT_OBJECT, // A dictionary object + P_STREAM // A pseudo state used to process a stream object +} p_state_t; + +typedef enum p_xref_substates +{ + P_XREF_TOKEN = 1, + P_XREF_END_TOKEN +} p_xref_t; + +typedef enum p_dict_substates +{ + P_DICT_OPEN = 1, + P_DICT_OPEN_TOK, + P_DICT_CLOSE_TOK, + P_DICT_FILTER, + P_DICT_SKIP, + P_DICT_ACTIVE +} p_dict_t; + +typedef enum p_indirect_object_substates +{ + P_OBJ_NUMBER = 1, + P_GEN_NUMBER, + P_OBJ_TOKEN, + P_OBJ_EOL, + P_STREAM_TOKEN, + P_STREAM_EOL, + P_STREAM_LF, + P_ENDSTREAM_TOKEN, + P_ENDOBJ_TOKEN +} p_indirect_object_substate_t; + +static struct filters_s +{ + const char *Token; + uint8_t Length; + uint8_t Type; +} Filter_Map[] = +{ + { TOK_DICT_FLATE, (sizeof(TOK_DICT_FLATE)-1), FILE_COMPRESSION_TYPE_DEFLATE }, + { TOK_DICT_FLATE_ALT, (sizeof(TOK_DICT_FLATE_ALT)-1), FILE_COMPRESSION_TYPE_DEFLATE }, + { TOK_DICT_NULL, (sizeof(TOK_DICT_NULL)-1), FILE_COMPRESSION_TYPE_NONE }, + { NULL, 0, FILE_COMPRESSION_TYPE_NONE } +}; + +/* Given a pointer to a /Filter value token, return the + associated compression type from the Filter_Map. */ +static inline uint8_t Get_Decomp_Type( uint8_t *Token, uint8_t Length ) +{ + int Index; + + Index=0; + + while( Filter_Map[Index].Token != NULL ) + { + if( (Filter_Map[Index].Length == Length) && + (strncmp( (const char *)Token, Filter_Map[Index].Token, Length ) == 0 ) ) + return( Filter_Map[Index].Type ); + else + Index += 1; + } + return( FILE_COMPRESSION_TYPE_NONE ); +} + +static inline void Process_One_Filter( fd_session_p_t SessionPtr, uint8_t *Token, uint8_t Length ) +{ + uint8_t Comp_Type; + + /* Lookup the token and see if it matches a known filter */ + Comp_Type = Get_Decomp_Type( Token, Length ); + + if( Comp_Type != FILE_COMPRESSION_TYPE_NONE ) + { + /* Check if we've found one already. Indicate cascading if we did. */ + if( SessionPtr->Decomp_Type != FILE_COMPRESSION_TYPE_NONE ) + { + File_Decomp_Alert( SessionPtr, HI_SERVER_PDF_CASC_COMP ); + SessionPtr->Decomp_Type = FILE_COMPRESSION_TYPE_NONE; + } + else + { + /* Found our first matching, supported filter type */ + SessionPtr->Decomp_Type = Comp_Type; + SessionPtr->Decomp_State.PDF.Decomp_Type = Comp_Type; + } + } + else + { + File_Decomp_Alert( SessionPtr, HI_SERVER_PDF_UNSUP_COMP_TYPE ); + SessionPtr->Decomp_Type = FILE_COMPRESSION_TYPE_NONE; + } +} + +/* Parse the buffered Filter_Spec and create a stream decompression + mode and/or event alerts. Return File_Decomp_OK if successfui. + Return File_Decomp_Error for a parsing error. */ +static fd_status_t Process_Filter_Spec( fd_session_p_t SessionPtr ) +{ + /* The following string contains CHR_ARRAY_OPEN, CHR_ARRAY_CLOSE, + and CHR_NAME_SEP. */ + const uint8_t Delim_Str[] = { "\011\012\014\015\040/[]" }; + bool Found_Array = false; + bool Found_Token = false; + uint8_t *Filter; + uint8_t Length; + uint8_t c; + int Index; + + fd_status_t Ret_Code = File_Decomp_OK; + fd_PDF_Parse_p_t p = &(SessionPtr->Decomp_State.PDF.Parse); + + /* Assume the 'no compression' result */ + SessionPtr->Decomp_Type = FILE_COMPRESSION_TYPE_NONE; + Filter = NULL; + Length = 0; + + for( Index=0; IndexFilter_Spec_Index; Index++ ) + { + c = p->Filter_Spec_Buf[Index]; + + if( (c == 0) || (strchr( (char *)Delim_Str, (int)c ) != 0) ) + { + if( c == CHR_ARRAY_OPEN ) + { + /* Looks like an array starting, but we are already + in an array, or have seen a filter spec already. */ + if( Found_Array || Found_Token || (Filter != NULL) ) + { + Ret_Code = File_Decomp_Error; + break; + } + else + { + Found_Array = true; + Filter = NULL; + Length = 0; + continue; // Nothing else to do, goto next char + } + } + else if( c == CHR_ARRAY_CLOSE ) + { + /* We MUST have an array open at this point. */ + if( !Found_Array ) + { + Ret_Code = File_Decomp_Error; + break; + } + Found_Array = false; + } + + /* The white-space or other separator terminates the + current filter name we are parsing. */ + if( (Filter != NULL) && (Length > 0) ) + { + Process_One_Filter( SessionPtr, Filter, Length ); + Filter = NULL; + Length = 0; + } + } + else // non-separator character + { + /* Start a token if we haven't already. */ + if( Filter == NULL ) + { + Found_Token = true; // Used in the array syntax checking + Filter = &(p->Filter_Spec_Buf[Index]); + Length = 1; // We've found one character so far + } + else + { + Length += 1; + } + } + } + + /* Indicate an error is we exit the parsing with the array open */ + if( Found_Array ) + Ret_Code = File_Decomp_Error; + + /* Any error code implies no compression type */ + if( Ret_Code == File_Decomp_Error ) + SessionPtr->Decomp_Type = FILE_COMPRESSION_TYPE_NONE; + /* Look for case where the filter name ends at the + last character of the filter_spec. */ + else if( (Filter != NULL) && (Length > 0) ) + Process_One_Filter( SessionPtr, Filter, Length ); + + return( Ret_Code ); +} + + +static inline void Init_Parser( fd_session_p_t SessionPtr ) +{ + fd_PDF_Parse_p_t p = &(SessionPtr->Decomp_State.PDF.Parse); + /* The parser starts in the P_COMMENT state we start + parsing the file just after the signature is located + and the signature is syntactially a comment. */ + p->State = P_COMMENT; + p->Parse_Stack_Index = 0; // Stack is empty +} + +static inline fd_status_t Push_State( fd_PDF_Parse_p_t p ) +{ + fd_PDF_Parse_Stack_p_t StckPtr; + + if( p->Parse_Stack_Index >= (PARSE_STACK_LEN-1) ) + return( File_Decomp_Error ); + + StckPtr = &(p->Parse_Stack[(p->Parse_Stack_Index)++]); + + StckPtr->State = p->State; + StckPtr->Sub_State = p->Sub_State; + + return( File_Decomp_OK ); +} + +static inline fd_status_t Pop_State( fd_PDF_Parse_p_t p ) +{ + fd_PDF_Parse_Stack_p_t StckPtr; + + if( p->Parse_Stack_Index == 0 ) + return( File_Decomp_Error ); + + StckPtr = &(p->Parse_Stack[--(p->Parse_Stack_Index)]); + + p->Elem_Index = 0; // Reset to beginning of token as can't push/pop in mid-token + p->State = StckPtr->State; + p->Sub_State = StckPtr->Sub_State; + + return( File_Decomp_OK ); +} + +/* If there's a previous state on the stack, return a pointer to it, else return NULL */ +static inline fd_PDF_Parse_Stack_p_t Get_Previous_State( fd_PDF_Parse_p_t p ) +{ + if( p->Parse_Stack_Index == 0 ) + return( (fd_PDF_Parse_Stack_p_t)NULL ); + + return( &(p->Parse_Stack[(p->Parse_Stack_Index)-1]) ); +} + +/* Objects are the heart and soul of the PDF. In particular, we need to concentrate on Dictionary + objects and objects that map to the Filter element in Dictionaries. 'null' is a valid object'. + Objects can be recursively composed of arrays of objects. In our limited parsing paradigm, we + will only process the contents of top level Dictionaries and ignore deeper levels. We will + only explore Dictionary objects within Indirect Objects. */ +static inline fd_status_t Handle_State_DICT_OBJECT( fd_session_p_t SessionPtr, uint8_t c ) +{ + char Filter_Tok[] = TOK_DICT_FILT; + fd_PDF_Parse_p_t p = &(SessionPtr->Decomp_State.PDF.Parse); + + /* enter with c being an EOL from the ind obj state */ + if( p->State != P_DICT_OBJECT ) + { + p->Sub_State = P_DICT_OPEN; // Looking to open a Dict` + p->Dict_Nesting_Cnt = 0; // No Dicts are 'active' + p->State = P_DICT_OBJECT; + p->Filter_Spec_Index = 0; + SessionPtr->Decomp_Type = FILE_COMPRESSION_TYPE_NONE; + return( File_Decomp_OK ); + } + + switch( p->Sub_State ) + { + /* look for the first angle bracket */ + case( P_DICT_OPEN ): + { + if( c == CHR_ANGLE_OPEN ) + { + p->Sub_State = P_DICT_OPEN_TOK; + } + else if( !IS_WHITESPACE(c) ) + { + /* for other objects, just skip and wait for the close of the + indirect object as we don't parse objects other than Dict's. */ + if( Pop_State( p ) == File_Decomp_Error ) + return( File_Decomp_Error ); + } + break; + } + /* now look for the second angle bracket */ + case( P_DICT_OPEN_TOK ): + { + if( c == CHR_ANGLE_OPEN ) + { + /* Only ACTIVE if this is the opening of the + 'base level' Dict, NOT a nested one. */ + if( p->Dict_Nesting_Cnt++ == 0 ) + { + p->Sub_State = P_DICT_ACTIVE; + } + else + { + p->Sub_State = P_DICT_SKIP; + } + } + else + { + /* for other objects, just skip and wait for the close of the + indirect object as we don't parse objects other than Dict's. */ + if( Pop_State( p ) == File_Decomp_Error ) + return( File_Decomp_Error ); + } + break; + } + + case( P_DICT_SKIP ): + case( P_DICT_ACTIVE ): + { + /* Main purpose is to search for the value portion of the + /Filter entry. Main loop looks for the /Filter token + and handles other diversion such as nested Dict objects. + If the /Filter token doesn't exist then we don't fill the + Filter_Spec_Buf[]. If in skip mode, no need to look for token. */ + if( (p->Sub_State == P_DICT_ACTIVE) && c == Filter_Tok[p->Elem_Index++] ) + { + if( Filter_Tok[p->Elem_Index] == '\0' ) + { + p->Sub_State = P_DICT_FILTER; + } + } + else + { + /* On a mis-match, reset back to the start of the token */ + p->Elem_Index = 0; + + /* we might find a Sub-Dict while we're looking */ + if( c == CHR_ANGLE_OPEN ) + { + /* Save where we are, and process the Dict */ + if( Push_State( p ) != File_Decomp_OK ) + return( File_Decomp_Error ); + p->Sub_State = P_DICT_OPEN_TOK; + } + else if( c == CHR_ANGLE_CLOSE ) + { + if( Push_State( p ) != File_Decomp_OK ) + return( File_Decomp_Error ); + p->Sub_State = P_DICT_CLOSE_TOK; + } + } + break; + } + + case( P_DICT_FILTER ): + { + if( (c == CHR_ANGLE_CLOSE) || + ((c == CHR_NAME_SEP) && (p->Dict_Nesting_Cnt==3) )) // See the large comment below + { + if( c == CHR_ANGLE_CLOSE ) + { + if( Push_State( p ) != File_Decomp_OK ) + return( File_Decomp_Error ); + p->Sub_State = P_DICT_CLOSE_TOK; + } + else + { + p->Sub_State = P_DICT_SKIP; + } + if( (Process_Filter_Spec( SessionPtr ) == File_Decomp_Error) ) + return( File_Decomp_Error ); + } + else + { + /* Since we don't have a full object parse, we need to assure + that we capture the entire filter spec string. The '>' is always + a terminator, but we also want to terminate on the next /Name entry + after a possible array of /Names. The Dict_Nesting_Cnt is used to + step through the transition options. The '/' character is only a valid + filter spec terminator if we've seen a valid array or one /Name entry. */ + if( (c == CHR_NAME_SEP) && (p->Dict_Nesting_Cnt==1) ) + p->Dict_Nesting_Cnt = 3; + else if( (c == CHR_ARRAY_OPEN) && (p->Dict_Nesting_Cnt==1) ) + p->Dict_Nesting_Cnt = 2; + else if( (c == CHR_ARRAY_CLOSE) && (p->Dict_Nesting_Cnt==2) ) + p->Dict_Nesting_Cnt = 3; + + if( p->Filter_Spec_Index < (FILTER_SPEC_BUF_LEN-1) ) + { + p->Filter_Spec_Buf[p->Filter_Spec_Index++] = c; + } + else + return( File_Decomp_Error ); + } + break; + } + + case( P_DICT_CLOSE_TOK ): + { + if( c == CHR_ANGLE_CLOSE ) + { + /* Pop the temp state just prior to the first > */ + if( Pop_State( p ) == File_Decomp_Error ) + return( File_Decomp_Error ); + + /* Pop back to the state before the <<. */ + /* But not so fast... Look at what state/sub-state we are popping + back to. If it's IND_OBJ, AND we have an active filter type, + we don't want to scan to the end of the stream but rather the beginning + of the stream. */ + if( SessionPtr->Decomp_Type != FILE_COMPRESSION_TYPE_NONE ) + { + fd_PDF_Parse_Stack_p_t StckPtr; + + if( (StckPtr = Get_Previous_State( p )) == NULL ) + { + /* There MUST be a previous state that got us here. */ + return( File_Decomp_Error ); + } + else + { + if( (StckPtr->State == P_IND_OBJ) && + (StckPtr->Sub_State == P_ENDOBJ_TOKEN) ) + { + StckPtr->Sub_State = P_STREAM_TOKEN; + } + } + } + if( Pop_State( p ) == File_Decomp_Error ) + return( File_Decomp_Error ); + } + else + /* Return to where we looking (didn't get >>) */ + if( Pop_State( p ) == File_Decomp_Error ) + return( File_Decomp_Error ); + break; + } + + default: + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + +static inline fd_status_t Process_Stream( fd_PDF_Parse_p_t p ) +{ + p->Sub_State = P_ENDSTREAM_TOKEN; + p->State = P_IND_OBJ; + + if( Push_State( p ) == File_Decomp_Error ) + return( File_Decomp_Error ); + else + { + p->State = P_STREAM; + p->Sub_State = 0; + } + return( File_Decomp_OK ); +} + +/* Indirect Objects occur only at the top level of the file and comprise the + bulk of the file content. */ +static inline fd_status_t Handle_State_IND_OBJ( fd_session_p_t SessionPtr, uint8_t c ) +{ + static uint8_t Ind_Obj_Token[] = { TOK_OBJ_OPEN }; + static uint8_t Ind_Obj_End_Token[] = { TOK_OBJ_CLOSE }; + static uint8_t Stream_Token[] = { TOK_STRM_OPEN }; + static uint8_t Stream_End_Token[] = { TOK_STRM_CLOSE }; + fd_PDF_Parse_p_t p = &(SessionPtr->Decomp_State.PDF.Parse); + + /* Upon initial entry, setup state context */ + if( p->State != P_IND_OBJ ) + { + p->State = P_IND_OBJ; + p->Sub_State = P_OBJ_NUMBER; + p->Elem_Index = 1; + p->Elem_Buf[0] = c; + return( File_Decomp_OK ); + } + + switch( p->Sub_State ) + { + case( P_OBJ_NUMBER ): + case( P_GEN_NUMBER ): + { + if( isdigit( c ) ) + { + if( p->Elem_Index < (sizeof(p->Elem_Buf)-1)) + { + p->Elem_Buf[p->Elem_Index++] = c; + } + else + return( File_Decomp_Error ); + } + else if( c == CHR_SPACE ) + { + uint32_t Value; + p->Elem_Buf[p->Elem_Index] = '\0'; + Value = (uint32_t)strtoul( (const char *)p->Elem_Buf, NULL, 10 ); + if( p->Sub_State == P_OBJ_NUMBER ) + { + p->Obj_Number = Value; + p->Sub_State = P_GEN_NUMBER; + p->Elem_Index = 0; + } + else + { + p->Gen_Number = Value; + p->Sub_State = P_OBJ_TOKEN; + p->Elem_Index = 0; + } + } + break; + } + + case( P_OBJ_TOKEN ): + { + if( c == Ind_Obj_Token[p->Elem_Index++] ) + { + if( Ind_Obj_Token[p->Elem_Index] == '\0' ) + { + p->Sub_State = P_OBJ_EOL; + break; + } + } + else + { + return( File_Decomp_Error ); + } + } + + case( P_OBJ_EOL ): + { + if( IS_EOL(c) ) + { + DEBUG_WRAP(DebugMessage(DEBUG_HTTPINSPECT, "Indirect Object: objnum: %u\n", p->Obj_Number);); + p->Sub_State = P_ENDOBJ_TOKEN; + /* Save our place in the IND_OBJ and go process an OBJECT */ + if( Push_State( p ) != File_Decomp_OK ) + return( File_Decomp_Error ); + return( Handle_State_DICT_OBJECT( SessionPtr, c ) ); + } + + break; + } + + + case( P_STREAM_TOKEN ): + { + if( c == Stream_Token[p->Elem_Index++] ) + { + if( Stream_Token[p->Elem_Index] == '\0' ) + { + /* Look for the limited EOL sequence */ + p->Sub_State = P_STREAM_EOL; + } + break; + } + else if( IS_WHITESPACE(c) ) + { + p->Elem_Index = 0; // reset and keep looking + } + else + return( File_Decomp_Error ); + + break; + } + + case( P_STREAM_EOL ): + { + if( c == CHR_CR ) + { + /* The next char MUST be a LF or error */ + p->Sub_State = P_STREAM_LF; + } + else if( c == CHR_LF ) + { + if( Process_Stream( p ) != File_Decomp_OK ) + return( File_Decomp_Error ); + } + else + return( File_Decomp_Error ); + + break; + } + + case( P_STREAM_LF ): + { + if( c == CHR_LF ) + { + if( Process_Stream( p ) != File_Decomp_OK ) + return( File_Decomp_Error ); + } + else + return( File_Decomp_Error ); + break; + } + + case( P_ENDSTREAM_TOKEN ): + { + if( c == Stream_End_Token[p->Elem_Index++] ) + { + if( Stream_End_Token[p->Elem_Index] == '\0' ) + { + p->Sub_State = P_ENDOBJ_TOKEN; + } + } + else + { + p->Elem_Index = 0; // reset and keep looking + } + + break; + } + + case( P_ENDOBJ_TOKEN ): + { + if( c == Ind_Obj_End_Token[p->Elem_Index++] ) + { + if( Ind_Obj_End_Token[p->Elem_Index] == '\0' ) + { + /* we found the end of the indirect object, return + back to the parent state (always START in this case) */ + return( Pop_State( p ) ); + } + } + else + { + /* Since we don't necessarily handle all object types correctly, + we will spin here searching for the end token. Not the best, + but should work if we don't have a full object parser. */ + p->Elem_Index = 0; // reset and keep looking + + } + + break; + } + + default: + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + +/* A simple state machine to process the xref/trailer/startxref file segments. No + semantic processing and only rough syntactical processing to allow us to skip through + this segment. */ +static inline fd_status_t Handle_State_XREF( fd_session_p_t SessionPtr, uint8_t c ) +{ + static uint8_t *Xref_Tok; + uint8_t Xref_End_Tok[] = { TOK_XRF_END }; + fd_PDF_Parse_p_t p = &(SessionPtr->Decomp_State.PDF.Parse); + + if( p->State != P_XREF ) + { + p->Sub_State = P_XREF_TOKEN; + p->Elem_Index = 1; // Aready matched the first char in START state + p->State = P_XREF; + Xref_Tok = (uint8_t *)((c == TOK_XRF_XREF[0]) ? TOK_XRF_XREF : TOK_XRF_STARTXREF); + return( File_Decomp_OK ); + } + + switch( p->Sub_State ) + { + case( P_XREF_TOKEN ): + { + if( c == Xref_Tok[p->Elem_Index++] ) + { + if( Xref_Tok[p->Elem_Index] == '\0' ) + { + p->Elem_Index = 0; + p->Sub_State = P_XREF_END_TOKEN; + } + } + else + { + return( File_Decomp_Error ); + } + break; + } + + case( P_XREF_END_TOKEN ): + { + if( c == Xref_End_Tok[p->Elem_Index++] ) + { + if( Xref_End_Tok[p->Elem_Index] == '\0' ) + { + p->State = P_START; + } + } + else + { + /* Since we don't necessarily handle all xref content correctly, + we will spin here searching for the end token. Not the best, + but should work if we don't have a full object parser. */ + p->Elem_Index = 0; // reset and keep looking + + } + + break; + } + + default: + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + +static inline fd_status_t Handle_State_START( fd_session_p_t SessionPtr, uint8_t c ) +{ + fd_PDF_Parse_p_t p = &(SessionPtr->Decomp_State.PDF.Parse); + /* Skip any whitespace. This will include + the LF as part of a EOL token. */ + if( IS_WHITESPACE(c) ) + { + return( File_Decomp_OK ); + } + if( c == CHR_COMMENT ) + { + p->State = P_COMMENT; + } + else if( isdigit( c ) ) + { + /* Save state and process an indirect object */ + if( Push_State( p ) != File_Decomp_OK ) + return( File_Decomp_Error ); + return( Handle_State_IND_OBJ( SessionPtr, c ) ); + } + else if( (c == TOK_XRF_XREF[0]) || (c == TOK_XRF_STARTXREF[0]) ) + { + /* Save state and process the xref block */ + if( Push_State( p ) != File_Decomp_OK ) + return( File_Decomp_Error ); + return( Handle_State_XREF( SessionPtr, c ) ); + } + else if( !(IS_WHITESPACE(c)) ) + { + /* If is not an ind_obj started, or a comment starting, then + we don't know what it is, so return an error. */ + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + +/* Incrementally search the incoming data for a PDF compressed stream + (of the type that we can decompress). Move bytes to outgoing data + up to the beginning of the compressed segment. If the FILE_REVERT_BIT + is set in the Session, remove the /Filter spec that was located by + replacing the name with null. */ + +/* Parse file until input blocked or stream located. */ +static fd_status_t Locate_Stream_Beginning( fd_session_p_t SessionPtr ) +{ + fd_PDF_Parse_p_t p = &(SessionPtr->Decomp_State.PDF.Parse); + fd_status_t Ret_Code = File_Decomp_OK; + uint8_t c; + + while( 1 ) + { + /* No reason to parse if there's no input or + room for output. */ + if( SessionPtr->Avail_In == 0 ) + return( File_Decomp_BlockIn ); + if( SessionPtr->Avail_Out == 0 ) + return( File_Decomp_BlockOut ); + + /* Get next byte in input queue */ + c = *SessionPtr->Next_In; + + switch( p->State ) + { + /* The 'ground' state of the parser. All indirect objects + should be located at this level. */ + case( P_START ): + { + if( (Ret_Code = Handle_State_START( SessionPtr, c )) != File_Decomp_OK ) + return( Ret_Code ); + break; + } + + case( P_COMMENT ): + { + /* CR or LF closes the comment. The optional LF + after a CR will be considered whitespace and + removed in the P_START state. */ + if( IS_EOL(c) ) + p->State = P_START; + break; + } + + case( P_IND_OBJ ): + { + if( (Ret_Code = Handle_State_IND_OBJ( SessionPtr, c )) != File_Decomp_OK ) + return( Ret_Code ); + break; + } + + case( P_DICT_OBJECT ): + { + if( (Ret_Code = Handle_State_DICT_OBJECT( SessionPtr, c )) != File_Decomp_OK ) + return( Ret_Code ); + break; + } + + case( P_XREF ): + { + if( (Ret_Code = Handle_State_XREF( SessionPtr, c )) != File_Decomp_OK ) + return( Ret_Code ); + break; + } + + case( P_STREAM ): + { + return( File_Decomp_Complete ); + } + + default: + return( File_Decomp_Error ); + } + /* After parsing, move the byte from the input to the + output stream. We can only be here if there's input + available and output space. */ + (void)Move_1(SessionPtr); + } +} + +static fd_status_t Init_Stream( fd_session_p_t SessionPtr ) +{ + fd_PDF_p_t StPtr = &(SessionPtr->Decomp_State.PDF); + + switch( StPtr->Decomp_Type ) + { + case FILE_COMPRESSION_TYPE_DEFLATE: + { + int z_ret; + + z_stream *z_s = &(StPtr->PDF_Decomp_State.Deflate.StreamDeflate); + + memset( (char *)z_s, 0, sizeof(z_stream)); + + z_s->zalloc = (alloc_func)NULL; + z_s->zfree = (free_func)NULL; + SYNC_IN(z_s) + + z_ret = inflateInit2(z_s, 47); + + if( z_ret != Z_OK ) + { + File_Decomp_Alert( SessionPtr, HI_SERVER_PDF_DEFL_FAILURE ); + return( File_Decomp_Error ); + } + + break; + } + default: + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + +static fd_status_t Decomp_Stream( fd_session_p_t SessionPtr ) +{ + fd_PDF_p_t StPtr = &(SessionPtr->Decomp_State.PDF); + + /* No reason to decompress if there's no input or + room for output. */ + if( SessionPtr->Avail_In == 0 ) + return( File_Decomp_BlockIn ); + if( SessionPtr->Avail_Out == 0 ) + return( File_Decomp_BlockOut ); + + switch( StPtr->Decomp_Type ) + { + case FILE_COMPRESSION_TYPE_DEFLATE: + { + int z_ret; + z_stream *z_s = &(StPtr->PDF_Decomp_State.Deflate.StreamDeflate); + + SYNC_IN(z_s) + + z_ret = inflate(z_s, Z_SYNC_FLUSH); + + SYNC_OUT(z_s) + + if( z_ret == Z_STREAM_END ) + { + return( File_Decomp_Complete ); + } + + if( z_ret != Z_OK ) + { + DEBUG_WRAP(DebugMessage(DEBUG_HTTPINSPECT, "Decompression Error: objnum: %u\n", StPtr->Parse.Obj_Number);); + File_Decomp_Alert( SessionPtr, HI_SERVER_PDF_DEFL_FAILURE ); + return( File_Decomp_Error ); + } + + break; + } + default: + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + +/* After processing a stream, close the decompession engine + and return the state of the parser. */ +static fd_status_t Close_Stream( fd_session_p_t SessionPtr ) +{ + /* Put the parser state back where it was interrupted */ + if( Pop_State( &(SessionPtr->Decomp_State.PDF.Parse) ) == File_Decomp_Error ) + return( File_Decomp_Error ); + + SessionPtr->Decomp_State.PDF.State = PDF_STATE_LOCATE_STREAM; + + return( File_Decomp_OK ); +} + +/* Abort the decompression session upon command from caller. */ +fd_status_t File_Decomp_End_PDF( fd_session_p_t SessionPtr ) +{ + fd_PDF_p_t StPtr; + + if( SessionPtr == NULL ) + return( File_Decomp_Error ); + + StPtr = &(SessionPtr->Decomp_State.PDF); + + if( (StPtr->State != PDF_STATE_INIT_STREAM) && + (StPtr->State != PDF_STATE_PROCESS_STREAM) ) + return( File_Decomp_OK ); + + switch( StPtr->Decomp_Type ) + { + case FILE_COMPRESSION_TYPE_DEFLATE: + { + int z_ret; + z_stream *z_s = &(StPtr->PDF_Decomp_State.Deflate.StreamDeflate); + + z_ret = inflateEnd(z_s); + + if( z_ret != Z_OK ) + { + File_Decomp_Alert( SessionPtr, HI_SERVER_PDF_DEFL_FAILURE ); + return( File_Decomp_Error ); + } + + break; + } + default: + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + + +/* From caller, initialize PDF state machine. */ +fd_status_t File_Decomp_Init_PDF( fd_session_p_t SessionPtr ) +{ + fd_PDF_p_t StPtr; + + if( SessionPtr == NULL ) + return( File_Decomp_Error ); + + StPtr = &(SessionPtr->Decomp_State.PDF); + + Init_Parser( SessionPtr ); + + StPtr->Decomp_Type = FILE_COMPRESSION_TYPE_NONE; + + /* Search for Dictionary/Stream object. */ + StPtr->State = PDF_STATE_LOCATE_STREAM; + + return( File_Decomp_OK ); +} + +/* Run the PDF state machine */ +fd_status_t File_Decomp_PDF( fd_session_p_t SessionPtr ) +{ + fd_status_t Ret_Code; + + if( (SessionPtr == NULL) || (SessionPtr->File_Type != FILE_TYPE_PDF) ) + return( File_Decomp_Error ); + + /* Process all data until blocked */ + while( 1 ) + { + switch( SessionPtr->Decomp_State.PDF.State ) + { + case( PDF_STATE_LOCATE_STREAM ): + { + /* Will return File_Decomp_Complete if/when the start of a valid compressed + stream is located. Decomp_Type will be set. The parsing will be suspended. */ + if( (Ret_Code = Locate_Stream_Beginning( SessionPtr ) ) == File_Decomp_Error) + { + SessionPtr->Error_Event = HI_SERVER_PDF_PARSE_FAILURE; + return( File_Decomp_DecompError ); + } + + /* If we didn't succeed then get more input */ + if( Ret_Code != File_Decomp_Complete ) + return( Ret_Code ); + + /* The Parsing state remains, we break out to perform the stream + decompression. */ + if( SessionPtr->Decomp_Type == FILE_COMPRESSION_TYPE_NONE ) + { + break; + } + else + { + DEBUG_WRAP(DebugMessage(DEBUG_HTTPINSPECT, "Compressed stream of type: %u\n", SessionPtr->Decomp_Type);); + SessionPtr->Decomp_State.PDF.State = PDF_STATE_INIT_STREAM; + /* If we've located the beginning of stream, set new state + and fall into next state */ + } + } + + case( PDF_STATE_INIT_STREAM ): + { + /* Initialize the selected decompression engine. */ + Ret_Code = Init_Stream( SessionPtr ); + if( Ret_Code != File_Decomp_OK ) + { + Ret_Code = File_Decomp_End_PDF( SessionPtr ); + if( Close_Stream( SessionPtr ) != File_Decomp_OK ) + return( File_Decomp_Error ); + File_Decomp_Alert( SessionPtr, HI_SERVER_PDF_DEFL_FAILURE ); + break; + } + + SessionPtr->Decomp_State.PDF.State = PDF_STATE_PROCESS_STREAM; + /* INTENTIONAL FALL-THROUGH INTO PDF_STATE_PROCESS_STREAM CASE. */ + } + + case( PDF_STATE_PROCESS_STREAM ): + { + Ret_Code = Decomp_Stream( SessionPtr ); + /* Has the decompressor indicated the end of the data */ + if( Ret_Code == File_Decomp_Error ) + { + Ret_Code = File_Decomp_End_PDF( SessionPtr ); + if( Close_Stream( SessionPtr ) != File_Decomp_OK ) + return( File_Decomp_Error ); + File_Decomp_Alert( SessionPtr, HI_SERVER_PDF_DEFL_FAILURE ); + break; + } + /* OK -> circle back for more input */ + else if( Ret_Code == File_Decomp_OK ) + break; + else if( Ret_Code != File_Decomp_Complete ) + return( Ret_Code ); + + /* Close the decompression engine */ + if( (Ret_Code = File_Decomp_End_PDF( SessionPtr ) ) == File_Decomp_Error) + return( File_Decomp_Error); + + /* Put the parser state back where it was interrupted */ + if( (Close_Stream( SessionPtr) ) == File_Decomp_Error ) + return( File_Decomp_Error ); + + break; + } + + default: + return( File_Decomp_Error ); + } // switch() + } // while() + + return( File_Decomp_OK ); +} + diff --git a/src/service_inspectors/http_inspect/hi_file_decomp_pdf.h b/src/service_inspectors/http_inspect/hi_file_decomp_pdf.h new file mode 100644 index 000000000..8d715692d --- /dev/null +++ b/src/service_inspectors/http_inspect/hi_file_decomp_pdf.h @@ -0,0 +1,82 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 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 HI_FILE_DECOMP_PDF_H +#define HI_FILE_DECOMP_PDF_H + +#include + +#define ELEM_BUF_LEN (12) +#define FILTER_SPEC_BUF_LEN (40) +#define PARSE_STACK_LEN (12) + +typedef enum pdf_states +{ + PDF_STATE_NEW, + PDF_STATE_LOCATE_STREAM, /* Found sig bytes, looking for dictionary & stream*/ + PDF_STATE_INIT_STREAM, /* Init stream */ + PDF_STATE_PROCESS_STREAM /* Processing stream */ +} fd_PDF_States; + +typedef struct fd_PDF_Parse_Stack_s +{ + uint8_t State; + uint8_t Sub_State; +} fd_PDF_Parse_Stack_t, *fd_PDF_Parse_Stack_p_t; + +typedef struct fd_PDF_Parse_s +{ + uint8_t Dict_Nesting_Cnt; + uint8_t Elem_Buf[ELEM_BUF_LEN]; + uint8_t Elem_Index; + uint8_t Filter_Spec_Buf[FILTER_SPEC_BUF_LEN+1]; + uint8_t Filter_Spec_Index; + fd_PDF_Parse_Stack_t Parse_Stack[PARSE_STACK_LEN]; + uint8_t Parse_Stack_Index; + uint32_t Obj_Number; + uint32_t Gen_Number; + uint8_t Sub_State; + uint8_t State; +} fd_PDF_Parse_t, *fd_PDF_Parse_p_t; + +typedef struct fd_PDF_Deflate_s +{ + z_stream StreamDeflate; +} fd_PDF_Deflate_t; + +typedef struct fd_PDF_s +{ + union + { + fd_PDF_Deflate_t Deflate; + } PDF_Decomp_State; + fd_PDF_Parse_t Parse; + uint8_t Decomp_Type; + uint8_t State; +} fd_PDF_t, *fd_PDF_p_t; + +/* API Functions */ + +fd_status_t File_Decomp_Init_PDF( fd_session_p_t SessionPtr ); + +fd_status_t File_Decomp_End_PDF( fd_session_p_t SessionPtr ); + +fd_status_t File_Decomp_PDF(fd_session_p_t SessionPtr); + +#endif diff --git a/src/service_inspectors/http_inspect/hi_file_decomp_swf.cc b/src/service_inspectors/http_inspect/hi_file_decomp_swf.cc new file mode 100644 index 000000000..43d8ddf55 --- /dev/null +++ b/src/service_inspectors/http_inspect/hi_file_decomp_swf.cc @@ -0,0 +1,340 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 Cisco and/or its affiliates. All rights reserved. +// +// 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. +//-------------------------------------------------------------------------- + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#ifdef LZMA +#include +#endif + +#include "hi_file_decomp.h" +#include "hi_file_decomp_swf.h" +#include "hi_events.h" + +#ifdef LZMA +#define LZMA_HEADER_LEN (13) +#define LZMA_PRP_OFFSET (0) +#define SWF_PRP_OFFSET (9) +#define LZMA_UCL_OFFSET (5) +#define SWF_UCL_OFFSET (1) +#define SWF_HDR_LEN (SWF_SIG_LEN + SWF_VER_LEN + SWF_UCL_LEN) + +static fd_status_t File_Decomp_Process_LZMA_Header( fd_session_p_t SessionPtr ) +{ + uint8_t LZMA_Header[LZMA_HEADER_LEN]; + uint8_t *SWF_Header = SessionPtr->Decomp_State.SWF.Header_Bytes; + uint64_t LZMA_Uncomp_Len; + uint32_t SWF_Uncomp_Len; + int idx; + + lzma_ret l_ret; + lzma_stream *l_s = &(SessionPtr->Decomp_State.SWF.StreamLZMA); + + SWF_Uncomp_Len = 0; + /* Read little-endian into value */ + for( idx=0; idx<4; idx++ ) + SWF_Uncomp_Len += + (uint32_t)((uint8_t)(*(SWF_Header + SWF_UCL_OFFSET + idx)) << (8*idx) ); + + if( SWF_Uncomp_Len < SWF_HDR_LEN ) + { + SessionPtr->Error_Event = HI_SERVER_SWF_LZMA_FAILURE; + return( File_Decomp_DecompError ); + } + + LZMA_Uncomp_Len = (uint64_t)(SWF_Uncomp_Len - (SWF_HDR_LEN)); + + /* Write little-endian from value */ + for( idx=0; idx<8; idx++ ) + *(LZMA_Header + LZMA_UCL_OFFSET + idx) = + (uint8_t)(LZMA_Uncomp_Len & (0xff << idx)); + + LZMA_Uncomp_Len = (uint64_t)(SWF_Uncomp_Len - (SWF_HDR_LEN)); + + /* Move the LZMA Properties */ + for( idx=0; idxnext_out = SessionPtr->Next_Out; + l_s->avail_out = SessionPtr->Avail_Out; + l_s->total_out = SessionPtr->Total_Out; + + l_s->next_in = LZMA_Header; + l_s->avail_in = sizeof(LZMA_Header); + + l_ret = lzma_code(l_s, LZMA_RUN); + + SessionPtr->Next_Out = l_s->next_out; + SessionPtr->Avail_Out = l_s->avail_out; + SessionPtr->Total_Out = l_s->total_out; + + if( l_ret != LZMA_OK ) + { + SessionPtr->Error_Event = HI_SERVER_SWF_LZMA_FAILURE; + return( File_Decomp_DecompError ); + } + + return( File_Decomp_OK ); +} +#endif + +static fd_status_t Decomp( fd_session_p_t SessionPtr ) +{ + switch( SessionPtr->Decomp_Type ) + { + case FILE_COMPRESSION_TYPE_ZLIB: + { + int z_ret; + z_stream *z_s = &(SessionPtr->Decomp_State.SWF.StreamZLIB); + + SYNC_IN(z_s) + + z_ret = inflate(z_s, Z_SYNC_FLUSH); + + SYNC_OUT(z_s) + + if( z_ret == Z_STREAM_END ) + { + return( File_Decomp_Complete ); + } + + if( z_ret != Z_OK ) + { + SessionPtr->Error_Event = HI_SERVER_SWF_ZLIB_FAILURE; + return( File_Decomp_DecompError ); + } + + + break; + } +#ifdef LZMA + case FILE_COMPRESSION_TYPE_LZMA: + { + lzma_ret l_ret; + lzma_stream *l_s = &(SessionPtr->Decomp_State.SWF.StreamLZMA); + + SYNC_IN(l_s) + + l_ret = lzma_code(l_s, LZMA_RUN); + + SYNC_OUT(l_s) + + if( l_ret == LZMA_STREAM_END ) + { + return( File_Decomp_Complete ); + } + + if( l_ret != LZMA_OK ) + { + SessionPtr->Error_Event = HI_SERVER_SWF_LZMA_FAILURE; + return( File_Decomp_DecompError ); + } + + break; + } +#endif + default: + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + +fd_status_t File_Decomp_End_SWF( fd_session_p_t SessionPtr ) +{ + if( SessionPtr == NULL ) + return( File_Decomp_Error ); + + switch( SessionPtr->Decomp_Type ) + { + case FILE_COMPRESSION_TYPE_ZLIB: + { + int z_ret; + z_stream *z_s = &(SessionPtr->Decomp_State.SWF.StreamZLIB); + + z_ret = inflateEnd(z_s); + + if( z_ret != Z_OK ) + { + SessionPtr->Error_Event = HI_SERVER_SWF_ZLIB_FAILURE; + return( File_Decomp_DecompError ); + } + + break; + } +#ifdef LZMA + case FILE_COMPRESSION_TYPE_LZMA: + { + lzma_stream *l_s = &(SessionPtr->Decomp_State.SWF.StreamLZMA); + + lzma_end(l_s); + + break; + } +#endif + default: + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + + +fd_status_t File_Decomp_Init_SWF( fd_session_p_t SessionPtr ) +{ + if( SessionPtr == NULL ) + return( File_Decomp_Error ); + + /* Indicate the we need to look for the remainder of the + uncompressed header. */ + SessionPtr->Decomp_State.SWF.State = SWF_STATE_GET_HEADER; + SessionPtr->Decomp_State.SWF.Header_Cnt = 0; + + switch( SessionPtr->Decomp_Type ) + { + case FILE_COMPRESSION_TYPE_ZLIB: + { + int z_ret; + z_stream *z_s; + + SessionPtr->Decomp_State.SWF.Header_Len = + SWF_VER_LEN + SWF_UCL_LEN; + + z_s = &(SessionPtr->Decomp_State.SWF.StreamZLIB); + + memset( (char *)z_s, 0, sizeof(z_stream)); + + z_s->zalloc = (alloc_func)NULL; + z_s->zfree = (free_func)NULL; + SYNC_IN(z_s) + + z_ret = inflateInit(z_s); + + if( z_ret != Z_OK ) + { + SessionPtr->Error_Event = HI_SERVER_SWF_ZLIB_FAILURE; + return( File_Decomp_DecompError ); + } + + break; + } +#ifdef LZMA + case FILE_COMPRESSION_TYPE_LZMA: + { + lzma_ret l_ret; + lzma_stream *l_s; + + SessionPtr->Decomp_State.SWF.Header_Len = + SWF_VER_LEN + SWF_UCL_LEN + SWF_LZMA_CML_LEN + SWF_LZMA_PRP_LEN; + + l_s = &(SessionPtr->Decomp_State.SWF.StreamLZMA); + + memset( (char *)l_s, 0, sizeof(lzma_stream)); + + SYNC_IN(l_s) + + l_ret = lzma_alone_decoder(l_s, UINT64_MAX); + + if( l_ret != LZMA_OK ) + { + SessionPtr->Error_Event = HI_SERVER_SWF_LZMA_FAILURE; + return( File_Decomp_DecompError ); + } + + + break; + } +#endif + default: + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + +fd_status_t File_Decomp_SWF( fd_session_p_t SessionPtr ) +{ + fd_status_t Ret_Code; + + if( (SessionPtr == NULL) || (SessionPtr->File_Type != FILE_TYPE_SWF) ) + return( File_Decomp_Error ); + + /* Are we still looking for the balance of the uncompressed header? */ + switch( SessionPtr->Decomp_State.SWF.State ) + { + case( SWF_STATE_GET_HEADER ): + { + uint8_t *Cnt_Ptr = &(SessionPtr->Decomp_State.SWF.Header_Cnt); // For convenience + uint8_t *Len_Ptr = &(SessionPtr->Decomp_State.SWF.Header_Len); // For convenience + + while( *Len_Ptr > *Cnt_Ptr ) + { + if( SessionPtr->Avail_In == 0 ) + return( File_Decomp_BlockIn ); + + if( SessionPtr->Avail_Out == 0 ) + return( File_Decomp_BlockOut ); + + SessionPtr->Decomp_State.SWF.Header_Bytes[*Cnt_Ptr] = + *(SessionPtr->Next_In); + + (void)Move_1(SessionPtr); + *Cnt_Ptr += 1; + } + + SessionPtr->Decomp_State.SWF.State = SWF_STATE_PROC_HEADER; + /* INTENTIONAL FALL-THROUGH INTO SWF_STATE_PROC_HEADER CASE. */ + } + case( SWF_STATE_PROC_HEADER ): + { +#ifdef LZMA + if( SessionPtr->Decomp_Type == FILE_COMPRESSION_TYPE_LZMA ) + { + Ret_Code = File_Decomp_Process_LZMA_Header( SessionPtr ); + if( Ret_Code != File_Decomp_OK ) + return( Ret_Code ); + } +#endif + + SessionPtr->Decomp_State.SWF.State = SWF_STATE_DATA; + /* INTENTIONAL FALL-THROUGH INTO SWF_STATE_DATA CASE. */ + } + case( SWF_STATE_DATA ): + { + Ret_Code = Decomp( SessionPtr ); + if( Ret_Code != File_Decomp_Complete ) + return( Ret_Code ); + + Ret_Code = File_Decomp_End_SWF( SessionPtr ); + if( Ret_Code != File_Decomp_OK ) + return( Ret_Code ); + + return( File_Decomp_Complete ); + } + default: + return( File_Decomp_Error ); + } + + return( File_Decomp_OK ); +} + diff --git a/src/service_inspectors/http_inspect/hi_file_decomp_swf.h b/src/service_inspectors/http_inspect/hi_file_decomp_swf.h new file mode 100644 index 000000000..46c243350 --- /dev/null +++ b/src/service_inspectors/http_inspect/hi_file_decomp_swf.h @@ -0,0 +1,76 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2015 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 HI_FILE_DECOMP_SWF_H +#define HI_FILE_DECOMP_SWF_H + +#include +#ifdef LZMA +#include +#endif + +/* Both ZLIB & LZMA files have an uncompressed eight byte header. The signature is + three bytes. The header consists of a three byte sig, a one byte version, + and a four byte uncompressed length (little-endian). */ + +#define SWF_SIG_LEN (3) +#define SWF_VER_LEN (1) +#define SWF_UCL_LEN (4) + +/* LZMA Files have an additional nine bytes of header prior to the compressed data. + This includes a four byte compressed length (little-endian) and five bytes + of LZMA properties. */ +#define SWF_LZMA_CML_LEN (4) +#define SWF_LZMA_PRP_LEN (5) + +/* AFTER the sig, the max number of header bytes to fetch. + VER+UCL+CML+LZMA_PRP -> 14 bytes. */ +#define SWF_MAX_HEADER (14) + +/* Types */ + +typedef enum swf_states +{ + SWF_STATE_NEW, + SWF_STATE_GET_HEADER, /* Found sig bytes, looking for end of uncomp header */ + SWF_STATE_PROC_HEADER, /* Found header bytes, now process the header */ + SWF_STATE_DATA /* Done with header, looking for start of data */ +} fd_SWF_States; + +typedef struct fd_SWF_s +{ + z_stream StreamZLIB; +#ifdef LZMA + lzma_stream StreamLZMA; +#endif + uint8_t Header_Bytes[SWF_MAX_HEADER]; + uint8_t State; + uint8_t Header_Len; + uint8_t Header_Cnt; +} fd_SWF_t; + +/* API Functions */ + +fd_status_t File_Decomp_Init_SWF( fd_session_p_t SessionPtr ); + +fd_status_t File_Decomp_SWF( fd_session_p_t SessionPtr ); + +fd_status_t File_Decomp_End_SWF( fd_session_p_t SessionPtr ); + +#endif diff --git a/src/service_inspectors/http_inspect/hi_main.cc b/src/service_inspectors/http_inspect/hi_main.cc index ab0001482..db444383d 100644 --- a/src/service_inspectors/http_inspect/hi_main.cc +++ b/src/service_inspectors/http_inspect/hi_main.cc @@ -132,17 +132,17 @@ HttpFlowData::HttpFlowData() : FlowData(flow_id) HttpFlowData::~HttpFlowData() { - FreeHttpsessionData(&session); + FreeHttpSessionData(&session); } -HttpsessionData* SetNewHttpsessionData(Packet *p, void*) +HttpSessionData* SetNewHttpSessionData(Packet *p, void*) { HttpFlowData* fd = new HttpFlowData; p->flow->set_application_data(fd); return &fd->session; } -static HttpsessionData* get_session_data(Flow* flow) +static HttpSessionData* get_session_data(Flow* flow) { HttpFlowData* fd = (HttpFlowData*)flow->get_application_data( HttpFlowData::flow_id); @@ -159,6 +159,14 @@ void HttpInspectRegisterXtraDataFuncs() xtra_jsnorm_id = stream.reg_xtra_data_cb(GetHttpJSNormData); } +static void PrintFileDecompOpt(HTTPINSPECT_CONF *ServerConf) +{ + LogMessage(" Decompress response files: %s %s %s\n", + ((ServerConf->file_decomp_modes & FILE_SWF_ZLIB_BIT) != 0) ? "SWF-ZLIB" : "", + ((ServerConf->file_decomp_modes & FILE_SWF_LZMA_BIT) != 0) ? "SWF-LZMA" : "", + ((ServerConf->file_decomp_modes & FILE_PDF_DEFL_BIT) != 0) ? "PDF-DEFL" : ""); +} + static int PrintConfOpt(HTTPINSPECT_CONF_OPT *ConfOpt, const char *Option) { if(!ConfOpt || !Option) @@ -239,6 +247,7 @@ int PrintServerConf(HTTPINSPECT_CONF *ServerConf) ServerConf->log_hostname ? "YES" : "NO"); LogMessage(" Extract Gzip from responses: %s\n", ServerConf->extract_gzip ? "YES" : "NO"); + PrintFileDecompOpt(ServerConf); PrintConfOpt(&ServerConf->ascii, "Ascii"); PrintConfOpt(&ServerConf->double_decoding, "Double Decoding"); @@ -425,7 +434,7 @@ static inline FilePosition getFilePoistion(Packet *p) // eg just once when captured; this function is called on every packet and // repeatedly sets the flags on session static inline void HttpLogFuncs( - HttpsessionData *hsd, Packet *p, int iCallDetect ) + HttpSessionData *hsd, Packet *p, int iCallDetect ) { if(!hsd) return; @@ -502,7 +511,7 @@ int HttpInspectMain(HTTPINSPECT_CONF* conf, Packet *p) int iInspectMode = 0; int iRet; int iCallDetect = 1; - HttpsessionData *hsd = NULL; + HttpSessionData *hsd = NULL; PROFILE_VARS; @@ -600,7 +609,7 @@ int HttpInspectMain(HTTPINSPECT_CONF* conf, Packet *p) } if (hsd == NULL) - hsd = SetNewHttpsessionData(p, (void *)session); + hsd = SetNewHttpSessionData(p, (void *)session); else { /* Gzip data should not be logged with all the packets of the session.*/ @@ -1000,7 +1009,50 @@ int HttpInspectMain(HTTPINSPECT_CONF* conf, Packet *p) detect_data_size = 0; } - set_file_data((uint8_t *)session->server.response.body, detect_data_size); + /* Do we have a file decompression object? */ + if( hsd->fd_state != 0 ) + { + fd_status_t Ret_Code; + + uint16_t Data_Len; + const uint8_t *Data; + + hsd->fd_state->Next_In = (uint8_t*)(Data = session->server.response.body); + hsd->fd_state->Avail_In = (Data_Len = (uint16_t)detect_data_size); + + (void)File_Decomp_SetBuf( hsd->fd_state ); + + Ret_Code = File_Decomp( hsd->fd_state ); + + if( Ret_Code == File_Decomp_DecompError ) + { + session->server.response.body = Data; + session->server.response.body_size = Data_Len; + + hi_set_event(GID_HTTP_SERVER, hsd->fd_state->Error_Event); + File_Decomp_StopFree( hsd->fd_state ); + hsd->fd_state = NULL; + } + /* If we didn't find a Sig, then clear the File_Decomp state + and don't keep looking. */ + else if( Ret_Code == File_Decomp_NoSig ) + { + File_Decomp_StopFree( hsd->fd_state ); + hsd->fd_state = NULL; + } + else + { + session->server.response.body = hsd->fd_state->Buffer; + session->server.response.body_size = hsd->fd_state->Total_Out; + } + + set_file_data((uint8_t *)session->server.response.body, (uint16_t)session->server.response.body_size); + } + + else + { + set_file_data((uint8_t *)session->server.response.body, detect_data_size); + } if (PacketHasPAFPayload(p) && file_api->file_process(p,(uint8_t *)session->server.response.body, (uint16_t)session->server.response.body_size, @@ -1065,7 +1117,7 @@ int HttpInspectInitializeGlobalConfig(HTTPINSPECT_GLOBAL_CONF* config) if (iRet) return iRet; - iRet = hi_client_init(config); + iRet = hi_client_init(); if (iRet) return iRet; @@ -1075,9 +1127,9 @@ int HttpInspectInitializeGlobalConfig(HTTPINSPECT_GLOBAL_CONF* config) return 0; } -void FreeHttpsessionData(void *data) +void FreeHttpSessionData(void *data) { - HttpsessionData *hsd = (HttpsessionData *)data; + HttpSessionData *hsd = (HttpSessionData *)data; if (hsd->decomp_state != NULL) { @@ -1092,11 +1144,17 @@ void FreeHttpsessionData(void *data) sfip_free(hsd->true_ip); file_api->free_mime_session(hsd->mime_ssn); + + if( hsd->fd_state != 0 ) + { + File_Decomp_StopFree(hsd->fd_state); // Stop & Stop & Free fd session object + hsd->fd_state = NULL; // ...just for good measure + } } int GetHttpTrueIP(Flow* flow, uint8_t **buf, uint32_t *len, uint32_t *type) { - HttpsessionData* hsd = get_session_data(flow); + HttpSessionData* hsd = get_session_data(flow); if(!hsd->true_ip) return 0; @@ -1119,7 +1177,7 @@ int GetHttpTrueIP(Flow* flow, uint8_t **buf, uint32_t *len, uint32_t *type) int IsGzipData(Flow* flow) { - HttpsessionData *hsd = NULL; + HttpSessionData *hsd = NULL; if (flow == NULL) return -1; @@ -1152,7 +1210,7 @@ int GetHttpGzipData(Flow* flow, uint8_t **buf, uint32_t *len, uint32_t *type) int IsJSNormData(Flow* flow) { - HttpsessionData *hsd = NULL; + HttpSessionData *hsd = NULL; if (flow == NULL) return -1; @@ -1184,7 +1242,7 @@ int GetHttpJSNormData(Flow* flow, uint8_t **buf, uint32_t *len, uint32_t *type) int GetHttpUriData(Flow* flow, uint8_t **buf, uint32_t *len, uint32_t *type) { - HttpsessionData *hsd = NULL; + HttpSessionData *hsd = NULL; if (flow == NULL) return 0; @@ -1208,7 +1266,7 @@ int GetHttpUriData(Flow* flow, uint8_t **buf, uint32_t *len, uint32_t *type) int GetHttpHostnameData(Flow* flow, uint8_t **buf, uint32_t *len, uint32_t *type) { - HttpsessionData *hsd = NULL; + HttpSessionData *hsd = NULL; if (flow == NULL) return 0; diff --git a/src/service_inspectors/http_inspect/hi_main.h b/src/service_inspectors/http_inspect/hi_main.h index 73cec8fb8..668cbe0d2 100644 --- a/src/service_inspectors/http_inspect/hi_main.h +++ b/src/service_inspectors/http_inspect/hi_main.h @@ -102,9 +102,8 @@ typedef struct s_HTTP_LOG_STATE uint8_t hostname_extracted[MAX_HOSTNAME]; }HTTP_LOG_STATE; -typedef struct _HttpsessionData +typedef struct _HttpSessionData { - uint32_t event_flags; HTTP_RESP_STATE resp_state; DECOMPRESS_STATE *decomp_state; HTTP_LOG_STATE *log_state; @@ -114,7 +113,8 @@ typedef struct _HttpsessionData uint8_t cli_small_chunk_count; uint8_t srv_small_chunk_count; MimeState *mime_ssn; -} HttpsessionData; + fd_session_p_t fd_state; +} HttpSessionData; class HttpFlowData : public FlowData { @@ -126,7 +126,7 @@ public: public: static unsigned flow_id; - HttpsessionData session; + HttpSessionData session; }; typedef struct _HISearch @@ -181,7 +181,7 @@ extern THREAD_LOCAL HISearch hi_html_search[HTML_LAST]; extern THREAD_LOCAL HISearch *hi_current_search; extern THREAD_LOCAL HISearchInfo hi_search_info; -void ApplyFlowDepth(HTTPINSPECT_CONF *, Packet *, HttpsessionData *, int, int, uint32_t); +void ApplyFlowDepth(HTTPINSPECT_CONF *, Packet *, HttpSessionData *, int, int, uint32_t); void HttpInspectRegisterXtraDataFuncs(); int HttpInspectMain(HTTPINSPECT_CONF *GlobalConf, Packet *p); @@ -189,8 +189,8 @@ int ProcessGlobalConf(HTTPINSPECT_GLOBAL_CONF *, char *, int); int PrintGlobalConf(HTTPINSPECT_GLOBAL_CONF *); int PrintServerConf(HTTPINSPECT_CONF*); int HttpInspectInitializeGlobalConfig(HTTPINSPECT_GLOBAL_CONF*); -HttpsessionData * SetNewHttpsessionData(Packet *, void *); -void FreeHttpsessionData(void *data); +HttpSessionData * SetNewHttpSessionData(Packet *, void *); +void FreeHttpSessionData(void *data); int GetHttpTrueIP(Flow*, uint8_t **buf, uint32_t *len, uint32_t *type); int GetHttpGzipData(Flow*, uint8_t **buf, uint32_t *len, uint32_t *type); int GetHttpJSNormData(Flow*, uint8_t **buf, uint32_t *len, uint32_t *type); @@ -230,7 +230,7 @@ static inline void ResetRespState(HTTP_RESP_STATE *ds) ds->max_seq = 0; } -static inline int SetLogBuffers(HttpsessionData *hsd) +static inline int SetLogBuffers(HttpSessionData *hsd) { int iRet = 0; diff --git a/src/service_inspectors/http_inspect/hi_mi.cc b/src/service_inspectors/http_inspect/hi_mi.cc index 785652413..52800e4ed 100644 --- a/src/service_inspectors/http_inspect/hi_mi.cc +++ b/src/service_inspectors/http_inspect/hi_mi.cc @@ -61,7 +61,7 @@ */ int hi_mi_mode_inspection(HI_SESSION *session, int iInspectMode, - Packet *p, HttpsessionData *hsd) + Packet *p, HttpSessionData *hsd) { int iRet; if (!session || !p->data || (p->dsize == 0)) diff --git a/src/service_inspectors/http_inspect/hi_mi.h b/src/service_inspectors/http_inspect/hi_mi.h index a475478f9..a71f3550e 100644 --- a/src/service_inspectors/http_inspect/hi_mi.h +++ b/src/service_inspectors/http_inspect/hi_mi.h @@ -36,7 +36,7 @@ #include "hi_include.h" #include "hi_main.h" -int hi_mi_mode_inspection(HI_SESSION *session, int iInspectMode, Packet *p, HttpsessionData *); +int hi_mi_mode_inspection(HI_SESSION *session, int iInspectMode, Packet *p, HttpSessionData *); #endif diff --git a/src/service_inspectors/http_inspect/hi_module.cc b/src/service_inspectors/http_inspect/hi_module.cc index f6abbf4b7..15ea6678b 100644 --- a/src/service_inspectors/http_inspect/hi_module.cc +++ b/src/service_inspectors/http_inspect/hi_module.cc @@ -350,12 +350,17 @@ static const Parameter hi_profile_server_params[] = { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } }; - static const Parameter hi_server_params[] = { { "allow_proxy_use", Parameter::PT_BOOL, nullptr, "false", "don't alert on proxy use for this server" }, + { "decompress_pdf", Parameter::PT_BOOL, nullptr, "false", + "????" }, // FIXIT-M need to figure out this parameter format and implement it + + { "decompress_swf", Parameter::PT_BOOL, nullptr, "false", + "????" }, // FIXIT-M need to figure out this parameter format and implement it with deflate and lzma + { "enable_cookies", Parameter::PT_BOOL, nullptr, "true", "extract cookies" }, @@ -413,6 +418,9 @@ static const Parameter hi_server_params[] = { "unlimited_decompress", Parameter::PT_BOOL, nullptr, "true", "decompress across multiple packets" }, + { "xff_headers", Parameter::PT_BOOL, nullptr, "false", + "????" }, // FIXIT-M need to figure out this parameter format and implement it + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } }; @@ -429,6 +437,12 @@ static const RuleMap hi_server_rules[] = { HI_SERVER_JS_OBFUSCATION_EXCD, HI_SERVER_JS_OBFUSCATION_EXCD_STR }, { HI_SERVER_JS_EXCESS_WS, HI_SERVER_JS_EXCESS_WS_STR }, { HI_SERVER_MIXED_ENCODINGS, HI_SERVER_MIXED_ENCODINGS_STR }, + { HI_SERVER_SWF_ZLIB_FAILURE, HI_SERVER_SWF_ZLIB_FAILURE_STR }, + { HI_SERVER_SWF_LZMA_FAILURE, HI_SERVER_SWF_LZMA_FAILURE_STR }, + { HI_SERVER_PDF_DEFL_FAILURE, HI_SERVER_PDF_DEFL_FAILURE_STR }, + { HI_SERVER_PDF_UNSUP_COMP_TYPE, HI_SERVER_PDF_UNSUP_COMP_TYPE_STR }, + { HI_SERVER_PDF_CASC_COMP, HI_SERVER_PDF_CASC_COMP_STR }, + { HI_SERVER_PDF_PARSE_FAILURE, HI_SERVER_PDF_PARSE_FAILURE_STR }, { 0, nullptr } }; @@ -480,6 +494,12 @@ bool HttpServerModule::set(const char*, Value& v, SnortConfig*) else if ( v.is("code_page") ) server->iis_unicode_codepage = v.get_long(); + else if ( v.is("decompress_pdf") ); // FIXIT-M need to figure this out and implement it + + else if ( v.is("decompress_swf") ); // FIXIT-M need to figure this out and implement it + // including #define DECOMPRESS_DEFLATE "deflate" and + // including #define DECOMPRESS_LZMA "lzma" + else if ( v.is("directory") ) server->directory.on = v.get_bool(); @@ -597,6 +617,8 @@ bool HttpServerModule::set(const char*, Value& v, SnortConfig*) else if ( v.is("whitespace_chars") ) v.get_bits(server->whitespace); + else if ( v.is("xff_headers") ); // FIXIT-M need to figure this out and implement it + else return false; diff --git a/src/service_inspectors/http_inspect/hi_norm.cc b/src/service_inspectors/http_inspect/hi_norm.cc index ffd702ed6..ba064de45 100644 --- a/src/service_inspectors/http_inspect/hi_norm.cc +++ b/src/service_inspectors/http_inspect/hi_norm.cc @@ -87,8 +87,8 @@ typedef struct s_URI_NORM_STATE typedef int (*DECODE_FUNC)(HI_SESSION *, const u_char *, const u_char *, const u_char **, URI_NORM_STATE *, uint16_t *); +static THREAD_LOCAL bool byte_decoded=false; -bool byte_decoded=false; /* ** NAME ** GetPtr:: @@ -1171,6 +1171,18 @@ static int CheckLongDir(HI_SESSION *session, URI_NORM_STATE *norm_state, return HI_SUCCESS; } +/* +** NAME +** EndPathField:: +*/ +/* This function determines whether a character marks the end of the URI path field. +** It recognizes the query field '?' and fragment field '#' delimiters. +** It will not accept a percent-encoded character as a valid delimiter. +*/ +static inline bool EndPathField (bool percent_encoded, u_char uri_character) { + return ((uri_character == '?') || (uri_character == '#')) && !percent_encoded; +} + /* ** NAME ** InspectUriChar:: @@ -1323,6 +1335,14 @@ static inline int InspectUriChar(HI_SESSION *session, int iChar, } } + // This block is necessary to detect '?' and '#' delimiters that immediately follow a '/'. + if(EndPathField(byte_decoded, (u_char)iDir)) + { + // This is the end of the path field. Check for a long directory following. + CheckLongDir(session, norm_state, *ub_ptr); + norm_state->param = *ub_ptr; + } + **ub_ptr = (u_char)iDir; (*ub_ptr)++; } @@ -1330,14 +1350,9 @@ static inline int InspectUriChar(HI_SESSION *session, int iChar, return HI_SUCCESS; } - if((!byte_decoded && (u_char)iChar == '?')) + if(EndPathField(byte_decoded, (u_char)iChar)) { - /* - ** We assume that this is the beginning of the parameter field, - ** and check for a long directory following. Event though seeing - ** a question mark does not guarantee the parameter field, thanks - ** IIS. - */ + // This is the end of the path field. Check for a long directory following. CheckLongDir(session, norm_state, *ub_ptr); norm_state->param = *ub_ptr; } @@ -1485,7 +1500,7 @@ int hi_norm_uri(HI_SESSION *session, u_char *uribuf, int *uribuf_size, ** @retval HI_SUCCESS function successful ** @retval HI_INVALID_ARG invalid argument */ -int hi_normalization(HI_SESSION *session, int iInspectMode, HttpsessionData *hsd) +int hi_normalization(HI_SESSION *session, int iInspectMode, HttpSessionData *hsd) { int iRet; diff --git a/src/service_inspectors/http_inspect/hi_norm.h b/src/service_inspectors/http_inspect/hi_norm.h index a9a417687..a12b81949 100644 --- a/src/service_inspectors/http_inspect/hi_norm.h +++ b/src/service_inspectors/http_inspect/hi_norm.h @@ -40,7 +40,7 @@ #define MAX_URI 8192 -int hi_normalization(HI_SESSION *session, int iInspectMode, HttpsessionData *hsd); +int hi_normalization(HI_SESSION *session, int iInspectMode, HttpSessionData *hsd); int hi_norm_uri(HI_SESSION *session, u_char *uribuf,int *uribuf_size, const u_char *uri, int uri_size, uint16_t *encodeType); diff --git a/src/service_inspectors/http_inspect/hi_server.cc b/src/service_inspectors/http_inspect/hi_server.cc index f25a077aa..50be3091f 100644 --- a/src/service_inspectors/http_inspect/hi_server.cc +++ b/src/service_inspectors/http_inspect/hi_server.cc @@ -81,6 +81,14 @@ static THREAD_LOCAL uint8_t dechunk_buffer[65535]; Server->response.header_norm_size = 0 ;\ Server->response.cookie.cookie = NULL;\ Server->response.cookie.cookie_end = NULL;\ + if(Server->response.cookie.next) {\ + COOKIE_PTR *cookie = Server->response.cookie.next; \ + do { \ + Server->response.cookie.next = Server->response.cookie.next->next; \ + free(cookie); \ + cookie = Server->response.cookie.next; \ + }while(cookie);\ + }\ Server->response.cookie.next = NULL;\ Server->response.cookie_norm = NULL;\ Server->response.cookie_norm_size = 0;\ @@ -167,7 +175,7 @@ static inline const u_char *MovePastDelims(const u_char *start, const u_char *en ** @retval HI_INVALID_ARG invalid argument ** @retval HI_SUCCESS function success */ -static int IsHttpServerData(HI_SESSION *session, Packet *p, HttpsessionData *sd) +static int IsHttpServerData(HI_SESSION *session, Packet *p, HttpSessionData *sd) { const u_char *start; const u_char *end; @@ -330,7 +338,7 @@ static inline int hi_server_extract_status_code( /* Grab the argument of "charset=foo" from a Content-Type header */ static inline const u_char *extract_http_content_type_charset( - HI_SESSION*, HttpsessionData *hsd, + HI_SESSION*, HttpSessionData *hsd, const u_char *p, const u_char*, const u_char *end ) { size_t cmplen; @@ -536,7 +544,7 @@ static inline const u_char *extract_http_content_encoding(HTTPINSPECT_CONF *Serv } const u_char *extract_http_transfer_encoding( - HI_SESSION*, HttpsessionData *hsd, + HI_SESSION*, HttpSessionData *hsd, const u_char *p, const u_char *start, const u_char *end, HEADER_PTR *header_ptr, int iInspectMode) { @@ -595,7 +603,7 @@ const u_char *extract_http_transfer_encoding( static inline const u_char *extractHttpRespHeaderFieldValues(HTTPINSPECT_CONF *ServerConf, const u_char *p, const u_char *offset, const u_char *start, const u_char *end, HEADER_PTR *header_ptr, - HEADER_FIELD_PTR *header_field_ptr, int parse_cont_encoding, HttpsessionData *hsd, + HEADER_FIELD_PTR *header_field_ptr, int parse_cont_encoding, HttpSessionData *hsd, HI_SESSION *session) { if (((p - offset) == 0) && ((*p == 'S') || (*p == 's'))) @@ -646,7 +654,7 @@ static inline const u_char *hi_server_extract_header( HI_SESSION *session, HTTPINSPECT_CONF *ServerConf, HEADER_PTR *header_ptr, const u_char *start, const u_char *end, int parse_cont_encoding, - HttpsessionData *hsd) + HttpSessionData *hsd) { const u_char *p; const u_char *offset; @@ -723,7 +731,7 @@ static inline const u_char *hi_server_extract_header( } static inline int hi_server_extract_body( - HI_SESSION *session, HttpsessionData *sd, + HI_SESSION *session, HttpSessionData *sd, const u_char *ptr, const u_char *end, URI_PTR *result) { HTTPINSPECT_CONF *ServerConf; @@ -809,7 +817,45 @@ static inline int hi_server_extract_body( return STAT_END; } -static void SetGzipBuffers(HttpsessionData *hsd, HI_SESSION *session) +static void LogFileDecomp(void*, int event) +{ + // FIXIT-H first argument is supposed to be pointer to session which may be needed to + // generate the event correctly. + hi_set_event(GID_HTTP_SERVER, event); +} + +static void InitFileDecomp(HttpSessionData *hsd, HI_SESSION *session) +{ + fd_session_p_t fd_session; + + if((hsd == NULL) || (session == NULL) || (session->server_conf == NULL) || + (session->global_conf == NULL)) + return; + + if( (fd_session = File_Decomp_New()) == (fd_session_p_t)NULL ) + return; + + hsd->fd_state = fd_session; + fd_session->Modes = session->server_conf->file_decomp_modes; + + fd_session->Alert_Callback = LogFileDecomp; + fd_session->Alert_Context = session; + + if( (session->server_conf->unlimited_decompress) != 0 ) + { + fd_session->Compr_Depth = 0; + fd_session->Decompr_Depth = 0; + } + else + { + fd_session->Compr_Depth = session->global_conf->compr_depth; + fd_session->Decompr_Depth = session->global_conf->decompr_depth; + } + + (void)File_Decomp_Init( fd_session ); +} + +static void SetGzipBuffers(HttpSessionData *hsd, HI_SESSION *session) { if ((hsd != NULL) && (hsd->decomp_state == NULL) && (session != NULL) && (session->server_conf != NULL) @@ -835,7 +881,7 @@ static void SetGzipBuffers(HttpsessionData *hsd, HI_SESSION *session) } int uncompress_gzip ( u_char *dest, int destLen, const u_char *source, - int sourceLen, HttpsessionData *sd, int *total_bytes_read, int compr_fmt) + int sourceLen, HttpSessionData *sd, int *total_bytes_read, int compr_fmt) { z_stream stream; int err; @@ -909,7 +955,7 @@ int uncompress_gzip ( u_char *dest, int destLen, const u_char *source, { /* If some of the compressed data is decompressed we need to provide that for detection */ - if( stream.total_out > 0) + if (( stream.total_out > 0) && (err != Z_DATA_ERROR)) { *total_bytes_read = stream.total_out; iRet = HI_NONFATAL_ERR; @@ -925,7 +971,7 @@ int uncompress_gzip ( u_char *dest, int destLen, const u_char *source, return HI_SUCCESS; } -static inline int hi_server_decompress(HI_SESSION *session, HttpsessionData *sd, const u_char *ptr, +static inline int hi_server_decompress(HI_SESSION *session, HttpSessionData *sd, const u_char *ptr, const u_char *end, URI_PTR *result) { const u_char *start = ptr; @@ -983,6 +1029,7 @@ static inline int hi_server_decompress(HI_SESSION *session, HttpsessionData *sd, if ((compr_avail <= 0) || (decompr_avail <= 0)) { + (void)File_Decomp_Reset(sd->fd_state); ResetGzipState(sd->decomp_state); ResetRespState(&(sd->resp_state)); return iRet; @@ -1052,6 +1099,7 @@ static inline int hi_server_decompress(HI_SESSION *session, HttpsessionData *sd, } else ResetRespState(&(sd->resp_state)); + (void)File_Decomp_Reset(sd->fd_state); ResetGzipState(sd->decomp_state); } @@ -1068,7 +1116,7 @@ static inline int hi_server_decompress(HI_SESSION *session, HttpsessionData *sd, } -static inline int hi_server_inspect_body(HI_SESSION *session, HttpsessionData *sd, const u_char *ptr, +static inline int hi_server_inspect_body(HI_SESSION *session, HttpSessionData *sd, const u_char *ptr, const u_char *end, URI_PTR *result) { int iRet = HI_SUCCESS; @@ -1079,6 +1127,7 @@ static inline int hi_server_inspect_body(HI_SESSION *session, HttpsessionData *s { if ((sd != NULL)) { + (void)File_Decomp_Reset(sd->fd_state); ResetGzipState(sd->decomp_state); ResetRespState(&(sd->resp_state)); } @@ -1108,7 +1157,7 @@ static inline int hi_server_inspect_body(HI_SESSION *session, HttpsessionData *s } void ApplyFlowDepth( HTTPINSPECT_CONF *ServerConf, Packet *p, - HttpsessionData *sd, int resp_header_size, int, uint32_t seq_num) + HttpSessionData *sd, int resp_header_size, int, uint32_t seq_num) { if(!ServerConf->server_flow_depth) { @@ -1167,14 +1216,15 @@ void ApplyFlowDepth( } } -static inline void ResetState (HttpsessionData* sd) +static inline void ResetState (HttpSessionData* sd) { + (void)File_Decomp_Reset(sd->fd_state); ResetGzipState(sd->decomp_state); ResetRespState(&(sd->resp_state)); } static int HttpResponseInspection(HI_SESSION *session, Packet *p, const unsigned char *data, - int dsize, HttpsessionData *sd) + int dsize, HttpSessionData *sd) { HTTPINSPECT_CONF *ServerConf; URI_PTR stat_code_ptr; @@ -1284,6 +1334,7 @@ static int HttpResponseInspection(HI_SESSION *session, Packet *p, const unsigned } else { + (void)File_Decomp_Reset(sd->fd_state); ResetGzipState(sd->decomp_state); ResetRespState(&(sd->resp_state)); } @@ -1304,6 +1355,7 @@ static int HttpResponseInspection(HI_SESSION *session, Packet *p, const unsigned } else { + (void)File_Decomp_Reset(sd->fd_state); ResetGzipState(sd->decomp_state); ResetRespState(&(sd->resp_state)); } @@ -1317,6 +1369,7 @@ static int HttpResponseInspection(HI_SESSION *session, Packet *p, const unsigned } else { + (void)File_Decomp_Reset(sd->fd_state); ResetGzipState(sd->decomp_state); ResetRespState(&(sd->resp_state)); } @@ -1377,6 +1430,7 @@ static int HttpResponseInspection(HI_SESSION *session, Packet *p, const unsigned ApplyFlowDepth(ServerConf, p, sd, resp_header_size, 0, seq_num); if ( not_stream_insert && (sd != NULL)) { + (void)File_Decomp_Reset(sd->fd_state); ResetGzipState(sd->decomp_state); ResetRespState(&(sd->resp_state)); } @@ -1396,6 +1450,7 @@ static int HttpResponseInspection(HI_SESSION *session, Packet *p, const unsigned expected_pkt = 0; if(sd != NULL) { + (void)File_Decomp_Reset(sd->fd_state); ResetGzipState(sd->decomp_state); ResetRespState(&(sd->resp_state)); sd->resp_state.flow_depth_excd = false; @@ -1512,6 +1567,11 @@ static int HttpResponseInspection(HI_SESSION *session, Packet *p, const unsigned sd->resp_state.inspect_body = 1; } + if( ServerConf->file_decomp_modes != 0 ) + { + InitFileDecomp(sd, session); + } + sd->resp_state.last_pkt_contlen = (header_ptr.content_len.len != 0); if(ServerConf->server_flow_depth == -1) sd->resp_state.flow_depth_excd = true; @@ -1580,8 +1640,13 @@ static int HttpResponseInspection(HI_SESSION *session, Packet *p, const unsigned { status = SafeMemcpy(HttpDecodeBuf.data, Server->response.body, alt_dsize, HttpDecodeBuf.data, HttpDecodeBuf.data + sizeof(HttpDecodeBuf.data)); - if( status != SAFEMEM_SUCCESS ) + if (status != SAFEMEM_SUCCESS) + { + CLR_SERVER_HEADER(Server); + CLR_SERVER_STAT_MSG(Server); + CLR_SERVER_STAT(Server); return HI_MEM_ALLOC_FAIL; + } SetHttpDecode((uint16_t)alt_dsize); Server->response.body = HttpDecodeBuf.data; @@ -1619,17 +1684,11 @@ static int HttpResponseInspection(HI_SESSION *session, Packet *p, const unsigned } } - { - /* There is no body to the HTTP response. - * In this case we need to inspect the entire HTTP response header. - */ - ApplyFlowDepth(ServerConf, p, sd, resp_header_size, 1, seq_num); - } - + ApplyFlowDepth(ServerConf, p, sd, resp_header_size, 1, seq_num); return HI_SUCCESS; } -int ServerInspection(HI_SESSION *session, Packet *p, HttpsessionData *hsd) +int ServerInspection(HI_SESSION *session, Packet *p, HttpSessionData *hsd) { int iRet; @@ -1655,7 +1714,7 @@ int ServerInspection(HI_SESSION *session, Packet *p, HttpsessionData *hsd) return HI_SUCCESS; } -int hi_server_inspection(void *S, Packet *p, HttpsessionData *hsd) +int hi_server_inspection(void *S, Packet *p, HttpSessionData *hsd) { HI_SESSION *session; diff --git a/src/service_inspectors/http_inspect/hi_server.h b/src/service_inspectors/http_inspect/hi_server.h index 19e88e741..6e5ea8ea6 100644 --- a/src/service_inspectors/http_inspect/hi_server.h +++ b/src/service_inspectors/http_inspect/hi_server.h @@ -66,7 +66,7 @@ typedef struct s_HI_SERVER HI_SERVER_RESP response; } HI_SERVER; -int hi_server_inspection(void *, Packet *, HttpsessionData *); +int hi_server_inspection(void *, Packet *, HttpSessionData *); #endif diff --git a/src/service_inspectors/http_inspect/hi_server_norm.cc b/src/service_inspectors/http_inspect/hi_server_norm.cc index c28722bc7..3d74d54dc 100644 --- a/src/service_inspectors/http_inspect/hi_server_norm.cc +++ b/src/service_inspectors/http_inspect/hi_server_norm.cc @@ -57,7 +57,7 @@ #include "detection_util.h" -int hi_server_norm(HI_SESSION *session, HttpsessionData *hsd) +int hi_server_norm(HI_SESSION *session, HttpSessionData *hsd) { static THREAD_LOCAL u_char HeaderBuf[MAX_URI]; static THREAD_LOCAL u_char CookieBuf[MAX_URI]; diff --git a/src/service_inspectors/http_inspect/hi_server_norm.h b/src/service_inspectors/http_inspect/hi_server_norm.h index eb8886cf3..4775f0b54 100644 --- a/src/service_inspectors/http_inspect/hi_server_norm.h +++ b/src/service_inspectors/http_inspect/hi_server_norm.h @@ -35,6 +35,6 @@ #include "hi_include.h" #include "hi_si.h" -int hi_server_norm(HI_SESSION *session, HttpsessionData *hsd); +int hi_server_norm(HI_SESSION *session, HttpSessionData *hsd); #endif diff --git a/src/service_inspectors/http_inspect/hi_si.h b/src/service_inspectors/http_inspect/hi_si.h index cc47df5a7..046d89d79 100644 --- a/src/service_inspectors/http_inspect/hi_si.h +++ b/src/service_inspectors/http_inspect/hi_si.h @@ -121,12 +121,12 @@ int hi_si_session_inspection(HTTPINSPECT_CONF *GlobalConf, Packet *p); extern int CheckChunkEncoding(HI_SESSION *, const u_char *, const u_char *, const u_char **, u_char *, - uint32_t , uint32_t, uint32_t *, uint32_t *, HttpsessionData *, int); + uint32_t , uint32_t, uint32_t *, uint32_t *, HttpSessionData *, int); extern int IsHttpVersion(const u_char **, const u_char *); extern const u_char *extract_http_cookie(const u_char *p, const u_char *end, HEADER_PTR *, HEADER_FIELD_PTR *); extern const u_char *extract_http_content_length(HI_SESSION *, HTTPINSPECT_CONF *, const u_char *, const u_char *, const u_char *, HEADER_PTR *, HEADER_FIELD_PTR *) ; -extern const u_char *extract_http_transfer_encoding(HI_SESSION *, HttpsessionData *, +extern const u_char *extract_http_transfer_encoding(HI_SESSION *, HttpSessionData *, const u_char *, const u_char *, const u_char *, HEADER_PTR *, int); #endif diff --git a/src/service_inspectors/http_inspect/hi_ui_config.h b/src/service_inspectors/http_inspect/hi_ui_config.h index 5e1fec198..99d8da715 100644 --- a/src/service_inspectors/http_inspect/hi_ui_config.h +++ b/src/service_inspectors/http_inspect/hi_ui_config.h @@ -39,6 +39,7 @@ #include "sf_ip.h" #include "hi_util_kmap.h" #include "file_api/file_api.h" +#include "hi_file_decomp.h" #include "framework/bits.h" /* @@ -47,6 +48,7 @@ #define HI_UI_CONFIG_MAX_HDR_DEFAULT 0 #define HI_UI_CONFIG_MAX_HEADERS_DEFAULT 0 #define HI_UI_CONFIG_MAX_SPACES_DEFAULT 200 +#define HI_UI_CONFIG_MAX_XFF_FIELD_NAMES 8 /* ** Special characters treated as whitespace before or after URI @@ -152,11 +154,20 @@ struct HTTPINSPECT_CONF char uri_only; char enable_cookie; char inspect_response; + uint8_t *xff_headers[HI_UI_CONFIG_MAX_XFF_FIELD_NAMES]; + uint8_t xff_header_lengths[HI_UI_CONFIG_MAX_XFF_FIELD_NAMES]; char enable_xff; char log_uri; char log_hostname; bool unlimited_decompress; char extract_gzip; + unsigned long file_decomp_modes; + +/* NOTE: The XFF_BUILTING_NAMES value must match the code in snort_httpinspect.c that + adds the builtin names to the list. */ +#define HI_UI_CONFIG_XFF_FIELD_NAME "X-Forwarded-For" +#define HI_UI_CONFIG_TCI_FIELD_NAME "True-Client-IP" +#define XFF_BUILTIN_NAMES (2) /* Support Extended ascii codes in the URI */ char extended_ascii_uri; diff --git a/src/service_inspectors/http_inspect/http_inspect.cc b/src/service_inspectors/http_inspect/http_inspect.cc index c18a661b2..30fec3450 100644 --- a/src/service_inspectors/http_inspect/http_inspect.cc +++ b/src/service_inspectors/http_inspect/http_inspect.cc @@ -47,6 +47,7 @@ #include "snort_debug.h" #include "util.h" #include "parser.h" +#include "hi_file_decomp.h" #include "hi_client.h" #include "hi_ui_config.h" @@ -70,8 +71,6 @@ #include "framework/inspector.h" #include "managers/data_manager.h" -#define ERRSTRLEN 1000 - int hex_lookup[256]; int valid_lookup[256]; @@ -127,7 +126,7 @@ static void CheckGzipConfig(HTTPINSPECT_GLOBAL_CONF *pPolicyConfig) pPolicyConfig->decompr_depth = DEFAULT_DECOMP_DEPTH; pPolicyConfig->max_gzip_sessions = - pPolicyConfig->max_gzip_mem / sizeof(DECOMPRESS_STATE); + pPolicyConfig->max_gzip_mem / (sizeof(DECOMPRESS_STATE) + sizeof(fd_session_t)); } static void CheckMemcap(HTTPINSPECT_GLOBAL_CONF *pPolicyConfig)