📄 hi_client.c
字号:
/**** @file hi_client.c**** @author Daniel Roelker <droelker@sourcefire.com>**** @brief Main file for all the client functions and inspection** flow.**** The job of the client module is to analyze and inspect the HTTP** protocol, finding where the various fields begin and end. This must** be accomplished in a stateful and stateless manner.**** While the fields are being determined, we also do checks for ** normalization, so we don't normalize fields that don't need it.**** Currently, the only fields we check for this is the URI and the** parameter fields.** ** NOTES:** - 3.8.03: Initial development. DJR*/#include <stdlib.h>#include <stdio.h>#include <string.h>#include <ctype.h>#include <sys/types.h>#include "hi_ui_config.h"#include "hi_si.h"#include "hi_mi.h"#include "hi_client.h"#include "hi_eo_log.h"#include "hi_util.h"#include "hi_util_hbm.h"#include "hi_return_codes.h"#define URI_END 1#define NO_URI -1#define INVALID_HEX_VAL -1/**** This structure holds pointers to the different sections of an HTTP** request. We need to track where whitespace begins and ends, so we** can evaluate the placement of the URI correctly.**** For example,**** GET / HTTP/1.0** ^ ^ ** start end**** The end space pointers are set to NULL if there is space until the end** of the buffer.*/typedef struct s_URI_PTR{ u_char *uri; /* the beginning of the URI */ u_char *uri_end; /* the end of the URI */ u_char *norm; /* ptr to first normalization occurence */ u_char *ident; /* ptr to beginning of the HTTP identifier */ u_char *first_sp_start; /* beginning of first space delimiter */ u_char *first_sp_end; /* end of first space delimiter */ u_char *second_sp_start; /* beginning of second space delimiter */ u_char *second_sp_end; /* end of second space delimiter */ u_char *param; /* '?' (beginning of parameter field) */ u_char *delimiter; /* HTTP URI delimiter (\r\n\) */ u_char *last_dir; /* ptr to last dir, so we catch long dirs */ u_char *proxy; /* ptr to the absolute URI */} URI_PTR;/**** This makes passing function arguments much more readable and easier** to follow.*/typedef int (*LOOKUP_FCN)(HI_SESSION *, u_char *, u_char *, u_char **, URI_PTR *);/*** The lookup table contains functions for different HTTP delimiters ** (like whitespace and the HTTP delimiter \r and \n).*/static LOOKUP_FCN lookup_table[256];static int hex_lookup[256];/*** NAME** CheckChunkEncoding::*//**** This routine checks for chunk encoding anomalies in an HTTP client request** packet.** ** We convert potential chunk lengths and test them against the user-defined** max chunk length. We log events on any chunk lengths that are over this** defined chunk lengths.** ** Chunks are skipped to save time when the chunk is contained in the packet.** ** We assume coming into this function that we are pointed at the beginning** of what may be a chunk length. That's why the iCheckChunk var is set** to 1.** ** @param Session pointer to the Session construct** @param start pointer to where to beginning of buffer** @param end pointer to the end of buffer** ** @return integer** ** @retval HI_SUCCESS function successful** @retval HI_INVALID_ARG invalid argument*/static int CheckChunkEncoding(HI_SESSION *Session, u_char *start, u_char *end){ u_int iChunkLen = 0; int iChunkChars = 0; int iCheckChunk = 1; u_char *ptr; u_char *jump_ptr; if(!start || !end) return HI_INVALID_ARG; ptr = start; while(hi_util_in_bounds(start, end, ptr)) { if(*ptr == '\n') { if(iCheckChunk && iChunkLen != 0) { if(Session->server_conf->chunk_length < iChunkLen) hi_eo_client_event_log(Session, HI_EO_CLIENT_LARGE_CHUNK, NULL, NULL); jump_ptr = ptr + iChunkLen; if(jump_ptr <= ptr) { break; } if(hi_util_in_bounds(start, end, jump_ptr)) { ptr = jump_ptr; } else { /* ** Chunk too large for packet, so we bail */ break; } } iCheckChunk = 1; iChunkLen = 0; iChunkChars = 0; ptr++; continue; } if(iCheckChunk) { if(hex_lookup[*ptr] == INVALID_HEX_VAL) { if(*ptr == '\r') { ptr++; if(!hi_util_in_bounds(start, end, ptr)) break; if(*ptr == '\n') continue; } else if(*ptr == ';') { /* ** This is where we skip through the chunk name=value ** field. */ ptr++; while(hi_util_in_bounds(start, end, ptr)) { if(*ptr == '\n') break; ptr++; } continue; } iCheckChunk = 0; iChunkLen = 0; iChunkChars = 0; } else { if(iChunkChars >= 8) { iCheckChunk = 0; iChunkLen = 0; iChunkChars = 0; } else { iChunkLen <<= 4; iChunkLen |= hex_lookup[*ptr]; iChunkChars++; } } } ptr++; } return HI_SUCCESS;}/*** NAME** FindPipelineReq::*//**** Catch multiple requests per packet, by returning pointer to after the** end of the request header if there is another request.** ** There are 4 types of "valid" delimiters that we look for. They are:** "\r\n\r\n"** "\r\n\n"** "\n\r\n"** "\n\n"** The only patterns that we really only need to look for are:** "\n\r\n"** "\n\n"** The reason being that these two patterns are suffixes of the other ** patterns. So once we find those, we are all good.** ** @param start pointer to the start of text** @param end pointer to the end of text** ** @return pointer** ** @retval NULL Did not find pipeline request** @retval !NULL Found another possible request.*/static INLINE u_char *FindPipelineReq(u_char *start, u_char *end){ u_char *p; if(!start || !end) return NULL; p = start; /* ** We say end - 6 because we need at least six bytes to verify that ** there is an end to the URI and still a request afterwards. To be ** exact, we should only subtract 1, but we are not interested in a ** 1 byte method, uri, etc. ** ** a.k.a there needs to be data after the initial request to inspect ** to make it worth our while. */ while(p < (end - 6)) { if(*p == '\n') { p++; if(*p < 0x0E) { if(*p == '\r') { p++; if(*p == '\n') { return ++p; } } else if(*p == '\n') { return ++p; } } } p++; } return NULL;}/*** NAME** IsHttpVersion::*//**** This checks that there is a version following a space with in an HTTP** packet.** ** This function gets called when a whitespace area has ended, and we want** to know if a version identifier is followed directly after. So we look** for the rfc standard "HTTP/" and report appropriately. We also need** to make sure that the function succeeds given an end of buffer, so for** instance if the buffer ends like " HTT", we still assume that this is** a valid version identifier because of TCP segmentation.** ** We also check for the 0.9 standard of GET URI\r\n. When we see a \r or** a \n, then we just return with the pointer still pointing to that char.** The reason is because on the next loop, we'll do the evaluation that** we normally do and finish up processing there.** ** @param start pointer to the start of the version identifier** @param end pointer to the end of the buffer (could be the end of the** data section, or just to the beginning of the delimiter.** ** @return integer** ** @retval 1 this is an HTTP version identifier** @retval 0 this is not an HTTP identifier, or bad parameters*/static int IsHttpVersion(u_char **ptr, u_char *end){ static u_char s_acHttpDelimiter[] = "HTTP/"; static int s_iHttpDelimiterLen = 5; int len; int iCtr; if(*ptr >= end) { return 0; } len = end - *ptr; if(len > s_iHttpDelimiterLen) { len = s_iHttpDelimiterLen; } /* ** This is where we check for the defunct method again. This method ** allows a request of "GET /index.html \r[\n]". So we need to ** check validate this as a legal identifier. */ if(**ptr == '\n' || **ptr == '\r') { /* ** We don't increment the pointer because we check for a legal ** identifier in the delimiter checking. Read the comments for ** setting the defunct variable in these functions. */ return 1; } for(iCtr = 0; iCtr < len; iCtr++) { if(s_acHttpDelimiter[iCtr] != (u_char)toupper((int)**ptr)) { return 0; } (*ptr)++; } /* ** This means that we match all the chars that we could given the ** remaining length so we should increment the pointer by that much ** since we don't need to inspect this again. */ (*ptr)++; return 1;}/*** NAME** find_rfc_delimiter::*//**** Check for standard RFC HTTP delimiter.**** If we find the delimiter, we return that URI_PTR structures should** be checked, which bails us out of the loop. If there isn't a RFC** delimiter, then we bail with a no URI. Otherwise, we check for out** of bounds.**** @param ServerConf pointer to the server configuration** @param start pointer to the start of payload** @param end pointer to the end of the payload** @param ptr pointer to the pointer of the current index** @param uri_ptr pointer to the URI_PTR construct** ** @return integer** ** @retval HI_OUT_OF_BOUNDS ** @retval URI_END end of the URI is found, check URI_PTR.** @retval NO_URI malformed delimiter, no URI.*/static int find_rfc_delimiter(HI_SESSION *Session, u_char *start, u_char *end, u_char **ptr, URI_PTR *uri_ptr){ if(*ptr == start || !uri_ptr->uri) return NO_URI; /* ** This is important to catch the defunct way of getting URIs without ** specifying "HTTP/major.minor\r\n\r\n". This is a quick way for ** us to tell if we are in that state. ** ** We check for a legal identifier to deal with the case of ** "some_of_the_uri_in segmented packet \r\n" in the defunct case. ** Since we find a "valid" (still defunct) delimiter, we account for ** it here, so that we don't set the uri_end to the delimiter. ** ** NOTE: ** We now assume that the defunct method is in effect and if there is ** a valid identifier, then we don't update the uri_end because it's ** already been set when the identifier was validated. */ (*ptr)++; if(!hi_util_in_bounds(start, end, *ptr)) { return HI_OUT_OF_BOUNDS; } if(**ptr == '\n') { uri_ptr->delimiter = (*ptr)-1; if(!uri_ptr->ident) uri_ptr->uri_end = uri_ptr->delimiter; return URI_END; } return NO_URI;}/*** NAME** find_non_rfc_delimiter::*//**** Check for non standard delimiter '\n'.**** It now appears that apache and iis both take this non-standard ** delimiter. So, we most likely will always look for it, but maybe** give off a special alert or something.**** @param ServerConf pointer to the server configuration** @param start pointer to the start of payload** @param end pointer to the end of the payload** @param ptr pointer to the pointer of the current index** @param uri_ptr pointer to the URI_PTR construct ** ** @return integer** ** @retval URI_END delimiter found, end of URI** @retval NO_URI */static int find_non_rfc_delimiter(HI_SESSION *Session, u_char *start, u_char *end, u_char **ptr, URI_PTR *uri_ptr){ HTTPINSPECT_CONF *ServerConf = Session->server_conf; if(*ptr == start || !uri_ptr->uri) return NO_URI; /* ** This is important to catch the defunct way of getting URIs without ** specifying "HTTP/major.minor\r\n\r\n". This is a quick way for ** us to tell if we are in that state. ** ** We check for a legal identifier to deal with the case of ** "some_of_the_uri_in segmented packet \r\n" in the defunct case. ** Since we find a "valid" (still defunct) delimiter, we account for ** it here, so that we don't set the uri_end to the delimiter. ** ** NOTE: ** We now assume that the defunct method is in effect and if there is ** a valid identifier, then we don't update the uri_end because it's ** already been set when the identifier was validated. */ if(ServerConf->iis_delimiter.on) { if(hi_eo_generate_event(Session, ServerConf->iis_delimiter.alert)) { hi_eo_client_event_log(Session, HI_EO_CLIENT_IIS_DELIMITER, NULL, NULL); } uri_ptr->delimiter = *ptr; if(!uri_ptr->ident) uri_ptr->uri_end = uri_ptr->delimiter; return URI_END; } /* ** This allows us to do something if the delimiter check is not turned ** on. Most likely this is worthy of an alert, IF it's not normal to ** see these requests. ** ** But for now, we always return true. */ uri_ptr->delimiter = *ptr; if(!uri_ptr->ident) uri_ptr->uri_end = uri_ptr->delimiter; return URI_END;}/*** NAME** NextNonWhiteSpace::*//**** Update the URI_PTR fields spaces, find the next non-white space char,** and validate the HTTP version identifier after the spaces.** ** This is the main part of the URI algorithm. This verifies that there** isn't too many spaces in the data to be a URI, it checks that after the** second space that there is an HTTP identifier or otherwise it's no good.** Also, if we've found an identifier after the first whitespace, and** find another whitespace, there is no URI.** ** The uri and uri_end pointers are updated in this function depending** on what space we are at, and if the space was followed by the HTTP** identifier. (NOTE: the HTTP delimiter is no longer "HTTP/", but** can also be "\r\n", "\n", or "\r". This is the defunct method, and** we deal with it in the IsHttpVersion and delimiter functions.)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -