Beispiel #1
0
/**
 * Parses a single v0 request cookie and places the results into tx->request_cookies.
 *
 * @param[in] connp
 * @param[in] data
 * @param[in] len
 * @return HTP_OK on success, HTP_ERROR on error.
 */
int htp_parse_single_cookie_v0(htp_connp_t *connp, unsigned char *data, size_t len) {
    if (len == 0) return HTP_OK;
    
    size_t pos = 0;

    // Look for '='
    while ((pos < len) && (data[pos] != '=')) pos++;
    if (pos == 0) return HTP_OK; // Ignore nameless cookies

    bstr *name = bstr_dup_mem(data, pos);
    if (name == NULL) return HTP_ERROR;

    bstr *value = NULL;
    if (pos == len) {
        // Cookie is empty
        value = bstr_dup_c("");
    } else {
        // Cookie is not empty
        value = bstr_dup_mem(data + pos + 1, len - pos - 1);
    }

    if (value == NULL) {
        bstr_free(name);
        return HTP_ERROR;
    }

    // Add cookie directly
    htp_table_addn(connp->in_tx->request_cookies, name, value);

    return HTP_OK;
}
Beispiel #2
0
TEST(BstrTest, DupMem) {
    bstr *dst;
    dst = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 18);
    EXPECT_EQ(0, memcmp("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", bstr_ptr(dst), 18));

    bstr_free(dst);
}
Beispiel #3
0
TEST(BstrTest, IndexOf) {
    bstr *haystack = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20);
    bstr *p1 = bstr_dup_c("NOPQ");
    bstr *p2 = bstr_dup_c("siej");
    bstr *p3 = bstr_dup_c("TUVWXYZ");
    bstr *p4 = bstr_dup_c("nopq");
    EXPECT_EQ(13, bstr_index_of(haystack, p1));
    EXPECT_EQ(-1, bstr_index_of(haystack, p2));
    EXPECT_EQ(-1, bstr_index_of(haystack, p3));

    EXPECT_EQ(-1, bstr_index_of(haystack, p4));
    EXPECT_EQ(13, bstr_index_of_nocase(haystack, p4));

    EXPECT_EQ(16, bstr_index_of_c(haystack, "QRS"));
    EXPECT_EQ(-1, bstr_index_of_c(haystack, "qrs"));
    EXPECT_EQ(16, bstr_index_of_c_nocase(haystack, "qrs"));

    EXPECT_EQ(16, bstr_index_of_mem(haystack, "QRSSDF",3));
    EXPECT_EQ(-1, bstr_index_of_mem(haystack, "qrssdf",3));
    EXPECT_EQ(16, bstr_index_of_mem_nocase(haystack, "qrssdf",3));

    bstr_free(p1);
    bstr_free(p2);
    bstr_free(p3);
    bstr_free(p4);
    bstr_free(haystack);
}
Beispiel #4
0
TEST(BstrTest, CharAt) {
    bstr *str = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20);
    EXPECT_EQ('\000', bstr_char_at(str, 12));
    EXPECT_EQ(-1, bstr_char_at(str, 45));

    bstr_free(str);
}
Beispiel #5
0
static bstr *copy_or_wrap_mem(const void *data, size_t len, enum htp_alloc_strategy_t alloc) {
    if (alloc == HTP_ALLOC_REUSE) {
        return bstr_wrap_mem(data, len);
    } else {
        return bstr_dup_mem(data, len);
    }
}
Beispiel #6
0
TEST(BstrTest, CharAtEnd) {
    bstr *str = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20);
    EXPECT_EQ('T', bstr_char_at_end(str, 0));
    EXPECT_EQ('\000', bstr_char_at_end(str, 7));
    EXPECT_EQ(-1, bstr_char_at_end(str, bstr_len(str)));

    bstr_free(str);
}
Beispiel #7
0
/**
 * Determines whether inbound parsing needs to continue or stop. In
 * case the data appears to be plain text HTTP, we try to continue.
 *
 * @param[in] connp
 * @return HTP_OK if the parser can resume parsing, HTP_DATA_BUFFER if
 *         we need more data.
 */
htp_status_t htp_connp_REQ_CONNECT_PROBE_DATA(htp_connp_t *connp) {
    for (;;) {//;i < max_read; i++) {
        IN_PEEK_NEXT(connp);
        // Have we reached the end of the line? For some reason
        // we can't test after IN_COPY_BYTE_OR_RETURN */
        if (connp->in_next_byte == LF || connp->in_next_byte == 0x00)
            break;

        IN_COPY_BYTE_OR_RETURN(connp);

    }

    unsigned char *data;
    size_t len;
    if (htp_connp_req_consolidate_data(connp, &data, &len) != HTP_OK) {
        fprintf(stderr, "htp_connp_req_consolidate_data fail");
        return HTP_ERROR;
    }
#ifdef HTP_DEBUG
    fprint_raw_data(stderr, "PROBING", data, len);
#endif

    size_t pos = 0;
    size_t mstart = 0;
    // skip past leading whitespace. IIS allows this
    while ((pos < len) && htp_is_space(data[pos]))
        pos++;
    if (pos)
        mstart = pos;
    // The request method starts at the beginning of the
    // line and ends with the first whitespace character.
    while ((pos < len) && (!htp_is_space(data[pos])))
        pos++;

    int methodi = HTP_M_UNKNOWN;
    bstr *method = bstr_dup_mem(data + mstart, pos - mstart);
    if (method) {
        methodi = htp_convert_method_to_number(method);
        bstr_free(method);
    }
    if (methodi != HTP_M_UNKNOWN) {
#ifdef HTP_DEBUG
        fprint_raw_data(stderr, "htp_connp_REQ_CONNECT_PROBE_DATA: tunnel contains plain text HTTP", data, len);
#endif
        connp->in_state = htp_connp_REQ_IDLE;
    } else {
#ifdef HTP_DEBUG
        fprint_raw_data(stderr, "htp_connp_REQ_CONNECT_PROBE_DATA: tunnel is not HTTP", data, len);
#endif
        connp->in_status = HTP_STREAM_TUNNEL;
        connp->out_status = HTP_STREAM_TUNNEL;
    }

    // not calling htp_connp_req_clear_buffer, we're not consuming the data

    return HTP_OK;
}
Beispiel #8
0
TEST(BstrTest, DupBin) {
    bstr *src = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20);
    bstr *dst;
    dst = bstr_dup(src);

    EXPECT_EQ(bstr_len(src), bstr_len(dst));
    EXPECT_EQ(0, memcmp(bstr_ptr(src), bstr_ptr(dst), bstr_len(src)));

    bstr_free(src);
    bstr_free(dst);
}
Beispiel #9
0
TEST(BstrTest, DupToC) {
    char *c;
    bstr *str = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20);

    c = bstr_util_memdup_to_c("1234\0006789", 9);
    EXPECT_STREQ("1234\\06789", c);
    free(c);

    c = bstr_util_strdup_to_c(str);
    EXPECT_STREQ("ABCDEFGHIJKL\\0NOPQRST", c);

    free(c);
    bstr_free(str);
}
Beispiel #10
0
/**
 * Parses request line.
 *
 * @param[in] connp
 * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
 */
