diff --git a/CHANGES b/CHANGES index 43965da1..bc01726c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +22 Nov 2010 - 2.5.13-dev3 + + * Add SecReadStateLimit to limit the number of BUSY connections + + * Fixed redirect action was not expanding macros in chained rules + + 04 Nov 2010 - 2.5.13-dev2 -------------------- diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c index b7138fc3..e044668f 100644 --- a/apache2/apache2_config.c +++ b/apache2/apache2_config.c @@ -1343,6 +1343,34 @@ static const char *cmd_guardian_log(cmd_parms *cmd, void *_dcfg, return NULL; } +/* +* \brief Add SecReadStateLimit configuration option +* +* \param cmd Pointer to configuration data +* \param _dcfg Pointer to directory configuration +* \param p1 Pointer to configuration option +* +* \retval NULL On failure +* \retval apr_psprintf On Success +*/ +static const char *cmd_conn_read_state_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 SecReadStateLimit: %s", p1); + } + + conn_read_state_limit = limit; + + return NULL; +} + static const char *cmd_request_body_inmemory_limit(cmd_parms *cmd, void *_dcfg, const char *p1) { @@ -2208,6 +2236,14 @@ const command_rec module_directives[] = { "On or Off" ), + AP_INIT_TAKE1 ( + "SecReadStateLimit", + cmd_conn_read_state_limit, + NULL, + CMD_SCOPE_ANY, + "maximum number of server in busy state" + ), + AP_INIT_TAKE1 ( "SecRequestBodyInMemoryLimit", cmd_request_body_inmemory_limit, diff --git a/apache2/mod_security2.c b/apache2/mod_security2.c index 91b805fc..d76abdcc 100644 --- a/apache2/mod_security2.c +++ b/apache2/mod_security2.c @@ -54,6 +54,15 @@ unsigned long int DSOLOCAL msc_pcre_match_limit = 0; unsigned long int DSOLOCAL msc_pcre_match_limit_recursion = 0; +unsigned long int DSOLOCAL conn_read_state_limit = 0; + +static int server_limit, thread_limit; + +typedef struct { + int child_num; + int thread_num; +} sb_handle; + /* -- Miscellaneous functions -- */ /** @@ -1060,6 +1069,61 @@ static void modsec_register_operator(const char *name, void *fn_init, void *fn_e } } +/* +* \brief Connetion hook to limit the number of +* connections in BUSY state +* +* \param conn Pointer to connection struct +* +* \retval DECLINED On failure +* \retval OK On Success +*/ +static int hook_connection_early(conn_rec *conn) +{ + sb_handle *sb = conn->sbh; + int i, j; + int ip_count = 0; + int limit = 0; + worker_score *ws_record = NULL; + + if(sb != NULL && conn_read_state_limit > 0) { + + ws_record = &ap_scoreboard_image->servers[sb->child_num][sb->thread_num]; + if(ws_record == NULL) + return DECLINED; + + apr_cpystrn(ws_record->client, conn->remote_ip, sizeof(ws_record->client)); + + for (i = 0; i < server_limit; ++i) { + for (j = 0; j < thread_limit; ++j) { + + ws_record = ap_get_scoreboard_worker(i, j); + + if(ws_record == NULL) + return DECLINED; + + switch (ws_record->status) { + case SERVER_BUSY_READ: + if (strcmp(conn->remote_ip, ws_record->client) == 0) + ip_count++; + break; + default: + break; + } + } + } + + if (ip_count > conn_read_state_limit) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, "ModSecurity: Access denied with code 400. Too many connections [%d] of %ld allowed in READ state from %s - Possible DoS Consumption Attack [Rejected]", ip_count,conn_read_state_limit,conn->remote_ip); + return OK; + } else { + return DECLINED; + } + } + + return DECLINED; +} + /** * This function is exported for other Apache modules to * register new variables. @@ -1117,6 +1181,10 @@ static void register_hooks(apr_pool_t *mp) { APR_REGISTER_OPTIONAL_FN(modsec_register_variable); #endif + /* For connection level hook */ + ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit); + ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit); + /* Main hooks */ ap_hook_pre_config(hook_pre_config, NULL, NULL, APR_HOOK_FIRST); ap_hook_post_config(hook_post_config, postconfig_beforeme_list, @@ -1127,6 +1195,9 @@ static void register_hooks(apr_pool_t *mp) { * // ap_hook_handler(hook_handler, NULL, NULL, APR_HOOK_MIDDLE); */ + /* Connection processing hooks */ + ap_hook_process_connection(hook_connection_early, NULL, NULL, APR_HOOK_FIRST); + /* Transaction processing hooks */ ap_hook_post_read_request(hook_request_early, postread_beforeme_list, postread_afterme_list, APR_HOOK_REALLY_FIRST); diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index 630a43a0..3d64e848 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -131,6 +131,8 @@ extern DSOLOCAL unsigned long int msc_pcre_match_limit; extern DSOLOCAL unsigned long int msc_pcre_match_limit_recursion; +extern DSOLOCAL unsigned long int conn_read_state_limit; + #define RESBODY_STATUS_NOT_READ 0 /* we were not configured to read the body */ #define RESBODY_STATUS_ERROR 1 /* error occured while we were reading the body */ #define RESBODY_STATUS_PARTIAL 2 /* partial body content available in the brigade */ diff --git a/apache2/msc_release.h b/apache2/msc_release.h index f0cc1af9..624c04ad 100644 --- a/apache2/msc_release.h +++ b/apache2/msc_release.h @@ -50,7 +50,7 @@ extern DSOLOCAL modsec_build_type_rec modsec_build_type[]; #define MODSEC_VERSION_MINOR "5" #define MODSEC_VERSION_MAINT "13" #define MODSEC_VERSION_TYPE "dev" -#define MODSEC_VERSION_RELEASE "2" +#define MODSEC_VERSION_RELEASE "3" #define MODSEC_VERSION_SUFFIX MODSEC_VERSION_TYPE MODSEC_VERSION_RELEASE