diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c index c89cbd65..b7138fc3 100644 --- a/apache2/apache2_config.c +++ b/apache2/apache2_config.c @@ -630,6 +630,12 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, int type, "be specified by chain starter rules."); } + /* Must NOT specify a skipafter action. */ + if (rule->actionset->skip_after != NOT_SET_P) { + return apr_psprintf(cmd->pool, "ModSecurity: SkipAfter actions can only " + "be specified by chain starter rules."); + } + /* Must NOT specify a phase. */ if (rule->actionset->phase != NOT_SET) { return apr_psprintf(cmd->pool, "ModSecurity: Execution phases can only be " diff --git a/apache2/modsecurity.c b/apache2/modsecurity.c index d75bb506..663f5030 100644 --- a/apache2/modsecurity.c +++ b/apache2/modsecurity.c @@ -131,6 +131,18 @@ int modsecurity_init(msc_engine *msce, apr_pool_t *mp) { } #endif + rc = apr_global_mutex_create(&msce->geo_lock, NULL, APR_LOCK_DEFAULT, mp); + if (rc != APR_SUCCESS) { + return -1; + } + + #ifdef __SET_MUTEX_PERMS + rc = unixd_set_global_mutex_perms(msce->geo_lock); + if (rc != APR_SUCCESS) { + return -1; + } + #endif + return 1; } @@ -147,6 +159,14 @@ void modsecurity_child_init(msc_engine *msce) { // ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to child-init auditlog mutex"); } } + + if (msce->geo_lock != NULL) { + apr_status_t rc = apr_global_mutex_child_init(&msce->geo_lock, NULL, msce->mp); + if (rc != APR_SUCCESS) { + // ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to child-init geo mutex"); + } + } + } /** diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index 8aa0b5f5..630a43a0 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -490,6 +490,7 @@ struct error_message { struct msc_engine { apr_pool_t *mp; apr_global_mutex_t *auditlog_lock; + apr_global_mutex_t *geo_lock; msre_engine *msre; unsigned int processing_mode; }; diff --git a/apache2/msc_geo.c b/apache2/msc_geo.c index 7909e86f..2e7626bd 100644 --- a/apache2/msc_geo.c +++ b/apache2/msc_geo.c @@ -287,6 +287,7 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro apr_size_t nbytes; unsigned int rec_val = 0; apr_off_t seekto = 0; + apr_status_t ret; int rc; int country = 0; int level; @@ -332,6 +333,12 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro msr_log(msr, 9, "GEO: Using address \"%s\" (0x%08lx).", targetip, ipnum); } + ret = apr_global_mutex_lock(msr->modsecurity->geo_lock); + if (ret != APR_SUCCESS) { + msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s", + get_apr_error(msr->mp, ret)); + } + for (level = 31; level >= 0; level--) { /* Read the record */ seekto = 2 * reclen * rec_val; @@ -365,6 +372,13 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro if ((country <= 0) || (country > GEO_COUNTRY_LAST)) { *error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\" (country %d).", log_escape(msr->mp, target), country); msr_log(msr, 4, "%s", *error_msg); + + ret = apr_global_mutex_unlock(msr->modsecurity->geo_lock); + if (ret != APR_SUCCESS) { + msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s", + get_apr_error(msr->mp, ret)); + } + return 0; } @@ -389,6 +403,13 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro if ((country <= 0) || (country > GEO_COUNTRY_LAST)) { *error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\" (country %d).", log_escape(msr->mp, target), country); msr_log(msr, 4, "%s", *error_msg); + + ret = apr_global_mutex_unlock(msr->modsecurity->geo_lock); + if (ret != APR_SUCCESS) { + msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s", + get_apr_error(msr->mp, ret)); + } + return 0; } if (msr->txcfg->debuglog_level >= 9) { @@ -477,6 +498,13 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro } *error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" succeeded.", log_escape(msr->mp, target)); + + ret = apr_global_mutex_unlock(msr->modsecurity->geo_lock); + if (ret != APR_SUCCESS) { + msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s", + get_apr_error(msr->mp, ret)); + } + return 1; } diff --git a/apache2/msc_lua.c b/apache2/msc_lua.c index c278c9df..9721d910 100644 --- a/apache2/msc_lua.c +++ b/apache2/msc_lua.c @@ -32,6 +32,12 @@ typedef struct { int index; } msc_lua_dumpr_t; +typedef struct { + size_t buffer_size; + char data[1]; +} VarBuffer; + + /** * */ @@ -337,10 +343,83 @@ static int l_getvars(lua_State *L) { return 1; } +/* +* \brief Function used to get user input via script +* +* \param L Pointer to Lua state +* \param size buffer size +* +* \retval NULL On failure +* \retval data On Success +*/ +static char *get_varbuffer (lua_State *L, size_t size) { + VarBuffer *var_chunk = NULL; + + lua_getfield(L, LUA_REGISTRYINDEX, "_buffer"); + var_chunk = (VarBuffer *) lua_touserdata(L, -1); + + if (var_chunk == NULL || var_chunk->buffer_size < size) { + var_chunk = (VarBuffer *) lua_newuserdata(L, sizeof(VarBuffer) + size); + var_chunk->buffer_size = size; + lua_setfield(L, LUA_REGISTRYINDEX, "_buffer"); + } + + lua_pop(L, 1); + return var_chunk->data; +} + +/* +* \brief New setvar function for Lua API. Users can put back +* data in modsecurity core via new variables +* +* \param L Pointer to Lua state +* +* \retval -1 On failure +* \retval 0 On Collection failure +* \retval 1 On Success +*/ +static int l_setvar(lua_State *L) { + modsec_rec *msr = NULL; + msre_rule *rule = NULL; + char *var_value = NULL; + char *var_name = NULL; + size_t size; + int nargs = lua_gettop(L); + + lua_getglobal(L, "__msr"); + msr = (modsec_rec *)lua_topointer(L, -1); + + lua_getglobal(L, "__rule"); + rule = (msre_rule *)lua_topointer(L, -1); + + if(nargs != 2) { + msr_log(msr, 8, "m.setvar: Failed m.setvar funtion must has 2 arguments"); + return -1; + } + + //const char *var_value = luaL_checkstring (L, 2); + //const char *var_name = luaL_checkstring (L, 1); + + const char *setvar_value = lua_tolstring(L, 2, &size); + var_value = get_varbuffer(L, size); + + if(var_value != NULL && setvar_value != NULL) + strncpy(var_value, setvar_value, size + 1); + + const char *setvar_name = lua_tolstring(L, 1, &size); + var_name = get_varbuffer(L, size); + + if(var_name != NULL && setvar_name != NULL) + strncpy(var_name, setvar_name, size + 1); + + return msre_action_setvar_execute(msr,msr->msc_rule_mptmp,rule,var_name,var_value); +} + static const struct luaL_Reg mylib[] = { { "log", l_log }, { "getvar", l_getvar }, { "getvars", l_getvars }, + { "setvar", l_setvar }, { NULL, NULL } }; diff --git a/apache2/msc_release.h b/apache2/msc_release.h index ed4cd94a..f0cc1af9 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 "1" +#define MODSEC_VERSION_RELEASE "2" #define MODSEC_VERSION_SUFFIX MODSEC_VERSION_TYPE MODSEC_VERSION_RELEASE diff --git a/apache2/re.c b/apache2/re.c index 92137cbb..0eb72a23 100644 --- a/apache2/re.c +++ b/apache2/re.c @@ -843,7 +843,7 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) msre_rule **rules; apr_status_t rc; const char *skip_after = NULL; - int i, mode, skip; + int i, mode, skip, skipped; /* First determine which set of rules we need to use. */ switch (msr->phase) { @@ -873,6 +873,7 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) /* Loop through the rules in the selected set. */ skip = 0; + skipped = 0; mode = NEXT_RULE; rules = (msre_rule **)arr->elts; for (i = 0; i < arr->nelts; i++) { @@ -900,6 +901,18 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) } } + + msre_rule *last_rule = rules[i-1]; + + if(last_rule->actionset->is_chained) { + mode = NEXT_RULE; + skipped = 1; + --i; + } else { + mode = SKIP_RULES; + skipped = 0; + } + continue; } if (msr->txcfg->debuglog_level >= 9) { @@ -1074,6 +1087,11 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) continue; } + if(skipped == 1) { + mode = SKIP_RULES; + continue; + } + /* We had a match but the transaction was not * intercepted. In that case we proceed with the * next rule... diff --git a/apache2/re.h b/apache2/re.h index 663d4bb9..960a6c6a 100644 --- a/apache2/re.h +++ b/apache2/re.h @@ -49,6 +49,8 @@ typedef struct msre_cache_rec msre_cache_rec; /* Actions, variables, functions and operator functions */ +apr_status_t DSOLOCAL msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptmp, msre_rule *rule, char *var_name, char *var_value); + apr_status_t DSOLOCAL collection_original_setvar(modsec_rec *msr, const char *col_name, const msc_string *orig_var); int DSOLOCAL expand_macros(modsec_rec *msr, msc_string *var, msre_rule *rule, apr_pool_t *mptmp); diff --git a/apache2/re_actions.c b/apache2/re_actions.c index f24d0ff9..001deed5 100644 --- a/apache2/re_actions.c +++ b/apache2/re_actions.c @@ -1168,35 +1168,19 @@ static apr_status_t msre_action_setenv_execute(modsec_rec *msr, apr_pool_t *mptm } /* setvar */ -static apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptmp, - msre_rule *rule, msre_action *action) +apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptmp, + msre_rule *rule, char *var_name, char *var_value) { - char *data = apr_pstrdup(mptmp, action->param); - char *col_name = NULL, *var_name = NULL, *var_value = NULL; + char *col_name = NULL; char *s = NULL; apr_table_t *target_col = NULL; int is_negated = 0; msc_string *var = NULL; - /* Extract the name and the value. */ - /* IMP1 We have a function for this now, parse_name_eq_value? */ - s = strstr(data, "="); - if (s == NULL) { - var_name = data; - var_value = "1"; - } else { - var_name = data; - var_value = s + 1; - *s = '\0'; - - while ((*var_value != '\0')&&(isspace(*var_value))) var_value++; - } - if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "Setting variable: %s=%s", var_name, var_value); } - /* Expand and escape any macros in the name */ var = apr_palloc(msr->mp, sizeof(msc_string)); if (var == NULL) { @@ -1335,6 +1319,43 @@ static apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptm return 1; } +/* +* \brief Parse fuction for setvar input +* +* \param msr Pointer to the engine +* \param mptmp Pointer to the pool +* \param rule Pointer to rule struct +* \param action input data +* +* \retval -1 On failure +* \retval 0 On Collection failure +* \retval 1 On Success +*/ +static apr_status_t msre_action_setvar_parse(modsec_rec *msr, apr_pool_t *mptmp, + msre_rule *rule, msre_action *action) +{ + char *data = apr_pstrdup(mptmp, action->param); + char *var_name = NULL, *var_value = NULL; + char *s = NULL; + + /* Extract the name and the value. */ + /* IMP1 We have a function for this now, parse_name_eq_value? */ + s = strstr(data, "="); + if (s == NULL) { + var_name = data; + var_value = "1"; + } else { + var_name = data; + var_value = s + 1; + *s = '\0'; + + while ((*var_value != '\0')&&(isspace(*var_value))) var_value++; + } + + return msre_action_setvar_execute(msr,mptmp,rule,var_name,var_value); +} + + /* expirevar */ static apr_status_t msre_action_expirevar_execute(modsec_rec *msr, apr_pool_t *mptmp, msre_rule *rule, msre_action *action) @@ -2264,7 +2285,7 @@ void msre_engine_register_default_actions(msre_engine *engine) { ACTION_CGROUP_NONE, NULL, NULL, - msre_action_setvar_execute + msre_action_setvar_parse ); /* expirevar */ diff --git a/apache2/re_operators.c b/apache2/re_operators.c index 311c466f..2d1dd36e 100644 --- a/apache2/re_operators.c +++ b/apache2/re_operators.c @@ -153,9 +153,13 @@ static int msre_op_rx_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c apr_table_setn(msr->tx_vars, s->name, (void *)s); *error_msg = apr_psprintf(msr->mp, - "Rule execution error - " + "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - " + "Execution error - " "PCRE limits exceeded (%d): %s", - rc, my_error_msg); + rule,((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-", + rule->filename != NULL ? rule->filename : "-", + rule->line_num,rc, my_error_msg); + msr_log(msr, 3, "%s.", *error_msg); diff --git a/apache2/re_tfns.c b/apache2/re_tfns.c index 46b80671..2446a83a 100644 --- a/apache2/re_tfns.c +++ b/apache2/re_tfns.c @@ -25,6 +25,30 @@ #include "re.h" #include "msc_util.h" +/* Base64 tables used in decodeBase64Ext */ + +static const char b64_pad = '='; + +static const short b64_reverse_t[256] = { + -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -2, -1, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2, + -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2, + -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2 +}; + + /* lowercase */ static int msre_fn_lowercase_execute(apr_pool_t *mptmp, unsigned char *input, @@ -572,6 +596,95 @@ static int msre_fn_parityOdd7bit_execute(apr_pool_t *mptmp, unsigned char *input return changed; } +/* +* \brief Decode Base64 data with special chars +* +* \param plain_text Pointer to plain text data +* \param input Pointer to input data +* \param input_len Input data length +* +* \retval 0 On failure +* \retval string length On Success +*/ +int decode_base64_ext(char *plain_text, const char *input, int input_len) +{ + const char *encoded = input; + int i = 0, j = 0, k = 0; + int ch = 0; + + while ((ch = *encoded++) != '\0' && input_len-- > 0) { + if (ch == b64_pad) { + if (*encoded != '=' && (i % 4) == 1) { + return 0; + } + continue; + } + + ch = b64_reverse_t[ch]; + if (ch < 0 || ch == -1) { + continue; + } else if (ch == -2) { + return 0; + } + + switch(i % 4) { + case 0: + plain_text[j] = ch << 2; + break; + case 1: + plain_text[j++] |= ch >> 4; + plain_text[j] = (ch & 0x0f) << 4; + break; + case 2: + plain_text[j++] |= ch >>2; + plain_text[j] = (ch & 0x03) << 6; + break; + case 3: + plain_text[j++] |= ch; + break; + } + i++; + } + + k = j; + if (ch == b64_pad) { + switch(i % 4) { + case 1: + return 0; + case 2: + k++; + case 3: + plain_text[k] = 0; + } + } + + plain_text[j] = '\0'; + + return j; +} + +/* +* \brief Base64 transformation function based on RFC2045 +* +* \param mptmp Pointer to resource poil +* \param input Pointer to input data +* \param input_len Input data length +* \param rval Pointer to decoded buffer +* \param rval_len Decoded buffer length +* +* \retval 0 On failure +* \retval 1 On Success +*/ +static int msre_fn_decodeBase64Ext_execute(apr_pool_t *mptmp, unsigned char *input, long int input_len, char **rval, long int *rval_len) +{ + *rval_len = input_len; + *rval = apr_palloc(mptmp, *rval_len); + *rval_len = decode_base64_ext(*rval, (const char *)input, input_len); + + return *rval_len ? 1 : 0; +} + + /* ------------------------------------------------------------------------------ */ /** @@ -775,4 +888,11 @@ void msre_engine_register_default_tfns(msre_engine *engine) { "urlEncode", msre_fn_urlEncode_execute ); + + /* decodeBase64Ext */ + msre_engine_tfn_register(engine, + "decodeBase64Ext", + msre_fn_decodeBase64Ext_execute + ); + }