htp_status_t htp_connp_REQ_LINE(htp_connp_t *connp) {
    for (;;) {
        // Get one byte
        IN_COPY_BYTE_OR_RETURN(connp);       

        // Have we reached the end of the line?
        if (connp->in_next_byte == LF) {
            unsigned char *data;
            size_t len;

            htp_connp_req_consolidate_data(connp, &data, &len);

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, data, len);
            #endif

            // Is this a line that should be ignored?
            if (htp_connp_is_line_ignorable(connp, data, len)) {
                // We have an empty/whitespace line, which we'll note, ignore and move on.
                connp->in_tx->request_ignored_lines++;

                // TODO How many empty lines are we willing to accept?

                htp_connp_req_clear_buffer(connp);

                return HTP_OK;
            }

            // Process request line.

            htp_chomp(data, &len);

            connp->in_tx->request_line = bstr_dup_mem(data, len);
            if (connp->in_tx->request_line == NULL) return HTP_ERROR;

            if (connp->cfg->parse_request_line(connp) != HTP_OK) return HTP_ERROR;

            // Finalize request line parsing.

            if (htp_tx_state_request_line(connp->in_tx) != HTP_OK) return HTP_ERROR;

            htp_connp_req_clear_buffer(connp);

            return HTP_OK;
        }
    }

    return HTP_ERROR;
}
Beispiel #11
0
htp_header_t *htp_connp_header_parse(htp_connp_t *reqp, unsigned char *data, size_t len) {
    htp_header_t *h = calloc(1, sizeof (htp_header_t));
    if (h == NULL) return NULL;

    // Parse the header line    
    if (reqp->impl_header_parse(data, len, h) < 0) {
        // Invalid header line
        h->is_parsed = 0;
        h->name = bstr_dup_mem(data, len);

        return h;
    }

    // Now extract the name and the value
    h->name = bstr_dup_mem(data + h->name_offset, h->name_len);
    h->value = bstr_dup_mem(data + h->value_offset, h->value_len);
    h->is_parsed = 1;

    // Because header names are case-insensitive, we will convert
    // the name to lowercase to use it as a lookup key.
    h->name_lowercase = bstr_to_lowercase(h->name);

    return h;
}
Beispiel #12
0
/**
 * Base64-decode input, given as memory range.
 *
 * @param[in] data
 * @param[in] len
 * @return new base64-decoded bstring
 */
bstr *htp_base64_decode_mem(const void *data, size_t len) {
    htp_base64_decoder decoder;
    bstr *r = NULL;

    htp_base64_decoder_init(&decoder);

    unsigned char *tmpstr = malloc(len);
    if (tmpstr == NULL) return NULL;

    int resulting_len = htp_base64_decode(&decoder, data, len, tmpstr, len);
    if (resulting_len > 0) {
        r = bstr_dup_mem(tmpstr, resulting_len);
    }

    free(tmpstr);

    return r;
}
Beispiel #13
0
/**
 * Parse the request line.
 *
 * @param[in] connp
 * @returns HTP_OK on succesful parse, HTP_ERROR on error.
 */
htp_status_t htp_connp_REQ_LINE_complete(htp_connp_t *connp) {
    unsigned char *data;
    size_t len;

    if (htp_connp_req_consolidate_data(connp, &data, &len) != HTP_OK) {
        return HTP_ERROR;
    }

    #ifdef HTP_DEBUG
    fprint_raw_data(stderr, __FUNCTION__, data, len);
    #endif

    // Is this a line that should be ignored?
    if (htp_connp_is_line_ignorable(connp, data, len)) {
        // We have an empty/whitespace line, which we'll note, ignore and move on.
        connp->in_tx->request_ignored_lines++;

        htp_connp_req_clear_buffer(connp);

        return HTP_OK;
    }

    // Process request line.

    htp_chomp(data, &len);

    connp->in_tx->request_line = bstr_dup_mem(data, len);
    if (connp->in_tx->request_line == NULL)
        return HTP_ERROR;

    if (connp->cfg->parse_request_line(connp) != HTP_OK)
        return HTP_ERROR;

    // Finalize request line parsing.

    if (htp_tx_state_request_line(connp->in_tx) != HTP_OK)
        return HTP_ERROR;

    htp_connp_req_clear_buffer(connp);

    return HTP_OK;
}
Beispiel #14
0
// We return a HTP::URI and throw an exception on error.
VALUE rbhtp_parse_uri( VALUE self, VALUE input )
{
	Check_Type( input, T_STRING );
	bstr* input_b = bstr_dup_mem( RSTRING_PTR( input ), RSTRING_LEN( input ) );
	htp_uri_t* uri = NULL; // htp_parse_uri will alloc.
	
	int result = htp_parse_uri( input_b, &uri );
	if ( result != HTP_OK ) {
		bstr_free( &input_b );
		free( uri );
		rb_raise( rb_eRuntimeError, "HTP error in htp_parse_uri: %d", result );
		return Qnil; // Ignored?
	}

	bstr_free( &input_b ); // Okay, as htp_parse_uri dups the data it needs.

	return rb_funcall( cURI, rb_intern( "new" ), 1,
		Data_Wrap_Struct( rb_cObject, 0, rbhtp_free_uri, uri )
	);
}
Beispiel #15
0
TEST(BstrTest, BeginsWith) {
    bstr *haystack = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20);
    bstr *p1 = bstr_dup_c("ABCD");
    bstr *p2 = bstr_dup_c("aBcD");

    EXPECT_EQ(1, bstr_begins_with(haystack,p1));
    EXPECT_NE(1, bstr_begins_with(haystack,p2));
    EXPECT_EQ(1, bstr_begins_with_nocase(haystack,p2));

    EXPECT_EQ(1, bstr_begins_with_c(haystack, "AB"));
    EXPECT_NE(1, bstr_begins_with_c(haystack, "ab"));
    EXPECT_EQ(1, bstr_begins_with_c_nocase(haystack, "ab"));

    EXPECT_EQ(1, bstr_begins_with_mem(haystack, "ABq",2));
    EXPECT_NE(1, bstr_begins_with_mem(haystack, "abq",2));
    EXPECT_EQ(1, bstr_begins_with_mem_nocase(haystack, "abq",2));

    bstr_free(p1);
    bstr_free(p2);
    bstr_free(haystack);
}
Beispiel #16
0
/**
 * Parses response headers.
 *
 * @param[in] connp
 * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
 */
htp_status_t htp_connp_RES_HEADERS(htp_connp_t *connp) {
    for (;;) {
        OUT_COPY_BYTE_OR_RETURN(connp);       
        
        // Have we reached the end of the line?
        if (connp->out_next_byte == LF) {
            unsigned char *data;
            size_t len;

            htp_connp_res_consolidate_data(connp, &data, &len);

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, data, len);
            #endif

            // Should we terminate headers?
            if (htp_connp_is_line_terminator(connp, data, len)) {
                // Parse previous header, if any.
                if (connp->out_header != NULL) {
                    if (connp->cfg->process_response_header(connp, bstr_ptr(connp->out_header),
                            bstr_len(connp->out_header)) != HTP_OK) return HTP_ERROR;

                    bstr_free(connp->out_header);
                    connp->out_header = NULL;
                }
                
                htp_connp_res_clear_buffer(connp);               

                // We've seen all response headers.
                if (connp->out_tx->progress == HTP_RESPONSE_HEADERS) {
                    // Response headers.

                    // The next step is to determine if this response has a body.
                    connp->out_state = htp_connp_RES_BODY_DETERMINE;
                } else {
                    // Response trailer.

                    // Finalize sending raw trailer data.
                    htp_status_t rc = htp_connp_res_receiver_finalize_clear(connp);
                    if (rc != HTP_OK) return rc;

                    // Run hook response_TRAILER.
                    rc = htp_hook_run_all(connp->cfg->hook_response_trailer, connp);
                    if (rc != HTP_OK) return rc;

                    // The next step is to finalize this response.
                    connp->out_state = htp_connp_RES_FINALIZE;
                }

                return HTP_OK;
            }

            htp_chomp(data, &len);

            // Check for header folding.
            if (htp_connp_is_line_folded(data, len) == 0) {
                // New header line.

                // Parse previous header, if any.
                if (connp->out_header != NULL) {
                    if (connp->cfg->process_response_header(connp, bstr_ptr(connp->out_header),
                            bstr_len(connp->out_header)) != HTP_OK) return HTP_ERROR;

                    bstr_free(connp->out_header);
                    connp->out_header = NULL;
                }

                OUT_PEEK_NEXT(connp);

                if (htp_is_folding_char(connp->out_next_byte) == 0) {
                    // Because we know this header is not folded, we can process the buffer straight away.
                    if (connp->cfg->process_response_header(connp, data, len) != HTP_OK) return HTP_ERROR;
                } else {
                    // Keep the partial header data for parsing later.
                    connp->out_header = bstr_dup_mem(data, len);
                    if (connp->out_header == NULL) return HTP_ERROR;
                }
            } else {
                // Folding; check that there's a previous header line to add to.
                if (connp->out_header == NULL) {
                    // Invalid folding.

                    // Warn only once per transaction.
                    if (!(connp->out_tx->flags & HTP_INVALID_FOLDING)) {
                        connp->out_tx->flags |= HTP_INVALID_FOLDING;
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Invalid response field folding");
                    }

                    // Keep the header data for parsing later.
                    connp->out_header = bstr_dup_mem(data, len);
                    if (connp->out_header == NULL) return HTP_ERROR;
                } else {
                    // Add to the existing header.                    
                    bstr *new_out_header = bstr_add_mem(connp->out_header, data, len);
                    if (new_out_header == NULL) return HTP_ERROR;
                    connp->out_header = new_out_header;
                }
            }

            htp_connp_res_clear_buffer(connp);
        }
    }

    return HTP_ERROR;
}
Beispiel #17
0
/**
 * Parses response line.
 *
 * @param[in] connp
 * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
 */
