htp_status_t htp_table_add(htp_table_t *table, const bstr *key, const void *element) { if ((table == NULL)||(key == NULL)) return HTP_ERROR; // Keep track of how keys are allocated, and // ensure that all invocations are consistent. if (table->alloc_type == HTP_TABLE_KEYS_ALLOC_UKNOWN) { table->alloc_type = HTP_TABLE_KEYS_COPIED; } else { if (table->alloc_type != HTP_TABLE_KEYS_COPIED) { #ifdef HTP_DEBUG fprintf(stderr, "# Inconsistent key management strategy. Actual %d. Attempted %d.\n", table->alloc_type, HTP_TABLE_KEYS_COPIED); #endif return HTP_ERROR; } } bstr *dupkey = bstr_dup(key); if (dupkey == NULL) return HTP_ERROR; if (_htp_table_add(table, dupkey, element) != HTP_OK) { free(dupkey); return HTP_ERROR; } return HTP_OK; }
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); }
TEST(BstrTest, DupStr) { bstr *p1; bstr *p2; p1 = bstr_dup_c("s0123456789abcdefghijklmnopqrstuvwxyz"); p2 = bstr_dup(p1); EXPECT_EQ(bstr_len(p1), bstr_len(p2)); EXPECT_EQ(0, memcmp(bstr_ptr(p1), bstr_ptr(p2), bstr_len(p1))); bstr_free(p1); bstr_free(p2); }
/** * \brief Generates the normalized uri. * * Libhtp doesn't recreate the whole normalized uri and save it. * That duty has now been passed to us. A lot of this code has been * copied from libhtp. * * Keep an eye out on the tx->parsed_uri struct and how the parameters * in it are generated, just in case some modifications are made to * them in the future. * * \param uri_include_all boolean to indicate if scheme, username/password, hostname and port should be part of the buffer */ bstr *SCHTPGenerateNormalizedUri(htp_tx_t *tx, htp_uri_t *uri, int uri_include_all) { if (uri == NULL) return NULL; // On the first pass determine the length of the final string size_t len = 0; if (uri_include_all) { if (uri->scheme != NULL) { len += bstr_len(uri->scheme); len += 3; // "://" } if ((uri->username != NULL) || (uri->password != NULL)) { if (uri->username != NULL) { len += bstr_len(uri->username); } len += 1; // ":" if (uri->password != NULL) { len += bstr_len(uri->password); } len += 1; // "@" } if (uri->hostname != NULL) { len += bstr_len(uri->hostname); } if (uri->port != NULL) { len += 1; // ":" len += bstr_len(uri->port); } } if (uri->path != NULL) { len += bstr_len(uri->path); } if (uri->query != NULL) { len += 1; // "?" len += bstr_len(uri->query); } if (uri->fragment != NULL) { len += 1; // "#" len += bstr_len(uri->fragment); } // On the second pass construct the string /* FIXME in memcap */ bstr *r = bstr_alloc(len); if (r == NULL) { return NULL; } if (uri_include_all) { if (uri->scheme != NULL) { bstr_add_noex(r, uri->scheme); bstr_add_c_noex(r, "://"); } if ((uri->username != NULL) || (uri->password != NULL)) { if (uri->username != NULL) { bstr_add_noex(r, uri->username); } bstr_add_c_noex(r, ":"); if (uri->password != NULL) { bstr_add_noex(r, uri->password); } bstr_add_c_noex(r, "@"); } if (uri->hostname != NULL) { bstr_add_noex(r, uri->hostname); } if (uri->port != NULL) { bstr_add_c_noex(r, ":"); bstr_add_noex(r, uri->port); } } if (uri->path != NULL) { bstr_add_noex(r, uri->path); } if (uri->query != NULL) { bstr *query = bstr_dup(uri->query); if (query) { uint64_t flags = 0; htp_urldecode_inplace(tx->cfg, HTP_DECODER_URLENCODED, query, &flags); bstr_add_c_noex(r, "?"); bstr_add_noex(r, query); bstr_free(query); } } if (uri->fragment != NULL) { bstr_add_c_noex(r, "#"); bstr_add_noex(r, uri->fragment); } return r; }
/** * 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; }
static htp_status_t htp_tx_process_request_headers(htp_tx_t *tx) { if (tx == NULL) return HTP_ERROR; // Determine if we have a request body, and how it is packaged. htp_status_t rc = HTP_OK; htp_header_t *cl = htp_table_get_c(tx->request_headers, "content-length"); htp_header_t *te = htp_table_get_c(tx->request_headers, "transfer-encoding"); // Check for the Transfer-Encoding header, which would indicate a chunked request body. if (te != NULL) { // Make sure it contains "chunked" only. // TODO The HTTP/1.1 RFC also allows the T-E header to contain "identity", which // presumably should have the same effect as T-E header absence. However, Apache // (2.2.22 on Ubuntu 12.04 LTS) instead errors out with "Unknown Transfer-Encoding: identity". // And it behaves strangely, too, sending a 501 and proceeding to process the request // (e.g., PHP is run), but without the body. It then closes the connection. if (bstr_cmp_c(te->value, "chunked") != 0) { // Invalid T-E header value. tx->request_transfer_coding = HTP_CODING_INVALID; tx->flags |= HTP_REQUEST_INVALID_T_E; tx->flags |= HTP_REQUEST_INVALID; } else { // Chunked encoding is a HTTP/1.1 feature, so check that an earlier protocol // version is not used. The flag will also be set if the protocol could not be parsed. // // TODO IIS 7.0, for example, would ignore the T-E header when it // it is used with a protocol below HTTP 1.1. This should be a // personality trait. if (tx->request_protocol_number < HTP_PROTOCOL_1_1) { tx->flags |= HTP_REQUEST_INVALID_T_E; tx->flags |= HTP_REQUEST_SMUGGLING; } // If the T-E header is present we are going to use it. tx->request_transfer_coding = HTP_CODING_CHUNKED; // We are still going to check for the presence of C-L. if (cl != NULL) { // According to the HTTP/1.1 RFC (section 4.4): // // "The Content-Length header field MUST NOT be sent // if these two lengths are different (i.e., if a Transfer-Encoding // header field is present). If a message is received with both a // Transfer-Encoding header field and a Content-Length header field, // the latter MUST be ignored." // tx->flags |= HTP_REQUEST_SMUGGLING; } } } else if (cl != NULL) { // Check for a folded C-L header. if (cl->flags & HTP_FIELD_FOLDED) { tx->flags |= HTP_REQUEST_SMUGGLING; } // Check for multiple C-L headers. if (cl->flags & HTP_FIELD_REPEATED) { tx->flags |= HTP_REQUEST_SMUGGLING; // TODO Personality trait to determine which C-L header to parse. // At the moment we're parsing the combination of all instances, // which is bound to fail (because it will contain commas). } // Get the body length. tx->request_content_length = htp_parse_content_length(cl->value); if (tx->request_content_length < 0) { tx->request_transfer_coding = HTP_CODING_INVALID; tx->flags |= HTP_REQUEST_INVALID_C_L; tx->flags |= HTP_REQUEST_INVALID; } else { // We have a request body of known length. tx->request_transfer_coding = HTP_CODING_IDENTITY; } } else { // No body. tx->request_transfer_coding = HTP_CODING_NO_BODY; } // If we could not determine the correct body handling, // consider the request invalid. if (tx->request_transfer_coding == HTP_CODING_UNKNOWN) { tx->request_transfer_coding = HTP_CODING_INVALID; tx->flags |= HTP_REQUEST_INVALID; } // Check for PUT requests, which we need to treat as file uploads. if (tx->request_method_number == HTP_M_PUT) { if (htp_tx_req_has_body(tx)) { // Prepare to treat PUT request body as a file. tx->connp->put_file = calloc(1, sizeof (htp_file_t)); if (tx->connp->put_file == NULL) return HTP_ERROR; tx->connp->put_file->source = HTP_FILE_PUT; } else { // TODO Warn about PUT request without a body. } return HTP_OK; } // Determine hostname. // Use the hostname from the URI, when available. if (tx->parsed_uri->hostname != NULL) { tx->request_hostname = bstr_dup(tx->parsed_uri->hostname); if (tx->request_hostname == NULL) return HTP_ERROR; } tx->request_port_number = tx->parsed_uri->port_number; // Examine the Host header. htp_header_t *h = htp_table_get_c(tx->request_headers, "host"); if (h == NULL) { // No host information in the headers. // HTTP/1.1 requires host information in the headers. if (tx->request_protocol_number >= HTP_PROTOCOL_1_1) { tx->flags |= HTP_HOST_MISSING; } } else { // Host information available in the headers. bstr *hostname; int port; rc = htp_parse_header_hostport(h->value, &hostname, &port, &(tx->flags)); if (rc != HTP_OK) return rc; // Is there host information in the URI? if (tx->request_hostname == NULL) { // There is no host information in the URI. Place the // hostname from the headers into the parsed_uri structure. tx->request_hostname = hostname; tx->request_port_number = port; } else { // The host information appears in the URI and in the headers. It's // OK if both have the same thing, but we want to check for differences. if ((bstr_cmp_nocase(hostname, tx->request_hostname) != 0) || (port != tx->request_port_number)) { // The host information is different in the headers and the URI. The // HTTP RFC states that we should ignore the header copy. tx->flags |= HTP_HOST_AMBIGUOUS; } bstr_free(hostname); } } // Determine Content-Type. htp_header_t *ct = htp_table_get_c(tx->request_headers, "content-type"); if (ct != NULL) { rc = htp_parse_ct_header(ct->value, &tx->request_content_type); if (rc != HTP_OK) return rc; } // Parse cookies. if (tx->connp->cfg->parse_request_cookies) { rc = htp_parse_cookies_v0(tx->connp); if (rc != HTP_OK) return rc; } // Parse authentication information. if (tx->connp->cfg->parse_request_auth) { rc = htp_parse_authorization(tx->connp); if (rc == HTP_DECLINED) { // Don't fail the stream if an authorization header is invalid, just set a flag. tx->flags |= HTP_AUTH_INVALID; } else { if (rc != HTP_OK) return rc; } } // Finalize sending raw header data. rc = htp_connp_req_receiver_finalize_clear(tx->connp); if (rc != HTP_OK) return rc; // Run hook REQUEST_HEADERS. rc = htp_hook_run_all(tx->connp->cfg->hook_request_headers, tx); if (rc != HTP_OK) return rc; // We cannot proceed if the request is invalid. if (tx->flags & HTP_REQUEST_INVALID) { return HTP_ERROR; } return HTP_OK; }