diff --git a/CHANGES b/CHANGES index c7b56f64..cec15afd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,7 +1,9 @@ -26 Nov 2007 - 2.5.0-dev3 +29 Nov 2007 - 2.5.0-dev3 ------------------------ + * Implemented SecRequestBodyNoFilesLimit. + * Enhance handling of the case where we run out of disk space while writing to audit log entry. diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c index 300e3d9e..a62d88f7 100644 --- a/apache2/apache2_config.c +++ b/apache2/apache2_config.c @@ -34,6 +34,7 @@ void *create_directory_config(apr_pool_t *mp, char *path) { dcfg->reqbody_access = NOT_SET; dcfg->reqbody_inmemory_limit = NOT_SET; dcfg->reqbody_limit = NOT_SET; + dcfg->reqbody_no_files_limit = NOT_SET; dcfg->resbody_access = NOT_SET; dcfg->debuglog_name = NOT_SET_P; @@ -221,6 +222,8 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) { ? parent->reqbody_inmemory_limit : child->reqbody_inmemory_limit); merged->reqbody_limit = (child->reqbody_limit == NOT_SET ? parent->reqbody_limit : child->reqbody_limit); + merged->reqbody_no_files_limit = (child->reqbody_no_files_limit == NOT_SET + ? parent->reqbody_no_files_limit : child->reqbody_no_files_limit); merged->resbody_access = (child->resbody_access == NOT_SET ? parent->resbody_access : child->resbody_access); @@ -453,6 +456,7 @@ void init_directory_config(directory_config *dcfg) { if (dcfg->reqbody_inmemory_limit == NOT_SET) dcfg->reqbody_inmemory_limit = REQUEST_BODY_DEFAULT_INMEMORY_LIMIT; if (dcfg->reqbody_limit == NOT_SET) dcfg->reqbody_limit = REQUEST_BODY_DEFAULT_LIMIT; + if (dcfg->reqbody_no_files_limit == NOT_SET) dcfg->reqbody_no_files_limit = REQUEST_BODY_NO_FILES_DEFAULT_LIMIT; if (dcfg->resbody_access == NOT_SET) dcfg->resbody_access = 0; if (dcfg->of_limit == NOT_SET) dcfg->of_limit = RESPONSE_BODY_DEFAULT_LIMIT; if (dcfg->of_limit_action == NOT_SET) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT; @@ -1022,6 +1026,22 @@ static const char *cmd_request_body_limit(cmd_parms *cmd, void *_dcfg, const cha return NULL; } +static const char *cmd_request_body_no_files_limit(cmd_parms *cmd, void *_dcfg, const char *p1) { + directory_config *dcfg = (directory_config *)_dcfg; + long int limit; + + if (dcfg == NULL) return NULL; + + limit = strtol(p1, NULL, 10); + if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyNoFilesLimit: %s", p1); + } + + dcfg->reqbody_no_files_limit = limit; + + return NULL; +} + static const char *cmd_request_body_access(cmd_parms *cmd, void *_dcfg, const char *p1) { directory_config *dcfg = (directory_config *)_dcfg; if (dcfg == NULL) return NULL; @@ -1668,7 +1688,15 @@ const command_rec module_directives[] = { cmd_request_body_limit, NULL, CMD_SCOPE_ANY, - "maximum request body size ModSecurity is allowed to access." + "maximum request body size ModSecurity will accept." + ), + + AP_INIT_TAKE1 ( + "SecRequestBodyNoFilesLimit", + cmd_request_body_no_files_limit, + NULL, + CMD_SCOPE_ANY, + "maximum request body size ModSecurity will accept, but excluding the size of uploaded files." ), AP_INIT_TAKE1 ( diff --git a/apache2/apache2_io.c b/apache2/apache2_io.c index cd0b6c8b..2bb85e49 100644 --- a/apache2/apache2_io.c +++ b/apache2/apache2_io.c @@ -221,7 +221,14 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) { } if (buflen != 0) { - if (modsecurity_request_body_store(msr, buf, buflen, error_msg) < 0) { + int rcbs = modsecurity_request_body_store(msr, buf, buflen, error_msg); + if (rcbs < 0) { + if (rcbs == -5) { + *error_msg = apr_psprintf(msr->mp, "Requests body no files data length is larger than the " + "configured limit (%lu).", msr->txcfg->reqbody_no_files_limit); + return -5; + } + return -1; } diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index 35ed26e4..4d0b42bc 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -107,6 +107,7 @@ extern DSOLOCAL modsec_build_type_rec modsec_build_type[]; #define REQUEST_BODY_HARD_LIMIT 1073741824L #define REQUEST_BODY_DEFAULT_INMEMORY_LIMIT 131072 #define REQUEST_BODY_DEFAULT_LIMIT 134217728 +#define REQUEST_BODY_NO_FILES_DEFAULT_LIMIT 1048576 #define RESPONSE_BODY_DEFAULT_LIMIT 524288 #define RESPONSE_BODY_HARD_LIMIT 1073741824L @@ -306,6 +307,8 @@ struct modsec_rec { int msc_reqbody_error; const char *msc_reqbody_error_msg; + apr_size_t msc_reqbody_no_files_length; + multipart_data *mpd; /* MULTIPART processor data structure */ #ifdef WITH_LIBXML2 @@ -366,6 +369,7 @@ struct directory_config { int reqbody_access; long int reqbody_inmemory_limit; long int reqbody_limit; + long int reqbody_no_files_limit; int resbody_access; long int of_limit; diff --git a/apache2/msc_multipart.c b/apache2/msc_multipart.c index ba8bd5e3..41a6475b 100644 --- a/apache2/msc_multipart.c +++ b/apache2/msc_multipart.c @@ -192,6 +192,9 @@ 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); + if (len > 1) { if (msr->mpd->buf[len - 2] == '\r') { msr->mpd->flag_crlf_line = 1; @@ -422,6 +425,9 @@ static int multipart_process_part_data(modsec_rec *msr, char **error_msg) { } else if (msr->mpd->mpp->type == MULTIPART_FORMDATA) { value_part_t *value_part = apr_pcalloc(msr->mp, sizeof(value_part_t)); + + /* The buffer contains data so increase the data length counter. */ + msr->msc_reqbody_no_files_length += (MULTIPART_BUF_SIZE - msr->mpd->bufleft) + msr->mpd->reserve[0]; /* add this part to the list of parts */ diff --git a/apache2/msc_reqbody.c b/apache2/msc_reqbody.c index 19db695a..1f3126f6 100644 --- a/apache2/msc_reqbody.c +++ b/apache2/msc_reqbody.c @@ -25,7 +25,6 @@ static apr_status_t modsecurity_request_body_start_init(modsec_rec *msr, char ** msr->msc_reqbody_chunks = apr_array_make(msr->msc_reqbody_mp, 32, sizeof(msc_data_chunk *)); if (msr->msc_reqbody_chunks == NULL) { - *error_msg = apr_pstrdup(msr->mp, "Input filter: Failed to prepare in-memory storage."); return -1; } @@ -263,6 +262,11 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr, char *my_error_msg = NULL; if (strcmp(msr->msc_reqbody_processor, "MULTIPART") == 0) { + /* The per-request data length counter will + * be updated by the multipart parser. + */ + + /* Process data as multipart/form-data. */ if (multipart_process_chunk(msr, data, length, &my_error_msg) < 0) { *error_msg = apr_psprintf(msr->mp, "Request body processor error: %s", my_error_msg); msr->msc_reqbody_error = 1; @@ -273,6 +277,10 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr, #ifdef WITH_LIBXML2 else if (strcmp(msr->msc_reqbody_processor, "XML") == 0) { + /* Increase per-request data length counter. */ + msr->msc_reqbody_no_files_length += length; + + /* Process data as XML. */ if (xml_process_chunk(msr, data, length, &my_error_msg) < 0) { *error_msg = apr_psprintf(msr->mp, "Request body processor error: %s", my_error_msg); msr->msc_reqbody_error = 1; @@ -283,7 +291,10 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr, #endif else if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) { - /* Do nothing, URLENCODED processor does not support streaming. */ + /* Increase per-request data length counter. */ + msr->msc_reqbody_no_files_length += length; + + /* Do nothing else, URLENCODED processor does not support streaming. */ } else { *error_msg = apr_psprintf(msr->mp, "Unknown request body processor: %s", @@ -291,6 +302,11 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr, return -1; } } + + /* 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) { + return -5; + } /* Store data. */ if (msr->msc_reqbody_storage == MSC_REQBODY_MEMORY) { @@ -395,8 +411,10 @@ apr_status_t modsecurity_request_body_end(modsec_rec *msr, char **error_msg) { } } + /* Note that we've read the body. */ msr->msc_reqbody_read = 1; + /* Finalise body processing. */ if ((msr->msc_reqbody_processor != NULL)&&(msr->msc_reqbody_error == 0)) { char *my_error_msg = NULL; @@ -432,6 +450,9 @@ apr_status_t modsecurity_request_body_end(modsec_rec *msr, char **error_msg) { #endif } + /* Note the request body no files length. */ + msr_log(msr, 4, "Reqest body no files length: %lu", msr->msc_reqbody_no_files_length); + return 1; } diff --git a/doc/modsecurity2-apache-reference.xml b/doc/modsecurity2-apache-reference.xml index 95c932f8..d210f087 100644 --- a/doc/modsecurity2-apache-reference.xml +++ b/doc/modsecurity2-apache-reference.xml @@ -1278,8 +1278,6 @@ SecAuditLogStorageDir logs/audit Example Usage: SecRequestBodyLimit 134217728 - Processing Phase: N/A - Scope: Any Dependencies/Notes: 131072 KB (134217728 @@ -1287,6 +1285,36 @@ SecAuditLogStorageDir logs/audit with status code 413 Request Entity Too Large. There is a hard limit of 1 GB. + +
+ <literal>SecRequestBodyNoFilesLimit</literal> + + Description: Configures the maximum request + body size ModSecurity will accept for buffering, excluding the size of + files being transported in the request. This directive comes handy to + further reduce susceptability to DoS attacks when someone is sending + request bodies of very large sizes. Web applications that require file + uploads must configure SecRequestBodyLimit to a + high value. Since large files are streamed to disk file uploads will + not increase memory consumption. However, it's still possible for + someone to take advantage of a large request body limit and send + non-upload requests with large body sizes. This directive eliminates + that loophole. + + Syntax: SecRequestBodyNoFilesLimit NUMBER_IN_BYTES + + Example Usage: SecRequestBodyLimit 131072 + + Scope: Any + + Dependencies/Notes: 1 MB (1048576 + bytes) is the default setting. This value is very conservative. For + most applications you should be able to reduce it down to 128 KB or + lower. Anything over the limit will be rejected with status code 413 + Request Entity Too Large. There is a hard limit of 1 GB. +
<literal>SecRequestBodyInMemoryLimit</literal>