htp_status_t htp_connp_RES_LINE(htp_connp_t *connp) {
    for (;;) {
        // Get one byte
        OUT_COPY_BYTE_OR_RETURN(connp);

        // Have we reached the end of the line?
        if (connp->out_next_byte == LF) {
            unsigned char *data;
            size_t len;

            htp_connp_res_consolidate_data(connp, &data, &len);

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, data, len);
            #endif

            // Is this a line that should be ignored?
            if (htp_connp_is_line_ignorable(connp, data, len)) {
                // We have an empty/whitespace line, which we'll note, ignore and move on
                connp->out_tx->response_ignored_lines++;

                // TODO How many lines are we willing to accept?

                // Start again
                htp_connp_res_clear_buffer(connp);

                return HTP_OK;
            }
           
            // Deallocate previous response line allocations, which we would have on a 100 response.

            if (connp->out_tx->response_line != NULL) {
                bstr_free(connp->out_tx->response_line);
                connp->out_tx->response_line = NULL;
            }           

            if (connp->out_tx->response_protocol != NULL) {
                bstr_free(connp->out_tx->response_protocol);
                connp->out_tx->response_protocol = NULL;
            }

            if (connp->out_tx->response_status != NULL) {
                bstr_free(connp->out_tx->response_status);
                connp->out_tx->response_status = NULL;
            }

            if (connp->out_tx->response_message != NULL) {
                bstr_free(connp->out_tx->response_message);
                connp->out_tx->response_message = NULL;
            }

            // Process response line.           

            int chomp_result = htp_chomp(data, &len);

            connp->out_tx->response_line = bstr_dup_mem(data, len);
            if (connp->out_tx->response_line == NULL) return HTP_ERROR;            

            if (connp->cfg->parse_response_line(connp) != HTP_OK) return HTP_ERROR;

            // If the response line is invalid, determine if it _looks_ like
            // a response line. If it does not look like a line, process the
            // data as a response body because that is what browsers do.

            if (htp_treat_response_line_as_body(connp->out_tx)) {
                connp->out_tx->response_content_encoding_processing = HTP_COMPRESSION_NONE;
                
                int rc = htp_tx_res_process_body_data(connp->out_tx, data, len + chomp_result);
                if (rc != HTP_OK) return rc;
               
                // Continue to process response body. Because we don't have
                // any headers to parse, we assume the body continues until
                // the end of the stream.
                connp->out_tx->response_transfer_coding = HTP_CODING_IDENTITY;
                connp->out_tx->progress = HTP_RESPONSE_BODY;
                connp->out_state = htp_connp_RES_BODY_IDENTITY_STREAM_CLOSE;
                connp->out_body_data_left = -1;
                
                return HTP_OK;
            }
           
            int rc = htp_tx_state_response_line(connp->out_tx);
            if (rc != HTP_OK) return rc;
            
            htp_connp_res_clear_buffer(connp);

            // Move on to the next phase.
            connp->out_state = htp_connp_RES_HEADERS;
            connp->out_tx->progress = HTP_RESPONSE_HEADERS;

            return HTP_OK;
        }
    }

    return HTP_ERROR;
}
Beispiel #18
0
/**
 * Generic response header parser.
 * 
 * @param[in] connp
 * @param[in] h
 * @param[in] data
 * @param[in] len
 * @return HTP status
 */
htp_status_t htp_parse_response_header_generic(htp_connp_t *connp, htp_header_t *h, unsigned char *data, size_t len) {
    size_t name_start, name_end;
    size_t value_start, value_end;
    size_t prev;

    htp_chomp(data, &len);

    name_start = 0;

    // Look for the first colon.
    size_t colon_pos = 0;
    while ((colon_pos < len) && (data[colon_pos] != ':')) colon_pos++;

    if (colon_pos == len) {
        // Header line with a missing colon.

        h->flags |= HTP_FIELD_UNPARSEABLE;
        h->flags |= HTP_FIELD_INVALID;

        if (!(connp->out_tx->flags & HTP_FIELD_UNPARSEABLE)) {
            // Only once per transaction.
            connp->out_tx->flags |= HTP_FIELD_UNPARSEABLE;
            connp->out_tx->flags |= HTP_FIELD_INVALID;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: missing colon.");
        }
       
        // Reset the position. We're going to treat this invalid header
        // as a header with an empty name. That will increase the probability
        // that the content will be inspected.
        colon_pos = 0;
        name_end = 0;
        value_start = 0;
    } else {
        // Header line with a colon.
        
        if (colon_pos == 0) {
            // Empty header name.

            h->flags |= HTP_FIELD_INVALID;

            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
                // Only once per transaction.
                connp->out_tx->flags |= HTP_FIELD_INVALID;
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: empty name.");
            }
        }

        name_end = colon_pos;

        // Ignore LWS after field-name.
        prev = name_end;
        while ((prev > name_start) && (htp_is_lws(data[prev - 1]))) {
            prev--;
            name_end--;

            h->flags |= HTP_FIELD_INVALID;

            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
                // Only once per transaction.
                connp->out_tx->flags |= HTP_FIELD_INVALID;
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: LWS after name.");
            }
        }

        value_start = colon_pos + 1;
    }

    // Header value.   

    // Ignore LWS before field-content.
    while ((value_start < len) && (htp_is_lws(data[value_start]))) {
        value_start++;
    }

    // Look for the end of field-content.
    value_end = len;    
    
    // Check that the header name is a token.
    size_t i = name_start;
    while (i < name_end) {
        if (!htp_is_token(data[i])) {
            h->flags |= HTP_FIELD_INVALID;

            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
                connp->out_tx->flags |= HTP_FIELD_INVALID;
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response header name is not a token.");
            }

            break;
        }

        i++;
    }

    // Now extract the name and the value.
    h->name = bstr_dup_mem(data + name_start, name_end - name_start);
    h->value = bstr_dup_mem(data + value_start, value_end - value_start);
    if ((h->name == NULL) || (h->value == NULL)) {
        bstr_free(h->name);
        bstr_free(h->value);
        return HTP_ERROR;
    }

    return HTP_OK;
}
Beispiel #19
0
/**
 * This method is invoked whenever a piece of data, belonging to a single field (name or value)
 * becomes available. It will either create a new parameter or store the transient information
 * until a parameter can be created.
 *
 * @param urlenp
 * @param data
 * @param startpos
 * @param endpos
 * @param c Should contain -1 if the reason this function is called is because the end of
 *          the current data chunk is reached.
 */
