diff --git a/apache2/apache2_io.c b/apache2/apache2_io.c index 8deeb01c9a..cf76c25086 100644 --- a/apache2/apache2_io.c +++ b/apache2/apache2_io.c @@ -17,6 +17,7 @@ #include "modsecurity.h" #include "apache2.h" #include "msc_crypt.h" +#include "msc_reqbody.h" #ifdef APLOG_USE_MODULE APLOG_USE_MODULE(security2); @@ -299,38 +300,41 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) { #endif } - msr->reqbody_length += buflen; - - if (buflen != 0) { - int rcbs = modsecurity_request_body_store(msr, buf, buflen, error_msg); + if (msr->reqbody_length + buflen > (apr_size_t)msr->txcfg->reqbody_limit) { + msr->reqbody_length_limit_exceeded = 1; + if (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL) { + buflen = (apr_size_t)msr->txcfg->reqbody_limit - msr->reqbody_length; + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Input filter: Bucket type %s shortened by %" APR_SIZE_T_FMT " bytes because of reqbody_limit and ProcessPartial.", + bucket->type->name, (apr_size_t)msr->txcfg->reqbody_limit - msr->reqbody_length); + } - if (msr->reqbody_length > (apr_size_t)msr->txcfg->reqbody_limit && msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL) { finished_reading = 1; + modsecurity_request_body_do_enable_partial_processing(msr); } + } + if (msr->reqbody_no_files_length_limit_exceeded) { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Input filter: Bucket type %s skip storing because of no_files_limit and ProcessPartial.", + bucket->type->name); + } + buflen = 0; + finished_reading = 1; + } + msr->reqbody_length += buflen; + + if (buflen != 0) { + int rcbs = modsecurity_request_body_store(msr, buf, buflen, error_msg); if (rcbs < 0) { if (rcbs == -5) { - if((msr->txcfg->is_enabled == MODSEC_ENABLED) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT)) { - *error_msg = apr_psprintf(msr->mp, "Request body no files data length is larger than the " - "configured limit (%ld).", msr->txcfg->reqbody_no_files_limit); - return HTTP_REQUEST_ENTITY_TOO_LARGE; - } else if ((msr->txcfg->is_enabled == MODSEC_ENABLED) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL)) { - *error_msg = apr_psprintf(msr->mp, "Request body no files data length is larger than the " - "configured limit (%ld).", msr->txcfg->reqbody_no_files_limit); - } else if ((msr->txcfg->is_enabled == MODSEC_DETECTION_ONLY) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL)) { - *error_msg = apr_psprintf(msr->mp, "Request body no files data length is larger than the " - "configured limit (%ld).", msr->txcfg->reqbody_no_files_limit); - } else { - *error_msg = apr_psprintf(msr->mp, "Request body no files data length is larger than the " - "configured limit (%ld).", msr->txcfg->reqbody_no_files_limit); - return HTTP_REQUEST_ENTITY_TOO_LARGE; - } + return HTTP_REQUEST_ENTITY_TOO_LARGE; } - if((msr->txcfg->is_enabled == MODSEC_ENABLED) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT)) + if ((msr->txcfg->is_enabled == MODSEC_ENABLED) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT)) { return HTTP_INTERNAL_SERVER_ERROR; + } } - } if (APR_BUCKET_IS_EOS(bucket)) { @@ -351,11 +355,14 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) { msr->if_status = IF_STATUS_WANTS_TO_RUN; - if (rcbe == -5) { - return HTTP_REQUEST_ENTITY_TOO_LARGE; - } if (rcbe < 0) { - return HTTP_INTERNAL_SERVER_ERROR; + if (rcbe == -5) { + return HTTP_REQUEST_ENTITY_TOO_LARGE; + } + + if ((msr->txcfg->is_enabled == MODSEC_ENABLED) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT)) { + return HTTP_INTERNAL_SERVER_ERROR; + } } return APR_SUCCESS; } diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index 2894717031..e291bb4243 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -279,6 +279,9 @@ struct modsec_rec { unsigned int if_started_forwarding; apr_size_t reqbody_length; + unsigned int reqbody_partial_processing_enabled; + unsigned int reqbody_length_limit_exceeded; + unsigned int reqbody_no_files_length_limit_exceeded; apr_bucket_brigade *of_brigade; unsigned int of_status; diff --git a/apache2/msc_json.c b/apache2/msc_json.c index cae7bf4f70..234fea2ebf 100644 --- a/apache2/msc_json.c +++ b/apache2/msc_json.c @@ -187,7 +187,7 @@ static int yajl_start_array(void *ctx) { msr->json->current_depth++; if (msr->json->current_depth > msr->txcfg->reqbody_json_depth_limit) { msr->json->depth_limit_exceeded = 1; - return 0; + return 0; } if (msr->txcfg->debuglog_level >= 9) { @@ -262,7 +262,7 @@ static int yajl_start_map(void *ctx) msr->json->current_depth++; if (msr->json->current_depth > msr->txcfg->reqbody_json_depth_limit) { msr->json->depth_limit_exceeded = 1; - return 0; + return 0; } if (msr->txcfg->debuglog_level >= 9) { @@ -367,6 +367,10 @@ int json_init(modsec_rec *msr, char **error_msg) { return 1; } +void json_allow_partial_values(modsec_rec *msr) { + (void)yajl_config(msr->json->handle, yajl_allow_partial_values, 1); +} + /** * Feed one chunk of data to the JSON parser. */ @@ -380,16 +384,16 @@ int json_process_chunk(modsec_rec *msr, const char *buf, unsigned int size, char /* Feed our parser and catch any errors */ msr->json->status = yajl_parse(msr->json->handle, buf, size); if (msr->json->status != yajl_status_ok) { - if (msr->json->depth_limit_exceeded) { - *error_msg = "JSON depth limit exceeded"; - } else { - if (msr->json->yajl_error) *error_msg = msr->json->yajl_error; - else { - char* yajl_err = yajl_get_error(msr->json->handle, 0, buf, size); - *error_msg = apr_pstrdup(msr->mp, yajl_err); - yajl_free_error(msr->json->handle, yajl_err); + if (msr->json->depth_limit_exceeded) { + *error_msg = "JSON depth limit exceeded"; + } else { + if (msr->json->yajl_error) *error_msg = msr->json->yajl_error; + else { + char* yajl_err = yajl_get_error(msr->json->handle, 0, buf, size); + *error_msg = apr_pstrdup(msr->mp, yajl_err); + yajl_free_error(msr->json->handle, yajl_err); + } } - } return -1; } @@ -409,13 +413,13 @@ int json_complete(modsec_rec *msr, char **error_msg) { /* Wrap up the parsing process */ msr->json->status = yajl_complete_parse(msr->json->handle); if (msr->json->status != yajl_status_ok) { - if (msr->json->depth_limit_exceeded) { - *error_msg = "JSON depth limit exceeded"; - } else { - char *yajl_err = yajl_get_error(msr->json->handle, 0, NULL, 0); - *error_msg = apr_pstrdup(msr->mp, yajl_err); - yajl_free_error(msr->json->handle, yajl_err); - } + if (msr->json->depth_limit_exceeded) { + *error_msg = "JSON depth limit exceeded"; + } else { + char *yajl_err = yajl_get_error(msr->json->handle, 0, NULL, 0); + *error_msg = apr_pstrdup(msr->mp, yajl_err); + yajl_free_error(msr->json->handle, yajl_err); + } return -1; } diff --git a/apache2/msc_json.h b/apache2/msc_json.h index 089dab4763..c5fbc80df8 100644 --- a/apache2/msc_json.h +++ b/apache2/msc_json.h @@ -48,6 +48,8 @@ struct json_data { int DSOLOCAL json_init(modsec_rec *msr, char **error_msg); +void DSOLOCAL json_allow_partial_values(modsec_rec *msr); + int DSOLOCAL json_process(modsec_rec *msr, const char *buf, unsigned int size, char **error_msg); diff --git a/apache2/msc_multipart.c b/apache2/msc_multipart.c index dc24248de7..14fe1e0505 100644 --- a/apache2/msc_multipart.c +++ b/apache2/msc_multipart.c @@ -19,6 +19,9 @@ #include "msc_multipart.h" #include "msc_util.h" #include "msc_parsers.h" +#include "msc_reqbody.h" + +#define CONTENT_TYPE_MAX_LENGTH 1024 void validate_quotes(modsec_rec *msr, char *data, char quote) { assert(msr != NULL); @@ -271,7 +274,8 @@ static int multipart_process_part_header(modsec_rec *msr, char **error_msg) { } /* The buffer is data so increase the data length counter. */ - msr->msc_reqbody_no_files_length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft); + len = modsecurity_request_body_may_enable_partial_processing_for_no_files_length(msr, len, "MULTIPART"); + msr->msc_reqbody_no_files_length += len; if (len > 1) { if (msr->mpd->buf[len - 2] == '\r') { @@ -421,7 +425,7 @@ static int multipart_process_part_header(modsec_rec *msr, char **error_msg) { if (data == msr->mpd->buf) { *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid part header (header name missing)."); - return -1; + return -1; } /* check if multipart header contains any invalid characters */ @@ -469,7 +473,7 @@ static int multipart_process_part_data(modsec_rec *msr, char **error_msg) { assert(error_msg != NULL); char *p = msr->mpd->buf + (MULTIPART_BUF_SIZE - msr->mpd->bufleft); char localreserve[2] = { '\0', '\0' }; /* initialized to quiet warning */ - int bytes_reserved = 0; + int bytes_reserved = 0, len; *error_msg = NULL; @@ -585,7 +589,9 @@ static int multipart_process_part_data(modsec_rec *msr, char **error_msg) { value_part_t *value_part = apr_pcalloc(msr->mp, sizeof(value_part_t)); /* The buffer contains data so increase the data length counter. */ - msr->msc_reqbody_no_files_length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0]; + len = modsecurity_request_body_may_enable_partial_processing_for_no_files_length(msr, + (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0], "MULTIPART"); + msr->msc_reqbody_no_files_length += len; /* add this part to the list of parts */ @@ -595,14 +601,14 @@ static int multipart_process_part_data(modsec_rec *msr, char **error_msg) { } if (msr->mpd->reserve[0] != 0) { - value_part->data = apr_palloc(msr->mp, (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0]); + value_part->data = apr_palloc(msr->mp, len); memcpy(value_part->data, &(msr->mpd->reserve[1]), msr->mpd->reserve[0]); - memcpy(value_part->data + msr->mpd->reserve[0], msr->mpd->buf, (MULTIPART_BUF_SIZE - msr->mpd->bufleft)); + memcpy(value_part->data + msr->mpd->reserve[0], msr->mpd->buf, len - msr->mpd->reserve[0]); - value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0]; + value_part->length = len; msr->mpd->mpp->length += value_part->length; } else { - value_part->length = (MULTIPART_BUF_SIZE - msr->mpd->bufleft); + value_part->length = len; value_part->data = apr_pstrmemdup(msr->mp, msr->mpd->buf, value_part->length); msr->mpd->mpp->length += value_part->length; } @@ -822,7 +828,7 @@ int multipart_init(modsec_rec *msr, char **error_msg) { return -1; } - if (strlen(msr->request_content_type) > 1024) { + if (strlen(msr->request_content_type) > CONTENT_TYPE_MAX_LENGTH) { msr->mpd->flag_error = 1; *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary in C-T (length)."); return -1; @@ -1023,38 +1029,94 @@ int multipart_complete(modsec_rec *msr, char **error_msg) { * processed yet) in the buffer. */ if (msr->mpd->buf_contains_line) { - if ( ((unsigned int)(MULTIPART_BUF_SIZE - msr->mpd->bufleft) == (4 + strlen(msr->mpd->boundary))) + /* + * Note that the buffer may end with the final boundary followed by only CR, + * coming from the [CRLF epilogue], when allow_process_partial == 1 (which is + * set when SecRequestBodyLimitAction is ProcessPartial and the request body + * length exceeds SecRequestBodyLimit). + * + * The following definitions are copied from RFC 2046: + * + * dash-boundary := "--" boundary + * + * delimiter := CRLF dash-boundary + * + * close-delimiter := delimiter "--" + * + * multipart-body := [preamble CRLF] + * dash-boundary transport-padding CRLF + * body-part *encapsulation + * close-delimiter transport-padding + * [CRLF epilogue] + */ + unsigned int buf_data_len = (unsigned int)(MULTIPART_BUF_SIZE - msr->mpd->bufleft); + size_t boundary_len = strnlen(msr->mpd->boundary, CONTENT_TYPE_MAX_LENGTH); + if ( (buf_data_len >= 2 + boundary_len) && (*(msr->mpd->buf) == '-') && (*(msr->mpd->buf + 1) == '-') - && (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, strlen(msr->mpd->boundary)) == 0) - && (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary)) == '-') - && (*(msr->mpd->buf + 2 + strlen(msr->mpd->boundary) + 1) == '-') ) + && (strncmp(msr->mpd->buf + 2, msr->mpd->boundary, boundary_len) == 0) ) { - if ((msr->mpd->crlf_state_buf_end == 2) && (msr->mpd->flag_lf_line != 1)) { - msr->mpd->flag_lf_line = 1; - if (msr->mpd->flag_crlf_line) { - msr_log(msr, 4, "Multipart: Warning: mixed line endings used (CRLF/LF)."); - } else { - msr_log(msr, 4, "Multipart: Warning: incorrect line endings used (LF)."); + if ( (buf_data_len >= 2 + boundary_len + 2) + && (*(msr->mpd->buf + 2 + boundary_len) == '-') + && (*(msr->mpd->buf + 2 + boundary_len + 1) == '-') ) + { + /* If body fits in limit and ends with final boundary plus just CR, reject it. */ + if ( (msr->mpd->allow_process_partial == 0) + && (buf_data_len == 2 + boundary_len + 2 + 1) + && (*(msr->mpd->buf + 2 + boundary_len + 2) == '\r') ) + { + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid epilogue after final boundary."); + return -1; } + + if ((msr->mpd->crlf_state_buf_end == 2) && (msr->mpd->flag_lf_line != 1)) { + msr->mpd->flag_lf_line = 1; + if (msr->mpd->flag_crlf_line) { + msr_log(msr, 4, "Multipart: Warning: mixed line endings used (CRLF/LF)."); + } else { + msr_log(msr, 4, "Multipart: Warning: incorrect line endings used (LF)."); + } + } + if (msr->mpd->mpp_substate_part_data_read == 0) { + /* it looks like the final boundary, but it's where part data should begin */ + msr->mpd->flag_invalid_part = 1; + msr_log(msr, 4, "Multipart: Warning: Invalid part (data contains final boundary)"); + } + /* Looks like the final boundary - process it. */ + if (multipart_process_boundary(msr, 1 /* final */, error_msg) < 0) { + msr->mpd->flag_error = 1; + return -1; + } + + /* The payload is complete after all. */ + msr->mpd->is_complete = 1; } - if (msr->mpd->mpp_substate_part_data_read == 0) { - /* it looks like the final boundary, but it's where part data should begin */ - msr->mpd->flag_invalid_part = 1; - msr_log(msr, 4, "Multipart: Warning: Invalid part (data contains final boundary)"); - } - /* Looks like the final boundary - process it. */ - if (multipart_process_boundary(msr, 1 /* final */, error_msg) < 0) { - msr->mpd->flag_error = 1; - return -1; + else if (msr->mpd->allow_process_partial == 1) { + if (buf_data_len >= 2 + boundary_len + 1) { + if (*(msr->mpd->buf + 2 + boundary_len) == '-') { + if ( (buf_data_len >= 2 + boundary_len + 2) + && (*(msr->mpd->buf + 2 + boundary_len + 1) != '-') ) { + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid final boundary."); + return -1; + } + } + else if ( (*(msr->mpd->buf + 2 + boundary_len) != '\r') + || ((buf_data_len >= 2 + boundary_len + 2) + && (*(msr->mpd->buf + 2 + boundary_len + 1) != '\n')) ) { + *error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary."); + return -1; + } + } + /* process it as a non-final boundary to avoid building a new part. */ + if (multipart_process_boundary(msr, 0, error_msg) < 0) { + msr->mpd->flag_error = 1; + return -1; + } } - - /* The payload is complete after all. */ - msr->mpd->is_complete = 1; } } - if (msr->mpd->is_complete == 0) { + if (msr->mpd->is_complete == 0 && msr->mpd->allow_process_partial == 0) { *error_msg = apr_psprintf(msr->mp, "Multipart: Final boundary missing."); return -1; } @@ -1296,10 +1358,10 @@ int multipart_process_chunk(modsec_rec *msr, const char *buf, if (c == 0x0a) { if (msr->mpd->crlf_state == 1) { msr->mpd->crlf_state = 3; - } else { + } else { msr->mpd->crlf_state = 2; - } - } + } + } msr->mpd->crlf_state_buf_end = msr->mpd->crlf_state; } diff --git a/apache2/msc_multipart.h b/apache2/msc_multipart.h index a9c20b9b19..8dfd473812 100644 --- a/apache2/msc_multipart.h +++ b/apache2/msc_multipart.h @@ -124,6 +124,7 @@ struct multipart_data { int seen_data; int is_complete; + int allow_process_partial; int flag_error; int flag_data_before; diff --git a/apache2/msc_parsers.c b/apache2/msc_parsers.c index 793549a5f6..30234c8d89 100644 --- a/apache2/msc_parsers.c +++ b/apache2/msc_parsers.c @@ -313,7 +313,7 @@ int parse_arguments(modsec_rec *msr, const char *s, apr_size_t inputlength, value = &buf[j]; } } - else { + else if (i < inputlength || msr->reqbody_partial_processing_enabled == 0) { arg->value_len = urldecode_nonstrict_inplace_ex((unsigned char *)value, arg->value_origin_len, invalid_count, &changed); arg->value = apr_pstrmemdup(msr->mp, value, arg->value_len); @@ -330,7 +330,7 @@ int parse_arguments(modsec_rec *msr, const char *s, apr_size_t inputlength, } /* the last parameter was empty */ - if (status == 1) { + if (status == 1 && msr->reqbody_partial_processing_enabled == 0) { arg->value_len = 0; arg->value = ""; diff --git a/apache2/msc_reqbody.c b/apache2/msc_reqbody.c index e00a4fc3fb..82b4bc3d79 100644 --- a/apache2/msc_reqbody.c +++ b/apache2/msc_reqbody.c @@ -18,7 +18,6 @@ #define CHUNK_CAPACITY 8192 - /** * */ @@ -312,6 +311,55 @@ static apr_status_t modsecurity_request_body_store_memory(modsec_rec *msr, return 1; } +/** + * Enable partial processing of request body data. + */ +void modsecurity_request_body_do_enable_partial_processing(modsec_rec *msr) { + msr->reqbody_partial_processing_enabled = 1; + if (msr->msc_reqbody_processor == NULL) { + msr_log(msr, 9, "enable_partial_processing for none reqbody_processor"); + } + else if (strcmp(msr->msc_reqbody_processor, "MULTIPART") == 0) { + msr->mpd->allow_process_partial = 1; + msr_log(msr, 4, "Multipart: Allow partial processing of request body"); + } + else if (strcmp(msr->msc_reqbody_processor, "XML") == 0) { + msr->xml->allow_ill_formed = 1; + msr_log(msr, 4, "XML: Allow partial processing of request body"); + } + else if (strcmp(msr->msc_reqbody_processor, "JSON") == 0) { + json_allow_partial_values(msr); + msr_log(msr, 4, "JSON: Allow partial processing of request body"); + } + else if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) { + msr_log(msr, 4, "URLENCODED: Allow partial processing of request body"); + } +} + +/** + * Enable partial processing if no_files_len exceeds limit and action is ProcessPartial. + * Returns maybe adjusted adding_length. + */ +apr_ssize_t modsecurity_request_body_may_enable_partial_processing_for_no_files_length(modsec_rec *msr, + apr_ssize_t adding_length, const char *reqbody_processor) +{ + /* Enable partial processing if no_files_len exceeds limit and action is ProcessPartial */ + apr_ssize_t excess = (apr_ssize_t)msr->msc_reqbody_no_files_length + adding_length + - (apr_ssize_t)msr->txcfg->reqbody_no_files_limit; + if (excess > 0) { + msr->reqbody_no_files_length_limit_exceeded = 1; + if (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL) { + adding_length -= excess; + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "%s: adding length shortened by %" APR_SIZE_T_FMT " bytes because of no_files_len limit.", + reqbody_processor, excess); + } + modsecurity_request_body_do_enable_partial_processing(msr); + } + } + return adding_length; +} + /** * Stores one chunk of request body data. Returns -1 on error. */ @@ -360,6 +408,7 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr, } else if (strcmp(msr->msc_reqbody_processor, "XML") == 0) { /* Increase per-request data length counter. */ + length = modsecurity_request_body_may_enable_partial_processing_for_no_files_length(msr, length, "XML"); msr->msc_reqbody_no_files_length += length; /* Process data as XML. */ @@ -372,6 +421,7 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr, } else if (strcmp(msr->msc_reqbody_processor, "JSON") == 0) { /* Increase per-request data length counter. */ + length = modsecurity_request_body_may_enable_partial_processing_for_no_files_length(msr, length, "JSON"); msr->msc_reqbody_no_files_length += length; /* Process data as JSON. */ @@ -391,6 +441,7 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr, } else if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) { /* Increase per-request data length counter. */ + length = modsecurity_request_body_may_enable_partial_processing_for_no_files_length(msr, length, "URLENCODED"); msr->msc_reqbody_no_files_length += length; /* Do nothing else, URLENCODED processor does not support streaming. */ @@ -402,24 +453,24 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr, } } else if (msr->txcfg->reqbody_buffering != REQUEST_BODY_FORCEBUF_OFF) { /* Increase per-request data length counter if forcing buffering. */ + length = modsecurity_request_body_may_enable_partial_processing_for_no_files_length(msr, length, "forceBuf"); msr->msc_reqbody_no_files_length += length; } /* Check that we are not over the request body no files limit. */ - if (msr->msc_reqbody_no_files_length > (unsigned long) msr->txcfg->reqbody_no_files_limit) { + if (msr->reqbody_no_files_length_limit_exceeded) { *error_msg = apr_psprintf(msr->mp, "Request body no files data length is larger than the " - "configured limit (%ld).", msr->txcfg->reqbody_no_files_limit); + "configured limit (%ld).", msr->txcfg->reqbody_no_files_limit); if (msr->txcfg->debuglog_level >= 1) { msr_log(msr, 1, "%s", *error_msg); } - msr->msc_reqbody_error = 1; + if (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT) { + msr->msc_reqbody_error = 1; + } - if ((msr->txcfg->is_enabled == MODSEC_ENABLED) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT)) { + if ((msr->txcfg->is_enabled == MODSEC_ENABLED) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT)) { return -5; - } else if (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL) { - if(msr->txcfg->is_enabled == MODSEC_ENABLED) - return -5; } } @@ -636,7 +687,9 @@ static apr_status_t modsecurity_request_body_end_urlencoded(modsec_rec *msr, cha /* Parse URL-encoded arguments in the request body. */ - if (parse_arguments(msr, msr->msc_reqbody_buffer, msr->msc_reqbody_length, + unsigned int length = msr->msc_reqbody_length > msr->msc_reqbody_no_files_length + ? msr->msc_reqbody_no_files_length : msr->msc_reqbody_length; + if (parse_arguments(msr, msr->msc_reqbody_buffer, length, msr->txcfg->argument_separator, "BODY", msr->arguments, &invalid_count) < 0) { *error_msg = apr_pstrdup(msr->mp, "Initialisation: Error occurred while parsing BODY arguments."); @@ -671,14 +724,20 @@ apr_status_t modsecurity_request_body_end(modsec_rec *msr, char **error_msg) { /* Check that we are not over the request body no files limit. */ - if (msr->msc_reqbody_no_files_length > (unsigned long)msr->txcfg->reqbody_no_files_limit) { + if (msr->reqbody_no_files_length_limit_exceeded) { *error_msg = apr_psprintf(msr->mp, "Request body no files data length is larger than the " "configured limit (%ld).", msr->txcfg->reqbody_no_files_limit); if (msr->txcfg->debuglog_level >= 1) { msr_log(msr, 1, "%s", *error_msg); } - return -5; + if (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT) { + msr->msc_reqbody_error = 1; + } + + if ((msr->txcfg->is_enabled == MODSEC_ENABLED) && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_REJECT)) { + return -5; + } } diff --git a/apache2/msc_reqbody.h b/apache2/msc_reqbody.h new file mode 100644 index 0000000000..17fba52656 --- /dev/null +++ b/apache2/msc_reqbody.h @@ -0,0 +1,24 @@ +/* +* ModSecurity for Apache 2.x, http://www.modsecurity.org/ +* Copyright (c) 2004-2022 Trustwave Holdings, Inc. (http://www.trustwave.com/) +* +* You may not use this file except in compliance with +* the License.  You may obtain a copy of the License at +* +*     http://www.apache.org/licenses/LICENSE-2.0 +* +* If any of the files related to licensing are missing or if you have any +* other questions related to licensing please contact Trustwave Holdings, Inc. +* directly using the email address security@modsecurity.org. +*/ + +#ifndef _MSC_REQBODY_H_ +#define _MSC_REQBODY_H_ + +#include "modsecurity.h" + +void DSOLOCAL modsecurity_request_body_do_enable_partial_processing(modsec_rec *msr); +apr_ssize_t DSOLOCAL modsecurity_request_body_may_enable_partial_processing_for_no_files_length(modsec_rec *msr, + apr_ssize_t adding_length, const char *reqbody_processor); + +#endif diff --git a/apache2/msc_xml.c b/apache2/msc_xml.c index 4f0e07ca05..abc26ba298 100644 --- a/apache2/msc_xml.c +++ b/apache2/msc_xml.c @@ -267,7 +267,7 @@ int xml_process_chunk(modsec_rec *msr, const char *buf, unsigned int size, char if (msr->xml->parsing_ctx != NULL && msr->txcfg->parse_xml_into_args != MSC_XML_ARGS_ONLYARGS) { xmlParseChunk(msr->xml->parsing_ctx, buf, size, 0); - if (msr->xml->parsing_ctx->wellFormed != 1) { + if (!msr->xml->allow_ill_formed && msr->xml->parsing_ctx->wellFormed != 1) { *error_msg = apr_psprintf(msr->mp, "XML: Failed to parse document."); return -1; } @@ -304,10 +304,12 @@ int xml_complete(modsec_rec *msr, char **error_msg) { /* Only if we have a context, meaning we've done some work. */ if (msr->xml->parsing_ctx != NULL || msr->xml->parsing_ctx_arg != NULL) { + int terminate = !msr->reqbody_partial_processing_enabled; + if (msr->xml->parsing_ctx != NULL && msr->txcfg->parse_xml_into_args != MSC_XML_ARGS_ONLYARGS) { /* This is how we signal the end of parsing to libxml. */ - xmlParseChunk(msr->xml->parsing_ctx, NULL, 0, 1); + xmlParseChunk(msr->xml->parsing_ctx, NULL, 0, terminate); /* Preserve the results for our reference. */ msr->xml->well_formed = msr->xml->parsing_ctx->wellFormed; @@ -318,7 +320,7 @@ int xml_complete(modsec_rec *msr, char **error_msg) { msr->xml->parsing_ctx = NULL; msr_log(msr, 4, "XML: Parsing complete (well_formed %u).", msr->xml->well_formed); - if (msr->xml->well_formed != 1) { + if (!msr->xml->allow_ill_formed && msr->xml->well_formed != 1) { *error_msg = apr_psprintf(msr->mp, "XML: Failed to parse document."); return -1; } @@ -326,7 +328,7 @@ int xml_complete(modsec_rec *msr, char **error_msg) { if (msr->xml->parsing_ctx_arg != NULL && msr->txcfg->parse_xml_into_args != MSC_XML_ARGS_OFF) { - if (xmlParseChunk(msr->xml->parsing_ctx_arg, NULL, 0, 1) != 0) { + if (xmlParseChunk(msr->xml->parsing_ctx_arg, NULL, 0, terminate) != 0) { if (msr->xml->xml_error) { *error_msg = msr->xml->xml_error; } diff --git a/apache2/msc_xml.h b/apache2/msc_xml.h index 73999443a2..b56905dbcd 100644 --- a/apache2/msc_xml.h +++ b/apache2/msc_xml.h @@ -43,6 +43,7 @@ struct xml_data { xmlDocPtr doc; unsigned int well_formed; + unsigned int allow_ill_formed; /* error reporting and XML array flag */ char *xml_error; diff --git a/iis/ModSecurityIIS.vcxproj b/iis/ModSecurityIIS.vcxproj index 7f2822a409..c61e30b7de 100755 --- a/iis/ModSecurityIIS.vcxproj +++ b/iis/ModSecurityIIS.vcxproj @@ -317,6 +317,7 @@ net start W3SVC /y + diff --git a/iis/ModSecurityIIS.vcxproj.filters b/iis/ModSecurityIIS.vcxproj.filters index cdc9bb8d90..764be53be1 100755 --- a/iis/ModSecurityIIS.vcxproj.filters +++ b/iis/ModSecurityIIS.vcxproj.filters @@ -159,6 +159,9 @@ ModSecurity + + ModSecurity + ModSecurity diff --git a/standalone/Makefile.am b/standalone/Makefile.am index c85d40ea65..bb44437d9d 100644 --- a/standalone/Makefile.am +++ b/standalone/Makefile.am @@ -1,164 +1,164 @@ -pkglibdir = $(prefix)/lib - -pkglib_LTLIBRARIES = standalone.la - -standalone_la_SOURCES = ../apache2/acmp.c \ - ../apache2/apache2_config.c \ - ../apache2/apache2_io.c \ - ../apache2/apache2_util.c \ - ../apache2/others/libinjection/src/libinjection_html5.c \ - ../apache2/others/libinjection/src/libinjection_sqli.c \ - ../apache2/others/libinjection/src/libinjection_xss.c \ - ../apache2/mod_security2.c \ - ../apache2/modsecurity.c \ - ../apache2/msc_status_engine.c \ - ../apache2/msc_crypt.c \ - ../apache2/msc_geo.c \ - ../apache2/msc_gsb.c \ - ../apache2/msc_json.c \ - ../apache2/msc_logging.c \ - ../apache2/msc_lua.c \ - ../apache2/msc_multipart.c \ - ../apache2/msc_parsers.c \ - ../apache2/msc_pcre.c \ - ../apache2/msc_release.c \ - ../apache2/msc_remote_rules.c \ - ../apache2/msc_reqbody.c \ - ../apache2/msc_tree.c \ - ../apache2/msc_unicode.c \ - ../apache2/msc_util.c \ - ../apache2/msc_xml.c \ - ../apache2/persist_dbm.c \ - ../apache2/re_actions.c \ - ../apache2/re.c \ - ../apache2/re_operators.c \ - ../apache2/re_tfns.c \ - ../apache2/re_variables.c \ - api.c \ - buckets.c \ - config.c \ - filters.c \ - hooks.c \ - regex.c \ - server.c - -standalone_la_CFLAGS = -DVERSION_STANDALONE \ - @APR_CFLAGS@ \ - @APU_CFLAGS@ \ - @APXS_CFLAGS@ \ - @CURL_CFLAGS@ \ - @LIBXML2_CFLAGS@ \ - @LUA_CFLAGS@ \ - @MODSEC_EXTRA_CFLAGS@ \ - @PCRE_CFLAGS@ \ - @YAJL_CFLAGS@ \ - @SSDEEP_CFLAGS@ - -standalone_la_CPPFLAGS = @APR_CPPFLAGS@ \ - @LIBXML2_CPPFLAGS@ \ - @PCRE_CPPFLAGS@ - -standalone_la_LIBADD = @APR_LDADD@ \ - @APU_LDADD@ \ - @LIBXML2_LDADD@ \ - @LUA_LDADD@ \ - @PCRE_LDADD@ \ - @YAJL_LDADD@ \ - @SSDEEP_CFLAGS@ - -if AIX -standalone_la_LDFLAGS = -module -avoid-version \ - @APR_LDFLAGS@ \ - @APU_LDFLAGS@ \ - @APXS_LDFLAGS@ \ - @LIBXML2_LDFLAGS@ \ - @LUA_LDFLAGS@ \ - @PCRE_LDFLAGS@ \ - @YAJL_LDFLAGS@ \ - @SSDEEP_LDFLAGS@ -endif - -if HPUX -standalone_la_LDFLAGS = -module -avoid-version \ - @APR_LDFLAGS@ \ - @APU_LDFLAGS@ \ - @APXS_LDFLAGS@ \ - @LIBXML2_LDFLAGS@ \ - @LUA_LDFLAGS@ \ - @PCRE_LDFLAGS@ \ - @YAJL_LDFLAGS@ \ - @SSDEEP_LDFLAGS@ -endif - -if MACOSX -standalone_la_LDFLAGS = -module -avoid-version \ - @APR_LDFLAGS@ \ - @APU_LDFLAGS@ \ - @APXS_LDFLAGS@ \ - @LIBXML2_LDFLAGS@ \ - @LUA_LDFLAGS@ \ - @PCRE_LDFLAGS@ \ - @YAJL_LDFLAGS@ \ - @SSDEEP_LDFLAGS@ -endif - -if SOLARIS -standalone_la_LDFLAGS = -module -avoid-version \ - @APR_LDFLAGS@ \ - @APU_LDFLAGS@ \ - @APXS_LDFLAGS@ \ - @LIBXML2_LDFLAGS@ \ - @LUA_LDFLAGS@ \ - @PCRE_LDFLAGS@ \ - @YAJL_LDFLAGS@ \ - @SSDEEP_LDFLAGS@ -endif - -if LINUX -standalone_la_LDFLAGS = -no-undefined -module -avoid-version \ - @APR_LDFLAGS@ \ - @APU_LDFLAGS@ \ - @APXS_LDFLAGS@ \ - @LIBXML2_LDFLAGS@ \ - @LUA_LDFLAGS@ \ - @PCRE_LDFLAGS@ \ - @YAJL_LDFLAGS@ \ - @SSDEEP_LDFLAGS@ -endif - -if FREEBSD -standalone_la_LDFLAGS = -no-undefined -module -avoid-version \ - @APR_LDFLAGS@ \ - @APU_LDFLAGS@ \ - @APXS_LDFLAGS@ \ - @LIBXML2_LDFLAGS@ \ - @LUA_LDFLAGS@ \ - @PCRE_LDFLAGS@ \ - @YAJL_LDFLAGS@ \ - @SSDEEP_LDFLAGS@ -endif - -if OPENBSD -standalone_la_LDFLAGS = -no-undefined -module -avoid-version \ - @APR_LDFLAGS@ \ - @APU_LDFLAGS@ \ - @APXS_LDFLAGS@ \ - @LIBXML2_LDFLAGS@ \ - @LUA_LDFLAGS@ \ - @PCRE_LDFLAGS@ \ - @YAJL_LDFLAGS@ \ - @SSDEEP_LDFLAGS@ -endif - -if NETBSD -standalone_la_LDFLAGS = -no-undefined -module -avoid-version \ - @APR_LDFLAGS@ \ - @APU_LDFLAGS@ \ - @APXS_LDFLAGS@ \ - @LIBXML2_LDFLAGS@ \ - @LUA_LDFLAGS@ \ - @PCRE_LDFLAGS@ \ - @YAJL_LDFLAGS@ \ - @SSDEEP_LDFLAGS@ -endif - +pkglibdir = $(prefix)/lib + +pkglib_LTLIBRARIES = standalone.la + +standalone_la_SOURCES = ../apache2/acmp.c \ + ../apache2/apache2_config.c \ + ../apache2/apache2_io.c \ + ../apache2/apache2_util.c \ + ../apache2/others/libinjection/src/libinjection_html5.c \ + ../apache2/others/libinjection/src/libinjection_sqli.c \ + ../apache2/others/libinjection/src/libinjection_xss.c \ + ../apache2/mod_security2.c \ + ../apache2/modsecurity.c \ + ../apache2/msc_status_engine.c \ + ../apache2/msc_crypt.c \ + ../apache2/msc_geo.c \ + ../apache2/msc_gsb.c \ + ../apache2/msc_json.c \ + ../apache2/msc_logging.c \ + ../apache2/msc_lua.c \ + ../apache2/msc_multipart.c \ + ../apache2/msc_parsers.c \ + ../apache2/msc_pcre.c \ + ../apache2/msc_release.c \ + ../apache2/msc_remote_rules.c \ + ../apache2/msc_reqbody.c \ + ../apache2/msc_tree.c \ + ../apache2/msc_unicode.c \ + ../apache2/msc_util.c \ + ../apache2/msc_xml.c \ + ../apache2/persist_dbm.c \ + ../apache2/re_actions.c \ + ../apache2/re.c \ + ../apache2/re_operators.c \ + ../apache2/re_tfns.c \ + ../apache2/re_variables.c \ + api.c \ + buckets.c \ + config.c \ + filters.c \ + hooks.c \ + regex.c \ + server.c + +standalone_la_CFLAGS = -DVERSION_STANDALONE \ + @APR_CFLAGS@ \ + @APU_CFLAGS@ \ + @APXS_CFLAGS@ \ + @CURL_CFLAGS@ \ + @LIBXML2_CFLAGS@ \ + @LUA_CFLAGS@ \ + @MODSEC_EXTRA_CFLAGS@ \ + @PCRE_CFLAGS@ \ + @YAJL_CFLAGS@ \ + @SSDEEP_CFLAGS@ + +standalone_la_CPPFLAGS = @APR_CPPFLAGS@ \ + @LIBXML2_CPPFLAGS@ \ + @PCRE_CPPFLAGS@ + +standalone_la_LIBADD = @APR_LDADD@ \ + @APU_LDADD@ \ + @LIBXML2_LDADD@ \ + @LUA_LDADD@ \ + @PCRE_LDADD@ \ + @YAJL_LDADD@ \ + @SSDEEP_CFLAGS@ + +if AIX +standalone_la_LDFLAGS = -module -avoid-version \ + @APR_LDFLAGS@ \ + @APU_LDFLAGS@ \ + @APXS_LDFLAGS@ \ + @LIBXML2_LDFLAGS@ \ + @LUA_LDFLAGS@ \ + @PCRE_LDFLAGS@ \ + @YAJL_LDFLAGS@ \ + @SSDEEP_LDFLAGS@ +endif + +if HPUX +standalone_la_LDFLAGS = -module -avoid-version \ + @APR_LDFLAGS@ \ + @APU_LDFLAGS@ \ + @APXS_LDFLAGS@ \ + @LIBXML2_LDFLAGS@ \ + @LUA_LDFLAGS@ \ + @PCRE_LDFLAGS@ \ + @YAJL_LDFLAGS@ \ + @SSDEEP_LDFLAGS@ +endif + +if MACOSX +standalone_la_LDFLAGS = -module -avoid-version \ + @APR_LDFLAGS@ \ + @APU_LDFLAGS@ \ + @APXS_LDFLAGS@ \ + @LIBXML2_LDFLAGS@ \ + @LUA_LDFLAGS@ \ + @PCRE_LDFLAGS@ \ + @YAJL_LDFLAGS@ \ + @SSDEEP_LDFLAGS@ +endif + +if SOLARIS +standalone_la_LDFLAGS = -module -avoid-version \ + @APR_LDFLAGS@ \ + @APU_LDFLAGS@ \ + @APXS_LDFLAGS@ \ + @LIBXML2_LDFLAGS@ \ + @LUA_LDFLAGS@ \ + @PCRE_LDFLAGS@ \ + @YAJL_LDFLAGS@ \ + @SSDEEP_LDFLAGS@ +endif + +if LINUX +standalone_la_LDFLAGS = -no-undefined -module -avoid-version \ + @APR_LDFLAGS@ \ + @APU_LDFLAGS@ \ + @APXS_LDFLAGS@ \ + @LIBXML2_LDFLAGS@ \ + @LUA_LDFLAGS@ \ + @PCRE_LDFLAGS@ \ + @YAJL_LDFLAGS@ \ + @SSDEEP_LDFLAGS@ +endif + +if FREEBSD +standalone_la_LDFLAGS = -no-undefined -module -avoid-version \ + @APR_LDFLAGS@ \ + @APU_LDFLAGS@ \ + @APXS_LDFLAGS@ \ + @LIBXML2_LDFLAGS@ \ + @LUA_LDFLAGS@ \ + @PCRE_LDFLAGS@ \ + @YAJL_LDFLAGS@ \ + @SSDEEP_LDFLAGS@ +endif + +if OPENBSD +standalone_la_LDFLAGS = -no-undefined -module -avoid-version \ + @APR_LDFLAGS@ \ + @APU_LDFLAGS@ \ + @APXS_LDFLAGS@ \ + @LIBXML2_LDFLAGS@ \ + @LUA_LDFLAGS@ \ + @PCRE_LDFLAGS@ \ + @YAJL_LDFLAGS@ \ + @SSDEEP_LDFLAGS@ +endif + +if NETBSD +standalone_la_LDFLAGS = -no-undefined -module -avoid-version \ + @APR_LDFLAGS@ \ + @APU_LDFLAGS@ \ + @APXS_LDFLAGS@ \ + @LIBXML2_LDFLAGS@ \ + @LUA_LDFLAGS@ \ + @PCRE_LDFLAGS@ \ + @YAJL_LDFLAGS@ \ + @SSDEEP_LDFLAGS@ +endif + diff --git a/standalone/modules.mk b/standalone/modules.mk index 048f33bc84..592f869b68 100644 --- a/standalone/modules.mk +++ b/standalone/modules.mk @@ -4,7 +4,7 @@ MOD_SECURITY2 = mod_security2 apache2_config apache2_io apache2_util \ persist_dbm msc_reqbody pdf_protect msc_geo msc_gsb msc_unicode acmp msc_lua H = re.h modsecurity.h msc_logging.h msc_multipart.h msc_parsers.h \ - msc_pcre.h msc_util.h msc_xml.h persist_dbm.h apache2.h pdf_protect.h \ + msc_pcre.h msc_util.h msc_xml.h persist_dbm.h msc_reqbody.h apache2.h pdf_protect.h \ msc_geo.h msc_gsb.h msc_unicode.h acmp.h utf8tables.h msc_lua.h ${MOD_SECURITY2:=.slo}: ${H} diff --git a/standalone/standalone.vcxproj.filters b/standalone/standalone.vcxproj.filters index f51478e8f5..2dadf4433e 100644 --- a/standalone/standalone.vcxproj.filters +++ b/standalone/standalone.vcxproj.filters @@ -161,6 +161,9 @@ ModSecurity Headers + + ModSecurity Headers + ModSecurity Headers diff --git a/tests/regression/config/10-reqbody-limit-action-forcebodybuf.t b/tests/regression/config/10-reqbody-limit-action-forcebodybuf.t new file mode 100644 index 0000000000..ff92266428 --- /dev/null +++ b/tests/regression/config/10-reqbody-limit-action-forcebodybuf.t @@ -0,0 +1,194 @@ +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (forcebodybuf, <=NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_URI "/test.txt" "id:500219,phase:1,t:none,pass,ctl:forceRequestBodyVariable=On" + ), + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/plain", + "Content-Length" => "16384", + ], + "a" x 16384, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (forcebodybuf, >NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_URI "/test.txt" "id:500219,phase:1,t:none,pass,ctl:forceRequestBodyVariable=On" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/plain", + "Content-Length" => "16385", + ], + "a" x 16385, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (forcebodybuf, >Limit, <=NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_URI "/test.txt" "id:500219,phase:1,t:none,pass,ctl:forceRequestBodyVariable=On" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + "a" x 16385, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (forcebodybuf, >Limit, >NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_URI "/test.txt" "id:500219,phase:1,t:none,pass,ctl:forceRequestBodyVariable=On" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(32768\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "32769", + ], + "a" x 32769, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (forcebodybuf, >NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_URI "/test.txt" "id:500219,phase:1,t:none,pass,ctl:forceRequestBodyVariable=On" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/plain", + "Content-Length" => "16385", + ], + "a" x 16385, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (forcebodybuf, >Limit, <=NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_URI "/test.txt" "id:500219,phase:1,t:none,pass,ctl:forceRequestBodyVariable=On" + ), + match_log => { + error => [ qr/ModSecurity: Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + debug => [ qr/enable_partial_processing for none reqbody_processor/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/plain", + "Content-Length" => "16385", + ], + "a" x 16385, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (forcebodybuf, >Limit, >NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_URI "/test.txt" "id:500219,phase:1,t:none,pass,ctl:forceRequestBodyVariable=On" + ), + match_log => { + error => [ qr/ModSecurity: Request body \(Content-Length\) is larger than the configured limit \(32768\)\./, 1 ], + debug => [ qr/enable_partial_processing for none reqbody_processor/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "32769", + ], + "a" x 32769, + ), +}, diff --git a/tests/regression/config/10-reqbody-limit-action-json.t b/tests/regression/config/10-reqbody-limit-action-json.t new file mode 100644 index 0000000000..92404a23b9 --- /dev/null +++ b/tests/regression/config/10-reqbody-limit-action-json.t @@ -0,0 +1,600 @@ +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (JSON, <=NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16384", + ], + '{"a":"' . '1' x 16376 . '"}', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (JSON, <=NoFilesLimit, deny bad value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16384", + ], + '{"a":"' . '1' x 16360 . '","b":"bad_value"}', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (JSON, <=NoFilesLimit, deny bad name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16384", + ], + '{"a":"' . '1' x 16363 . '","bad_name":1}', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (JSON, >NoFilesLimit, too long)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16377 . '"}', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (JSON, >Limit, <=NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16377 . '"}', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (JSON, >Limit, >NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(32768\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "32769", + ], + '{"a":"' . '1' x 32761 . '"}', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, <=NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16384", + ], + '{"a":"' . '1' x 16376 . '"}', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, <=NoFilesLimit, deny bad value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16384", + ], + '{"a":"' . '1' x 16360 . '","b":"bad_value"}', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, <=NoFilesLimit, deny bad name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16384", + ], + '{"a":"' . '1' x 16363 . '","bad_name":1}', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, >NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16377 . '"}', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, >NoFilesLimit, deny bad value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16361 . '","b":"bad_value",', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, >NoFilesLimit, pass bad value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16361 . '","b":"bad_value "', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, >NoFilesLimit, deny bad name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16364 . '","bad_name":1 ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, >NoFilesLimit, pass bad name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16364 . '","bad_name": 1', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, >Limit, <=NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16377 . '"}', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, >Limit, <=NoFilesLimit, deny bad value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16361 . '","b":"bad_value" ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, >Limit, <=NoFilesLimit, pass bad value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16361 . '","b":" bad_value"', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, >Limit, <=NoFilesLimit, deny bad name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16364 . '","bad_name":1 ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, >Limit, <=NoFilesLimit, pass bad name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "16385", + ], + '{"a":"' . '1' x 16364 . '","bad_name": 1', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (JSON, >Limit, >NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(32768\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "32769", + ], + '{"a":"' . '1' x 32761 . '"}', + ), +}, diff --git a/tests/regression/config/10-reqbody-limit-action-multipart-chunked.t b/tests/regression/config/10-reqbody-limit-action-multipart-chunked.t new file mode 100644 index 0000000000..e06f279ed0 --- /dev/null +++ b/tests/regression/config/10-reqbody-limit-action-multipart-chunked.t @@ -0,0 +1,1256 @@ +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (multipart, chunked, <=NoFilesLimit, no bad)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Multipart flags: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + error => [ qr/Multipart flags: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0./, 1], + debug => [ qr/Request body no files length: 16384/, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 4096 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . "b" x 16278 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (multipart, chunked, <=NoFilesLimit, deny bad name)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Multipart flags: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + error => [ qr/Multipart flags: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0./, 1], + debug => [ qr/Request body no files length: 16384/, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 4096 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="bad_name" + + ) . "b" x 16271 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (multipart, chunked, <=NoFilesLimit, deny bad value)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Multipart flags: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + error => [ qr/Multipart flags: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0./, 1], + debug => [ qr/Request body no files length: 16384/, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 4096 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . 'b' x 16269 . q(bad_value + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (multipart, chunked, >NoFilesLimit)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Multipart flags: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + -error => [ qr/Multipart flags:/, 1], + debug => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1], + }, + match_response => { + status => qr/^413$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 4096 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . "b" x 16279 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (multipart, chunked, >Limit)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Multipart flags: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + -error => [ qr/Multipart flags:/, 1], + debug => [ qr/Request body is larger than the configured limit \(32768\)\./, 1], + }, + match_response => { + status => qr/^413$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 16199 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . "b" x 16278 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart, chunked, =NoFilesLimit, no bad)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + 4096 + ), + match_log => { + error => [ qr/heck values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Request body no files length: 16384/, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 4096 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . "b" x 16278 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart, chunked, =NoFilesLimit, deny bad name)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + 4096 + ), + match_log => { + error => [ qr/heck values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Request body no files length: 16384/, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 4096 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="bad_name" + + ) . "b" x 16271 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart, chunked, =NoFilesLimit, deny bad value)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + 4096 + ), + match_log => { + error => [ qr/heck values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Request body no files length: 16384/, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 4096 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . 'b' x 16269 . q(bad_value + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart, chunked, >NoFilesLimit, no bad)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + 4096 + ), + match_log => { + error => [ qr/heck values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 4096 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . "b" x 16279 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart, chunked, >NoFilesLimit, deny bad name)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + 4096 + ), + match_log => { + error => [ qr/heck values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 4096 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="bad_name" + + ) . "b" x 16272 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart, chunked, >NoFilesLimit, deny bad value)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + 4096 + ), + match_log => { + error => [ qr/heck values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 4096 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . 'b' x 16269 . q(bad_valueb + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart, chunked, =Limit, no bad)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + error => [ qr/Check values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Input filter: Completed receiving request body \(length 32768\)\./, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 16199 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . "b" x 16278 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart, chunked, =Limit, deny bad name)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + error => [ qr/Check values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Input filter: Completed receiving request body \(length 32768\)\./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 16199 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="bad_name" + + ) . "b" x 16271 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart, chunked, =Limit, deny bad value)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + error => [ qr/Check values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Input filter: Completed receiving request body \(length 32768\)\./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 16199 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . "b" x 16270 . q(ad_value + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart, chunked, >Limit, no bad)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + error => [ qr/Check values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Multipart: Allow partial processing of request body/, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 16199 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . "b" x 16279 . q( + -----------------------------69343412719991675451336310646-- + ) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart no CRLF after final boundary, chunked, >Limit, deny bad name)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + error => [ qr/Check values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Multipart: Allow partial processing of request body/, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 16199 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="bad_name" + + ) . "b" x 16274 . q( + -----------------------------69343412719991675451336310646--) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart no CRLF after final boundary, chunked, >Limit, pass bad name)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + error => [ qr/Check values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Multipart: Allow partial processing of request body/, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 16199 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="bad_name" + + ) . "b" x 16275 . q( + -----------------------------69343412719991675451336310646--) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart no CRLF after final boundary, chunked, >Limit, deny bad value)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + error => [ qr/Check values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Multipart: Allow partial processing of request body/, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 16199 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . "b" x 16270 . q(ad_valueb + -----------------------------69343412719991675451336310646--) + ), + 4096 + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart no CRLF after final boundary, chunked, >Limit, pass bad value)", + conf => trim_action_indent( + qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQBODY_PROCESSOR "^MULTIPART\$" \\ + "id:'200000',phase:2,t:none,log, \\ + msg:'Check values for test: \\ + RE %{REQBODY_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}, \\ + UB %{MULTIPART_UNMATCHED_BOUNDARY}'" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ) + ), + match_log => { + error => [ qr/Check values for test: RE 0, BQ 0, BW 0, DB 0, DA 0, HF 0, LF 0, SM 0, IQ 0, IP 0, IH 0, FL 0, UB 0/, 1], + debug => [ qr/Multipart: Allow partial processing of request body/, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => normalize_raw_request_data( + qq( + POST /test.txt HTTP/1.1 + Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT} + User-Agent: $ENV{USER_AGENT} + Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646 + Transfer-Encoding: chunked + + ), + ) + .encode_chunked( + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="a"; filename="a.txt" + + ) . "a" x 16199 . q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="b" + + ) . "b" x 16274 . q(ad_value + -----------------------------69343412719991675451336310646--) + ), + 4096 + ), +}, diff --git a/tests/regression/config/10-reqbody-limit-action-urlencoded.t b/tests/regression/config/10-reqbody-limit-action-urlencoded.t new file mode 100644 index 0000000000..7c2135b277 --- /dev/null +++ b/tests/regression/config/10-reqbody-limit-action-urlencoded.t @@ -0,0 +1,580 @@ +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (urlencoded, <=NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 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", + "Content-Length" => "16384", + ], + 'a=1&b=' . '2' x 16378, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (urlencoded, <=NoFilesLimit, deny bad_value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "16384", + ], + 'a=' . '1' x 16370 . '&b=bad_value', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (urlencoded, <=NoFilesLimit, deny bad_name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "16384", + ], + 'a=' . '1' x 16373 . '&bad_name', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (urlencoded, >NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "16385", + ], + 'a=1&b=' . '2' x 16379, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (urlencoded, >Limit, <=NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "16385", + ], + 'a=1&b=' . '2' x 16379, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (urlencoded, >Limit, >NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(32768\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "32769", + ], + 'a=1&b=' . '2' x 32763, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, <=NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 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", + "Content-Length" => "16384", + ], + 'a=1&b=' . '2' x 16378, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, <=NoFilesLimit, deny bad_value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "16384", + ], + 'a=' . '1' x 16370 . '&b=bad_value', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, <=NoFilesLimit, deny bad_name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "16384", + ], + 'a=' . '1' x 16373 . '&bad_name', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, >NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 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", + "Content-Length" => "16385", + ], + 'a=' . '1' x 16379 . '&b=2', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, >NoFilesLimit, deny bad value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "16385", + ], + 'a=' . '1' x 16369 . '&b=bad_value&c', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, >NoFilesLimit, pass bad value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 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", + "Content-Length" => "16385", + ], + 'a=' . '1' x 16370 . '&b=bad_value&', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, >NoFilesLimit, deny bad name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "16385", + ], + 'a=' . '1' x 16370 . '&bad_name=2&c', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, >NoFilesLimit, deny bad name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 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", + "Content-Length" => "16385", + ], + 'a=' . '1' x 16371 . '&bad_name=2&', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, >Limit, <=NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 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", + "Content-Length" => "16385", + ], + 'a=1&b=' . '2' x 16379, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, >Limit, <=NoFilesLimit, deny bad value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "16385", + ], + 'a=' . '1' x 16369 . '&b=bad_value&c', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, >Limit, <=NoFilesLimit, pass bad value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 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", + "Content-Length" => "16385", + ], + 'a=' . '1' x 16370 . '&b=bad_value&', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, >Limit, <=NoFilesLimit, deny bad name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "16385", + ], + 'a=' . '1' x 16370 . '&bad_name=2&c', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, >Limit, <=NoFilesLimit, pass bad name)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 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", + "Content-Length" => "16385", + ], + 'a=' . '1' x 16371 . '&bad_name=2&', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (urlencoded, >Limit, >NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(32768\)\./, 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", + "Content-Length" => "32769", + ], + 'a=1&b=' . '2' x 32763, + ), +}, diff --git a/tests/regression/config/10-reqbody-limit-action-xml.t b/tests/regression/config/10-reqbody-limit-action-xml.t new file mode 100644 index 0000000000..faa58b13f6 --- /dev/null +++ b/tests/regression/config/10-reqbody-limit-action-xml.t @@ -0,0 +1,793 @@ +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (XML, <=NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16384", + ], + '' . '1' x 16364 . '', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (XML, <=NoFilesLimit, deny)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16384", + ], + '' . '1' x 16355 . 'bad_value', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (XML, >NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16365 . '', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (XML, >Limit, <=NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16365 . '', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction Reject (XML, >Limit, >NoFilesLimit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(32768\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "32769", + ], + '' . '1' x 32749 . '', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, <=NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16384", + ], + '' . '1' x 16364 . '', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, <=NoFilesLimit, deny)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16384", + ], + '' . '1' x 16355 . 'bad_value', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, non-leaf, <=NoFilesLimit, deny)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16384", + ], + '' . '1' x 16351 . 'bad_value', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, ARGS, non-leaf, <=NoFilesLimit, pass)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecParseXmlIntoArgs On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule ARGS "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16384", + ], + '' . '1' x 16351 . 'bad_value', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, ARGS, leaf, <=NoFilesLimit, deny)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecParseXmlIntoArgs On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule ARGS "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16384", + ], + '' . '1' x 16355 . 'bad_value', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16365 . '', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >NoFilesLimit, deny)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16366 . 'bad_value ', + ), +}, + +# The 8 tests below checks whether the partial text content is added or not. +# +# xmlParseChunk adds a text content whose length is greater than or equal to 300 bytes even when +# you pass 0 as the terminate argument. If the length is less than 300 bytes it does not a text content +# at end of the chunk without a tag being followed. +# https://gitlab.gnome.org/GNOME/libxml2/-/blob/ca6a8cf94672e6f3c48c08d6af2201599788fc17/parser.c#L11061-11079 +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >NoFilesLimit, chunk_len>=300, deny)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16068 . '' . '2' x 291 . 'bad_value ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >NoFilesLimit, chunk_len<300, pass)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16069 . '' . '2' x 290 . 'bad_value ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >NoFilesLimit, nested, chunk_len>=300, deny)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16072 . '' . '2' x 291 . 'bad_value ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >NoFilesLimit, nested, chunk_len<300, pass)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16073 . '' . '2' x 290 . 'bad_value ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, short, >NoFilesLimit, chunk_len>=300, deny)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 1024 + SecRequestBodyLimit 2048 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(1024\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "1025", + ], + '' . '1' x 708 . '' . '2' x 291 . 'bad_value ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, short, >NoFilesLimit, chunk_len<300, pass)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 1024 + SecRequestBodyLimit 2048 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(1024\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "1025", + ], + '' . '1' x 709 . '' . '2' x 290 . 'bad_value ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, short, >NoFilesLimit, nested, chunk_len>=300, deny)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 1024 + SecRequestBodyLimit 2048 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(1024\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "1025", + ], + '' . '1' x 712 . '' . '2' x 291 . 'bad_value ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, short, >NoFilesLimit, nested, chunk_len<300, pass)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 1024 + SecRequestBodyLimit 2048 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(1024\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "1025", + ], + '' . '1' x 713 . '' . '2' x 290 . 'bad_value ', + ), +}, + +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >NoFilesLimit, pass)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16366 . ' bad_value', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >Limit, <=NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16365 . '', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >Limit, <=NoFilesLimit, deny)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16366 . 'bad_value ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >Limit, <=NoFilesLimit, pass)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 32768 + SecRequestBodyLimit 16384 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "16385", + ], + '' . '1' x 16366 . ' bad_value', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >Limit, >NoFilesLimit, no bad)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(32768\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "32769", + ], + '' . '1' x 32749 . '', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >Limit, >NoFilesLimit, deny)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "32769", + ], + '' . '1' x 16366 . 'bad_value ' . '1' x 16384, + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (XML, >Limit, >NoFilesLimit, pass)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16384 + SecRequestBodyLimit 32768 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body no files data length is larger than the configured limit \(16384\)\./, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "32769", + ], + '' . '1' x 16366 . ' bad_value' . '1' x 16384, + ), +}, diff --git a/tests/regression/config/10-request-directives.t b/tests/regression/config/10-request-directives.t index 69aa190ccb..8ca6035da7 100644 --- a/tests/regression/config/10-request-directives.t +++ b/tests/regression/config/10-request-directives.t @@ -499,6 +499,23 @@ SecRequestBodyAccess On SecRequestBodyLimitAction Reject SecRequestBodyLimit 20 + SecRule MULTIPART_STRICT_ERROR "!\@eq 0" \\ + "id:'200003',phase:2,t:none,log,deny,status:400, \\ + msg:'Multipart request body failed strict validation: \\ + PE %{REQBODY_PROCESSOR_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'" + SecRule MULTIPART_UNMATCHED_BOUNDARY "!\@eq 0" \\ + "id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'" ), match_log => { debug => [ qr/Request body is larger than the configured limit \(20\)./, 1 ], @@ -545,7 +562,7 @@ SecRequestBodyLimit 131072 ), match_log => { - -debug => [ qr/Request body is larger than the configured limit \(131072\).. Deny with code \(413\)/, 1 ], + -debug => [ qr/Request body is larger than the configured limit \(131072\)./, 1 ], }, match_response => { status => qr/^413$/, @@ -576,12 +593,29 @@ SecRequestBodyAccess On SecRequestBodyLimitAction ProcessPartial SecRequestBodyLimit 131072 + SecRule MULTIPART_STRICT_ERROR "!\@eq 0" \\ + "id:'200003',phase:2,t:none,log,deny,status:400, \\ + msg:'Multipart request body failed strict validation: \\ + PE %{REQBODY_PROCESSOR_ERROR}, \\ + BQ %{MULTIPART_BOUNDARY_QUOTED}, \\ + BW %{MULTIPART_BOUNDARY_WHITESPACE}, \\ + DB %{MULTIPART_DATA_BEFORE}, \\ + DA %{MULTIPART_DATA_AFTER}, \\ + HF %{MULTIPART_HEADER_FOLDING}, \\ + LF %{MULTIPART_LF_LINE}, \\ + SM %{MULTIPART_MISSING_SEMICOLON}, \\ + IQ %{MULTIPART_INVALID_QUOTING}, \\ + IP %{MULTIPART_INVALID_PART}, \\ + IH %{MULTIPART_INVALID_HEADER_FOLDING}, \\ + FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'" + SecRule MULTIPART_UNMATCHED_BOUNDARY "!\@eq 0" \\ + "id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'" ), match_log => { - error => [ qr/Multipart parsing error: Multipart: Final boundary missing./, 1], + -error => [ qr/Multipart parsing error: Multipart: Final boundary missing./, 1], }, match_response => { - status => qr/^500$/, + status => qr/^200$/, }, request => normalize_raw_request_data( qq( @@ -610,6 +644,1715 @@ 131072*3 ), }, +# Known issue on nginx, disable it for now. +#{ +# type => "config", +# comment => "SecRequestBodyLimitAction ProcessPartial (plain/greater)", +# conf => qq( +# SecRuleEngine On +# SecDebugLog $ENV{DEBUG_LOG} +# SecDebugLogLevel 9 +# SecRequestBodyAccess On +# SecRequestBodyLimitAction ProcessPartial +# SecRequestBodyLimit 131072 +# ), +# match_log => { +# -debug => [ qr/Request body is larger than the configured limit/, 1], +# }, +# match_response => { +# status => qr/^200$/, +# }, +# request => new HTTP::Request( +# POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", +# [ +# "Content-Type" => "application/json", +# ], +# normalize_raw_request_data( +# q( +# { +# ) . "'abcdefghijlmnopq'='abcdefghijlmnopqrstuvxz',\\n" x 99000 . q( +# }, +# ), +# ), +# ), +#}, + +# SecRequestBodyLimitAction ProcessPartial +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/bad_name before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 59 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_NAME "bad_name" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="bad_name" + ), + ) . "\r\n" . "a", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/bad_name after limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 58 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_NAME "bad_name" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="bad_name" + ), + ) . "\r\n", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/bad_filename before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 81 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_FILENAME "bad_filename" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="bad_filename" + ), + ) . "\r\n" . "a", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/bad_filename after limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 80 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_FILENAME "bad_filename" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="bad_filename" + ), + ) . "\r\n", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/no epilogue)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 176 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + ), + match_log => { + -error => [ qr/Multipart parsing error: Multipart: Final boundary missing./, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="name1" + + value1 + -----------------------------69343412719991675451336310646--), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CR after limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 176 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + ), + match_log => { + -error => [ qr/Multipart parsing error: Multipart: Final boundary missing./, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="name1" + + value1 + -----------------------------69343412719991675451336310646--) . "\r", + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CR just in limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 177 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + ), + match_log => { + -error => [ qr/"Multipart: Invalid epilogue after final boundary."/, 1], + }, + match_response => { + status => qr/^400$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="name1" + + value1 + -----------------------------69343412719991675451336310646--) . "\r", + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF across limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 177 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + ), + match_log => { + -error => [ qr/Multipart parsing error: Multipart: Final boundary missing./, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="name1" + + value1 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CR before limit, non-LF after)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 177 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + ), + match_log => { + -error => [ qr/Multipart parsing error: Multipart: Final boundary missing./, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="name1" + + value1 + -----------------------------69343412719991675451336310646--) . "\rbad epilogue after just CR", + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/empty epilogue just in limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 178 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + ), + match_log => { + -error => [ qr/Multipart parsing error: Multipart: Final boundary missing./, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646", + ], + normalize_raw_request_data( + q( + -----------------------------69343412719991675451336310646 + Content-Disposition: form-data; name="name1" + + value1 + -----------------------------69343412719991675451336310646-- + ), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/bad-header in part across limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 114 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS:name1 "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 115 bytes./, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: bad_type + + value + --000), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/bad-header in part before limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 115 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS:name1 "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 116 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: bad_type + + value + --0000), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/parital/bad-header in part before limit #2)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 116 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS:name1 "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 117 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: bad_type + + value + --0000), + ) . "\rX", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/bad-header in part before limit #3)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 117 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS:name1 "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 118 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: bad_type + + value + --0000 + ) + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/bad-header in part before limit #4)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 118 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS:name1 "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 119 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: bad_type + + value + --0000 + ) + ) . q(C) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/bad-header in part before limit #5)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 160 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS:name1 "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 161 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: bad_type + + value + --0000 + ) + ) . q(Content-Disposition: form-data; name="name2) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/bad-header in final part across limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 116 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 117 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: bad_type + + value + --0000-), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/bad-header in final part before limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 117 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 118 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: bad_type + + value + --0000--), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/bad-header in final part across limit #2)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 205 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 206 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: text/plain + + value + --0000 + Content-Disposition: form-data; name="name2" + Content-Type: bad_type + + value + --0000-), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/bad-header in final part before limit #2)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 206 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 207 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: text/plain + + value + --0000 + Content-Disposition: form-data; name="name2" + Content-Type: bad_type + + value + --0000--), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/invalid boundary before limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 118 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 119 bytes./, 1], + error => [ qr/Multipart parsing error: Multipart: Invalid boundary./, 1], + }, + match_response => { + status => qr/^400$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: text/plain + + value + --0000!) + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/invalid boundary before limit #2)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 119 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 120 bytes./, 1], + error => [ qr/Multipart parsing error: Multipart: Invalid boundary./, 1], + }, + match_response => { + status => qr/^400$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: text/plain + + value + --0000) + ) . "\r!" . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/CRLF/partial/invalid final boundary before limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 119 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 120 bytes./, 1], + error => [ qr/Multipart parsing error: Multipart: Invalid final boundary./, 1], + }, + match_response => { + status => qr/^400$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + normalize_raw_request_data( + q( + --0000 + Content-Disposition: form-data; name="name1"; filename="name1.txt" + Content-Type: text/plain + + value + --0000-!), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/LF/partial/bad-header in part across limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 109 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS:name1 "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 110 bytes./, 1], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + join("\n", + q(--0000), + q(Content-Disposition: form-data; name="name1"; filename="name1.txt"), + q(Content-Type: bad_type), + q(), + q(value), + q(--000), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/LF/partial/bad-header in part before limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 110 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS:name1 "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 111 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + join("\n", + q(--0000), + q(Content-Disposition: form-data; name="name1"; filename="name1.txt"), + q(Content-Type: bad_type), + q(), + q(value), + q(--0000), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/LF/parital/bad-header in part before limit #2)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 111 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS:name1 "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 112 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + join("\n", + q(--0000), + q(Content-Disposition: form-data; name="name1"; filename="name1.txt"), + q(Content-Type: bad_type), + q(), + q(value), + q(--0000), + ) . "\n" . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/LF/parital/bad-header in part before limit #3)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 112 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS:name1 "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 113 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + join("\n", + q(--0000), + q(Content-Disposition: form-data; name="name1"; filename="name1.txt"), + q(Content-Type: bad_type), + q(), + q(value), + q(--0000), + q(C), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/LF/parital/bad-header in part before limit #4)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 154 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS:name1 "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 155 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + join("\n", + q(--0000), + q(Content-Disposition: form-data; name="name1"; filename="name1.txt"), + q(Content-Type: bad_type), + q(), + q(value), + q(--0000), + q(Content-Disposition: form-data; name="name2), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/LF/partial/bad-header in final part across limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 111 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 112 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + join("\n", + q(--0000), + q(Content-Disposition: form-data; name="name1"; filename="name1.txt"), + q(Content-Type: bad_type), + q(), + q(value), + q(--0000-), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/LF/partial/bad-header in final part before limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 112 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 113 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + join("\n", + q(--0000), + q(Content-Disposition: form-data; name="name1"; filename="name1.txt"), + q(Content-Type: bad_type), + q(), + q(value), + q(--0000--), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/LF/partial/bad-header in final part across limit #2)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 195 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 196 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + join("\n", + q(--0000), + q(Content-Disposition: form-data; name="name1"; filename="name1.txt"), + q(Content-Type: text/plain), + q(), + q(value), + q(--0000), + q(Content-Disposition: form-data; name="name2"), + q(Content-Type: bad_type), + q(), + q(value), + q(--0000-), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/LF/partial/bad-header in final part before limit #2)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 196 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule MULTIPART_PART_HEADERS "content-type:.*bad_type" "id:'200002',phase:2,t:none,t:lowercase,deny + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 197 bytes./, 1], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + join("\n", + q(--0000), + q(Content-Disposition: form-data; name="name1"; filename="name1.txt"), + q(Content-Type: text/plain), + q(), + q(value), + q(--0000), + q(Content-Disposition: form-data; name="name2"), + q(Content-Type: bad_type), + q(), + q(value), + q(--0000--), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/LF/partial/invalid boundary before limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 113 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 114 bytes./, 1], + error => [ qr/Multipart parsing error: Multipart: Invalid boundary./, 1], + }, + match_response => { + status => qr/^400$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + join("\n", + q(--0000), + q(Content-Disposition: form-data; name="name1"; filename="name1.txt"), + q(Content-Type: text/plain), + q(), + q(value), + q(--0000!), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (multipart/LF/partial/invalid final boundary before limit #1)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 114 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + ), + match_log => { + debug => [ qr/Input filter: Bucket type HEAP contains 115 bytes./, 1], + error => [ qr/Multipart parsing error: Multipart: Invalid final boundary./, 1], + }, + match_response => { + status => qr/^400$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "multipart/form-data; boundary=0000", + ], + join("\n", + q(--0000), + q(Content-Disposition: form-data; name="name1"; filename="name1.txt"), + q(Content-Type: text/plain), + q(), + q(value), + q(--0000-!), + ) . "X", + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (url-encoded/entire/bad_name without value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 16 + SecRequestBodyLimit 16 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + "Content-Length" => "16", + ], + normalize_raw_request_data( + q(a=1&b=2&bad_name), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (url-encoded/partial/bad_name without value without delimeter before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 8 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny + ), + 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", + ], + normalize_raw_request_data( + q(bad_nameX), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (url-encoded/partial/bad_name without value with delimiter before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 9 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + normalize_raw_request_data( + q(bad_name&X), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (url-encoded/entire/bad_name with value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 10 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + normalize_raw_request_data( + q(bad_name=1), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (url-encoded/partial/bad_name with value without delimeter before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 10 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny + ), + 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", + ], + normalize_raw_request_data( + q(bad_name=1X), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (url-encoded/partial/bad_name with value with delimiter before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 11 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + normalize_raw_request_data( + q(bad_name=1&X), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (url-encoded/entire/bad_value)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 11 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + normalize_raw_request_data( + q(a=bad_value), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (url-encoded/partial/bad_value without delimeter before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 11 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200002',phase:2,t:none,deny + ), + 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", + ], + normalize_raw_request_data( + q(a=bad_valueX), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (url-encoded/partial/bad_value with delimeter before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 12 + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/x-www-form-urlencoded", + ], + normalize_raw_request_data( + q(a=bad_value&X), + ), + ), +},{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (json/bad_name after limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 12 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + normalize_raw_request_data( + q({"bad_name":1}), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (json/bad_name before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 13 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS_NAMES "bad_name" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + normalize_raw_request_data( + q({"bad_name":1}), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (json/bad_value after limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 15 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + normalize_raw_request_data( + q({"a":"bad_value"}), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (json/bad_value before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 16 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + normalize_raw_request_data( + q({"a":"bad_value"}), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (json/ill-formed after limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 17 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + normalize_raw_request_data( + q({"a":"bad_value"}]), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (json/ill-formed before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 18 + SecRule REQUEST_HEADERS:Content-Type "application/json" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^400$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + normalize_raw_request_data( + q({"a":"bad_value"}]), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (xml/bad_value after limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 13 + SecRequestBodyLimit 13 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "14", + ], + 'bad_value <', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (xml/bad_value before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 13 + SecRequestBodyLimit 13 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + "Content-Length" => "14", + ], + 'bad_value< ', + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (xml/ill-formed after limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 19 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + ], + normalize_raw_request_data( + q(bad_value), + ), + ), +}, +{ + type => "config", + comment => "SecRequestBodyLimitAction ProcessPartial (xml/ill-formed before limit)", + conf => qq( + SecRuleEngine On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyLimit 20 + SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\\+|/)|text/)xml" "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + SecRule REQBODY_ERROR "!\@eq 0" "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule XML:/* "bad_value" "id:'200002',phase:2,t:none,deny + ), + match_response => { + status => qr/^400$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/xml", + ], + normalize_raw_request_data( + q(bad_value), + ), + ), +}, # SecCookieFormat { diff --git a/tests/regression/rule/10-xml.t b/tests/regression/rule/10-xml.t index 45085063e3..7ad8e73de7 100644 --- a/tests/regression/rule/10-xml.t +++ b/tests/regression/rule/10-xml.t @@ -8,7 +8,7 @@ conf => qq( SecRuleEngine On SecRequestBodyAccess On - SecXmlExternalEntity On + SecXmlExternalEntity On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\ @@ -56,7 +56,7 @@ conf => qq( SecRuleEngine On SecRequestBodyAccess On - SecXmlExternalEntity On + SecXmlExternalEntity On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 SecAuditEngine RelevantOnly @@ -106,7 +106,7 @@ conf => qq( SecRuleEngine On SecRequestBodyAccess On - SecXmlExternalEntity On + SecXmlExternalEntity On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 SecAuditEngine RelevantOnly @@ -157,7 +157,7 @@ conf => qq( SecRuleEngine On SecRequestBodyAccess On - SecXmlExternalEntity On + SecXmlExternalEntity On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 SecAuditEngine RelevantOnly @@ -208,7 +208,7 @@ conf => qq( SecRuleEngine On SecRequestBodyAccess On - SecXmlExternalEntity On + SecXmlExternalEntity On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 SecAuditEngine RelevantOnly @@ -259,7 +259,7 @@ conf => qq( SecRuleEngine On SecRequestBodyAccess On - SecXmlExternalEntity On + SecXmlExternalEntity On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500020, \\ @@ -303,7 +303,7 @@ conf => qq( SecRuleEngine On SecRequestBodyAccess On - SecXmlExternalEntity On + SecXmlExternalEntity On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500023, \\ @@ -347,7 +347,7 @@ conf => qq( SecRuleEngine On SecRequestBodyAccess On - SecXmlExternalEntity On + SecXmlExternalEntity On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 SecAuditEngine RelevantOnly @@ -393,7 +393,7 @@ conf => qq( SecRuleEngine On SecRequestBodyAccess On - SecXmlExternalEntity On + SecXmlExternalEntity On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" "id:500029, \\ diff --git a/tests/regression/rule/15-json.t b/tests/regression/rule/15-json.t index 9c17817503..a85ecaf78f 100644 --- a/tests/regression/rule/15-json.t +++ b/tests/regression/rule/15-json.t @@ -258,6 +258,263 @@ ), ), ), -} - - +}, +{ + type => "rule", + comment => "LimitAction ProcessPartial, bad format and whole body before limit", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 21 + SecRequestBodyLimit 21 + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "application/json" \\ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" \\ + "id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Access denied with code 400 \(phase 2\)\. Match of "eq 0" against "REQBODY_ERROR" required\./, 1 ], + debug => [ qr/Adding JSON argument 'b' with value 'value'|JSON support was not enabled/, 1 ], + -debug => [ qr/JSON: Allow partial processing of request body|JSON support was not enabled/, 1 ], + }, + match_response => { + status => qr/^400$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "21", + ], + q({"a":1234,"b":"value"), + ), +}, +{ + type => "rule", + comment => "LimitAction ProcessPartial, bad format and length exceeds limit", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 21 + SecRequestBodyLimit 21 + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "application/json" \\ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" \\ + "id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + -error => [ qr/Access denied with code 400 \(phase 2\)\. Match of "eq 0" against "REQBODY_ERROR" required\./, 1 ], + debug => [ qr/Adding JSON argument 'b' with value 'value'|JSON support was not enabled/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "22", + ], + q({"a":1234,"b":"value" ), + ), +}, +{ + type => "rule", + comment => "LimitAction ProcessPartial, bad value and whole body before limit", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 26 + SecRequestBodyLimit 26 + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "application/json" \\ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" \\ + "id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 2\)\. Pattern match "bad_value" at ARGS:b\./, 1 ], + debug => [ qr/Adding JSON argument 'b' with value 'bad_value'|JSON support was not enabled/, 1 ], + -debug => [ qr/JSON: Allow partial processing of request body|JSON support was not enabled/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + q({"a":1234,"b":"bad_value"}), + ), +}, +{ + type => "rule", + comment => "LimitAction ProcessPartial, bad value before limit", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 26 + SecRequestBodyLimit 26 + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "application/json" \\ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" \\ + "id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 2\)\. Pattern match "bad_value" at ARGS:b\./, 1 ], + debug => [ qr/JSON: Allow partial processing of request body|JSON support was not enabled/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + "Content-Length" => "27", + ], + q({"a":12345,"b":"bad_value"}), + ), +}, +{ + type => "rule", + comment => "LimitAction ProcessPartial, bad value after limit", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimitAction ProcessPartial + SecRequestBodyNoFilesLimit 26 + SecRequestBodyLimit 26 + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "application/json" \\ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" \\ + "id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + debug => [ qr/JSON: Allow partial processing of request body|JSON support was not enabled/, 1 ], + }, + match_response => { + status => qr/^200$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + q({"a":123456,"b":"bad_value"}), + ), +}, +{ + type => "rule", + comment => "LimitAction Reject, bad value and whole body before limit", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 26 + SecRequestBodyLimit 26 + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "application/json" \\ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" \\ + "id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Access denied with code 403 \(phase 2\)\. Pattern match "bad_value" at ARGS:b\./, 1 ], + debug => [ qr/Adding JSON argument 'b' with value 'bad_value'|JSON support was not enabled/, 1 ], + -debug => [ qr/JSON: Allow partial processing of request body|JSON support was not enabled/, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + q({"a":1234,"b":"bad_value"}), + ), +}, +{ + type => "rule", + comment => "LimitAction Reject, bad value before limit", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 26 + SecRequestBodyLimit 26 + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "application/json" \\ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" \\ + "id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(26\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + q({"a":12345,"b":"bad_value"}), + ), +}, +{ + type => "rule", + comment => "LimitAction Reject, bad value after limit", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecRequestBodyLimitAction Reject + SecRequestBodyNoFilesLimit 26 + SecRequestBodyLimit 26 + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecRule REQUEST_HEADERS:Content-Type "application/json" \\ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + SecRule REQBODY_ERROR "!\@eq 0" \\ + "id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + SecRule ARGS "bad_value" "id:'200003',phase:2,t:none,deny" + ), + match_log => { + error => [ qr/Request body \(Content-Length\) is larger than the configured limit \(26\)\./, 1 ], + }, + match_response => { + status => qr/^413$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "application/json", + ], + q({"a":123456,"b":"bad_value"}), + ), +}, diff --git a/tests/run-regression-tests.pl.in b/tests/run-regression-tests.pl.in index 87b1933748..c84ad724ed 100755 --- a/tests/run-regression-tests.pl.in +++ b/tests/run-regression-tests.pl.in @@ -354,6 +354,13 @@ sub runfile { msg(sprintf("Passed: %2d; Failed: %2d", $pass, $testnum ? (1 - $pass) : ($n - $pass))); } +# Trim indent in action strings +sub trim_action_indent { + my $r = $_[0]; + $r =~ s/[\t]+//mg; + return $r; +} + # Take out any indenting and translate LF -> CRLF sub normalize_raw_request_data { my $r = $_[0];