static INLINE int hi_server_extract_status_code(HI_SESSION *Session, const u_char *start, const u_char *ptr, const u_char *end, URI_PTR *result) { int iRet = HI_SUCCESS; SkipBlankSpace(start,end,&ptr); result->uri = ptr; while( hi_util_in_bounds(start, end, ptr) ) { if(isdigit((int)*ptr)) { SkipDigits(start, end, &ptr); if ( hi_util_in_bounds(start, end, ptr) ) { if(isspace((int)*ptr)) { result->uri_end = ptr; iRet = STAT_END; return iRet; } else { iRet = HI_NONFATAL_ERR; return iRet; } } else { iRet = HI_OUT_OF_BOUNDS; return iRet; } } else { if(hi_eo_generate_event(Session, HI_EO_SERVER_INVALID_STATCODE)) { hi_eo_server_event_log(Session, HI_EO_SERVER_INVALID_STATCODE, NULL, NULL); } ptr++; } } iRet = HI_OUT_OF_BOUNDS; return iRet; }
static INLINE int hi_server_extract_status_msg( const u_char *start, const u_char *ptr, const u_char *end, URI_PTR *result) { int iRet = HI_SUCCESS; SkipBlankSpace(start,end,&ptr); if ( hi_util_in_bounds(start, end, ptr) ) { const u_char *crlf = (u_char *)SnortStrnStr((const char *)ptr, end - ptr, "\n"); result->uri = ptr; if (crlf) { if(crlf[-1] == '\r') result->uri_end = crlf - 1; else result->uri_end = crlf; ptr = crlf; } else { result->uri_end =end; } if(result->uri < result->uri_end) iRet = STAT_END; else iRet = HI_OUT_OF_BOUNDS; } else iRet = HI_OUT_OF_BOUNDS; return iRet; }
static INLINE const u_char *MovePastDelims(const u_char *start, const u_char *end,const u_char *ptr) { while(hi_util_in_bounds(start, end, ptr)) { if(*ptr < 0x21) { if(*ptr < 0x0E && *ptr > 0x08) { ptr++; continue; } else { if(*ptr == 0x20) { ptr++; continue; } } } break; } return ptr; }
/** ** This routine is for getting bytes in the U decode. ** ** This checks the current bounds and checking for the double decoding. ** This routine differs from the other Get routines because it returns ** other values than just END_OF_BUFFER and the char. ** ** We also return DOUBLE_ENCODING if there is a % and double decoding ** is turned on. ** ** When using this function it is important to note that it increments ** the buffer before checking the bounds. So, if you call this function ** in a loop and don't check for END_OF_BUFFER being returned, then ** you are going to overwrite the buffer. If I put the check in, you ** would just be in an never-ending loop. So just use this correctly. ** ** @param ServerConf the server configuration ** @param start the start of the URI ** @param end the end of the URI ** @param ptr the current pointer into the URI ** ** @return integer ** ** @retval END_OF_BUFFER the end of the buffer has been reached. ** @retval DOUBLE_ENCODING a percent was found and double decoding is on ** @retval <= 0xff an ASCII char */ static int GetPtr(HI_SESSION *Session, const u_char *start, const u_char *end, const u_char **ptr, URI_NORM_STATE *norm_state, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; (*ptr)++; if(!hi_util_in_bounds(start, end, *ptr)) return END_OF_BUFFER; if(ServerConf->double_decoding.on && **ptr == '%') { *encodeType |= HTTP_ENCODE_TYPE__DOUBLE_ENCODE ; return DOUBLE_ENCODING; } return (int)**ptr; }
/** ** 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; }
/** ** This function checks for an absolute URI in the URI. ** ** @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_SUCCESS function successful */ static int SetProxy(HI_SESSION *Session, u_char *start, u_char *end, u_char **ptr, URI_PTR *uri_ptr) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; if(!uri_ptr->ident && !uri_ptr->last_dir) { if(Session->global_conf->proxy_alert && !ServerConf->allow_proxy) { if(hi_util_in_bounds(start, end, ((*ptr)+2))) { if(*((*ptr)+1) == '/' && *((*ptr)+1) == '/') { uri_ptr->proxy = *ptr; } } } } (*ptr)++; return HI_SUCCESS; }
/** ** Wrapper for PercentDecode() and handles the return values from ** PercentDecode(). ** ** This really decodes the chars for UnicodeDecode(). If the char is ** a percent then we process stuff, otherwise we just increment the ** pointer and return. ** ** @param ServerConf the server configuration ** @param start the start of the URI ** @param end the end of the URI ** @param ptr the current pointer into the URI ** @param bare_byte value for a non-ASCII char or a decoded non-ASCII char ** ** @return integer ** ** @retval END_OF_BUFFER End of the buffer has been reached before decode. ** @retval NON_ASCII_CHAR End of buffer during decoding, return decoded char. ** @retval char return the valid decoded/undecoded char ** ** @see PercentDecode() ** @see GetByte() */ static int GetChar(HI_SESSION *Session, const u_char *start, const u_char *end, const u_char **ptr, int *bare_byte, URI_NORM_STATE *norm_state, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; int iNorm; if(!hi_util_in_bounds(start, end, *ptr)) return END_OF_BUFFER; iNorm = (int)(**ptr); if(**ptr == '%' && ServerConf->ascii.on) { /* ** We go into percent encoding. */ iNorm = PercentDecode(Session, start, end, ptr, norm_state, encodeType); /* ** If during the course of PercentDecode() we run into the end ** of the buffer, then we return early (WITHOUT INCREMENTING ptr) ** with a NON_ASCII_CHAR. */ if(iNorm == END_OF_BUFFER) return NON_ASCII_CHAR; *bare_byte = 0; } else { if(ServerConf->bare_byte.on && (u_char)iNorm > 0x7f) { *encodeType |= HTTP_ENCODE_TYPE__BARE_BYTE; if(hi_eo_generate_event(Session, ServerConf->bare_byte.alert) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_BARE_BYTE, NULL, NULL); } /* ** Set the bare_byte flag */ *bare_byte = 0; } else { /* ** Set the bare_byte flag negative. */ *bare_byte = 1; } } /* ** Increment the buffer. */ (*ptr)++; return iNorm; }
/** ** Normalize the URI into the URI normalize buffer. ** ** This is the routine that users call to normalize the URI. It iterates ** through the URI buffer decoding the next character and is then checked ** for any directory problems before writing the decoded character into the ** normalizing buffer. ** ** We return the length of the normalized URI buffer in the variable, ** uribuf_size. This value is passed in as the max size of the normalization ** buffer, which we then set in iMaxUriBufSize for later reference. ** ** If there was some sort of problem during normalizing we set the normalized ** URI buffer size to 0 and return HI_NONFATAL_ERR. ** ** @param ServerConf the pointer to the server configuration ** @param uribuf the pointer to the normalize uri buffer ** @param uribuf_size the size of the normalize buffer ** @param uri the pointer to the unnormalized uri buffer ** @param uri_size the size of the unnormalized uri buffer ** ** @return integer ** ** @retval HI_NONFATAL_ERR there was a problem during normalizing, the ** uribuf_size is also set to 0 ** @retval HI_SUCCESS Normalizing the URI was successful */ int hi_norm_uri(HI_SESSION *Session, u_char *uribuf, int *uribuf_size, const u_char *uri, int uri_size, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf; int iChar; int iRet; int iMaxUriBufSize; URI_NORM_STATE norm_state; u_char *ub_ptr; const u_char *ptr; const u_char *start; const u_char *end; u_char *ub_start; u_char *ub_end; ServerConf = Session->server_conf; iMaxUriBufSize = *uribuf_size; start = uri; end = uri + uri_size; ub_start = uribuf; ub_end = uribuf + iMaxUriBufSize; ub_ptr = uribuf; ptr = uri; /* ** Initialize the URI directory normalization state */ norm_state.dir_count = 0; norm_state.param = NULL; while(hi_util_in_bounds(ub_start, ub_end, ub_ptr)) { byte_decoded = false; iChar = GetDecodedByte(Session, start, end, &ptr, &norm_state, encodeType); if(iChar == END_OF_BUFFER) break; /* ** Look for user-defined Non-Rfc chars. If we find them ** then log an alert. */ if(ServerConf->non_rfc_chars[(u_char)iChar]) { if(hi_eo_generate_event(Session, HI_EO_CLIENT_NON_RFC_CHAR) && !norm_state.param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_NON_RFC_CHAR, NULL, NULL); } } iRet = InspectUriChar(Session, iChar, &norm_state, start, end, &ptr, ub_start, ub_end, &ub_ptr, encodeType); if (iRet) { if(iRet == END_OF_BUFFER) break; /* ** This is the default case when we don't want anything to do with ** the char besides writing the value into the buffer. */ *ub_ptr = (u_char)iChar; ub_ptr++; } } /* ** Now that we are done, let's make sure that we didn't just have a ** single large directory, with the rest in the next packet. */ CheckLongDir(Session, &norm_state, ub_ptr); /* ** This means that we got to the end of the URI, so we set the length, ** check it, and move on. */ *uribuf_size = ub_ptr - ub_start; if(*uribuf_size > uri_size || *uribuf_size < 1) return HI_NONFATAL_ERR; return HI_SUCCESS; }
/** ** This function inspects the normalized chars for any other processing ** that we need to do, such as directory traversals. ** ** The main things that we check for here are '/' and '?'. There reason ** for '/' is that we do directory traversals. If it's a slash, we call ** the routine that will normalize mutli-slashes, self-referential dirs, ** and dir traversals. We do all that processing here and call the ** appropriate functions. ** ** The '?' is so we can mark the parameter field, and check for oversize ** directories one last time. Once the parameter field is set, we don't ** do any more oversize directory checks since we aren't in the url ** any more. ** ** @param Session pointer to the current session ** @param iChar the char to inspect ** @param norm_state the normalization state ** @param start the start of the URI buffer ** @param end the end of the URI buffer ** @param ptr the address of the pointer index into the URI buffer ** @param ub_start the start of the norm buffer ** @param ub_end the end of the norm buffer ** @param ub_ptr the address of the pointer index into the norm buffer ** ** @return integer ** ** @retval END_OF_BUFFER we've reached the end of the URI or norm buffer ** @retval HI_NONFATAL_ERR no special char, so just write the char and ** increment the ub_ptr. ** @retval HI_SUCCESS normalized the special char and already ** incremented the buffers. */ static inline int InspectUriChar(HI_SESSION *Session, int iChar, URI_NORM_STATE *norm_state, const u_char *start, const u_char *end, const u_char **ptr, u_char *ub_start, u_char *ub_end, u_char **ub_ptr, uint16_t *encodeType) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; int iDir; /* ** Let's add absolute URI/proxy support everyone. */ if(!norm_state->dir_count && (u_char)iChar == ':' && hi_util_in_bounds(start, end, ((*ptr)+2))) { if(**ptr == '/' && *((*ptr)+1) == '/') { /* ** We've found absolute vodka. */ if(!hi_util_in_bounds(ub_start, ub_end, ((*ub_ptr)+2))) return END_OF_BUFFER; /* ** Write the : */ **ub_ptr = (u_char)iChar; (*ub_ptr)++; /* ** This increments us past the first slash, so at the next ** slash we will track a directory. ** ** The reason we do this is so that an attacker can't trick ** us into normalizing a directory away that ended in a :. ** For instance, if we got a URL that was separated in by a ** packet boundary like this, and we were looking for the ** URL real_dir:/file.html: ** real_dir://obfuscate_dir/../file.html ** we would normalize it with proxy support to: ** /file.html ** because we never tracked the :// as a valid directory. So ** even though this isn't the best solution, it is the best ** we can do given that we are working with stateless ** inspection. */ (*ptr)++; return HI_SUCCESS; } } /* ** Now that we have the "true" byte, we check this byte for other ** types of normalization: ** - directory traversals ** - multiple slashes */ if((u_char)iChar == '/') { /* ** First thing we do is check for a long directory. */ CheckLongDir(Session, norm_state, *ub_ptr); iDir = DirNorm(Session, start, end, ptr, norm_state, encodeType); if(iDir == DIR_TRAV) { /* ** This is the case where we have a directory traversal. ** ** The DirTrav function will reset the ub_ptr to the previous ** slash. After that, we just continue through the loop because ** DirNorm has already set ptr to the slash, so we can just ** continue on. */ DirTrav(Session,norm_state, ub_start, ub_ptr); } else { /* ** This is the case where we didn't have a directory traversal, ** and we are now just writing the char after the '/'. ** ** We call DirSet, because all this function does is write a ** '/' into the buffer and increment the ub_ptr. We then ** check the return code and return END_OF_BUFFER if ** needed. */ DirSet(norm_state, ub_ptr); if(iDir == END_OF_BUFFER) return END_OF_BUFFER; /* ** We check the bounds before we write the next byte */ if(!hi_util_in_bounds(ub_start, ub_end, *ub_ptr)) return END_OF_BUFFER; /* ** Set the char to what we got in DirNorm() */ /* ** Look for user-defined Non-Rfc chars. If we find them ** then log an alert. */ if(ServerConf->non_rfc_chars[(u_char)iDir]) { if(hi_eo_generate_event(Session, HI_EO_CLIENT_NON_RFC_CHAR) && !norm_state->param) { hi_eo_client_event_log(Session, HI_EO_CLIENT_NON_RFC_CHAR, NULL, NULL); } } **ub_ptr = (u_char)iDir; (*ub_ptr)++; } return HI_SUCCESS; } if((!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. */ CheckLongDir(Session, norm_state, *ub_ptr); norm_state->param = *ub_ptr; } /* ** This is neither char, so we just bail and let the loop finish ** for us. */ return HI_NONFATAL_ERR; }
/** ** Check for any directory traversal or multi-slash normalization. ** ** @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_SUCCESS function successful ** @retval HI_OUT_OF_BOUNDS reached the end of the buffer */ static int SetSlashNorm(HI_SESSION *Session, u_char *start, u_char *end, u_char **ptr, URI_PTR *uri_ptr) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; CheckLongDir(Session, uri_ptr, *ptr); uri_ptr->last_dir = *ptr; if(!uri_ptr->norm && !uri_ptr->ident) { uri_ptr->norm = *ptr; (*ptr)++; if(!hi_util_in_bounds(start,end, *ptr)) { /* ** This is the case where there is a slash as the last char ** and we don't want to normalize that since there really ** is nothing to normalize. */ uri_ptr->norm = NULL; return HI_OUT_OF_BOUNDS; } /* ** Check for directory traversals */ if(ServerConf->directory.on) { if(**ptr == '.') { (*ptr)++; if(!hi_util_in_bounds(start, end, *ptr)) { uri_ptr->norm = NULL; return HI_OUT_OF_BOUNDS; } if(**ptr == '.' || ** ptr == '/') { return HI_SUCCESS; } } } /* ** Check for multiple slash normalization */ if(ServerConf->multiple_slash.on) { if(**ptr == '/') { return HI_SUCCESS; } } uri_ptr->norm = NULL; return HI_SUCCESS; } (*ptr)++; return HI_SUCCESS; }
/** ** 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.) ** ** @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_SUCCESS found the next non-whitespace ** @retval HI_OUT_OF_BOUNDS whitespace to the end of the buffer ** @retval URI_END delimiter found, end of URI ** @retval NO_URI */ static int NextNonWhiteSpace(HI_SESSION *Session, u_char *start, u_char *end, u_char **ptr, URI_PTR *uri_ptr) { HTTPINSPECT_CONF *ServerConf = Session->server_conf; u_char **start_sp; u_char **end_sp; /* ** Reset the identifier, because we've just seen another space. We ** should only see the identifier immediately after a space followed ** by a delimiter. */ if(uri_ptr->ident) { if(ServerConf->non_strict) { /* ** In non-strict mode it is ok to see spaces after the ** "identifier", so we just increment the ptr and return. */ (*ptr)++; return HI_SUCCESS; } else { /* ** This means that we've already seen a space and a version ** identifier, and now that we've seen another space, we know ** that this can't be the URI so we just bail out with no ** URI. */ return NO_URI; } } uri_ptr->ident = NULL; /* ** We only check for one here, because both should be set if one ** is. */ if(uri_ptr->first_sp_end) { /* ** If the second space has been set, then this means that we have ** seen a third space, which we shouldn't see in the URI so we ** are now done and know there is no URI in this packet. */ if(uri_ptr->second_sp_end) { return NO_URI; } /* ** Since we've seen the second space, we need to update the uri ptr ** to the end of the first space, since the URI cannot be before the ** first space. */ uri_ptr->uri = uri_ptr->first_sp_end; uri_ptr->second_sp_start = *ptr; uri_ptr->second_sp_end = NULL; start_sp = &uri_ptr->second_sp_start; end_sp = &uri_ptr->second_sp_end; } else { /* ** This means that there is whitespace at the beginning of the line ** and we unset the URI so we can set it later if need be. ** ** This is mainly so we handle data that is all spaces correctly. ** ** In the normal case where we've seen text and then the first space, ** we leave the uri ptr pointing at the beginning of the data, and ** set the uri end after we've determined where to put it. */ if(start == *ptr) uri_ptr->uri = NULL; uri_ptr->first_sp_start = *ptr; uri_ptr->first_sp_end = NULL; start_sp = &uri_ptr->first_sp_start; end_sp = &uri_ptr->first_sp_end; } while(hi_util_in_bounds(start, end, *ptr)) { /* ** Check for whitespace */ if(**ptr == ' ') { (*ptr)++; continue; } else if((**ptr == '\t')) { if(ServerConf->apache_whitespace.on) { if(hi_eo_generate_event(Session, ServerConf->apache_whitespace.alert)) { hi_eo_client_event_log(Session, HI_EO_CLIENT_APACHE_WS, NULL, NULL); } (*ptr)++; continue; } else { return NO_URI; } } else { /* ** This sets the sp_end for whatever space delimiter we are on, ** whether that is the first space or the second space. */ *end_sp = *ptr; if(!IsHttpVersion(ptr, end)) { /* ** This is the default method and what we've been doing ** since the start of development. */ if(uri_ptr->second_sp_start) { /* ** There is no HTTP version indentifier at the beginning ** of the second space, and this means that there is no ** URI. */ if(ServerConf->non_strict) { /* ** In non-strict mode, we must assume the URI is ** between the first and second space, so now ** that we've seen the second space that's the ** identifier. */ uri_ptr->ident = *end_sp; uri_ptr->uri_end = *start_sp; return HI_SUCCESS; } else { /* ** Since we are in strict mode here, it means that ** we haven't seen a valid identifier, so there was ** no URI. */ return NO_URI; } } /* ** RESET NECESSARY URI_PTRs HERE. This is the place where ** the uri is updated. It can only happen once, so do it ** right here. ** ** When we get here it means that we have found the end of ** the FIRST whitespace, and that there was no delimiter, ** so we reset the uri pointers and other related ** pointers. */ uri_ptr->uri = *end_sp; uri_ptr->uri_end = end; uri_ptr->norm = NULL; uri_ptr->last_dir = NULL; uri_ptr->param = NULL; uri_ptr->proxy = NULL; } else { /* ** Means we found the HTTP version identifier and we reset ** the uri_end pointer to point to the beginning of the ** whitespace detected. ** ** This works for both "uri_is_here HTTP/1.0" and ** "METHOD uri_is_here HTTP/1.0", so it works when the ** identifier is after either the first or the second ** whitespace. */ uri_ptr->ident = *end_sp; uri_ptr->uri_end = *start_sp; } /* ** We found a non-whitespace char */ return HI_SUCCESS; } } /* ** This is the case where we've seen text and found a whitespace until ** the end of the buffer. In that case, we set the uri_end to the ** beginning of the whitespace. */ uri_ptr->uri_end = *start_sp; return HI_OUT_OF_BOUNDS; }
/** ** Find the URI and determine whether the URI needs to be normalized. ** ** This is a big step in stateless inspection, because we need to reliably ** find the URI and when possible filter out non-URIs. We do this using a ** simple state machine that is based on characters found in the data ** buffer. ** ** Another important aspect of the stateless inspection is the ability to ** track and inspect pipelined requests. It is VERY IMPORTANT to reset the ** pipeline_req pointer, since we don't memset the whole structure. This ** pointer is reset in the hi_si_session_inspection() function. Check there ** for more details. ** ** Normalization is detected when we are looking at the packet for the URI. ** We look for the following issues: ** - //// ** - /../ ** - /./ ** - non-ascii charss ** - % ** - \ ** When these things are seen we point to the first occurence in the URI, or ** where we have to start normalizing. If the URI is updated to a new ** pointer, then the normalization pointer is reset and we start over. ** Using this method should cut down the memcpy()s per URI, since most ** URIs are not normalized. ** ** If this function returns HI_NONFATAL_ERR, we return out of mode_inspection ** with an error and abort HttpInspect processing, and continue on with ** any other processing we do. The Session parameters that we use here are ** reset in the next time that we do session_inspection, so we don't do ** any initialization here. ** ** @param Session pointer to the HTTP session ** @param data pointer to the start of the packet payload ** @param dsize size of the payload ** ** @return integer ** ** @retval HI_INVALID_ARG invalid argument ** @retval HI_NONFATAL_ERR no URI detected ** @retval HI_SUCCESS URI detected and Session pointers updated */ static int StatelessInspection(HI_SESSION *Session, unsigned char *data, int dsize) { HTTPINSPECT_CONF *ServerConf; HTTPINSPECT_CONF *ClientConf; HI_CLIENT *Client; URI_PTR uri_ptr; u_char *start; u_char *end; u_char *ptr; int iRet; if(!Session || !data || dsize < 1) { return HI_INVALID_ARG; } ServerConf = Session->server_conf; if(!ServerConf) { return HI_INVALID_ARG; } ClientConf = Session->client_conf; if(!ClientConf) { return HI_INVALID_ARG; } Client = &Session->client; memset(&uri_ptr, 0x00, sizeof(URI_PTR)); /* ** We set the starting boundary depending on whether this request is ** a normal request or a pipeline request. The end boundary is always ** the same whether it is a pipeline request or other. */ if(Client->request.pipeline_req) { start = Client->request.pipeline_req; } else { start = data; } end = data + dsize; ptr = start; /* ** Apache and IIS strike again . . . Thanks Kanatoko ** - Ignore CRLFs at the beginning of the request. */ while(hi_util_in_bounds(start, end, ptr)) { if(*ptr < 0x21) { if(*ptr < 0x0E && *ptr > 0x08) { ptr++; continue; } else { if(*ptr == 0x20) { ptr++; continue; } } } break; } uri_ptr.uri = ptr; uri_ptr.uri_end = end; /* ** This loop compares each char to an array of functions ** (one for each char) and calling that function if there is one. ** ** If there is no function, then we just increment the char ptr and ** continue processing. ** ** If there is a function, we call that function and process. It's ** important to note that the function that is called is responsible ** for incrementing the ptr to the next char to be inspected. The ** loop does not increment the pointer when a function is called to ** allow the maximum flexibility to the functions. */ while(hi_util_in_bounds(start, end, ptr)) { if(lookup_table[*ptr]) { if((iRet = (lookup_table[*ptr])(Session, start, end, &ptr, &uri_ptr))) { if(iRet == URI_END) { /* ** You found a URI, let's break and check it out. */ break; } else if(iRet == HI_OUT_OF_BOUNDS) { /* ** Means you've reached the end of the buffer. THIS ** DOESN'T MEAN YOU HAVEN'T FOUND A URI. */ break; } else /* NO_URI */ { /* ** Check for chunk encoding, because the delimiter can ** also be a space, which would look like a pipeline request ** to us if we don't do this first. */ if(Session->server_conf->chunk_length) CheckChunkEncoding(Session, start, end); /* ** We only inspect the packet for another pipeline ** request if there wasn't a previous pipeline request. ** The reason that we do this is because */ if(!Client->request.pipeline_req) { /* ** Just because there was no URI in the first part ** the packet, doesn't mean that this isn't a ** pipelined request that has been segmented. */ if(!ServerConf->no_pipeline) { if((Client->request.pipeline_req = FindPipelineReq(ptr, end))) { return HI_SUCCESS; } } } return HI_NONFATAL_ERR; } } else { /* ** This means that we found the next non-whitespace char ** and since we are already pointed there, so we just ** continue. */ continue; } } ptr++; } /* ** If there is a pipelined request in this packet, we should always ** see the first space followed by text (which is the URI). Without ** that first space, then we never get to the URI, so we should just ** return, since there is nothing else to inspect. */ if(Client->request.pipeline_req) { if(uri_ptr.uri != uri_ptr.first_sp_end) { if(Session->server_conf->chunk_length) CheckChunkEncoding(Session, start, end); return HI_NONFATAL_ERR; } } /* ** We set the HI_CLIENT variables from the URI_PTR structure. We also ** do error checking for the values in this routine as well. */ if((iRet = SetClientVars(Client, &uri_ptr, dsize))) { return iRet; } /* ** One last check for an oversize directory. This gets the long ** directory when there is a beginning slash and no other slashes ** until the end of the packet. ** ** We do this check after we set the variables, just in case there ** was some errors while setting the variables. This could save some ** false positives on a bad URI setting. */ CheckLongDir(Session, &uri_ptr, ptr); /* ** Check for absolute URI and alert for proxy comm if necessary ** ** NOTE: ** Also check ClientConf for proxy configuration so we don't ** alert on outbound requests from legitimate proxies. */ if(uri_ptr.proxy && Session->global_conf->proxy_alert && (!ServerConf->allow_proxy && !ClientConf->allow_proxy)) { if(hi_eo_generate_event(Session, HI_EO_CLIENT_PROXY_USE)) { hi_eo_client_event_log(Session, HI_EO_CLIENT_PROXY_USE, NULL, NULL); } } /* ** Find the next pipeline request, if one is there. If we don't find ** a pipeline request, then we return NULL here, so this is always ** set to the correct value. */ if(!ServerConf->no_pipeline) { Client->request.pipeline_req = FindPipelineReq(uri_ptr.delimiter, end); } else { Client->request.pipeline_req = NULL; } if(Session->server_conf->chunk_length) CheckChunkEncoding(Session, uri_ptr.delimiter, end); return HI_SUCCESS; }
/** ** 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' || *ptr == ' ' || *ptr == '\t') { if(iCheckChunk && iChunkLen != 0) { if(Session->server_conf->chunk_length < iChunkLen && hi_eo_generate_event(Session, HI_EO_CLIENT_LARGE_CHUNK)) { 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; } } /* ** If we've already evaluated the chunk, or we have a valid delimiter ** for handling new chunks, we reset and starting evaluating possible ** chunk lengths. */ if(iCheckChunk || *ptr == '\n') { 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 |= (unsigned int)hex_lookup[*ptr]; iChunkChars++; } } } ptr++; } return HI_SUCCESS; }
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) { const u_char *p; const u_char *offset; HEADER_FIELD_PTR header_field_ptr ; if(!start || !end) return NULL; p = start; offset = (u_char*)p; header_ptr->header.uri = p; header_ptr->header.uri_end = end; header_ptr->content_encoding.compress_fmt = 0; header_ptr->content_len.len = 0; while (hi_util_in_bounds(start, end, p)) { if(*p == '\n') { p++; offset = (u_char*)p; if (!hi_util_in_bounds(start, end, p)) { header_ptr->header.uri_end = p; return p; } if (*p < 0x0E) { if(*p == '\r') { p++; if(hi_util_in_bounds(start, end, p) && (*p == '\n')) { p++; header_ptr->header.uri_end = p; return p; } } else if(*p == '\n') { p++; header_ptr->header.uri_end = p; return p; } } else if ( (p = extractHttpRespHeaderFieldValues(ServerConf, p, offset, start, end, header_ptr, &header_field_ptr, parse_cont_encoding, hsd, Session)) == end) { return end; } } else if( (p == header_ptr->header.uri) && (p = extractHttpRespHeaderFieldValues(ServerConf, p, offset, start, end, header_ptr, &header_field_ptr, parse_cont_encoding, hsd, Session)) == end) { return end; } if ( *p == '\n') continue; p++; } header_ptr->header.uri_end = p; return p; }
static INLINE const u_char *extract_http_content_encoding(HTTPINSPECT_CONF *ServerConf, const u_char *p, const u_char *start, const u_char *end, HEADER_PTR *header_ptr, HEADER_FIELD_PTR *header_field_ptr) { const u_char *crlf; int space_present = 0; if (header_ptr->content_encoding.cont_encoding_start) { header_ptr->header.uri_end = p; header_ptr->content_encoding.compress_fmt = 0; return p; } else { header_field_ptr->content_encoding = &header_ptr->content_encoding; p = p + HTTPRESP_HEADER_LENGTH__CONTENT_ENCODING; } SkipBlankSpace(start,end,&p); if(hi_util_in_bounds(start, end, p) && *p == ':') { p++; if ( hi_util_in_bounds(start, end, p) ) { if ( ServerConf->profile == HI_APACHE || ServerConf->profile == HI_ALL) { SkipWhiteSpace(start,end,&p); } else { SkipBlankAndNewLine(start,end,&p); } if( hi_util_in_bounds(start, end, p)) { if ( *p == '\n' ) { while(hi_util_in_bounds(start, end, p)) { if ( *p == '\n') { p++; while( hi_util_in_bounds(start, end, p) && ( *p == ' ' || *p == '\t')) { space_present = 1; p++; } if ( space_present ) { if ( isalpha((int)*p)) break; else if(isspace((int)*p) && (ServerConf->profile == HI_APACHE || ServerConf->profile == HI_ALL) ) { SkipWhiteSpace(start,end,&p); } else { header_field_ptr->content_encoding->cont_encoding_start= header_field_ptr->content_encoding->cont_encoding_end = NULL; header_field_ptr->content_encoding->compress_fmt = 0; return p; } } else { header_field_ptr->content_encoding->cont_encoding_start= header_field_ptr->content_encoding->cont_encoding_end = NULL; header_field_ptr->content_encoding->compress_fmt = 0; return p; } } else break; } } else if(isalpha((int)*p)) { header_field_ptr->content_encoding->cont_encoding_start = p; while(hi_util_in_bounds(start, end, p) && *p!='\n' ) { if(IsHeaderFieldName(p, end, HTTPRESP_HEADER_NAME__GZIP, HTTPRESP_HEADER_LENGTH__GZIP) || IsHeaderFieldName(p, end, HTTPRESP_HEADER_NAME__XGZIP, HTTPRESP_HEADER_LENGTH__XGZIP)) { header_field_ptr->content_encoding->compress_fmt |= HTTP_RESP_COMPRESS_TYPE__GZIP; p = p + HTTPRESP_HEADER_LENGTH__GZIP; continue; } else if(IsHeaderFieldName(p, end, HTTPRESP_HEADER_NAME__DEFLATE, HTTPRESP_HEADER_LENGTH__DEFLATE)) { header_field_ptr->content_encoding->compress_fmt |= HTTP_RESP_COMPRESS_TYPE__DEFLATE; p = p + HTTPRESP_HEADER_LENGTH__DEFLATE; continue; } else p++; } /*crlf = (u_char *)SnortStrnStr((const char *)p, end - p, "\n"); if(crlf) { p = crlf; } else { header_ptr->header.uri_end = end ; return end; }*/ } else { header_field_ptr->content_encoding->cont_encoding_start= header_field_ptr->content_encoding->cont_encoding_end = NULL; header_field_ptr->content_encoding->compress_fmt = 0; return p; } } } } else { if(hi_util_in_bounds(start, end, p)) { crlf = (u_char *)SnortStrnStr((const char *)p, end - p, "\n"); if(crlf) { p = crlf; } else { header_ptr->header.uri_end = end ; return end; } } } if(!p || !hi_util_in_bounds(start, end, p)) p = end; return p; }
int HttpResponseInspection(HI_SESSION *Session, Packet *p, const unsigned char *data, int dsize, HttpSessionData *sd) { HTTPINSPECT_CONF *ServerConf; URI_PTR stat_code_ptr; URI_PTR stat_msg_ptr; HEADER_PTR header_ptr; URI_PTR body_ptr; HI_SERVER *Server; const u_char *start; const u_char *end; const u_char *ptr; int len; int iRet = 0; int resp_header_size = 0; /* Refers to the stream reassembled packets when reassembly is turned on. * Refers to all packets when reassembly is turned off. */ int not_stream_insert = 1; #ifdef ZLIB int parse_cont_encoding = 1; int status; #endif int expected_pkt = 0; int alt_dsize; uint32_t seq_num = 0; if (!Session || !p || !data || (dsize == 0)) return HI_INVALID_ARG; ServerConf = Session->server_conf; if(!ServerConf) return HI_INVALID_ARG; Server = &(Session->server); clearHttpRespBuffer(Server); seq_num = GET_PKT_SEQ(p); if ( (sd != NULL) ) { /* If the previously inspected packet in this session identified as a body * and if the packets are stream inserted wait for reassembled */ if (sd->resp_state.inspect_reassembled) { if(p->packet_flags & PKT_STREAM_INSERT) { #ifdef ZLIB parse_cont_encoding = 0; #endif not_stream_insert = 0; } } /* If this packet is the next expected packet to be inspected and is out of sequence * clear out the resp state*/ #ifdef ZLIB if(( sd->decomp_state && sd->decomp_state->decompress_data) && parse_cont_encoding) { if( sd->resp_state.next_seq && (seq_num == sd->resp_state.next_seq) ) { sd->resp_state.next_seq = seq_num + p->dsize; expected_pkt = 1; } else { ResetGzipState(sd->decomp_state); ResetRespState(&(sd->resp_state)); } } else #endif if(sd->resp_state.inspect_body && not_stream_insert) { /* If the server flow depth is 0 then we need to check if the packet * is in sequence */ if(!ServerConf->server_flow_depth) { if( sd->resp_state.next_seq && (seq_num == sd->resp_state.next_seq) ) { sd->resp_state.next_seq = seq_num + p->dsize; expected_pkt = 1; } else { #ifdef ZLIB ResetGzipState(sd->decomp_state); #endif ResetRespState(&(sd->resp_state)); } } else { /*Check if the sequence number of the packet is within the allowed * flow_depth */ if( (sd->resp_state.is_max_seq) && SEQ_LT(seq_num, (sd->resp_state.max_seq))) { expected_pkt = 1; } else { #ifdef ZLIB ResetGzipState(sd->decomp_state); #endif ResetRespState(&(sd->resp_state)); } } } } memset(&stat_code_ptr, 0x00, sizeof(URI_PTR)); memset(&stat_msg_ptr, 0x00, sizeof(URI_PTR)); memset(&header_ptr, 0x00, sizeof(HEADER_PTR)); memset(&body_ptr, 0x00, sizeof(URI_PTR)); start = data; end = data + dsize; ptr = start; /* moving past the CRLF */ while(hi_util_in_bounds(start, end, ptr)) { if(*ptr < 0x21) { if(*ptr < 0x0E && *ptr > 0x08) { ptr++; continue; } else { if(*ptr == 0x20) { ptr++; continue; } } } break; } /*after doing this we need to basically check for version, status code and status message*/ len = end - ptr; if ( len > 4 ) { if(!IsHttpVersion(&ptr, end)) { if(expected_pkt) { ptr = start; p->packet_flags |= PKT_HTTP_DECODE; } else { p->packet_flags |= PKT_HTTP_DECODE; ApplyFlowDepth(ServerConf, p, sd, resp_header_size, 0, seq_num); if ( not_stream_insert && (sd != NULL)) { #ifdef ZLIB ResetGzipState(sd->decomp_state); #endif ResetRespState(&(sd->resp_state)); } CLR_SERVER_HEADER(Server); return HI_SUCCESS; } } else { p->packet_flags |= PKT_HTTP_DECODE; /* This is a next expected packet to be decompressed but the packet is a * valid HTTP response. So the gzip decompression ends here */ if(expected_pkt) { expected_pkt = 0; #ifdef ZLIB ResetGzipState(sd->decomp_state); #endif ResetRespState(&(sd->resp_state)); } while(hi_util_in_bounds(start, end, ptr)) { if (isspace((int)*ptr)) break; ptr++; } } } else if (!expected_pkt) { return HI_SUCCESS; } /*If this is the next expected packet to be decompressed, send this packet * decompression */ if (expected_pkt) { if (hi_util_in_bounds(start, end, ptr)) { iRet = hi_server_inspect_body(Session, sd, ptr, end, &body_ptr); } } else { iRet = hi_server_extract_status_code(Session, start,ptr,end , &stat_code_ptr); if ( iRet == STAT_END ) { Server->response.status_code = stat_code_ptr.uri; Server->response.status_code_size = stat_code_ptr.uri_end - stat_code_ptr.uri; if ( (int)Server->response.status_code_size <= 0) { CLR_SERVER_STAT(Server); } else { iRet = hi_server_extract_status_msg(start, stat_code_ptr.uri_end , end, &stat_msg_ptr); if ( stat_msg_ptr.uri ) { Server->response.status_msg = stat_msg_ptr.uri; Server->response.status_msg_size = stat_msg_ptr.uri_end - stat_msg_ptr.uri; if ((int)Server->response.status_msg_size <= 0) { CLR_SERVER_STAT(Server); } else { #ifdef ZLIB ptr = hi_server_extract_header(Session, ServerConf, &header_ptr, stat_msg_ptr.uri_end , end, parse_cont_encoding, sd ); #else /* We dont need the content-encoding header when zlib is not enabled */ ptr = hi_server_extract_header(Session, ServerConf, &header_ptr, stat_msg_ptr.uri_end , end, 0, sd ); #endif } } else { CLR_SERVER_STAT(Server); } } if (header_ptr.header.uri) { Server->response.header_raw = header_ptr.header.uri; Server->response.header_raw_size = header_ptr.header.uri_end - header_ptr.header.uri; if(!Server->response.header_raw_size) { CLR_SERVER_HEADER(Server); } else { resp_header_size = (header_ptr.header.uri_end - p->data); hi_stats.resp_headers++; Server->response.header_norm = header_ptr.header.uri; if (header_ptr.cookie.cookie) { hi_stats.resp_cookies++; Server->response.cookie.cookie = header_ptr.cookie.cookie; Server->response.cookie.cookie_end = header_ptr.cookie.cookie_end; Server->response.cookie.next = header_ptr.cookie.next; } else { Server->response.cookie.cookie = NULL; Server->response.cookie.cookie_end = NULL; Server->response.cookie.next = NULL; } if (sd != NULL) { #ifdef ZLIB if( header_ptr.content_encoding.compress_fmt ) { hi_stats.gzip_pkts++; /* We've got gzip data - grab buffer from mempool and attach * to session data if server is configured to do so */ if (sd->decomp_state == NULL) SetGzipBuffers(sd, Session); if (sd->decomp_state != NULL) { sd->decomp_state->decompress_data = 1; sd->decomp_state->compress_fmt = header_ptr.content_encoding.compress_fmt; } } else #endif { sd->resp_state.inspect_body = 1; } sd->resp_state.last_pkt_contlen = header_ptr.content_len.len; if(ServerConf->server_flow_depth == -1) sd->resp_state.is_max_seq = 0; else { sd->resp_state.is_max_seq = 1; sd->resp_state.max_seq = seq_num + (header_ptr.header.uri_end - start)+ ServerConf->server_flow_depth; } if (p->packet_flags & PKT_STREAM_INSERT) { if(header_ptr.content_len.cont_len_start && ((end - (header_ptr.header.uri_end)) >= header_ptr.content_len.len)) { /* change this when the api is fixed to flush correctly */ //stream_api->response_flush_stream(p); expected_pkt = 1; } else sd->resp_state.inspect_reassembled = 1; } else { if(p->packet_flags & PKT_REBUILT_STREAM) sd->resp_state.inspect_reassembled = 1; expected_pkt = 1; } if(expected_pkt) { sd->resp_state.next_seq = seq_num + p->dsize; if(hi_util_in_bounds(start, end, header_ptr.header.uri_end)) { iRet = hi_server_inspect_body(Session, sd, header_ptr.header.uri_end, end, &body_ptr); } } } } } else { CLR_SERVER_HEADER(Server); } } else { CLR_SERVER_STAT(Server); } } if( body_ptr.uri ) { Server->response.body = body_ptr.uri; Server->response.body_size = body_ptr.uri_end - body_ptr.uri; if( Server->response.body_size > 0) { if ( Server->response.body_size < sizeof(DecodeBuffer.data) ) { alt_dsize = Server->response.body_size; } else { alt_dsize = sizeof(DecodeBuffer.data); } #ifdef ZLIB if(sd->decomp_state && sd->decomp_state->decompress_data) { status = SafeMemcpy(DecodeBuffer.data, Server->response.body, alt_dsize, DecodeBuffer.data, DecodeBuffer.data + sizeof(DecodeBuffer.data)); if( status != SAFEMEM_SUCCESS ) return HI_MEM_ALLOC_FAIL; p->data_flags |= DATA_FLAGS_GZIP; SetAltDecode(p, alt_dsize); SetDetectLimit(p, alt_dsize); } else #endif { if(sd->resp_state.last_pkt_chunked) { p->data_flags |= DATA_FLAGS_RESP_BODY; SetAltDecode(p, alt_dsize); SetDetectLimit(p, alt_dsize); } else { p->data_flags |= DATA_FLAGS_RESP_BODY; p->packet_flags |= PKT_HTTP_RESP_BODY; SetDetectLimit(p, (alt_dsize + resp_header_size)); } } if (get_decode_utf_state_charset(&(sd->utf_state)) != CHARSET_DEFAULT) { if ( Server->response.body_size < sizeof(DecodeBuffer.data) ) { alt_dsize = Server->response.body_size; } else { alt_dsize = sizeof(DecodeBuffer.data); } SetDetectLimit(p, alt_dsize); SetAltDecode(p, alt_dsize); } } } else { /* 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); } return HI_SUCCESS; }