static void htp_urlenp_add_field_piece(htp_urlenp_t *urlenp, unsigned char *data, size_t startpos, size_t endpos, int c) {
    // Add field if we know it ended or if we know that
    // we've used all of the input data
    if ((c != -1) || (urlenp->_complete)) {
        // Add field
        bstr *field = NULL;

        // Did we use the string builder for this field?
        if (bstr_builder_size(urlenp->_bb) > 0) {
            // The current field consists of more than once piece,
            // we have to use the string builder

            // Add current piece to string builder
            if (endpos - startpos > 0) {
                bstr_builder_append_mem(urlenp->_bb, (char *) data + startpos, endpos - startpos);
            }

            // Generate the field and clear the string builder
            field = bstr_builder_to_str(urlenp->_bb);
            if (field == NULL) return;
            bstr_builder_clear(urlenp->_bb);
        } else {
            // We only have the current piece to work with, so
            // no need to involve the string builder
            field = bstr_dup_mem((char *) data + startpos, endpos - startpos);
            if (field == NULL) return;
        }

        // Process the field differently, depending on the current state
        if (urlenp->_state == HTP_URLENP_STATE_KEY) {
            // Store the name for later
            urlenp->_name = field;

            if (urlenp->_complete) {
                // Param with key but no value
                bstr *name = urlenp->_name;
                bstr *value = bstr_dup_c("");

                if (urlenp->decode_url_encoding) {                    
                    // htp_uriencoding_normalize_inplace(name);
                    htp_decode_urlencoded_inplace(urlenp->tx->connp->cfg, urlenp->tx, name);
                }
                
                table_addn(urlenp->params, name, value);
                urlenp->_name = NULL;

                #ifdef HTP_DEBUG
                fprint_raw_data(stderr, "NAME", (unsigned char *) bstr_ptr(name), bstr_len(name));
                fprint_raw_data(stderr, "VALUE", (unsigned char *) bstr_ptr(value), bstr_len(value));
                #endif
            }
        } else {
            // Param with key and value                        
            bstr *name = urlenp->_name;
            bstr *value = field;
            
            if (urlenp->decode_url_encoding) {                
                htp_decode_urlencoded_inplace(urlenp->tx->connp->cfg, urlenp->tx, name);
                htp_decode_urlencoded_inplace(urlenp->tx->connp->cfg, urlenp->tx, value);
            }

            table_addn(urlenp->params, name, value);
            urlenp->_name = NULL;

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, "NAME", (unsigned char *) bstr_ptr(name), bstr_len(name));
            fprint_raw_data(stderr, "VALUE", (unsigned char *) bstr_ptr(value), bstr_len(value));
            #endif
        }
    } else {
        // Make a copy of the data and store it in an array for later
        if (endpos - startpos > 0) {
            bstr_builder_append_mem(urlenp->_bb, (char *) data + startpos, endpos - startpos);
        }
    }
}
Beispiel #20
0
/**
 * Generic response line parser.
 * 
 * @param[in] connp
 * @return HTP status
 */
htp_status_t htp_parse_response_line_generic(htp_connp_t *connp) {
    htp_tx_t *tx = connp->out_tx;
    unsigned char *data = bstr_ptr(tx->response_line);
    size_t len = bstr_len(tx->response_line);
    size_t pos = 0;

    tx->response_protocol = NULL;
    tx->response_protocol_number = HTP_PROTOCOL_INVALID;
    tx->response_status = NULL;
    tx->response_status_number = HTP_STATUS_INVALID;
    tx->response_message = NULL;

    // Ignore whitespace at the beginning of the line.
    while ((pos < len) && (htp_is_space(data[pos]))) pos++;

    size_t start = pos;

    // Find the end of the protocol string.
    while ((pos < len) && (!htp_is_space(data[pos]))) pos++;    
    if (pos - start == 0) return HTP_OK;
    
    tx->response_protocol = bstr_dup_mem(data + start, pos - start);
    if (tx->response_protocol == NULL) return HTP_ERROR;    

    tx->response_protocol_number = htp_parse_protocol(tx->response_protocol);

    #ifdef HTP_DEBUG
    fprint_raw_data(stderr, "Response protocol", bstr_ptr(tx->response_protocol), bstr_len(tx->response_protocol));
    fprintf(stderr, "Response protocol number: %d\n", tx->response_protocol_number);
    #endif

    // Ignore whitespace after the response protocol.
    while ((pos < len) && (htp_is_space(data[pos]))) pos++;
    if (pos == len) return HTP_OK;

    start = pos;

    // Find the next whitespace character.
    while ((pos < len) && (!htp_is_space(data[pos]))) pos++;    
    if (pos - start == 0) return HTP_OK;
    
    tx->response_status = bstr_dup_mem(data + start, pos - start);
    if (tx->response_status == NULL) return HTP_ERROR;

    tx->response_status_number = htp_parse_status(tx->response_status);

    #ifdef HTP_DEBUG
    fprint_raw_data(stderr, "Response status (as text)", bstr_ptr(tx->response_status), bstr_len(tx->response_status));
    fprintf(stderr, "Response status number: %d\n", tx->response_status_number);
    #endif

    // Ignore whitespace that follows the status code.
    while ((pos < len) && (isspace(data[pos]))) pos++;
    if (pos == len) return HTP_OK;

    // Assume the message stretches until the end of the line.    
    tx->response_message = bstr_dup_mem(data + pos, len - pos);
    if (tx->response_message == NULL) return HTP_ERROR;    

    #ifdef HTP_DEBUG
    fprint_raw_data(stderr, "Response status message", bstr_ptr(tx->response_message), bstr_len(tx->response_message));
    #endif

    return HTP_OK;
}
Beispiel #21
0
/**
 * Transcode one bstr.
 *
 * @param[in] cd
 * @param[in] input
 * @param[in] output
 */
int htp_transcode_bstr(iconv_t cd, bstr *input, bstr **output) {
    // Reset conversion state for every new string
    iconv(cd, NULL, 0, NULL, 0);

    bstr_builder_t *bb = NULL;

    const size_t buflen = 10;
    unsigned char *buf = malloc(buflen);
    if (buf == NULL) {
        return HTP_ERROR;
    }

    const char *inbuf = (const char *)bstr_ptr(input);
    size_t inleft = bstr_len(input);
    char *outbuf = (char *)buf;
    size_t outleft = buflen;

    int loop = 1;
    while (loop) {
        loop = 0;

        if (iconv(cd, (ICONV_CONST char **)&inbuf, &inleft, (char **)&outbuf, &outleft) == (size_t) - 1) {
            if (errno == E2BIG) {
                // Create bstr builder on-demand
                if (bb == NULL) {
                    bb = bstr_builder_create();
                    if (bb == NULL) {
                        free(buf);
                        return HTP_ERROR;
                    }
                }

                // The output buffer is full
                bstr_builder_append_mem(bb, buf, buflen - outleft);

                outbuf = (char *)buf;
                outleft = buflen;

                // Continue in the loop, as there's more work to do
                loop = 1;
            } else {
                // Error
                if (bb != NULL) bstr_builder_destroy(bb);
                free(buf);
                return HTP_ERROR;
            }
        }
    }
    
    if (bb != NULL) {
        bstr_builder_append_mem(bb, buf, buflen - outleft);
        *output = bstr_builder_to_str(bb);
        bstr_builder_destroy(bb);
        if (*output == NULL) {
            free(buf);
            return HTP_ERROR;
        }
    } else {
        *output = bstr_dup_mem(buf, buflen - outleft);
        if (*output == NULL) {
            free(buf);
            return HTP_ERROR;
        }
    }
    
    free(buf);

    return HTP_OK;
}
Beispiel #22
0
/**
 * This method is invoked whenever a piece of data, belonging to a single field (name or value)
 * becomes available. It will either create a new parameter or store the transient information
 * until a parameter can be created.
 *
 * @param[in] urlenp
 * @param[in] data
 * @param[in] startpos
 * @param[in] endpos
 * @param[in] c Should contain -1 if the reason this function is called is because the end of
 *          the current data chunk is reached.
 */
