/** ** Inspect an HTTP server response packet to determine the state. ** ** We inspect this packet and determine whether we are in the beginning ** of a response header or if we are looking at payload. We limit the ** amount of inspection done on responses by only inspecting the HTTP header ** and some payload. If the whole packet is a payload, then we just ignore ** it, since we inspected the previous header and payload. ** ** We limit the amount of the payload by adjusting the Server structure ** members, header and header size. ** ** @param Server the server structure ** @param data pointer to the beginning of payload ** @param dsize the size of the payload ** @param flow_depth the amount of header and payload to inspect ** ** @return integer ** ** @retval HI_INVALID_ARG invalid argument ** @retval HI_SUCCESS function success */ static int IsHttpServerData(HI_SESSION *Session, Packet *p, HttpSessionData *sd) { const u_char *start; const u_char *end; const u_char *ptr; int len; uint32_t seq_num = 0; HI_SERVER *Server; HTTPINSPECT_CONF *ServerConf; ServerConf = Session->server_conf; if(!ServerConf) return HI_INVALID_ARG; Server = &(Session->server); clearHttpRespBuffer(Server); /* ** HTTP:Server-Side-Session-Performance-Optimization ** This drops Server->Client packets which are not part of the ** HTTP Response header. It can miss part of the response header ** if the header is sent as multiple packets. */ if(!(p->data)) { return HI_INVALID_ARG; } seq_num = GET_PKT_SEQ(p); /* ** Let's set up the data pointers. */ Server->response.header_raw = p->data; Server->response.header_raw_size = p->dsize; start = p->data; end = p->data + p->dsize; ptr = start; ptr = MovePastDelims(start,end,ptr); len = end - ptr; if ( len > 4 ) { if(!IsHttpVersion(&ptr, end)) { p->packet_flags |= PKT_HTTP_DECODE; ApplyFlowDepth(ServerConf, p, sd, 0, 0, seq_num); return HI_SUCCESS; } else { if(ServerConf->server_flow_depth > 0) { sd->resp_state.is_max_seq = 1; sd->resp_state.max_seq = seq_num + ServerConf->server_flow_depth; } p->packet_flags |= PKT_HTTP_DECODE; ApplyFlowDepth(ServerConf, p, sd, 0, 1, seq_num); return HI_SUCCESS; } } else { return HI_SUCCESS; } 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; }
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; }