diff --git a/apache2/apache2_io.c b/apache2/apache2_io.c
index 8deeb01c9..0220791f4 100644
--- a/apache2/apache2_io.c
+++ b/apache2/apache2_io.c
@@ -299,38 +299,39 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) {
#endif
}
+ if (msr->reqbody_length + buflen > (apr_size_t)msr->txcfg->reqbody_limit && 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);
+ }
+
+ finished_reading = 1;
+ modsecurity_request_body_enable_partial_processing(msr);
+ } else if ( (msr->msc_reqbody_no_files_length > (unsigned long)msr->txcfg->reqbody_no_files_limit)
+ && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL))
+ {
+ 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 (msr->reqbody_length > (apr_size_t)msr->txcfg->reqbody_limit && msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL) {
- finished_reading = 1;
- }
-
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 +352,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 289471703..6f94a856f 100644
--- a/apache2/modsecurity.h
+++ b/apache2/modsecurity.h
@@ -279,6 +279,7 @@ struct modsec_rec {
unsigned int if_started_forwarding;
apr_size_t reqbody_length;
+ unsigned int reqbody_partial_processing_enabled;
apr_bucket_brigade *of_brigade;
unsigned int of_status;
@@ -736,6 +737,8 @@ apr_status_t DSOLOCAL modsecurity_request_body_start(modsec_rec *msr, char **err
apr_status_t DSOLOCAL modsecurity_request_body_store(modsec_rec *msr,
const char *data, apr_size_t length, char **error_msg);
+void DSOLOCAL modsecurity_request_body_enable_partial_processing(modsec_rec *msr);
+
apr_status_t DSOLOCAL modsecurity_request_body_end(modsec_rec *msr, char **error_msg);
apr_status_t DSOLOCAL modsecurity_request_body_to_stream(modsec_rec *msr, const char *buffer, int buflen, char **error_msg);
diff --git a/apache2/msc_json.c b/apache2/msc_json.c
index cae7bf4f7..234fea2eb 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 089dab476..c5fbc80df 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 dc24248de..8c4d2842d 100644
--- a/apache2/msc_multipart.c
+++ b/apache2/msc_multipart.c
@@ -251,6 +251,23 @@ static int multipart_parse_content_disposition(modsec_rec *msr, char *c_d_value)
return 1;
}
+/* Enable partial processing if no_files_len exceeds limit and action is ProcessPartial */
+static void modsecurity_request_body_enable_partial_processing_for_no_files_length(modsec_rec *msr,
+ int *length)
+{
+ /* Enable partial processing if no_files_len exceeds limit and action is ProcessPartial */
+ if ( (msr->msc_reqbody_no_files_length > (unsigned long)msr->txcfg->reqbody_no_files_limit)
+ && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL))
+ {
+ *length -= msr->msc_reqbody_no_files_length - (unsigned long)msr->txcfg->reqbody_no_files_limit;
+ if (msr->txcfg->debuglog_level >= 9) {
+ msr_log(msr, 9, "MULTIPART: length shortened by %" APR_SIZE_T_FMT " bytes because of no_files_len limit.",
+ msr->msc_reqbody_no_files_length - (unsigned long)msr->txcfg->reqbody_no_files_limit);
+ }
+ modsecurity_request_body_enable_partial_processing(msr);
+ }
+}
+
/**
*
*/
@@ -272,6 +289,7 @@ 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);
+ modsecurity_request_body_enable_partial_processing_for_no_files_length(msr, &msr->mpd->bufleft);
if (len > 1) {
if (msr->mpd->buf[len - 2] == '\r') {
@@ -421,7 +439,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 */
@@ -586,6 +604,7 @@ static int multipart_process_part_data(modsec_rec *msr, char **error_msg) {
/* 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];
+ modsecurity_request_body_enable_partial_processing_for_no_files_length(msr, &msr->mpd->bufleft);
/* add this part to the list of parts */
@@ -1023,38 +1042,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 = strlen(msr->mpd->boundary);
+ 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 +1371,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 a9c20b9b1..8dfd47381 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 793549a5f..30234c8d8 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 e00a4fc3f..f8ceed804 100644
--- a/apache2/msc_reqbody.c
+++ b/apache2/msc_reqbody.c
@@ -312,6 +312,24 @@ static apr_status_t modsecurity_request_body_store_memory(modsec_rec *msr,
return 1;
}
+/* Enable partial processing if no_files_len exceeds limit and action is ProcessPartial */
+static void modsecurity_request_body_enable_partial_processing_for_no_files_length(modsec_rec *msr,
+ apr_size_t *length, const char *reqbody_processor)
+{
+ /* Enable partial processing if no_files_len exceeds limit and action is ProcessPartial */
+ if ( (msr->msc_reqbody_no_files_length > (unsigned long)msr->txcfg->reqbody_no_files_limit)
+ && (msr->txcfg->if_limit_action == REQUEST_BODY_LIMIT_ACTION_PARTIAL))
+ {
+ *length -= msr->msc_reqbody_no_files_length - (unsigned long)msr->txcfg->reqbody_no_files_limit;
+ if (msr->txcfg->debuglog_level >= 9) {
+ msr_log(msr, 9, "%s: length shortened by %" APR_SIZE_T_FMT " bytes because of no_files_len limit.",
+ reqbody_processor,
+ msr->msc_reqbody_no_files_length - (unsigned long)msr->txcfg->reqbody_no_files_limit);
+ }
+ modsecurity_request_body_enable_partial_processing(msr);
+ }
+}
+
/**
* Stores one chunk of request body data. Returns -1 on error.
*/
@@ -361,6 +379,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. */
msr->msc_reqbody_no_files_length += length;
+ modsecurity_request_body_enable_partial_processing_for_no_files_length(msr, &length, "XML");
/* Process data as XML. */
if (xml_process_chunk(msr, data, length, &my_error_msg) < 0) {
@@ -373,6 +392,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. */
msr->msc_reqbody_no_files_length += length;
+ modsecurity_request_body_enable_partial_processing_for_no_files_length(msr, &length, "JSON");
/* Process data as JSON. */
#ifdef WITH_YAJL
@@ -392,6 +412,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. */
msr->msc_reqbody_no_files_length += length;
+ modsecurity_request_body_enable_partial_processing_for_no_files_length(msr, &length, "URLENCODED");
/* Do nothing else, URLENCODED processor does not support streaming. */
}
@@ -403,23 +424,23 @@ 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. */
msr->msc_reqbody_no_files_length += length;
+ modsecurity_request_body_enable_partial_processing_for_no_files_length(msr, &length, "forceBuf");
}
/* 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->msc_reqbody_no_files_length > (unsigned long)msr->txcfg->reqbody_no_files_limit) {
*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;
}
}
@@ -438,6 +459,31 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr,
return -1;
}
+/**
+ * Enable partial processing of request body data.
+ */
+void modsecurity_request_body_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");
+ }
+}
+
apr_status_t modsecurity_request_body_to_stream(modsec_rec *msr, const char *buffer, int buflen, char **error_msg) {
assert(msr != NULL);
assert(error_msg != NULL);
@@ -678,7 +724,13 @@ apr_status_t modsecurity_request_body_end(modsec_rec *msr, char **error_msg) {
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_xml.c b/apache2/msc_xml.c
index 4f0e07ca0..abc26ba29 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 73999443a..b56905dbc 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/tests/regression/config/10-reqbody-limit-action-forcebodybuf.t b/tests/regression/config/10-reqbody-limit-action-forcebodybuf.t
new file mode 100644
index 000000000..ff9226642
--- /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 000000000..92404a23b
--- /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 000000000..f05690eed
--- /dev/null
+++ b/tests/regression/config/10-reqbody-limit-action-multipart-chunked.t
@@ -0,0 +1,1270 @@
+{
+ 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: \\
+ 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}, \\
+ 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: PE 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: \\
+ 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}, \\
+ 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: PE 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: \\
+ 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}, \\
+ 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: PE 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: \\
+ 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}, \\
+ 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: \\
+ 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}, \\
+ 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}, \\
+ 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}, \\
+ 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, PE 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}, \\
+ 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}, \\
+ 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, PE 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}, \\
+ 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}, \\
+ 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, PE 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}, \\
+ 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}, \\
+ 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, PE 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}, \\
+ 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}, \\
+ 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, PE 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}, \\
+ 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}, \\
+ 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, PE 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 16270 . q(bad_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}, \\
+ 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}, \\
+ 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, PE 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}, \\
+ 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}, \\
+ 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, PE 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}, \\
+ 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}, \\
+ 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, PE 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}, \\
+ 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}, \\
+ 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, PE 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}, \\
+ 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}, \\
+ 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, PE 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}, \\
+ 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}, \\
+ 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, PE 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}, \\
+ 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}, \\
+ 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, PE 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 16273 . q(ad_value
+ -----------------------------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}, \\
+ 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}, \\
+ 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, PE 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 000000000..7c2135b27
--- /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 000000000..faa58b13f
--- /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 69aa190cc..8ca6035da 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 ad1ed9194..17ced4429 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, \\
@@ -428,3 +428,232 @@
),
),
},
+{
+ type => "rule",
+ comment => "xml ProcessPartial, bad format and whole body before limit",
+ conf => qq(
+ SecRuleEngine On
+ SecRequestBodyAccess On
+ SecRequestBodyLimitAction ProcessPartial
+ SecRequestBodyLimit 57
+ SecRequestBodyNoFilesLimit 57
+ SecXmlExternalEntity Off
+ SecDebugLog $ENV{DEBUG_LOG}
+ SecDebugLogLevel 9
+ SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\
+ phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML"
+ SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006
+ SecRule REQBODY_ERROR "!\@eq 0" \\
+ "id:'500007', 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:'500008',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 ],
+ },
+ match_response => {
+ status => qr/^400$/,
+ },
+ request => new HTTP::Request(
+ POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
+ [
+ "Content-Type" => "text/xml",
+ "Content-Length" => "57",
+ ],
+ 'value "rule",
+ comment => "xml ProcessPartial, bad format and length exceeds limit",
+ conf => qq(
+ SecRuleEngine On
+ SecRequestBodyAccess On
+ SecRequestBodyLimitAction ProcessPartial
+ SecRequestBodyLimit 57
+ SecRequestBodyNoFilesLimit 57
+ SecXmlExternalEntity Off
+ SecDebugLog $ENV{DEBUG_LOG}
+ SecDebugLogLevel 9
+ SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\
+ phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML"
+ SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006
+ SecRule REQBODY_ERROR "!\@eq 0" \\
+ "id:'500007', 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:'500008',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 ],
+ },
+ match_response => {
+ status => qr/^200$/,
+ },
+ request => new HTTP::Request(
+ POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
+ [
+ "Content-Type" => "text/xml",
+ "Content-Length" => "58",
+ ],
+ 'value "rule",
+ comment => "xml ProcessPartial, bad format and whole body before limit, no declaration",
+ conf => qq(
+ SecRuleEngine On
+ SecRequestBodyAccess On
+ SecRequestBodyLimitAction ProcessPartial
+ SecRequestBodyLimit 19
+ SecRequestBodyNoFilesLimit 19
+ SecXmlExternalEntity Off
+ SecDebugLog $ENV{DEBUG_LOG}
+ SecDebugLogLevel 9
+ SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\
+ phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML"
+ SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006
+ SecRule REQBODY_ERROR "!\@eq 0" \\
+ "id:'500007', 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:'500008',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 ],
+ },
+ match_response => {
+ status => qr/^400$/,
+ },
+ request => new HTTP::Request(
+ POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
+ [
+ "Content-Type" => "text/xml",
+ "Content-Length" => "19",
+ ],
+ 'value "rule",
+ comment => "xml ProcessPartial, bad format and length exceeds limit, no declaration",
+ conf => qq(
+ SecRuleEngine On
+ SecRequestBodyAccess On
+ SecRequestBodyLimitAction ProcessPartial
+ SecRequestBodyLimit 19
+ SecRequestBodyNoFilesLimit 19
+ SecXmlExternalEntity Off
+ SecDebugLog $ENV{DEBUG_LOG}
+ SecDebugLogLevel 9
+ SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\
+ phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML"
+ SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006
+ SecRule REQBODY_ERROR "!\@eq 0" \\
+ "id:'500007', 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:'500008',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 ],
+ },
+ match_response => {
+ status => qr/^200$/,
+ },
+ request => new HTTP::Request(
+ POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
+ [
+ "Content-Type" => "text/xml",
+ "Content-Length" => "20",
+ ],
+ 'value "rule",
+ comment => "xml ProcessPartial, bad value and whole body before limit",
+ conf => qq(
+ SecRuleEngine On
+ SecRequestBodyAccess On
+ SecRequestBodyLimitAction ProcessPartial
+ SecRequestBodyLimit 61
+ SecXmlExternalEntity Off
+ SecDebugLog $ENV{DEBUG_LOG}
+ SecDebugLogLevel 9
+ SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\
+ phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML"
+ SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006
+ SecRule XML:/* "bad_value" "id:'500007',phase:2,t:none,deny"
+ ),
+ match_log => {
+ error => [ qr/Access denied with code 403 \(phase 2\). Pattern match "bad_value" at XML\./, 1 ],
+ },
+ match_response => {
+ status => qr/^403$/,
+ },
+ request => new HTTP::Request(
+ POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
+ [
+ "Content-Type" => "text/xml",
+ ],
+ normalize_raw_request_data(
+ q(bad_value),
+ ),
+ ),
+},
+{
+ type => "rule",
+ comment => "xml ProcessPartial, bad value before limit",
+ conf => qq(
+ SecRuleEngine On
+ SecRequestBodyAccess On
+ SecRequestBodyLimitAction ProcessPartial
+ SecRequestBodyLimit 61
+ SecXmlExternalEntity Off
+ SecDebugLog $ENV{DEBUG_LOG}
+ SecDebugLogLevel 9
+ SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\
+ phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML"
+ SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006
+ SecRule XML:/* "bad_value" "id:'500007',phase:2,t:none,deny"
+ ),
+ match_log => {
+ error => [ qr/Access denied with code 403 \(phase 2\). Pattern match "bad_value" at XML\./, 1 ],
+ },
+ match_response => {
+ status => qr/^403$/,
+ },
+ request => new HTTP::Request(
+ POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
+ [
+ "Content-Type" => "text/xml",
+ ],
+ normalize_raw_request_data(
+ q(bad_valueok_value),
+ ),
+ ),
+},
+{
+ type => "rule",
+ comment => "xml ProcessPartial, bad value after limit",
+ conf => qq(
+ SecRuleEngine On
+ SecRequestBodyAccess On
+ SecRequestBodyLimitAction ProcessPartial
+ SecRequestBodyLimit 61
+ SecXmlExternalEntity Off
+ SecDebugLog $ENV{DEBUG_LOG}
+ SecDebugLogLevel 9
+ SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\
+ phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML"
+ SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006
+ SecRule XML:/* "bad_value" "id:'500007',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" => "text/xml",
+ ],
+ normalize_raw_request_data(
+ q(12bad_value),
+ ),
+ ),
+},
diff --git a/tests/regression/rule/15-json.t b/tests/regression/rule/15-json.t
index 9c1781750..a85ecaf78 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 87b193374..c84ad724e 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];