static void htp_urlenp_add_field_piece(htp_urlenp_t *urlenp, const unsigned char *data, size_t startpos, size_t endpos, int last_char) {    
    // Add field if we know it ended (last_char is something other than -1)
    // or if we know that there won't be any more input data (urlenp->_complete is true).
    if ((last_char != -1) || (urlenp->_complete)) {       
        // Prepare the field value, assembling from multiple pieces as necessary.
    
        bstr *field = NULL;

        // Did we use the string builder for this field?
        if (bstr_builder_size(urlenp->_bb) > 0) {            
            // The current field consists of more than once piece, we have to use the string builder.

            // Add current piece to string builder.
            if ((data != NULL) && (endpos - startpos > 0)) {
                bstr_builder_append_mem(urlenp->_bb, data + startpos, endpos - startpos);
            }

            // Generate the field and clear the string builder.
            field = bstr_builder_to_str(urlenp->_bb);
            if (field == NULL) return;

            bstr_builder_clear(urlenp->_bb);
        } else {            
            // We only have the current piece to work with, so no need to involve the string builder.
            if ((data != NULL) && (endpos - startpos > 0)) {
                field = bstr_dup_mem(data + startpos, endpos - startpos);
                if (field == NULL) return;
            }
        }

        // Process field as key or value, as appropriate.
        
        if (urlenp->_state == HTP_URLENP_STATE_KEY) {
            // Key.

            // If there is no more work left to do, then we have a single key. Add it.
            if ((urlenp->_complete)||(last_char == urlenp->argument_separator)) {                
                
                // Handling empty pairs is tricky. We don't want to create a pair for
                // an entirely empty input, but in some cases it may be appropriate
                // (e.g., /index.php?&q=2).
                if ((field != NULL)||(last_char == urlenp->argument_separator)) {
                    // Add one pair, with an empty value and possibly empty key too.

                    bstr *name = field;
                    if (name == NULL) {
                        name = bstr_dup_c("");
                        if (name == NULL) return;
                    }

                    bstr *value = bstr_dup_c("");
                    if (value == NULL) {
                        bstr_free(name);
                        return;
                    }

                    if (urlenp->decode_url_encoding) {
                        htp_tx_urldecode_params_inplace(urlenp->tx, name);
                    }

                    htp_table_addn(urlenp->params, name, value);

                    urlenp->_name = NULL;

                    #ifdef HTP_DEBUG
                    fprint_raw_data(stderr, "NAME", bstr_ptr(name), bstr_len(name));
                    fprint_raw_data(stderr, "VALUE", bstr_ptr(value), bstr_len(value));
                    #endif
                }
            } else {                
                // This key will possibly be followed by a value, so keep it for later.
                urlenp->_name = field;
            }
        } else {            
            // Value (with a key remembered from before).

            bstr *name = urlenp->_name;
            urlenp->_name = NULL;

            if (name == NULL) {
                name = bstr_dup_c("");
                if (name == NULL) {
                    bstr_free(field);
                    return;
                }
            }

            bstr *value = field;
            if (value == NULL) {
                value = bstr_dup_c("");
                if (value == NULL) {
                    bstr_free(name);
                    return;
                }
            }

            if (urlenp->decode_url_encoding) {
                htp_tx_urldecode_params_inplace(urlenp->tx, name);
                htp_tx_urldecode_params_inplace(urlenp->tx, value);
            }

            htp_table_addn(urlenp->params, name, value);           

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, "NAME", bstr_ptr(name), bstr_len(name));
            fprint_raw_data(stderr, "VALUE", bstr_ptr(value), bstr_len(value));
            #endif
        }        
    } else {
        // The field has not ended. We'll make a copy of of the available data for later.
        if ((data != NULL) && (endpos - startpos > 0)) {
            bstr_builder_append_mem(urlenp->_bb, data + startpos, endpos - startpos);
        }
    }
}
Beispiel #23
0
/**
 * This is a proof-of-concept processor that processes parameter names in
 * a way _similar_ to PHP. Whitespace at the beginning is removed, and the
 * remaining whitespace characters are converted to underscores. Proper
 * research of PHP's behavior is needed before we can claim to be emulating it.
 *
 * @param[in,out] p
 * @return HTP_OK on success, HTP_ERROR on failure.
 */
htp_status_t htp_php_parameter_processor(htp_param_t *p) {
    if (p == NULL) return HTP_ERROR;

    // Name transformation

    bstr *new_name = NULL;

    // Ignore whitespace characters at the beginning of parameter name.

    unsigned char *data = bstr_ptr(p->name);
    size_t len = bstr_len(p->name);
    size_t pos = 0;

    // Advance over any whitespace characters at the beginning of the name.
    while ((pos < len) && (isspace(data[pos]))) pos++;

    // Have we seen any whitespace?
    if (pos > 0) {
        // Make a copy of the name, starting with
        // the first non-whitespace character.
        new_name = bstr_dup_mem(data + pos, len - pos);
        if (new_name == NULL) return HTP_ERROR;
    }
    
    // Replace remaining whitespace characters with underscores.

    size_t offset = pos;
    pos = 0;
    
    // Advance to the end of name or to the first whitespace character.
    while ((offset + pos < len)&&(!isspace(data[pos]))) pos++;

    // Are we at the end of the name?
    if (offset + pos < len) {
        // Seen whitespace within the string.

        // Make a copy of the name if needed (which would be the case
        // with a parameter that does not have any whitespace in front).
        if (new_name == NULL) {
            new_name = bstr_dup(p->name);
            if (new_name == NULL) return HTP_ERROR;
        }
        
        // Change the pointers to the new name and ditch the offset.
        data = bstr_ptr(new_name);
        len = bstr_len(new_name);

        // Replace any whitespace characters in the copy with underscores.
        while (pos < len) {
            if (isspace(data[pos])) {
                data[pos] = '_';
            }
        
            pos++;
        }
    }

    // If we made any changes, free the old parameter name and put the new one in.
    if (new_name != NULL) {
        bstr_free(p->name);
        p->name = new_name;
    }

    return HTP_OK;
}
Beispiel #24
0
/**
 * Parses request headers.
 *
 * @param[in] connp
 * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
 */
