From d93ce9ceee0498ffff4223a13e88756048e7ef99 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Thu, 27 Feb 2014 10:39:07 -0800 Subject: [PATCH] Adds REQUEST_FULL and REQUEST_FULL_LENGTH variables This variable is a combination from REQUEST_LINE, REQUEST_HEADERS and REQUEST_BODY (if any). Expects for \n\n in between each of those values. --- apache2/mod_security2.c | 2 + apache2/modsecurity.c | 6 ++ apache2/modsecurity.h | 3 + apache2/msc_util.c | 66 ++++++++++++++++ apache2/msc_util.h | 2 + apache2/re_variables.c | 108 ++++++++++++++++++++++++++- tests/regression/target/00-targets.t | 104 ++++++++++++++++++++++++++ 7 files changed, 289 insertions(+), 2 deletions(-) diff --git a/apache2/mod_security2.c b/apache2/mod_security2.c index 892242a6..8ff9c8bd 100644 --- a/apache2/mod_security2.c +++ b/apache2/mod_security2.c @@ -519,6 +519,8 @@ static modsec_rec *create_tx_context(request_rec *r) { msr->request_headers = apr_table_copy(msr->mp, r->headers_in); msr->hostname = ap_get_server_name(r); + msr->msc_full_request_buffer = NULL; + msr->msc_full_request_length = 0; msr->msc_rule_mptmp = NULL; /* Invoke the engine to continue with initialisation */ diff --git a/apache2/modsecurity.c b/apache2/modsecurity.c index b36775d2..b9416790 100644 --- a/apache2/modsecurity.c +++ b/apache2/modsecurity.c @@ -261,6 +261,12 @@ static apr_status_t modsecurity_tx_cleanup(void *data) { msr_log(msr, 1, "%s", my_error_msg); } + if (msr->msc_full_request_length > 0 && + msr->msc_full_request_buffer != NULL) { + msr->msc_full_request_length = 0; + free(msr->msc_full_request_buffer); + } + #if defined(WITH_LUA) #ifdef CACHE_LUA if(msr->L != NULL) lua_close(msr->L); diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index 72cdea16..24d3d21b 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -355,6 +355,9 @@ struct modsec_rec { apr_size_t msc_reqbody_no_files_length; + char *msc_full_request_buffer; + int msc_full_request_length; + char *multipart_filename; char *multipart_name; multipart_data *mpd; /* MULTIPART processor data structure */ diff --git a/apache2/msc_util.c b/apache2/msc_util.c index 3d420860..8b3fc43c 100644 --- a/apache2/msc_util.c +++ b/apache2/msc_util.c @@ -2386,3 +2386,69 @@ char *construct_single_var(modsec_rec *msr, char *name) { return (char *)vx->value; } + + +/** + * @brief Transforms an apr_array_header_t to a text buffer + * + * Converts an apr_array_header_t into a plain/text buffer in a Key: Pair + * format. The generated buffer is not null terminated. + * + * If called with `buffer_length` set to 0 or with `buffer` set to NULL, + * it will _not_ fill any buffer, instead, it will return the length, that + * will be needed to save the entire content of `arr` into a buffer. + * + * @warning return is not NULL-terminated. + * @note memory management is in the responsibility of the caller. + * + * @param arr apr_array_header_t to be iterated. + * @param buffer pointer to the destination buffer. + * @param buffer_length length that will fully fill the buffer. + * @retval -1 Something went wrong in the process. Do not trust in + * buffer content. + * @retval n>0 size of the [needed|] buffer. + * + */ +int msc_headers_to_buffer(const apr_array_header_t *arr, char *buffer, + int buffer_length) +{ + int headers_length = 0; + int write_to_buffer = 0; + int i = 0; + const apr_table_entry_t *te = NULL; + + if (buffer != NULL && buffer_length > 0) { + write_to_buffer = 1; + } + + te = (apr_table_entry_t *)arr->elts; + for (i = 0; i < arr->nelts; i++) { + char *value = te[i].val; + char *key = te[i].key; + headers_length = headers_length + strlen(value) + strlen(key) + /* \n: */ 1 + + /* colum */ 1 + /* space: */ 1 ; + + if (write_to_buffer == 1) { + if (buffer_length < headers_length) { + headers_length = -1; + goto not_enough_memory; + } + + sprintf(buffer, "%s%s: %s\n", buffer, key, value); + } + } + + headers_length++; /* Save space for an extra '\n' between the hedaers and the request body */ + if (write_to_buffer) { + if (buffer_length < headers_length) { + headers_length = -1; + goto not_enough_memory; + } + + buffer[headers_length-1] = '\n'; + } + +not_enough_memory: + return headers_length; +} + diff --git a/apache2/msc_util.h b/apache2/msc_util.h index 1dd84a2e..9ce41f6e 100644 --- a/apache2/msc_util.h +++ b/apache2/msc_util.h @@ -149,4 +149,6 @@ char DSOLOCAL *format_all_performance_variables(modsec_rec *msr, apr_pool_t *mp) unsigned char DSOLOCAL is_netmask_v4(char *ip_strv4); unsigned char DSOLOCAL is_netmask_v6(char *ip_strv6); + +int DSOLOCAL msc_headers_to_buffer(const apr_array_header_t *arr, char *buffer, int max_length); #endif diff --git a/apache2/re_variables.c b/apache2/re_variables.c index ce35bc1b..d67b7f6b 100644 --- a/apache2/re_variables.c +++ b/apache2/re_variables.c @@ -1934,6 +1934,87 @@ static int var_request_basename_generate(modsec_rec *msr, msre_var *var, msre_ru return var_simple_generate(var, vartab, mptmp, value); } +/* FULL_REQUEST */ + +static int var_full_request_generate(modsec_rec *msr, msre_var *var, + msre_rule *rule, apr_table_t *vartab, apr_pool_t *mptmp) +{ + const apr_array_header_t *arr = NULL; + char *full_request = NULL; + int full_request_length = 0; + int headers_length = 0; + int request_line_length = 0; + + arr = apr_table_elts(msr->request_headers); + headers_length = msc_headers_to_buffer(arr, NULL, 0); + if (headers_length < 0) { + msr_log(msr, 9, "Variable FULL_REQUEST failed. Problems to measure " \ + "headers length."); + goto failed_measure_buffer; + } + + request_line_length = strlen(msr->request_line) + /* \n\n: */ 2; + full_request_length = request_line_length + headers_length + + msr->msc_reqbody_length + /* \0: */1; + + full_request = malloc(sizeof(char)*full_request_length); + if (full_request == NULL) { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 8, "Variable FULL_REQUEST will not be created, not " \ + "enough memory available."); + } + goto failed_not_enough_mem; + } + memset(full_request, '\0', sizeof(char)*msr->msc_full_request_length); + msr->msc_full_request_buffer = full_request; + msr->msc_full_request_length = full_request_length; + + apr_snprintf(full_request, request_line_length + 1, + /* +1 here because sprintf will place \0 in the end of the string.*/ + "%s\n\n", msr->request_line); + + headers_length = msc_headers_to_buffer(arr, full_request + + request_line_length, headers_length); + if (headers_length < 0) { + msr_log(msr, 9, "Variable FULL_REQUEST will not be created, failed " \ + "to fill headers buffer."); + goto failed_fill_buffer; + } + + if (msr->msc_reqbody_length > 0 && msr->msc_reqbody_buffer != NULL) { + memcpy(full_request + (headers_length + request_line_length), + msr->msc_reqbody_buffer, msr->msc_reqbody_length); + } + full_request[msr->msc_full_request_length-1] = '\0'; + + return var_simple_generate_ex(var, vartab, mptmp, full_request, + msr->msc_full_request_length); + +failed_fill_buffer: +failed_not_enough_mem: +failed_measure_buffer: +no_buffer: + return 0; +} + +/* FULL_REQUEST_LENGTH */ + +static int var_full_request_length_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + const apr_array_header_t *arr = NULL; + char *value = NULL; + int headers_length = 0; + + arr = apr_table_elts(msr->request_headers); + headers_length = msc_headers_to_buffer(arr, NULL, 0); + msr->msc_full_request_length = headers_length + msr->msc_reqbody_length + /* \0: */1; + + value = apr_psprintf(mptmp, "%d", msr->msc_full_request_length); + return var_simple_generate(var, vartab, mptmp, value); +} + + /* REQUEST_BODY */ static int var_request_body_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, @@ -3204,12 +3285,35 @@ void msre_engine_register_default_variables(msre_engine *engine) { PHASE_REQUEST_HEADERS ); - /* REQUEST_BODY */ + /* FULL_REQUEST */ msre_engine_variable_register(engine, - "REQUEST_BODY", + "FULL_REQUEST", VAR_SIMPLE, 0, 0, NULL, + var_full_request_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + /* FULL_REQUEST_LENGTH */ + msre_engine_variable_register(engine, + "FULL_REQUEST_LENGTH", + VAR_SIMPLE, + 0, 0, + NULL, + var_full_request_length_generate, + VAR_CACHE, + PHASE_REQUEST_BODY + ); + + + /* REQUEST_BODY */ + msre_engine_variable_register(engine, + "REQUEST_BODY", + VAR_LIST, + 0, 1, + var_generic_list_validate, var_request_body_generate, VAR_CACHE, PHASE_REQUEST_BODY diff --git a/tests/regression/target/00-targets.t b/tests/regression/target/00-targets.t index fac3a3b3..88bd9f18 100644 --- a/tests/regression/target/00-targets.t +++ b/tests/regression/target/00-targets.t @@ -373,6 +373,110 @@ "arg1=val1&arg2=val2", ), }, +# FULL_REQUEST +{ + type => "target", + comment => "FULL_REQUEST (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule FULL_REQUEST "arg1" "phase:4,log,pass,id:500211" + SecRule FULL_REQUEST "arg2" "phase:4,log,pass,id:500212" + ), + match_log => { + error => [ qr/Pattern match "arg1" at FULL_REQUEST.*Pattern match "arg2" at FULL_REQUEST/s, 1 ], + debug => [ qr/against FULL_REQUEST.*Target value: "GET \/test.txt\?arg1=val1\&arg2=val2 HTTP\/1.1\\n\\nTE: deflate,gzip;q=0.3\\nConnection: TE, close\\nHost: localhost:8088\\nUser-Agent: ModSecurity Regression Tests\/1.2.3\\n\\n\\x00"/s, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2", + ), +}, +{ + type => "target", + comment => "FULL_REQUEST (post)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule FULL_REQUEST "arg1" "phase:4,log,pass,id:500213" + SecRule FULL_REQUEST "arg2" "phase:4,log,pass,id:500214" + ), + match_log => { + error => [ qr/Pattern match "arg1" at FULL_REQUEST.*Pattern match "arg2" at FULL_REQUEST/s, 1 ], + debug => [ qr/against FULL_REQUEST.*Target value: "POST \/test.txt HTTP\/1.1\\n\\nTE: deflate,gzip;q=0.3\\nConnection: TE, close\\nHost: localhost:8088\\nUser-Agent: ModSecurity Regression Tests\/1.2.3\\nContent-Type: application\/x-www-form-urlencoded\\nContent-Length: 19\\n\\narg1=val1&arg2=val2\\x00"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, +# FULL_REQUEST_LENGTH +{ + type => "target", + comment => "FULL_REQUEST_LENGTH (get)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule FULL_REQUEST_LENGTH "\@eq 1" "phase:4,log,pass,id:500211" + SecRule FULL_REQUEST_LENGTH "\@eq 115" "phase:4,log,pass,id:500212" + ), + match_log => { + error => [ qr/Operator EQ matched 115 at FULL_REQUEST_LENGTH./s, 1 ], + debug => [ qr/against FULL_REQUEST_LENGTH.*Target value: "115"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2", + ), +}, +{ + type => "target", + comment => "FULL_REQUEST_LENGTH (post)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecResponseBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule FULL_REQUEST_LENGTH "\@eq 1" "phase:4,log,pass,id:500213" + SecRule FULL_REQUEST_LENGTH "\@eq 201" "phase:4,log,pass,id:500214" + ), + match_log => { + error => [ qr/Operator EQ matched 201 at FULL_REQUEST_LENGTH./s, 1 ], + debug => [ qr/against FULL_REQUEST_LENGTH.*Target value: "201"/s, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + "arg1=val1&arg2=val2", + ), +}, + + # AUTH_TYPE #{