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.
This commit is contained in:
Felipe Zimmerle 2014-02-27 10:39:07 -08:00
parent 62f3d02894
commit d93ce9ceee
7 changed files with 289 additions and 2 deletions

View File

@ -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->request_headers = apr_table_copy(msr->mp, r->headers_in);
msr->hostname = ap_get_server_name(r); msr->hostname = ap_get_server_name(r);
msr->msc_full_request_buffer = NULL;
msr->msc_full_request_length = 0;
msr->msc_rule_mptmp = NULL; msr->msc_rule_mptmp = NULL;
/* Invoke the engine to continue with initialisation */ /* Invoke the engine to continue with initialisation */

View File

@ -261,6 +261,12 @@ static apr_status_t modsecurity_tx_cleanup(void *data) {
msr_log(msr, 1, "%s", my_error_msg); 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) #if defined(WITH_LUA)
#ifdef CACHE_LUA #ifdef CACHE_LUA
if(msr->L != NULL) lua_close(msr->L); if(msr->L != NULL) lua_close(msr->L);

View File

@ -355,6 +355,9 @@ struct modsec_rec {
apr_size_t msc_reqbody_no_files_length; apr_size_t msc_reqbody_no_files_length;
char *msc_full_request_buffer;
int msc_full_request_length;
char *multipart_filename; char *multipart_filename;
char *multipart_name; char *multipart_name;
multipart_data *mpd; /* MULTIPART processor data structure */ multipart_data *mpd; /* MULTIPART processor data structure */

View File

@ -2386,3 +2386,69 @@ char *construct_single_var(modsec_rec *msr, char *name) {
return (char *)vx->value; 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;
}

View File

@ -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_v4(char *ip_strv4);
unsigned char DSOLOCAL is_netmask_v6(char *ip_strv6); 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 #endif

View File

@ -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); 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 */ /* REQUEST_BODY */
static int var_request_body_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, 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 PHASE_REQUEST_HEADERS
); );
/* REQUEST_BODY */ /* FULL_REQUEST */
msre_engine_variable_register(engine, msre_engine_variable_register(engine,
"REQUEST_BODY", "FULL_REQUEST",
VAR_SIMPLE, VAR_SIMPLE,
0, 0, 0, 0,
NULL, 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_request_body_generate,
VAR_CACHE, VAR_CACHE,
PHASE_REQUEST_BODY PHASE_REQUEST_BODY

View File

@ -373,6 +373,110 @@
"arg1=val1&arg2=val2", "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 # AUTH_TYPE
#{ #{