htp_status_t htp_connp_REQ_HEADERS(htp_connp_t *connp) {
    for (;;) {
        IN_COPY_BYTE_OR_RETURN(connp);

        // Have we reached the end of the line?
        if (connp->in_next_byte == LF) {
            unsigned char *data;
            size_t len;

            htp_connp_req_consolidate_data(connp, &data, &len);

            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, data, len);
            #endif           

            // Should we terminate headers?
            if (htp_connp_is_line_terminator(connp, data, len)) {
                // Parse previous header, if any.
                if (connp->in_header != NULL) {
                    if (connp->cfg->process_request_header(connp, bstr_ptr(connp->in_header),
                            bstr_len(connp->in_header)) != HTP_OK) return HTP_ERROR;

                    bstr_free(connp->in_header);
                    connp->in_header = NULL;
                }

                htp_connp_req_clear_buffer(connp);

                // We've seen all the request headers.
                return htp_tx_state_request_headers(connp->in_tx);
            }

            htp_chomp(data, &len);

            // Check for header folding.
            if (htp_connp_is_line_folded(data, len) == 0) {
                // New header line.

                // Parse previous header, if any.
                if (connp->in_header != NULL) {
                    if (connp->cfg->process_request_header(connp, bstr_ptr(connp->in_header),
                            bstr_len(connp->in_header)) != HTP_OK) return HTP_ERROR;

                    bstr_free(connp->in_header);
                    connp->in_header = NULL;
                }

                IN_PEEK_NEXT(connp);

                if (htp_is_folding_char(connp->in_next_byte) == 0) {
                    // Because we know this header is not folded, we can process the buffer straight away.
                    if (connp->cfg->process_request_header(connp, data, len) != HTP_OK) return HTP_ERROR;
                } else {
                    // Keep the partial header data for parsing later.
                    connp->in_header = bstr_dup_mem(data, len);
                    if (connp->in_header == NULL) return HTP_ERROR;
                }
            } else {
                // Folding; check that there's a previous header line to add to.
                if (connp->in_header == NULL) {
                    // Invalid folding.

                    // Warn only once per transaction.
                    if (!(connp->in_tx->flags & HTP_INVALID_FOLDING)) {
                        connp->in_tx->flags |= HTP_INVALID_FOLDING;
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Invalid request field folding");
                    }

                    // Keep the header data for parsing later.
                    connp->in_header = bstr_dup_mem(data, len);
                    if (connp->in_header == NULL) return HTP_ERROR;
                } else {
                    // Add to the existing header.                    
                    bstr *new_in_header = bstr_add_mem(connp->in_header, data, len);
                    if (new_in_header == NULL) return HTP_ERROR;
                    connp->in_header = new_in_header;
                }
            }

            htp_connp_req_clear_buffer(connp);
        }
    }

    return HTP_ERROR;
}
Beispiel #25
0
/**
 * Parses one part header.
 *
 * @param data
 * @param len
 * @param Success indication
 */
int htp_mpartp_parse_header(htp_mpart_part_t *part, unsigned char *data, size_t len) {
    size_t name_start, name_end;
    size_t value_start, value_end;

    name_start = 0;

    // Look for the colon
    size_t colon_pos = 0;

    while ((colon_pos < len) && (data[colon_pos] != ':')) colon_pos++;

    if (colon_pos == len) {
        // Missing colon
        // TODO Error message
        return -1;
    }

    if (colon_pos == 0) {
        // Empty header name
        // TODO Error message
    }

    name_end = colon_pos;

    // Ignore LWS after field-name
    size_t prev = name_end;
    while ((prev > name_start) && (htp_is_lws(data[prev - 1]))) {
        prev--;
        name_end--;

        // LWS after field name
        // TODO Error message
    }

    // Value

    value_start = colon_pos;

    // Go over the colon
    if (value_start < len) {
        value_start++;
    }

    // Ignore LWS before field-content
    while ((value_start < len) && (htp_is_lws(data[value_start]))) {
        value_start++;
    }

    // Look for the end of field-content
    value_end = value_start;

    while (value_end < len) value_end++;

    // Ignore LWS after field-content
    prev = value_end - 1;
    while ((prev > value_start) && (htp_is_lws(data[prev]))) {
        prev--;
        value_end--;
    }

    // Check that the header name is a token
    size_t i = name_start;
    while (i < name_end) {
        if (!htp_is_token(data[i])) {
            // Request field is not a token
            // TODO Error message

            break;
        }

        i++;
    }

    // Now extract the name and the value
    htp_header_t *h = calloc(1, sizeof (htp_header_t));
    if (h == NULL) return -1;

    h->name = bstr_dup_mem((char *) data + name_start, name_end - name_start);
    h->value = bstr_dup_mem((char *) data + value_start, value_end - value_start);

    // Check if the header already exists
    htp_header_t * h_existing = table_get(part->headers, h->name);
    if (h_existing != NULL) {
        // Add to existing header
        bstr *new_value = bstr_expand(h_existing->value, bstr_len(h_existing->value)
            + 2 + bstr_len(h->value));
        if (new_value == NULL) {
            bstr_free(&h->name);
            bstr_free(&h->value);
            free(h);
            return -1;
        }

        h_existing->value = new_value;
        bstr_add_mem_noex(h_existing->value, ", ", 2);
        bstr_add_noex(h_existing->value, h->value);

        // The header is no longer needed
        bstr_free(&h->name);
        bstr_free(&h->value);
        free(h);

        // Keep track of same-name headers
        h_existing->flags |= HTP_FIELD_REPEATED;
    } else {
        // Add as a new header
        table_add(part->headers, h->name, h);
    }

    return 1;
}
Beispiel #26
0
/**
 * Process part headers. In the current implementation, we only parse the
 * Content-Disposition header if it is present.
 *
 * @param part
 * @return Success indication
 */
int htp_mpart_part_process_headers(htp_mpart_part_t *part) {
    // Find C-D header
    htp_header_t *h = (htp_header_t *) table_get_c(part->headers, "content-disposition");
    if (h == NULL) {
        // TODO Error message
        return 0;
    }

    if (bstr_index_of_c(h->value, "form-data") != 0) {
        return -1;
    }

    // The parsing starts here
    unsigned char *data = (unsigned char *) bstr_ptr(h->value);
    size_t len = bstr_len(h->value);
    size_t pos = 9; // Start after "form-data"

    // Main parameter parsing loop (once per parameter)
    while (pos < len) {
        // Find semicolon and go over it
        while ((pos < len) && ((data[pos] == '\t') || (data[pos] == ' '))) pos++;
        if (pos == len) return -2;

        // Semicolon
        if (data[pos] != ';') return -3;
        pos++;

        // Go over the whitespace before parameter name
        while ((pos < len) && ((data[pos] == '\t') || (data[pos] == ' '))) pos++;
        if (pos == len) return -4;

        // Found starting position (name)
        size_t start = pos;

        // Look for ending position
        while ((pos < len) && (data[pos] != '\t') && (data[pos] != ' ') && (data[pos] != '=')) pos++;
        if (pos == len) return -5;

        // Ending position is in "pos" now

        // Is it a parameter we are interested in?
        int param_type = htp_mpartp_cd_param_type(data, start, pos);

        // Ignore whitespace
        while ((pos < len) && ((data[pos] == '\t') || (data[pos] == ' '))) pos++;
        if (pos == len) return -6;

        // Equals
        if (data[pos] != '=') return -7;
        pos++;

        // Go over the whitespace before value
        while ((pos < len) && ((data[pos] == '\t') || (data[pos] == ' '))) pos++;
        if (pos == len) return -8;

        // Found starting point (value)
        start = pos;

        // Quoting char indicator
        int qchar = -1;

        // Different handling for quoted and bare strings
        if (data[start] == '"') {
            // Quoted string
            qchar = data[start];
            start = ++pos;

            // Find the end of the value
            while ((pos < len) && (data[pos] != qchar)) {
                if (data[pos] == '\\') {
                    // Ignore invalid quoting pairs
                    if (pos + 1 < len) return -9;
                    // Go over the quoted character
                    pos++;
                }

                pos++;
            }
        } else {
            // Bare string
            while ((pos < len) && (!htp_is_token(data[pos]))) pos++;
        }

        switch (param_type) {
            case PARAM_NAME:
                // TODO Unquote quoted characters
                part->name = bstr_dup_mem((char *) data + start, pos - start);
                if (part->name == NULL) return -1;
                break;
            case PARAM_FILENAME:
                // TODO Unquote quoted characters
                part->file = calloc(1, sizeof (htp_file_t));
                if (part->file == NULL) return -1;
                part->file->filename = bstr_dup_mem((char *) data + start, pos - start);
                if (part->file->filename == NULL) return -1;
                part->file->source = HTP_FILE_MULTIPART;
                break;
            default:
                // Ignore unknown parameter
                // TODO Warn/log?
                break;
        }

        // Skip over the quoting character
        if (qchar != -1) {
            pos++;
        }

        // Continue to parse the next parameter, if any
    }

    return 1;
}
/**
 * Generic request header parser.
 *
 * @param[in] connp
 * @param[in] h
 * @param[in] data
 * @param[in] len
 * @return HTP_OK or HTP_ERROR
 */
