From 1260d2b097a40d319bcbb1eaa9099f3c9485f98e Mon Sep 17 00:00:00 2001 From: brenosilva Date: Thu, 23 Dec 2010 12:27:57 +0000 Subject: [PATCH] MODSEC-104 --- apache2/apache2_config.c | 43 +++++++++++++++++++++++++++++++++-- apache2/apache2_io.c | 9 +++++--- apache2/mod_security2.c | 15 +++++++++---- apache2/modsecurity.h | 10 +++++++-- apache2/re_variables.c | 48 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 113 insertions(+), 12 deletions(-) diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c index 228e3754..69b02312 100644 --- a/apache2/apache2_config.c +++ b/apache2/apache2_config.c @@ -58,6 +58,7 @@ void *create_directory_config(apr_pool_t *mp, char *path) dcfg->debuglog_fd = NOT_SET_P; dcfg->of_limit = NOT_SET; + dcfg->if_limit_action = NOT_SET; dcfg->of_limit_action = NOT_SET; dcfg->of_mime_types = NOT_SET_P; dcfg->of_mime_types_cleared = NOT_SET; @@ -253,6 +254,8 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) merged->of_limit = (child->of_limit == NOT_SET ? parent->of_limit : child->of_limit); + merged->if_limit_action = (child->if_limit_action == NOT_SET + ? parent->if_limit_action : child->if_limit_action); merged->of_limit_action = (child->of_limit_action == NOT_SET ? parent->of_limit_action : child->of_limit_action); merged->reqintercept_oe = (child->reqintercept_oe == NOT_SET @@ -492,6 +495,7 @@ void init_directory_config(directory_config *dcfg) 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->if_limit_action == NOT_SET) dcfg->if_limit_action = REQUEST_BODY_LIMIT_ACTION_REJECT; if (dcfg->of_limit_action == NOT_SET) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT; if (dcfg->of_mime_types == NOT_SET_P) { @@ -1469,6 +1473,11 @@ static const char *cmd_response_body_limit_action(cmd_parms *cmd, void *_dcfg, directory_config *dcfg = (directory_config *)_dcfg; if (dcfg == NULL) return NULL; + if (dcfg->is_enabled == MODSEC_DETECTION_ONLY) { + dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_PARTIAL; + return NULL; + } + if (strcasecmp(p1, "ProcessPartial") == 0) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_PARTIAL; else if (strcasecmp(p1, "Reject") == 0) dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT; @@ -1478,6 +1487,26 @@ static const char *cmd_response_body_limit_action(cmd_parms *cmd, void *_dcfg, return NULL; } +static const char *cmd_resquest_body_limit_action(cmd_parms *cmd, void *_dcfg, + const char *p1) +{ + directory_config *dcfg = (directory_config *)_dcfg; + if (dcfg == NULL) return NULL; + + if (dcfg->is_enabled == MODSEC_DETECTION_ONLY) { + dcfg->if_limit_action = REQUEST_BODY_LIMIT_ACTION_PARTIAL; + return NULL; + } + + if (strcasecmp(p1, "On") == 0) dcfg->if_limit_action = RESPONSE_BODY_LIMIT_ACTION_PARTIAL; + else + if (strcasecmp(p1, "Off") == 0) dcfg->if_limit_action = REQUEST_BODY_LIMIT_ACTION_REJECT; + else + return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRequestBodyProcessPartial: %s", p1); + + return NULL; +} + static const char *cmd_response_body_mime_type(cmd_parms *cmd, void *_dcfg, const char *_p1) { @@ -1526,8 +1555,10 @@ static const char *cmd_rule_engine(cmd_parms *cmd, void *_dcfg, const char *p1) else if (strcasecmp(p1, "off") == 0) dcfg->is_enabled = MODSEC_DISABLED; else - if (strcasecmp(p1, "detectiononly") == 0) dcfg->is_enabled = MODSEC_DETECTION_ONLY; - else + if (strcasecmp(p1, "detectiononly") == 0) { + dcfg->is_enabled = MODSEC_DETECTION_ONLY; + dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_PARTIAL; + } else return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRuleEngine: %s", p1); return NULL; @@ -2158,6 +2189,14 @@ const command_rec module_directives[] = { "what happens when the response body limit is reached" ), + AP_INIT_TAKE1 ( + "SecRequestBodyProcessPartial", + cmd_resquest_body_limit_action, + NULL, + CMD_SCOPE_ANY, + "what happens when the request body limit is reached" + ), + AP_INIT_ITERATE ( "SecResponseBodyMimeType", cmd_response_body_mime_type, diff --git a/apache2/apache2_io.c b/apache2/apache2_io.c index 1e605c96..cb883adc 100644 --- a/apache2/apache2_io.c +++ b/apache2/apache2_io.c @@ -391,6 +391,7 @@ static apr_status_t output_filter_init(modsec_rec *msr, ap_filter_t *f, if (len > msr->txcfg->of_limit) { msr_log(msr, 1, "Output filter: Content-Length (%s) over the limit (%ld).", log_escape_nq(r->pool, (char *)s_content_length), msr->txcfg->of_limit); + msr->outbound_error = 1; return -2; /* Over the limit. */ } } @@ -521,11 +522,11 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { if ((ae != NULL)&&(apr_table_get(f->r->headers_in, "Accept-Encoding") == NULL)) { apr_table_add(f->r->headers_in, "Accept-Encoding", ae); - } - + } + if ((te != NULL)&&(apr_table_get(f->r->headers_in, "TE") == NULL)) { apr_table_add(f->r->headers_in, "TE", te); - } + } } /* Initialise on first invocation */ @@ -555,6 +556,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { } } + msr->outbound_error = 0; /* Decide whether to observe the response body. */ rc = output_filter_init(msr, f, bb_in); switch(rc) { @@ -657,6 +659,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { * ready to accept. We need to decide what we want to do * about it. */ + msr->outbound_error = 1; if (msr->txcfg->of_limit_action == RESPONSE_BODY_LIMIT_ACTION_REJECT) { /* Reject response. */ msr_log(msr, 1, "Output filter: Response body too large (over limit of %ld, " diff --git a/apache2/mod_security2.c b/apache2/mod_security2.c index 1f067632..943fdd66 100644 --- a/apache2/mod_security2.c +++ b/apache2/mod_security2.c @@ -726,14 +726,18 @@ static int hook_request_late(request_rec *r) { /* Check that the request body is not too long, but only * if configuration allows for request body access. */ + msr->inbound_error = 0; if (msr->txcfg->reqbody_access == 1) { /* Check request body limit (non-chunked requests only). */ if (msr->request_content_length > msr->txcfg->reqbody_limit) { msr_log(msr, 1, "Request body (Content-Length) is larger than the " "configured limit (%ld).", msr->txcfg->reqbody_limit); - return HTTP_REQUEST_ENTITY_TOO_LARGE; + msr->inbound_error = 1; + + if(msr->txcfg->if_limit_action == RESPONSE_BODY_LIMIT_ACTION_REJECT) + return HTTP_REQUEST_ENTITY_TOO_LARGE; } - } + } /* Figure out whether to extract multipart files. */ if ((msr->txcfg->upload_keep_files != KEEP_FILES_OFF) /* user might want to keep them */ @@ -763,8 +767,11 @@ static int hook_request_late(request_rec *r) { if (my_error_msg != NULL) { msr_log(msr, 1, "%s", my_error_msg); } - r->connection->keepalive = AP_CONN_CLOSE; - return HTTP_REQUEST_ENTITY_TOO_LARGE; + msr->inbound_error = 1; + if(msr->txcfg->if_limit_action == RESPONSE_BODY_LIMIT_ACTION_REJECT) { + r->connection->keepalive = AP_CONN_CLOSE; + return HTTP_REQUEST_ENTITY_TOO_LARGE; + } break; default : /* allow through */ diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index 175ff2e2..9b3adbb7 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -94,6 +94,9 @@ typedef struct msc_string msc_string; #define REQUEST_BODY_FORCEBUF_OFF 0 #define REQUEST_BODY_FORCEBUF_ON 1 +#define REQUEST_BODY_LIMIT_ACTION_REJECT 0 +#define REQUEST_BODY_LIMIT_ACTION_PARTIAL 1 + #define SECACTION_TARGETS "REMOTE_ADDR" #define SECACTION_ARGS "@unconditionalMatch" @@ -264,8 +267,10 @@ struct modsec_rec { apr_table_t *request_headers_to_sanitize; apr_table_t *response_headers_to_sanitize; apr_table_t *request_cookies; - - unsigned int urlencoded_error; + + unsigned int urlencoded_error; + unsigned int inbound_error; + unsigned int outbound_error; unsigned int is_relevant; @@ -395,6 +400,7 @@ struct directory_config { apr_table_t *of_mime_types; int of_mime_types_cleared; int of_limit_action; + int if_limit_action; const char *debuglog_name; int debuglog_level; diff --git a/apache2/re_variables.c b/apache2/re_variables.c index baa48c8a..e8eca72a 100644 --- a/apache2/re_variables.c +++ b/apache2/re_variables.c @@ -1453,6 +1453,30 @@ static int var_urlencoded_error_generate(modsec_rec *msr, msre_var *var, msre_ru } } +/* INBOUND_DATA_ERROR */ + +static int var_inbound_error_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if (msr->inbound_error) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + +/* OUTBOUND_DATA_ERROR */ + +static int var_outbound_error_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + if (msr->outbound_error) { + return var_simple_generate(var, vartab, mptmp, "1"); + } else { + return var_simple_generate(var, vartab, mptmp, "0"); + } +} + apr_time_t calculate_perf_combined(modsec_rec *msr) { return msr->time_phase1 + msr->time_phase2 + msr->time_phase3 + msr->time_phase4 + msr->time_phase5 + msr->time_storage_write /* time_storage_read is already @@ -3196,7 +3220,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_CACHE, PHASE_RESPONSE_HEADERS ); - + /* URLENCODED_ERROR */ msre_engine_variable_register(engine, "URLENCODED_ERROR", @@ -3208,6 +3232,28 @@ void msre_engine_register_default_variables(msre_engine *engine) { PHASE_REQUEST_HEADERS ); + /* INBOUND_DATA_ERROR */ + msre_engine_variable_register(engine, + "INBOUND_DATA_ERROR", + VAR_SIMPLE, + 0, 0, + NULL, + var_inbound_error_generate, + VAR_DONT_CACHE, /* flag */ + PHASE_REQUEST_BODY + ); + + /* OUTBOUND_DATA_ERROR */ + msre_engine_variable_register(engine, + "OUTBOUND_DATA_ERROR", + VAR_SIMPLE, + 0, 0, + NULL, + var_outbound_error_generate, + VAR_DONT_CACHE, /* flag */ + PHASE_RESPONSE_BODY + ); + /* USER */ msre_engine_variable_register(engine, "USER",