htp_status_t htp_parse_request_header_generic(htp_connp_t *connp, htp_header_t *h, unsigned char *data, size_t len) {
    size_t name_start, name_end;
    size_t value_start, value_end;

    htp_chomp(data, &len);

    name_start = 0;

    // Look for the colon.
    size_t colon_pos = 0;
    while ((colon_pos < len) && (data[colon_pos] != '\0') && (data[colon_pos] != ':')) colon_pos++;

    if ((colon_pos == len) || (data[colon_pos] == '\0')) {
        // Missing colon.

        h->flags |= HTP_FIELD_UNPARSEABLE;

        // Log only once per transaction.
        if (!(connp->in_tx->flags & HTP_FIELD_UNPARSEABLE)) {
            connp->in_tx->flags |= HTP_FIELD_UNPARSEABLE;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: colon missing");
        }

        // We handle this case as a header with an empty name, with the value equal
        // to the entire input string.

        // TODO Apache will respond to this problem with a 400.

        // Now extract the name and the value
        h->name = bstr_dup_c("");
        if (h->name == NULL) return HTP_ERROR;

        h->value = bstr_dup_mem(data, len);
        if (h->value == NULL) {
            bstr_free(h->name);
            return HTP_ERROR;
        }

        return HTP_OK;
    }

    if (colon_pos == 0) {
        // Empty header name.

        h->flags |= HTP_FIELD_INVALID;

        // Log only once per transaction.
        if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) {
            connp->in_tx->flags |= HTP_FIELD_INVALID;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: empty name");
        }
    }

    name_end = colon_pos;

    // Ignore LWS after field-name.
    size_t prev = name_end;
    while ((prev > name_start) && (htp_is_lws(data[prev - 1]))) {
        // LWS after header name.

        prev--;
        name_end--;

        h->flags |= HTP_FIELD_INVALID;

        // Log only once per transaction.
        if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) {
            connp->in_tx->flags |= HTP_FIELD_INVALID;
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: LWS after name");
        }
    }

    // Header value.

    value_start = colon_pos;

    // Go over the colon.
    if (value_start < len) {
        value_start++;
    }

    // Ignore LWS before field-content.
    while ((value_start < len) && (htp_is_lws(data[value_start]))) {
        value_start++;
    }

    // Look for the end of field-content.
    value_end = value_start;
    while ((value_end < len) && (data[value_end] != '\0')) value_end++;

    // Ignore LWS after field-content.
    prev = value_end - 1;
    while ((prev > value_start) && (htp_is_lws(data[prev]))) {
        prev--;
        value_end--;
    }

    // Check that the header name is a token.
    size_t i = name_start;
    while (i < name_end) {
        if (!htp_is_token(data[i])) {
            // Incorrectly formed header name.

            h->flags |= HTP_FIELD_INVALID;

            // Log only once per transaction.
            if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) {
                connp->in_tx->flags |= HTP_FIELD_INVALID;
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request header name is not a token");
            }

            break;
        }

        i++;
    }

    // Now extract the name and the value
    h->name = bstr_dup_mem(data + name_start, name_end - name_start);
    if (h->name == NULL) return HTP_ERROR;

    h->value = bstr_dup_mem(data + value_start, value_end - value_start);
    if (h->value == NULL) {
        bstr_free(h->name);
        return HTP_ERROR;
    }

    return HTP_OK;
}
htp_status_t htp_parse_request_line_generic_ex(htp_connp_t *connp, int nul_terminates) {
    htp_tx_t *tx = connp->in_tx;
    unsigned char *data = bstr_ptr(tx->request_line);
    size_t len = bstr_len(tx->request_line);
    size_t pos = 0;
    size_t mstart = 0;
    size_t start;
    size_t bad_delim;

    if (nul_terminates) {
        // The line ends with the first NUL byte.
        
        size_t newlen = 0;
        while ((pos < len) && (data[pos] != '\0')) {
            pos++;
            newlen++;
        }

        // Start again, with the new length.
        len = newlen;
        pos = 0;
    }

    // skip past leading whitespace. IIS allows this
    while ((pos < len) && htp_is_space(data[pos])) pos++;
    if (pos) {
        htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request line: leading whitespace");
        mstart = pos;

        if (connp->cfg->requestline_leading_whitespace_unwanted != HTP_UNWANTED_IGNORE) {
            // reset mstart so that we copy the whitespace into the method
            mstart = 0;
            // set expected response code to this anomaly
            tx->response_status_expected_number = connp->cfg->requestline_leading_whitespace_unwanted;
        }
    }

    // The request method starts at the beginning of the
    // line and ends with the first whitespace character.
    while ((pos < len) && (!htp_is_space(data[pos]))) pos++;

    // No, we don't care if the method is empty.

    tx->request_method = bstr_dup_mem(data + mstart, pos - mstart);
    if (tx->request_method == NULL) return HTP_ERROR;

    #ifdef HTP_DEBUG
    fprint_raw_data(stderr, __FUNCTION__, bstr_ptr(tx->request_method), bstr_len(tx->request_method));
    #endif

    tx->request_method_number = htp_convert_method_to_number(tx->request_method);

    bad_delim = 0;
    // Ignore whitespace after request method. The RFC allows
    // for only one SP, but then suggests any number of SP and HT
    // should be permitted. Apache uses isspace(), which is even
    // more permitting, so that's what we use here.
    while ((pos < len) && (isspace(data[pos]))) {
        if (!bad_delim && data[pos] != 0x20) {
            bad_delim++;
        }
        pos++;
    }
    if (bad_delim) {
        htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request line: non-compliant delimiter between Method and URI");
    }

    // Is there anything after the request method?
    if (pos == len) {
        // No, this looks like a HTTP/0.9 request.

        tx->is_protocol_0_9 = 1;
        tx->request_protocol_number = HTP_PROTOCOL_0_9;

        return HTP_OK;
    }

    start = pos;
    bad_delim = 0;

    // The URI ends with the first whitespace.
    while ((pos < len) && (data[pos] != 0x20)) {
        if (!bad_delim && htp_is_space(data[pos])) {
            bad_delim++;
        }
        pos++;
    }
    /* if we've seen some 'bad' delimiters, we retry with those */
    if (bad_delim && pos == len) {
        // special case: even though RFC's allow only SP (0x20), many
        // implementations allow other delimiters, like tab or other
        // characters that isspace() accepts.
        pos = start;
        while ((pos < len) && (!htp_is_space(data[pos]))) pos++;
    }
    if (bad_delim) {
        // warn regardless if we've seen non-compliant chars
        htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request line: URI contains non-compliant delimiter");
    }

    tx->request_uri = bstr_dup_mem(data + start, pos - start);
    if (tx->request_uri == NULL) return HTP_ERROR;

    #ifdef HTP_DEBUG
    fprint_raw_data(stderr, __FUNCTION__, bstr_ptr(tx->request_uri), bstr_len(tx->request_uri));
    #endif

    // Ignore whitespace after URI.
    while ((pos < len) && (htp_is_space(data[pos]))) pos++;

    // Is there protocol information available?
    if (pos == len) {
        // No, this looks like a HTTP/0.9 request.

        tx->is_protocol_0_9 = 1;
        tx->request_protocol_number = HTP_PROTOCOL_0_9;

        return HTP_OK;
    }

    // The protocol information continues until the end of the line.
    tx->request_protocol = bstr_dup_mem(data + pos, len - pos);
    if (tx->request_protocol == NULL) return HTP_ERROR;

    tx->request_protocol_number = htp_parse_protocol(tx->request_protocol);

    #ifdef HTP_DEBUG
    fprint_raw_data(stderr, __FUNCTION__, bstr_ptr(tx->request_protocol), bstr_len(tx->request_protocol));
    #endif

    return HTP_OK;
}
Beispiel #29
0
/**
 * Parses request line.
 *
 * @param connp
 * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
 */
int htp_connp_REQ_LINE(htp_connp_t *connp) {
    for (;;) {
        // Get one byte
        IN_COPY_BYTE_OR_RETURN(connp);

        // Keep track of NUL bytes
        if (connp->in_next_byte == 0) {
            // Remember how many NULs there were
            connp->in_tx->request_line_nul++;

            // Store the offset of the first NUL byte
            if (connp->in_tx->request_line_nul_offset == -1) {
                connp->in_tx->request_line_nul_offset = connp->in_line_len;
            }
        }

        // Have we reached the end of the line?
        if (connp->in_next_byte == LF) {
            #ifdef HTP_DEBUG
            fprint_raw_data(stderr, __FUNCTION__, connp->in_line, connp->in_line_len);
            #endif

            // Is this a line that should be ignored?
            if (htp_connp_is_line_ignorable(connp, connp->in_line, connp->in_line_len)) {
                // We have an empty/whitespace line, which we'll note, ignore and move on
                connp->in_tx->request_ignored_lines++;

                // TODO How many empty lines are we willing to accept?

                // Start again
                connp->in_line_len = 0;

                return HTP_OK;
            }

            // Process request line

            connp->in_tx->request_line_raw = bstr_dup_mem((char *) connp->in_line, connp->in_line_len);
            if (connp->in_tx->request_line_raw == NULL) {
                return HTP_ERROR;
            }

            /// @todo Would be nice to reference request_line_raw data
            htp_chomp(connp->in_line, &connp->in_line_len);
            connp->in_tx->request_line = bstr_dup_ex(connp->in_tx->request_line_raw, 0, connp->in_line_len);
            if (connp->in_tx->request_line == NULL) {
                return HTP_ERROR;
            }

            // Parse request line
            if (connp->cfg->parse_request_line(connp) != HTP_OK) {
                // Note: downstream responsible for error logging
                return HTP_ERROR;
            }

            if (connp->in_tx->request_method_number == M_CONNECT) {
                // Parse authority
                if (htp_parse_authority(connp, connp->in_tx->request_uri, &(connp->in_tx->parsed_uri_incomplete)) != HTP_OK) {
                    // Note: downstream responsible for error logging
                    return HTP_ERROR;
                }
            } else {
                // Parse the request URI
                if (htp_parse_uri(connp->in_tx->request_uri, &(connp->in_tx->parsed_uri_incomplete)) != HTP_OK) {
                    // Note: downstream responsible for error logging
                    return HTP_ERROR;
                }

                // Keep the original URI components, but
                // create a copy which we can normalize and use internally
                if (htp_normalize_parsed_uri(connp, connp->in_tx->parsed_uri_incomplete, connp->in_tx->parsed_uri)) {
                    // Note: downstream responsible for error logging
                    return HTP_ERROR;
                }

                // Run hook REQUEST_URI_NORMALIZE
                int rc = hook_run_all(connp->cfg->hook_request_uri_normalize, connp);
                if (rc != HOOK_OK) {
                    switch (rc) {
                        case HOOK_STOP:
                            return HTP_STOP;
                        case HOOK_ERROR:
                        case HOOK_DECLINED:
                        default:
                            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                                "Request headers callback returned error (%d)", rc);
                            return HTP_ERROR;
                    }
                }

                // Now is a good time to generate request_uri_normalized, before we finalize
                // parsed_uri (and lose the information which parts were provided in the request and
                // which parts we added).
                if (connp->cfg->generate_request_uri_normalized) {
                    connp->in_tx->request_uri_normalized = htp_unparse_uri_noencode(connp->in_tx->parsed_uri);

                    if (connp->in_tx->request_uri_normalized == NULL) {
                        // There's no sense in logging anything on a memory allocation failure
                        return HTP_ERROR;
                    }

                    #ifdef HTP_DEBUG
                    fprint_raw_data(stderr, "request_uri_normalized",
                        (unsigned char *) bstr_ptr(connp->in_tx->request_uri_normalized),
                        bstr_len(connp->in_tx->request_uri_normalized));
                    #endif
                }

                // Finalize parsed_uri

                // Scheme
                if (connp->in_tx->parsed_uri->scheme != NULL) {
                    if (bstr_cmp_c(connp->in_tx->parsed_uri->scheme, "http") != 0) {
                        // TODO Invalid scheme
                    }
                } else {
                    connp->in_tx->parsed_uri->scheme = bstr_dup_c("http");
                    if (connp->in_tx->parsed_uri->scheme == NULL) {
                        return HTP_ERROR;
                    }
                }

                // Port
                if (connp->in_tx->parsed_uri->port != NULL) {
                    if (connp->in_tx->parsed_uri->port_number != -1) {
                        // Check that the port in the URI is the same
                        // as the port on which the client is talking
                        // to the server
                        if (connp->conn->use_local_port) {
                            if (connp->in_tx->parsed_uri->port_number != connp->conn->local_port) {
                                // Incorrect port; use the real port instead
                                connp->in_tx->parsed_uri->port_number = connp->conn->local_port;
                                // TODO Log
                            }
                        } else {
                            connp->in_tx->parsed_uri->port_number = connp->conn->remote_port;
                        }
                    } else {
                        // Invalid port; use the real port instead
                        if (connp->conn->use_local_port) {
                            connp->in_tx->parsed_uri->port_number = connp->conn->local_port;
                        } else {
                            connp->in_tx->parsed_uri->port_number = connp->conn->remote_port;
                        }
                        // TODO Log
                    }
                } else {
                    if (connp->conn->use_local_port) {
                        connp->in_tx->parsed_uri->port_number = connp->conn->local_port;
                    } else {
                        connp->in_tx->parsed_uri->port_number = connp->conn->remote_port;
                    }
                }

                // Path
                if (connp->in_tx->parsed_uri->path == NULL) {
                    connp->in_tx->parsed_uri->path = bstr_dup_c("/");
                    if (connp->in_tx->parsed_uri->path == NULL) {
                        return HTP_ERROR;
                    }
                }
            }

            // Run hook REQUEST_LINE
            int rc = hook_run_all(connp->cfg->hook_request_line, connp);
            if (rc != HOOK_OK) {
                switch (rc) {
                    case HOOK_STOP:
                        return HTP_STOP;
                    case HOOK_ERROR:
                    case HOOK_DECLINED:
                    default:
                        htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
                            "Request headers callback returned error (%d)", rc);
                        return HTP_ERROR;
                }
            }

            // Clean up.
            connp->in_line_len = 0;

            // Move on to the next phase.
            connp->in_state = htp_connp_REQ_PROTOCOL;

            return HTP_OK;
        }
    }
}
Beispiel #30
0
htp_status_t bstr_builder_append_mem(bstr_builder_t *bb, const void *data, size_t len) {
    bstr *b = bstr_dup_mem(data, len);
    if (b == NULL) return HTP_ERROR;
    return htp_list_push(bb->pieces, b);
}